Can a Redux action affect multiple parts of the state tree? - javascript

What is the consensus on an action affecting multiple parts of the state tree in Redux?
For example:
const ADD_POST = 'POST/ADD';
function postsReducer(state = initialState, action = {}) {
// switch ...
case ADD_POST:
return {
...state,
...action.result.post
}
}
function anotherReducer(state = initialState, action = {}) {
// switch ...
case ADD_POST:
return {
...state,
post_id: action.result.post.id
}
}
I'm seeking advice on:
Actions affecting multiple parts of the redux store/state

Yes, absolutely. It’s the whole reason why actions exist: to separate what happened from the component’s point of view from what actually happens in terms of state change.

Yes, it's ok. If that's what you want to happen.

Related

How to prevent useSelector from causing unnecessary renders?

I'm using useSelector hook to retrieve the values of my reducer, but it is causing a lot of unnecessary renders on my application.
It doesn't matter which property I'm using on a component, since they are all getting the same state object from the reducer, every time one property changes, all components that use useSelector are rendered.
This is the reducer:
const initialState = {
productsDataState: [], // => this is populated by multiple objects
searchProducts: [],
isSearchOn: false,
inputValue: '',
listOrder: [],
toastify: ['green', ''],
toastifyOpen: false
}
const reducer = ((state = initialState, action) => {
switch (action.type) {
case actionTypes.UPDATE_PRODUCT:
return {
...state,
productsDataState: action.products,
listOrder: action.listOrder
}
case actionTypes.SET_TOASTIFY:
return {
...state,
toastify: action.toastify,
toastifyOpen: action.open
}
case actionTypes.SET_SEARCH:
return {
...state,
searchProducts: action.searchProducts,
isSearchOn: action.isSearchOn,
inputValue: action.inputValue
}
default:
return state
}
})
One of the components is using isSearchOn, which is a boolean, so I solved the problem checking if the value is true before rendering it:
const { isSearchOn } = useSelector(state => state.isSearchOn && state)
But that's not the case for all components. The one I'm stuck right now uses the productsDataState property, which is an array of objects. I can't just make a simple validation before returning state. I thought about storing the initial value in a useState, make a deep comparison between the current value and the past one before returning the state, which would work similarly to what I did in the other component, but I can't see how this would be a good approach.
const { productsDataState } = useSelector(state => state)
Is there a way where I could take advantage of useSelector without comprimising the performance of the application?
I've being reading a lot and making a lot of tests, but I couldn't find a good way to do that so far.
I'd like to keep useSelector, but I'm open to suggestions, even if it involves other libraries.
What you should be doing is not selecting whole state, just the part you need :)
const productsDataState = useSelector(state => state.productsDataState)
#edit
If you want to select multiple data with one selector you will cause it to change reference if you would try to use an object for example.
const { productsDataState, other } = useSelector(state => ({ productsDataState: state.productsDataState, other: state.other }))
this will cause rerender on any state change as redux use strict equality check by default.
You should listen to official documentation and select each state separately
Call useSelector() multiple times, with each call returning a single field value

How can I simplify my reducer function or how long should a simple reducer function be?

I have a reducer function whose sole purpose it to toggle a style on and off. This is a global style and this is why it is in my Redux store.
However, the code looks overly obtuse for such a simple toggle.
const Style = (state = {current: true}, action) => {
if(action.type === "toggleAppStyle"){
const newState = { ...state };
newState.current = !state.current;
return newState;
}
return state;
};
I recently realized that redux runs all reducer functions for each single action, which I find strange, so the returned state must equal the initial state if the action.type is not called for that particular reducer.
Here is the one place I use it:
// ... snip
const mapStateToProps = state => {
return {
Style: state.Style
}
}
// ... snip
render() {
return (
<img className = 'icon_all'
id = {this.props.Style.current === true ? 'icon_10' : 'icon_90'}
onClick = {this.clickHandler} src='/images/favicon-optimized.svg'/>
)
}
the code looks overly obtuse for such a simple toggle.
Overly obtuse is a bit much, this is a pretty standard immutable update pattern, i.e. shallow copy into new object reference and update the necessary properties.
It can't get much simpler than what you've already got, but here is an example returning the new object directly.
const Style = (state = {current: true}, action) => {
if (action.type === "toggleAppStyle") {
return {
...state,
current: !state.current,
};
}
return state;
};
I recently realized that redux runs all reducer functions for each
single action, which I find strange, so the returned state must equal
the initial state if the action.type is not called for that particular
reducer.
Nothing strange here really. The reducer function either acts on the action and operates on the state, thus returning a new state reference and value, otherwise it simply returns the current state value. Other than the action part this is exactly how React reconciliation works. When you update state you are creating new object references or primitive values. This is what triggers rerenders.
Of course, if your objective is to be as short and succinct as possible, you could reduce the state slice to just the boolean value and use a ternary operator to return the toggled state or current state value. Adjust your redux selectors accordingly.
const Style = (state = true, action) => action.type === "toggleAppStyle"
? !state
: state;

Redux reducer - on state change

