Difference between redux nested selector and non-nested selector - reactjs

I have seen many example codes of redux and found that we use useSelector in mostly 2 ways.
nested
import React from 'react'
import { useSelector } from 'react-redux'
export const UserComponent = () => {
const name = useSelector((state) => state.user.name)
const age = useSelector((state) => state.user.age)
return <div>{name}, {age}</div>
}
non-nested
import React from 'react'
import { useSelector } from 'react-redux'
export const UserComponent = () => {
const user = useSelector((state) => state.user)
return <div>{user.name}, {user.age}</div>
}
As you can see, with approach 2, I will end up saving lots of code repetition and I only need to create 1 selector.
So I wanted to know the difference, any performance benefits, etc.

There is a difference between these two approaches, let's dive deeper into the useSelector hook and see how it works. So let's say you're the creator of this hook and you define it as:
// this is your redux state
const state = { user: { name: 'Haseeb', age: 10 } };
// defining our own selector hook
const useSelector = (selectorFunction) => {
const accessedProperty = selectorFunction(state);
// accessedProperty will contain whatever is returned from the selectorFunction
return accessedProperty;
}
Now, let's use our hook
// nested, subscribe to just the name property in user
const name = useSelector((state) => state.user.name);
// non-nested, subscribe to the complete user object
const user = useSelector((state) => state.user);
Talking about the performance, When an action is dispatched, useSelector() will do a reference comparison of the previous selector result value and the current result value. If they are different, the component will be forced to re-render. If they are the same, the component will not re-render.
So in your nested approach, useSelector will only do a reference equality comparison of the name value. AND in your non-nested approach, useSelector will do a reference equality comparison of the complete user object. In both cases, the result of the comparison determines whether the component should re-render. Chances are the non-nested approach will cause more re-renders than the nested approach because the non-nested approach is subscribing to more values than the nested approach.
Call useSelector() multiple times, with each call returning a single field value but it is also okay to use one useSelector() if further performance optimizations are not necessary

Related

How to memoize useSelector() with createSelector()?

