About useSelector equality check - reactjs

I have a question about redux useSelector equality check.
Refer to the React Redux Hook document (https://react-redux.js.org/api/hooks#equality-comparisons-and-updates)
useSelector will do reference comparison to the return value and the previous value, force re-render if result appears to be different
I have a default blog store state like this
searcherParam: {
keyword: '',
categories: [],
tags: [],
},
In the component, I use useSelector to retrieve the value
const searchParam = useSelector(state => state.Blog.searcherParam)
If I dispatch an action to update searcherParam with same value, the component will re-render because the return value is object (shallow compare)
So I retrieve the value by calling useSelector multiple times
const keyword = useSelector(state => state.Blog.searchrParam.keyword)
const categories = useSelector(state => state.Blog.searchrParam.categories)
const tags = useSelector(state => state.Blog.searchrParam.tags)
And I dispatch an action to update searcherParam with same value again
the component will not re-render
The point I can't understand is why component doesn't re-render ?
If useSelector do reference comparison, the categories value (array) should not be the same reference and the tags as well after dispatching
Do I have any misunderstanding ? Thanks
The reason about not re-rendering is because I save the categories (from redux store) via useState.
and use the useState's value to dispatch, so it is same reference...
here is the codesandbox, sorry for stupid question Q_Q
https://codesandbox.io/s/useselector-test-u6pvg

So when you do this :
const newKeyword = useSelector(state => state.Blog.searchrParam.keyword)
const newCategories = useSelector(state => state.Blog.searchrParam.categories)
const newTags = useSelector(state => state.Blog.searchrParam.tags)
This simply means that, you would want to have the latest value of each of them.
It does not translate to this :
searcherParam: {
keyword: newKeyword,
categories: newCategories,
tags: newTags,
},
Because searcherParam will always be a new reference when the action is dispatched.
If you just want to re-render, if any of the 3 properties change, then you can simply fetch state.searcherParam and dispatch and achieve a re-render.
Fetching individual properties help when you would want to re-render only on keyword change. You do not want to re-render when either of cateogories or tags have changed.
Note in this case if you just fetch searcherParam, it would not care which of 3 properties have changed, you will get a re-render because it is a new reference.
This is what the docs have mentioned.
After discussion in comments.
Made a trivial implementation. Check below

Rather than selecting the above properties separately, they all can be obtained via deconstructing the searchrParam object:
const { newKeyword, newCategories, newTags } = useSelector(
state => state.Blog.searchrParam
)

Since you trying to add same value to the store and also you are referring directly from the store (state.Blog.searchParam), your code below will not cause re-render.
const searchParam = useSelector(state => state.Blog.searcherParam)
In order to make it to re-render, you can try below
const searchParam = useSelector(state => ({keyword: state.keyword, categories: state.categories, tags: state.tags}))
The above code will always try to re-render, since the returned object reference will not be same.

Related

React - state not matching initial state [duplicate]

I'm refactoring to use Hooks and I've hit a very confusing wall
I have a basic functional component like so:
export const MakeComponent = props => {
const { path, value, info, update } = props;
const [val, setVal] = useState(value);
console.log(value, val); // abc undefined
return (...)
}
The log returns abc undefined - i.e. value in props is definitely defined, but the first argument returned from useState(value) is undefined
Just to test that hooks were working at all, I tried useState("abc") and that logs abc as expected.
I have no idea what I'm doing wrong - any ideas?
React version: 16.8.6
EDIT here is the parent component - nothing fancy going on here as far as I can see!
<MakeComponent
path={key}
value={item[key]}
info={value}
update={updateDiffs}
/>
As it is alluded to in the comments, useState(initialState) (or whatever you call it) will only use initialState on the first render.
During the initial render, the returned state (state) is the same as the value passed as the first argument (initialState).
(React Docs, emphasis mine)
After this, on re-renders the useState function does not change the state based on new changes to the props passed in.
To make changes reflected everytime value changes, register an effect on the input prop like so
export const MakeComponent = props => {
const { path, value, info, update } = props;
const [val, setVal] = useState(value);
useEffect(() => { setVal(value)}, [value] )
return (...)
}
Note that just setting state based on the props changing is a bit of an anti-pattern, as MonsterBucket notices you could just rely directly on the props changing to trigger a re-render:
export const MakeComponent = props => {
const { path, value, info, update } = props;
const [val, setVal] = useState(value);
if (val !== value) { // don't update unnecessarily
setVal(value);
}
return (...)
}
And instead reserve useEffect for side effects, mostly those outside of the React render cycle.
To see examples of these, have a look as these ReactJs docs - You might not need an effect, which covers lots of other examples.
Here you have to add useEffect if you want to update the state on props change, which will listen to prop change & will update the state value accordingly
here is the updated snippet
export const MakeComponent = props => {
const { path, value, info, update } = props;
const [val, setVal] = useState(value);
useEffect(() => { setVal(value)}, [value] )
return (<div>{val}</div>)
}
Attching sandbox link
https://codesandbox.io/s/confident-agnesi-ohkq7?file=/src/MakeComponent.js
By the time you pass the prop value to useState the value of it can be yet to set. value itself might have been undefined yet.
Also setState is not truly sync so if the useState uses same mechanism as setState your state val might not be set to value yet as the initial value.
In such cases, using props as the initial values to states, you should use a side effect with no dependency. Once the first render achieved the effect will run and set your state with prop. You can let the initial value of the component be undefined passing nothing with no problems.
export const MakeComponent = props => {
const { path, value, info, update } = props;
const [val, setVal] = useState();
// get initial state after initial render
useEffect(() => {
setVal(value)
}, [])
console.log(value, val); // abc undefined then will log abc abc
return (...)
}
Just keep in mind that props in React are meant to be read-only, state is for read and write. But it is perfectly fine, and no not an anti pattern, if you use a prop just as an initial value for a state and use the state you have set with the prop after that instead of the prop. That is for consistency since you might have two different values at a time from a prop and a state in circumstances.
Your case might need to care for the value of the prop at an arbitrary time depending on you needs as stressed in one of the above answers. You question does not state that. Still, if so, you can add the prop to the dependency array of the effect that sets the state with that prop, and yes write separate effects for other props you want the same, well, effect.
If you don't need writing for that data you do not need that pattern at all, just use prop and it will be up to date and you will have consistency. But you apparently do so I hope the pattern I suggest above works for you as it does for me.

Basis of two props wanted to update single state value without useEffect

What is the best way to update single state value, if specific two props changes, without useEffect, since performance point of view we should not be setting state inside useEffect.
For Example:
const NestedList = ({childItems, parentItems, selected, ...rest}) => {
const [currentItems, setCurrentItems] = useState({}); //assume this could be hash object
// Wrong X
useEffect(() => setCurrentItems(parentItems), [parentItems])
// Wrong X
useEffect(() => setCurrentItems(childItems), [childItems])
return ...
React component updates on both State change and Prop change. So you don't have to do anything additonal
Try below in single use effect. You can also compare previous props with new props.
useEffect(() => {
setCurrentItems(whateverthevalueis)
}, [prop1,prop2]);
What i try to figure out is the why in this case.
Why do you need a state if you always want to change it?
Why do the two props replace each other in the state?
There is nothing wrong with setting the state in a useEffect as alot of people already have said. However useEffect should be used for side effects.
There is no golden solution for all cases.
Use a state if you set the intial state with props and then the component update the state itself.
const processItems = (childItems, parentItems, ) => {
// Do what you want to create one state
return childItems
}
const NestedList = ({childItems, parentItems, selected, ...rest}) => {
// use props to set the inital state
const [currentItems, setCurrentItems] = useState(processItems(childItems, parentItems))
// We can later mutate the state, however if the props change we do not care
If the parent always pass the state as a prop, you do not need a state since the state is handled higher up.
const NestedList = ({childItems, parentItems, selected, ...rest}) => {
// No need for a state here, we can just use the props directly and
// the parent will pass the updated props when the state changes
If the parent pass several props and you need to calculate stuff based on the props, use memo hook.
const NestedList = ({childItems, parentItems, selected, ...rest}) => {
const currentItems = useMemo(() => {
// Add the condition of what items to show here
if(parentItems.length > 0) return parentItems
return childItems
}, [childItems,parentItems])
return ...

Redux createSelector doesn't appear to memoize results

I've been reading the docs on createSelector and i'm under the impression that it's supposed to memoize the results to avoid expensive calculations. However, for some reason my console.logs are firing in functions that should'nt be firing. Curious why that is, am I doing something wrong? By the time I reach this specific component, the data already exists in the store in the same shape so it should be memoized.
note: useAppDispatch and useAppSelector are the same as the defualts, they're just ts-typed in a central location.
in my component:
const dispatch = useAppDispatch();
const rawTranscript = useAppSelector(selectRawTranscript);
const parsedTranscript = useAppSelector(selectParsedTranscript);
const parsedResult = useMemo(() => {
// rawTranscript should be memoized ...
return transcriptParser(rawTranscript, undefined, 0.9);
}, [rawTranscript]);
useEffect(() => {
// this also fires, I initially had the parsed result here but the same behavior happened.
// const parsedResult = transcriptParser(rawTranscript, undefined, 0.9);
dispatch(updateParsedTranscript(parsedResult));
}, [dispatch, parsedResult]);
in my redux slice:
// selectRawTranscript should be memoized (createSelector is used)
export const selectRawTranscript = createSelector(
(state: RootState) => state.transcript.rawTranscript,
rawTranscript => rawTranscript
);
export const selectParsedTranscript = createSelector(
(state: RootState) => state.transcript.parsedTranscript,
parsedTranscript => parsedTranscript
);
Your selectors really aren't "memoizing" anything. Any time you have a use of createSelector where the output selector is just x => x, there's no real memoization happening. This is exactly equivalent to a plain function that is just state => state.x - you're just returning the field as-is.
Based on the code you've shown so far, the component will re-render any time the state.transcript.rawTranscript or state.transcript.parsedTranscript fields are updated by the reducer, and the effect will re-run every time the state.transcript.rawTranscript field is updated.
I'd suggest reading through the recently updated Redux docs page on selectors to get a better idea of when, how, and why to write memoized selector functions.
Also, having the effect depend on the parsed transcript, and then also dispatch an action that looks like it's saving the parsed value, seems a bit odd and with potential to cause extra re-renders.
Beyond that

How to prevent extra hook calls on redux state change?

I have a custom hook: useNavigation(). It returns the target route to redirect the user if it is required.
Inside the hook I use redux store (only to read values):
{ prop1, prop2 } = useTypedSelector(state => state.data);
Where useTypedSelector code is:
const useTypedSelector: TypedUseSelectorHook<RootState> = useSelector;
Where TypedUseSelectorHook and useSelector hooks come from react-redux and RootState is a type of my root reducer.
I have the following actual result:
If redux store changes, then the change triggers unexpected useNavigation hook call.
But my expected result is:
useNavigation hook uses redux store to read values, but they don't triggers useNavigation hook call on change.
How to prevent extra hook calls on redux state change?
You always want to select the minimum amount of data that you need with useSelector. Any changes to the value that is returned from useSelector will trigger a re-render.
You can make it marginally better by selecting prop1 and prop2 separately so that changes to other properties in state.data don't trigger re-rendering.
const prop1 = useTypedSelector(state => state.data.prop1);
const prop2 = useTypedSelector(state => state.data.prop2);
But I suspect that you can make it much better by moving some of the logic that is happening in your hook into a custom selector function instead. You may want to use reselect to memoize your selector.
const selectTargetRoute = (state: RootState) => {
const {prop1, prop2} = state.data;
// do some logic
// return a route or null/undefined
}
const useNavigation = () => {
const targetRoute = useTypedSelector(selectTargetRoute);
// do something with the route
}

React - useState not setting initial value

I'm refactoring to use Hooks and I've hit a very confusing wall
I have a basic functional component like so:
export const MakeComponent = props => {
const { path, value, info, update } = props;
const [val, setVal] = useState(value);
console.log(value, val); // abc undefined
return (...)
}
The log returns abc undefined - i.e. value in props is definitely defined, but the first argument returned from useState(value) is undefined
Just to test that hooks were working at all, I tried useState("abc") and that logs abc as expected.
I have no idea what I'm doing wrong - any ideas?
React version: 16.8.6
EDIT here is the parent component - nothing fancy going on here as far as I can see!
<MakeComponent
path={key}
value={item[key]}
info={value}
update={updateDiffs}
/>
As it is alluded to in the comments, useState(initialState) (or whatever you call it) will only use initialState on the first render.
During the initial render, the returned state (state) is the same as the value passed as the first argument (initialState).
(React Docs, emphasis mine)
After this, on re-renders the useState function does not change the state based on new changes to the props passed in.
To make changes reflected everytime value changes, register an effect on the input prop like so
export const MakeComponent = props => {
const { path, value, info, update } = props;
const [val, setVal] = useState(value);
useEffect(() => { setVal(value)}, [value] )
return (...)
}
Note that just setting state based on the props changing is a bit of an anti-pattern, as MonsterBucket notices you could just rely directly on the props changing to trigger a re-render:
export const MakeComponent = props => {
const { path, value, info, update } = props;
const [val, setVal] = useState(value);
if (val !== value) { // don't update unnecessarily
setVal(value);
}
return (...)
}
And instead reserve useEffect for side effects, mostly those outside of the React render cycle.
To see examples of these, have a look as these ReactJs docs - You might not need an effect, which covers lots of other examples.
Here you have to add useEffect if you want to update the state on props change, which will listen to prop change & will update the state value accordingly
here is the updated snippet
export const MakeComponent = props => {
const { path, value, info, update } = props;
const [val, setVal] = useState(value);
useEffect(() => { setVal(value)}, [value] )
return (<div>{val}</div>)
}
Attching sandbox link
https://codesandbox.io/s/confident-agnesi-ohkq7?file=/src/MakeComponent.js
By the time you pass the prop value to useState the value of it can be yet to set. value itself might have been undefined yet.
Also setState is not truly sync so if the useState uses same mechanism as setState your state val might not be set to value yet as the initial value.
In such cases, using props as the initial values to states, you should use a side effect with no dependency. Once the first render achieved the effect will run and set your state with prop. You can let the initial value of the component be undefined passing nothing with no problems.
export const MakeComponent = props => {
const { path, value, info, update } = props;
const [val, setVal] = useState();
// get initial state after initial render
useEffect(() => {
setVal(value)
}, [])
console.log(value, val); // abc undefined then will log abc abc
return (...)
}
Just keep in mind that props in React are meant to be read-only, state is for read and write. But it is perfectly fine, and no not an anti pattern, if you use a prop just as an initial value for a state and use the state you have set with the prop after that instead of the prop. That is for consistency since you might have two different values at a time from a prop and a state in circumstances.
Your case might need to care for the value of the prop at an arbitrary time depending on you needs as stressed in one of the above answers. You question does not state that. Still, if so, you can add the prop to the dependency array of the effect that sets the state with that prop, and yes write separate effects for other props you want the same, well, effect.
If you don't need writing for that data you do not need that pattern at all, just use prop and it will be up to date and you will have consistency. But you apparently do so I hope the pattern I suggest above works for you as it does for me.

Resources