I want to save the current state of a specific reducer into session storage without explicitly calling it on every action.
const { handleActions } = require("redux-actions");
// How do i make this logic run on any state change without adding it to every action?
const onStateChange = (state) => sessionStorage.save(keys.myReducer, state);
const myReducer = handleActions
({
[Actions.action1]: (state, action) => (
update(state, {
prop1: {$set: action.payload},
})
),
[Actions.action2]: (state, action) => (
update(state, {
prop2: {$set: action.payload},
})
)
},
//****************INITIAL STATE***********************
{
prop1: [],
prop2: []
}
);
Is there a way to catch the state change event for a specific reducer?
I know of store.subscribe, but that's for the entire store, i'm talking about listening to a specific reducer change
Thanks
Unfortunately you can't watch specific parts of your store because your store is actually an only reducer (combineReducers return a single big reducer). That said you can manually check if your reducer has changed by doing state part comparison.
let previousMyReducer = store.getState().myReducer;
store.subscribe(() => {
const { myReducer } = store.getState();
if (previousMyReducer !== myReducer) {
// store myReducer to sessionStorage here
previousMyReducer = myReducer;
}
})
You can use redux-saga to listen for specific actions and persist the store (and do your other logice) when they are fired. This seems like a good fit for your needs.
It's not exactly tied to specific store changes, but rather to a set of actions. However, I think this model (listening for actions, as opposed to state changes) is better suited to the redux design.
The nice thing is you don't have to change any code outside of the saga. I've used this one for autosaving form data and it worked great.
Here's an example of setting up a saga to persist on a few redux-form actions; note that it's not limited to persisting - you can do whatever you want during a saga.
function* autosaveSaga(apiClient) {
yield throttle(500,
reduxFormActionTypes.CHANGE,
saveFormValues,
apiClient);
yield takeLatest(
reduxFormActionTypes.ARRAY_PUSH,
saveFormValues,
apiClient);
yield takeLatest(
reduxFormActionTypes.ARRAY_POP,
saveFormValues,
apiClient);
}
If you want to listen to all action types, or do custom filtering of which actions fire your persistence code, you can pass a pattern, or a matching function to takeLatest:
yield takeLatest(
'*',
saveFormValues,
apiClient);
More on what you can pass to take, takeLatest etc can be found in the docs for take.

In redux, preloading state using with combineReducers

I am just getting introduced to redux, and am stucked with a problem of preloading the state for some time.
When using a single reducer, I was using the following code, and it used to work fine. Relevant snippets::
const head = (state = {}, action) => {
switch (action.type) {
case 'TOGGLE_VISIBLITY':
if (state.head.content !== action.id) {
return state
}
state.body.visible = !state.body.visible;
return state;
default:
return state
}
}
const heads = (state = [], action) => {
switch (action.type) {
case 'TOGGLE_VISIBLITY':
state.body = state.body.map(t =>
head(t, action)
);
}
return state;
}
export const store = createStore(heads, config);
But instead this I just changed to combinerReducers, and it started thowing JS errors.
Unexpected keys "head", "body" found in preloadedState argument passed to createStore. Expected to find one of the known reducer keys instead: "heads". Unexpected keys will be ignored.
My change was::
const plannerApp = combineReducers({
heads
});
export const store = createStore(plannerApp, config);
In case you wanna check the full code,please visit here.
Any help is highly appreciable. Thanks a lot in advance.. I appreciate your time and efforts...
In a nutshell, the preloaded state needs to match the structure of your reducers. Since you switched to using combineReducers, your state tree structure has changed. You now have a top level key of heads that has a child key of body, so you probably need to update your config to look like:
export default {
heads: {
body: {
...
As it is now, the config object contains top level keys of head and body, which do not have entries at the top level of your state tree.

Call an action inside the redux reducer

The code in my reducer is as follows:
import {ADD_FILTER, REMOVE_FILTER} from "../../../../actions/types";
const removeFilter = (state, name) => {
return state.filter(f => f.name !== name);
};
export default function addRemoveFilterReducer(state = [], action) {
switch (action.type) {
case ADD_FILTER:
let nState = state;
if(state.some(i => i.name === action.searchFilter.name))
nState = removeFilter(state, action.searchFilter.name);
return [...nState, {name: action.searchFilter.name, values: [action.searchFilter.values]}];
//Call another action
case REMOVE_FILTER:
return removeFilter(state, action.searchFilter.name);
default:
return state;
}
}
I have one component showroom and inside the showroom I have Search component and Content component.
Inside search component I handle filtering and I dispatch an action which is handled with this reducer.
After the filter is added I want to dispatch an action with all filters. How can I do that?
That action would be handled with an reducer where I would return only those cars that match search criteria and display them in the content component.
I hope you understand what I wanted to say.
Is this approach good at all?
You may consider to use redux-thunk for this.
You'll have two separate actions, one for adding filter and the other one for making search. Your addFilterAndMakeSearch thunk will be responsible for calling these actions in order. By this way, you won't be need to dispatch an action from your reducer.
// Thunk
function addFilterAndMakeSearch(searchFilter) {
return dispatch => {
dispatch(addFilter(searchFilter);
dispatch(makeSearch());
}
}
// Action Creator One
function addFilter(searchFilter) {
return {
type: 'ADD_FILTER',
payload: searchFilter,
};
}
// Action Creator Two
function makeSearch() {
return {
type: 'MAKE_SEARCH',
};
}
In order to make this work, you need to use addFilterAndMakeSearch as your onSubmit handler.
Calling an action is most probably side effect operation. As reducers should follow functional programming principles, I would argue it shouldn't trigger any actions.
To solve your use case, you can still fire two actions from place that triggered change in your reducer. We are doing that sometimes in our app. For example your component can trigger two actions or action can fire two Redux store updates.

Categories

Resources