I wish to explain the memoization on a Redux store on my programming website.
What I have now on CodeSandbox:
import { useCallback, useMemo, memo } from "react";
import { useSelector, useDispatch } from "react-redux";
import { createSelector } from "reselect";
const Plus = memo(({ onIncrement }) => {
console.log("rendering <Plus>...");
return <button onClick={onIncrement}>+</button>;
});
export default () => {
console.log("rendering <App>...");
const v = useSelector((state) => {
console.log("useSelector() invoked");
return state;
});
const dispatch = useDispatch();
const increment = useCallback(() => dispatch({ type: "INCREMENT" }), [
dispatch
]);
return (
<div>
<span>{v}</span>
<Plus onIncrement={increment} />
</div>
);
};
As you can see, I have successfully memorized the dispatch() function with useCallback(). However, every time the button is clicked, useSelector() is called twice.
Is there a way to call it once only? I am thinking about memorization with createSelector() from the 'reselect' library. I don't understand that library. Could someone provide some guidance?
References:
Using memorizing selectors 2) The 'reselect' library
They are working correctly by having running the selectors 2 times. It would be easier and more obvious if you also print out the state (the counter) inside the selector.
Reference: https://react-redux.js.org/api/hooks#equality-comparisons-and-updates
Things will be this way:
A button and 0 are rendered on the UI.
You click the button, which dispatches an action.
The selector runs for the first time as it sees an action dispatched, it detects a change in the returned value, so it forces the component to re-render. Log is printed out with the NEW value (not the OLD one).
However, when an action is dispatched to the Redux store, useSelector() only forces a re-render if the selector result appears to be different than the last result. As of v7.1.0-alpha.5, the default comparison is a strict === reference comparison. This is different than connect(), which uses shallow equality checks on the results of mapState calls to determine if re-rendering is needed. This has several implications on how you should use useSelector()
The component re-renders, which triggers the the second log ("rendering ...").
That also leads to a re-run of the selector, which explains the third log.
When the function component renders, the provided selector function will be called and its result will be returned from the useSelector() hook. (A cached result may be returned by the hook without re-running the selector if it's the same function reference as on a previous render of the component.)

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 - reusable Redux hook slice issue (Redux Toolkit, Typescript)

I'm trying to create some hooks to fetch slices of the Redux store so that the store can't access directly to return anything.
If I useSelector directly in the component it works great and doesn't unnecessarily re-render:
*** Modal.tsx ***
const { isModalOpen } = useSelector((state: RootState) => state.carsSlice); // Works great. No unnecessary re-renders
If I create a hook to return the carsSlice then it unnecessarily re-renders the whole page. Why?
E.g.,
*** StateHooks.ts ***
const useGlobalState = () => useSelector((state: RootState) => state);
export const useCarsState = () => useGlobalState().carsSlice;
*** Modal.tsx ***
const { isModalOpen } = useCarsState(); // Re-renders underlying page and is significantly slower than above approach
I'm fetching the specific value from the state so I don't understand why it would re-render the whole page it is being used on? Is there a way to do this?
I've also tried the below but it still causes page re-render:
const useCarsState = useSelector((state: RootState) => state.carsSlice); // Same result
The only way it works as expected is the below BUT I want the custom hooks above:
const { isModalOpen } = useSelector((state: RootState) => state.carsSlice); // Works great
Thanks all.
you can use useShallowEqualSelector
It will do only rerender when the value from the store is changed.
Moreover, you can put this into a separate hook like:
import { TypedUseSelectorHook, useSelector, shallowEqual } from "react-redux";
import { RootState } from "store/rootReducer";
const useShallowEqualSelector: TypedUseSelectorHook<RootState> = (selector) =>
useSelector(selector, shallowEqual);
export default useShallowEqualSelector;
in this case, you are able to use this hook everywhere and you have access to the hints from typescript
useSelector works by doing reference comparisons of the value you return. If that value changes, it forces the component to re-render.
Because of that, you should never return the entire root state from `useSelector! That will cause the component to always re-render.
It doesn't matter what additional destructuring you do with the returned value later - what matters is what the selector itself returns.
That's why you should always select the smallest piece of state needed by a given component, to ensure that it only re-renders when it absolutely needs to because its data actually changed.

Mixing Redux with useEffect Hook

I read that this is theoretically OK. In my small use case, I'm running into an issue, where mixing those technologies leads to double re-rendering.
First, when redux dispatch is executed and some components use a prop via useSelector. Then, after the functional component is already re-rendered, a useEffect hook is being applied which updates the state of some property. This update re-triggers the render again.
E.g. the below console log prints out twice in my case.
Question: should I remove the useEffect and useState and integrate it into redux' store?
import {useSelector} from "react-redux";
import React from "react";
const Dashboard = (props) => {
const selFilters = useSelector((state) => state.filter.selectedDashboardFilters);
const [columns, setColumns] = React.useState([]);
React.useEffect(() => {
let newColumns = getColumns();
setColumns(newColumns)
}, [selFilters]
)
console.log("RENDER")
return (
<h1>{columns.length}</h1>
)
}
If columns needs to be recomputed whenever selFilters changes, you almost certainly shouldn't be recomputing it within your component. If columns is computed from selFilters, then you likely don't need to store it as state at all. Instead, you could use reselect to create a getColumns() selector that derives the columns from the state whenever the relevant state changes. For example:
const getColumns = createSelector(
state => state.filter.selectedDashboardFilters,
selFilters => {
// Compute `columns` here
// ...
return columns
}
)

How do I use react-redux 'useSelector' with an additional variable?

I have a redux (sub) state that consists of a large number of similar entries.
export type PartnerCalculatorStateShape = {
m16_19:number;
m20_24:number;
m25_34:number;
m35_44:number;
m45_64:number;
m65_plus:number;
f16_19:number;
f20_24:number;
f25_34:number;
f35_44:number;
f45_64:number;
f65_plus:number;
};
I am using the Redux Toolkit so my reducer is of this form (note that Redux Toolkit uses immutable update logic, so I can assign modify the state directly)
type PartnerCalculatorPayload = {
key:string;
value:number;
}
export const partnerCalculatorSlice = createSlice({
name: 'PartnerCalculator',
initialState,
reducers: {
partnerCalculatorValueReceived(state, action: PayloadAction<PartnerCalculatorPayload>) {
state[action.payload.key] = action.payload.value;
}
}
});
I'm a bit stuck on how to use useSelector. What I want to do is have a selector function in my Redux file, something like this
export const selectorFunction = (state,key) => state[key]
where key would be m20_24, for example. Then I would use that selector function in my React component
const myVar = useSelector(selectorFunction)
But how do I pass in the key?
The official hooks documentation recommends using closure to pass additional variables
import React from 'react'
import { useSelector } from 'react-redux'
export const TodoListItem = props => {
const todo = useSelector(state => state.todos[props.id])
return <div>{todo.text}</div>
}
However. useSelector will be in my React component and I want to keep the selector function I pass to useSelector inside my redux file, so I can't see how to use closure.
I suppose I could just pass the entire state out from my selector function
const selectorFunction = state => state
and then treat it as an object in my React component and key into it there
const myState = useSelector(selectorFunction)
const myVar = myState["m20_24"]
but that seems kind of ugly.
If that's the way to go, would myVar update anytime any of the fields in my Redux state changed? I'm a bit unclear as to how the useSelector equality testing mechanism works -- it says it uses 'strict equality', so if any part of my state object changed (that is, if the field 'm20_24' changed) then myVar would be updated?
Thanks for any insights!
Pass an anonymous selector to useSelector, and then call the actual selector inside of there:
const value = useSelector(state => selectValueByKey(state, props.key));

Resources