I have a weather app that the api data comes in different formats so I made up an object of methods the I can convert that data to imperial format and time from UTC to GMT.
Right now I have those methods being called on the data in my reducer.
Question: Is that "okay" or should that conversions be done in the corresponding action prior to the payload being delivered to the reducer? Just curious what the best practice for something like this is.
FYI: I'm using axios
as my promise based HTTP client and redux-promise-middleware
, redux-lodger
, & redux-promise
as my middleware in the store.
Action Creator:
export const fetchCurrentWeather = (city) => {
const url = `${CURRENT_ROOT_URL}&q=${city},us`;
const promise = new Promise((resolve, reject) => {
axios.get(url)
.then(res => resolve(res.data))
.catch(err => reject(err));
});
return {
type: FETCH_CURRENT_WEATHER,
payload: promise
};
};
Reducer:
export default(state = initialState, action) => {
const data = action.payload;
switch (action.type) {
case `${FETCH_CURRENT_WEATHER}_PENDING`:
return {};
case `${FETCH_CURRENT_WEATHER}_FULFILLED`:
const prefix = 'wi wi-owm-';
const code = data.weather[0].id;
const icon = prefix + code;
return {
...state,
weatherData: {
humidity: data.main.humidity,
icon,
name: data.name,
pressure: unitConverter.toInchesHG(data.main.pressure),
sunrise: unitConverter.toGMT(data.sys.sunrise),
sunset: unitConverter.toGMT(data.sys.sunset),
temp: unitConverter.toFarenheit(data.main.temp),
winddir: unitConverter.toCardinal(data.wind.deg),
windspd: unitConverter.toMPH(data.wind.speed)
},
isFetched: true
};
case `${FETCH_CURRENT_WEATHER}_REJECTED`:
return {
...state,
isFetched: true,
err: data
};
default:
return state;
}
};
You have three locations where you could usefully process your raw data:
A component's render()
function
This is not normally a good idea as it would mean the data is processed each time the component is rendered. If you use a package like reselect you can mitigate the performance issues through caching, but even then the actual code e.g. sorting or filtering should be kept in the mapStateToProps()
.
In the reducer
A better case can be made for the processing data in the reducer but I would argue that for reasons of clarity, and separation of concerns, this is still not the best place. The reducer's job is pretty clear - to concede the action and merge the previous state with the action results, anything only else serves to blur the lines of responsibility and testability.
An action thunk
In my opinion, an action thunk is the correct location for once-only data transformations such as the normalisation / conversion of imported raw data. Not only is it typically a clear action sub-task (e.g. get weather data -> convert celsius to fahrenheit) but it also has the added advantage of not storing useless data, even if temporarily, in state.
To quote Dan Abramov:
... action objects [are] minimal representations of what happened and state objects [are] minimal representations of what’s necessary for rendering right now.
Final Note - Selectors
Although I said above that the component is not a good place to perform raw-data transformations, in fact I think there is an argument to be made for storing the raw-data in redux-state and using a package like reselect to present the normalised or computed values via selectors
as required.
One way to achieve this would be to have a selector
function that performs the data normalisation on a defined portion of the raw-data. Using the reselect package, this conversion would be cached and so only performed once. It would have the advantage of lazy-converting the data only as required.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With