In the current app I have set up multiple redux reducers to store data. Lets just call them 'user-reducers' and 'pet-reducers'. The 2 are very similar, they both have a posts[]. Now whenever user likes a post inside posts[], an action 'likeDone' is fired. Now I have 2 choices of implementing the redux update:
Option 1: both 'user-reducers' and 'pet-reducers' listens to 'likeDone'. IMO, this is inefficient in the later stages when I have more similar reducers and all of them listing to one action.
Option 2: change 'likeDone' into 2 more explicit actions ex. 'likeUserPostDone' 'likePetPostDone' and each reducer reacts to the matched action. This way, reducer updates seem more efficient but there will be more action types later on which will end up with lots of 'switch - case' and I'm not sure if that is a good practice.
Thanks for reading and please tell me what's the best for my case.
Have both reducers listen to your action if they both change when that action occurs. All redux reducers efficiently listen to all dispatched actions. They ignore actions if it doesn't change state.
const petPostReducer = (prev = defaultValue, action = {}) => {
const { type, payload} = action;
switch (type) {
case "LIKE_DONE": {
const {id} = payload || {};
const oldItem = prev[id] || {}
const newItem = {...oldItem, like: true}
return {
...prev
[id]: {...updateItem, [id]: newItem};
}
default: {
return prev; // Ignore all other actions != LIKE_DONE
}
}
};
It is fine to create more explicit actions, but you usually want to do so when you have different actions. (There are always exceptions)
When possible, avoid logic in your reducers.
If pets and user data are very similar, consider changing your state shape and use one reducer posts.
console.log(state.posts) =>
{
0: {type: 'user' like: false},
1: {type: 'pet' like: false},
2: {type: 'user' like: true},
}
Please avoid premature optimization. I often do things that are less efficient in favor of simple, concise, immutable, readable and useful code. If you are targeting a feature phone or find a real performance bottleneck, you will need to measure with performance monitoring tools. The JavaScript engine optimizations are continuously improving and results are often surprising.
Related
Is it bad practice to use useEffect?
And should useEffect be avoided if possible due to re-renders?
This question arised yesterday when a colleague asked for a code review and we had different opinions on how to solve this.
We are creating an app that shows some kind of documentation which could be sorted in chronological or reversed chronological order. This is decided by a button in the apps top bar with a default value of chronological order, this value is stored in a global redux state and will be used in every call to fetch documentation.
In this example we update sortOrder on button click and as an effect of that we fetch data.
If I understand this correctly, we render once when sortOrder state change, and once after data is fetched.
Pseudo code ish
interface AppState = {
sortOrder: SortOrder:
documentation: Documentation[];
}
reducer(){
case toggleSortOrder:
const order = state.sortOrder === 'asc' ? 'desc' : 'asc';
return {
....state,
sortOrder: order;
}
}
const AppBar = () => {
const dispatch = useDispatch();
return <div><button onClick={dispatch(toggleSortOrder)}>Change sort order</button>
</div>;
}
const DocumentationList = (type: DocumentationType) => {
const dispatch = useDispatch();
const sortOrder = useSelector((state) => state.appState.sortOrder);
const documentation = useSelector((state) => state.appState.documentation);
useEffect(() => {
// action is caught by redux-saga and a call to docApi is made through axios
dispatch(getDocumentation.request(type, sortOrder)
},[sortOrder]);
return documentation.map((doc) => <Documentation data={doc} />);
}
Is this bad practice?
Should we avoid useEffect and fetch data on click and update sortOrder in saga instead?
Reading docs and blogs I mostly see examples of how en when to use them.
In my opinion, I would go with solution more-less like yours, with splitting responsibilities between element which externally changes query params, and element which is displaying data based on current query params. If you decide to put all logic in button click handler then you are kinda coupling too much list and button, because in order to delegate all work to button click you must dig into DocumentationList fetch-data implementation and to copy it to another place(button related saga or in button click handler) in order to fetch data from another place in the app, and not just from the DocumentationList itslef.
From my perspective, only DocumentationList should be responsible to fetch data, and noone else. But you should provide way to subscribe, from documentation list, to some external query params(sort, filters etc) and when they change(if they exist) data should be loaded.
Right now you only have sort, but in case when you can potentially have more params that can be externally modified, then I would dedicate more complex redux part to query params, something like documentationQueryParams: { sort: "asc", filters: { name: "doc a", type: "type b" } }, and then inside DocumentationList I would use custom hook, for example const queryParms = useDocumentationQueryParams(); which will return standardized query params, and in useEffect I would subscribe to those queryParms change - whenever they change I will easily fetch new data since you know what is the structure of the queryParms(they must be standardized is some way). Like this you coupled them but in very flexible way, whenever you need new param you will update only filter/query-related component, because in DocumentationList you relay on standardized hook output and you can easily create generic mechanism to output query string, or body data, in order to make new request and to fetch new data.
In terms of performance, there is really no difference between hooks-based approach and moving all to click handlers, because in DocumentationList your render part should rerender only when list change, no matter how list is being changed.
As I understand it, when an action is called, all reducers respond. If action exists in the switch case statement of the reducer, it executes. If it doesn't, then the case: default executes which preserves the existing state.
When the action exists in the reducer but the particular property it's trying to update does not exist, it seems to behave OK as there's nothing to update.
For example, I have an action creator that is used to set the visible property of my modals. Each modal has its own Id. My code looks like this:
export default (state = initialState, action) => {
case types.SET_MODAL_IS_VISIBLE:
return Object.assign({}, state,
{ modal22: action.value }
)}
I have the SET_MODAL_IS_VISIBLE in multiple reducers but if modal22 is not defined in a particular reducer, nothing happens and no errors.
Now, I have a scenario that is throwing an error. I have a general purpose date picker component that I built that can be used as a single and independent date picker OR it can be "linked to" another one. The second scenario is useful if I need the user to give me two dates e.g. start and end dates.
I also built a feature where if the date picker is coupled with another one, when the user sets the date in the first date picker, I disable all the dates prior to that date in the second date picker because I don't want the user to unintentionally select an end date that is prior to the start date.
I define my date pickers as below:
const initialState = {
datePickers: {
"startDatePicker": {
activeDate: "8/25/2017",
disabledBefore: "",
linkedTo: "endDatePicker"
},
"endDatePicker": {
activeDate: "",
disabledBefore: "8/25/2017" // This date is set when the user sets the active date in startDatePicker
linkedTo: ""
}
}
}
This scenario is a bit interesting because a state change in one property in my reducer is triggering a state change in another. This is not difficult to do and I have a way of controlling when I do the update.
The action for setting disabled dates looks like below:
...
case types.SET_DISABLED_DATES:
return Object.assign({}, state,
datePickers: Object.assign({}, state.datePickers, {
datePickers[action.datePickerId]: Object.assign({}, state.datePickers[action.datePickerId], {
disabledBefore: action.value
})
})
Please keep in mind that I can and should be able to set disabledBefore even if the date picker is used as an independent one. So, I need my SET_DISABLED_DATES in every reducer.
The problem I'm running into is that whenever I call SET_DISABLED_DATES, I get errors in reducers where the date picker is used as a single/independent one because the date picker Id for its pair is NOT defined in the reducer.
For example, in projectsReducer I may use the date picker as part of a pair so both startDatePicker and endDatePicker are defined and everything works fine.
But I may be using a single instance date picker in the tasksReducer which also responds to the SET_DISABLED_DATES call but it fails because it cannot find the endDatePicker. In this scenario, the tasksReducer is responding to the call I made to set the disabledDates property of endDatePicker in projectsReducer.
I've posted two questions about this already and the only real solution I'm seeing here is that I need to have a condition in my reducer that looks like this:
...
case types.SET_DISABLED_DATES:
if(typeof state.datePickers[action.datePickerId] !== "undefined") { // Making sure that what I'm trying to update exists in the reducer
return Object.assign({}, state,
datePickers: Object.assign({}, state.datePickers, {
datePickers[action.datePickerId]: Object.assign({}, state.datePickers[action.datePickerId], {
disabledBefore: action.value
})
})
} else {
return state;
}
Admittedly, this looks a bit like a kludge but I couldn't really come up with another solution here.
Again, the problem is that for as long as all reducers respond to SET_DISABLED_DATES, it's guaranteed that a particular date picker will not be there and the Object.assign() will throw an error.
Any suggestions? Is the simple condition in the reducer the way to go here? Is it a kludge?
P.S. I tried this code and it works fine and fixes the problem. On the one hand, I feel this is a bit of an anti-pattern but on the other hand, it just seems like a good idea to make sure the property I want to update in my reducer exists before attempting to update it. I'd appreciate your feedback on this. Thanks.
You are just doing basic validation in the reducer before setting the state. That is perfectly fine. I don't think it will be a good practice to check the store in the action creator to prevent dispatching actions on objects not in the store (how would you do that anyway!).
What I don't understand is, how can a datepicker be linked to another datepicker that isn't in the store? Maybe dispatch a create and teardown action on the component's didMount and willUnmount?
I don't know your full requirements but I think we can make it a lot simpler. I'd do something like this:
The store:
{
datePickers: {
id1: {
value: '',
minValue: '',
maxValue: '',
},
id2: {
value: '',
minValue: '',
maxValue: '',
}
}
}
Now, unless you are making some kind of coupled datepicker components that will always behave in pairs, I believe the cleanest approach would be to set the disabled value in the linked datepicker in the mapDispactchToProps function in your parent component.
That is where you would set ids to the components, and you know exactly which component should be disabled before another.
Something like:
dispatch => ({
setArrivalDate(value) {
dispatch(datePickerActions.setValue(arrivalDateId, value);
dispatch(datePickerActions.setMaxValue(depatureDateId, value);
},
setDepatureDate(value) {
dispatch(datePickerActions.setValue(depatureDateId, value);
dispatch(datePickerActions.setMinValue(arrivalDateId, value);
}
})
This may not be abstract enough, but is clean.
You could do the same thing if you have a paired component, but you'd still need to know which date comes before another. It'd be a hassle to make a generic abstraction around it.
Remove the bold part in your code below
...
case types.SET_DISABLED_DATES:
if(typeof state.datePickers[action.datePickerId] !== "undefined") { // Making sure that what I'm trying to update exists in the reducer
return Object.assign({}, state,
datePickers: Object.assign({}, state.datePickers, {
datePickers[action.datePickerId]: Object.assign({}, state.datePickers[action.datePickerId], {
disabledBefore: action.value
})
})
} else {
return state;
}
Also, a little bit of es6 spread and a helper switchcase function makes this code much more readable.
const newReducer = (state = defaultState, action) => switchcase({
[types.SET_DISABLED_DATES]:
state.datePickers[action.datePickerId] === undefined
? state
: ({ ...state,
datePickers: { ...state.datePickers,
[action.datePickerId]: { ...state.datePickers[action.datePickerId],
disabledBefore: action.value,
},
},
}),
})(state)(action.type);
Using lodash/fp/set, the code becomes
const reducerWithLodash = (state = defaultState, action) =>
switchcase({
[types.SET_DISABLED_DATES]:
state.datePickers[action.datePickerId] === undefined
? state
: set({...state}, `datePickers.${action.datePickerId}.disabledBefore`, action.value)
})(state)(action.type)
I haven't tested the lodash version, so please take that with a grain of salt (Dan Abramov seems to approve)
I'm a bit confused on how I'm supposed to use selectors after I've normalized my Redux store.
I have the following setup for my store:
const DEFAULT_STATE = {
allId: [],
locations: {}
};
With the following for my reducer:
handleActions({
['UPDATE']: (state, action) => {
let newID = state.allId.length;
const allId = [...state.allId, newID];
const locations = {
...state.locations,
[newID]: action.payload
};
return {allId, locations};
}
}),
...
I figured I would want something like this for my component:
function mapStateToProps(state) {
return {
share: callMySelector(state)
};
}
But I don't see how my selector would do anything except return the location associated with the most recent ID. I'm thinking that normalizing is also not that great here - because I wouldn't end up searching by ID in a regular case.
The power of selectors is that it moves filtering logic away from the component consuming the data, and away from the actions/reducer into reusable functions. You mentioned getting the most recent location. From the update logic in the reducer, we'd just make a selector that grabs the last item.
function selectLatestLocation(state) {
const latestId = state.allIds[state.allIds.length - 1];
return state.locations[latestId];
}
This assumes the location data is structured with the location id as the key.
{
1: { id: 1, name: "USA" },
2: { id: 2, name: "Europe" }
}
In this case, normalizing the data isn't doing much. But let's say requirements change, and now we only want Europe locations. We could have another state property called europeIds that contains all Europe location ids.
function selectEuropeLocations(state) {
return state.europeIds.map(id => state.locations[id]);
}
Using selectors with normalized Redux state make it really easy to change how data is filtered. Now like you said, some cases might not need to be normalized. It is really up to the project, and what is being accomplished. But, it's definitely worth it for data that needs to be memoized, or filtered in different ways!
I'm using server side rendering for my React-Redux application. And I want at application startup to load some constants, for example list of cities with corresponding IDs:
[
{
name: "London",
id: 1
}
...
]
I think it's better to put this data into store on server side and provide it to client using window.__INITIAL_STATE__ as suggested here http://redux.js.org/docs/recipes/ServerRendering.html
This constants will be read-only, and I want to preload them just for data normalization purposes. For example later I can just retrieve list of users with city IDs, instead of users with city names: {name: "Alex", cities: [1,2]}
The problem is that if I put them into store, then I forced to create reducer for this constants, otherwise I'm getting this error:
Unexpected key "cities" found in preloadedState argument passed to
createStore. Expected to find one of the known reducer keys instead:
"colors". Unexpected keys will be ignored.
So I'm searching for some elegant way to handle this situation.
For now I have 2 ideas how to handle it:
Create empty reducer which always will return default state
export const cities = (state = [], action={ type: null }) => {
return state
}
Send from server initial actions with payloads, and execute them on client at startup:
// server.js
window.INITIAL_ACTIONS = [
{ type: "REQUEST_LOGIN_SUCCESS", user: {userId, userName, userEmail, etc} },
{ type: "REQUEST_CITIES_SUCCESS", [..listOfCities] },
]
And in my client-index.js, dispatch those actions right after creating the store:
//client-index.js
window.INITIAL_ACTIONS.forEach(store.dispatch)
So, is one of my approaches is good? Or may be you know some other, more elegant solution?
Thanks.
We do something similar with a dummy "settings" reducer. i.e.
const rootReducer = combineReducers({
...
settings: (state = {}) => state,
...
});
This gives us a convenient place to store all our app config.
Just make sure you key your initial state in the same manner. i.e.
window.__INITIAL_STATE__ = {
...
settings: { ... },
...
};
Some may object to this practise, but I think it's sound. Though settings may be constant, it is nonetheless state. It conforms to the redux practice of a single state object. (Besides, there may come a future point where the settings state slice will be dynamic and require a "real" reducer.)
I am used to computed properties in Ember Object Model. It's a convenient way to specify computed properties that depend on other properties.
Say fullName depends on firstName and lastName, I can setup computed properties as a function computeProperties and call computeProperties each time I make a change.
Example:
function computeFullName(state) {
const fullName = state.get('firstName') + state.get('lastName');
const nextState = state.set('fullName', fullName);
return nextState;
}
function computeProperties(state) {
const nextState = computeFullName(state);
return nextState;
}
// store action handler
[handleActionX](state) {
let nextState = state.set('firstName', 'John');
nextState = state.set('lastName', 'Doe');
nextState = computeProperties(nextState);
return nextState;
}
Is there a way to automatically setup computed properties so that I don't have to call extra functions each time. In Redux or in ImmutableJS.
Redux author here!
Using reselect as suggested by WildService is the way to go. I think we won't include this in core because reselect does its job well and we're fine with it being a separate library.
I wanted to note a couple of things:
Even with reselect, you don't want to compute data inside your reducer. Selectors should operate on the state managed by reducers. In other words, selectors are the step between your Redux store state and your components—they are not inside your reducers. It is essential you keep Redux state normalized so it's easy to update.
We actually encourage you to define selectors alongside the relevant reducers, so that when you change the state shape, you don't have to change your components—they would be using the selectors instead. You can see an example of this in the Redux folder of Flux Comparison
We have a documentation page introducing reselect and describing how to use it for computing derived data. Check it out.
Check out reselect. Composable pure functions for efficiently computing derived data from stores. Afaik there are plans to roll reselect's selectors into Redux core at some stage if they prove popular. There's an example for usage with ImmutableJS at the bottom of the readme too.
To create computed properties you can use the standalone observable library mobservable.
var user = mobservable.props({
firstName: 'John',
lastName: 'Doe',
fullName: function() {
return this.firstName + this.lastName
}
});
var nameViewer = mobservable.ObservingComponent(React.createClass({
render: function() {
return (<span>{user.fullName}</span>)
}
});
That should be the gist of it, now any change to user.firstName or lastName will rerender your nameViewer component. You can further combine this with flux implementations like redux to change the data, and push the user itself through your component tree. But note that the user object itself is not immutable (in that case it wouldn't be observable after all ;-)) Also see this trivial and slightly more interesting fiddles for some examples.
What about something like this?
export const getWidgetsWithComputedProps = widgets => {
return widgets.map(w => getWidgetWithComputedProps(w));
};
export const selectWidgetType = widget => {
switch (widget.type) {
case 'line':
return 'time-series';
case 'pie':
case 'bar':
return 'cross-sectional';
default:
console.warn('Back up: that type of widget does not exist!', widget.type);
return null;
}
};
export const getWidgetWithComputedProps = createSelector(
widget => widget,
selectWidgetType,
(widget, _type) => {
return {...widget, _type}
}
);