How to memoize useSelector() with createSelector()? - reactjs

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.)

Related

Why is the updated state variable not shown in the browser console?

I understand that the method returned by useState is asynchronous, however, when I run the this code, I am delaying the console.log by upto 5 seconds, but it still logs the previous value and not the updated value of the state variable. The updated value would be 2, but it still logs 1. In the react developer tools however, I can see the state changing as I press the button, though I am curious to know why after even such a delay the console prints an obsolete value? This is not the case with class components and setState but with function components and useState.
import "./App.css";
import React, { useState, useEffect } from "react";
function App() {
const [variable, setVariable] = useState(1);
const handleClick = () => {
setVariable(2);
setTimeout(() => {
console.log(variable);
}, 2000);
};
return <button onClick={handleClick}>Button</button>;
}
export default App;
In your code your setTimeout is getting the variable value from the closure at the time it was invoked and the callback function to the setTimeout was created. Check this GitHub issue for the detailed explanation.
In the same issue, they talk about utilizing useRef to do what you are attempting. This article by Dan Abramov packages this into a convenient useInterval hook.
State updates are asynchronous. That means, that in order to view the new value, you need to log It on the next render using useEffect and adding it to the dependencies array:
In this example, give a look at the order the logs appear:
First, you will have the current one, and once triggered, you will have the new value, and then it will become the 'old value' until triggered again.
import "./App.css";
import React, { useState, useEffect } from "react";
function App() {
const [counter, setCounter] = useState(0);
useEffect(() => { console.log(`new state rolled: ${counter}`);
}, [counter]);
console.log(`Before rolling new State value: ${counter}`);
const handleClick = () => setCounter(counter++)
return <button onClick={handleClick}>Button</button>;
}
export default App;
Another technic to console.log a value afterward a state change is to attach a callback to the setState:
setCounter(counter++, ()=> console.log(counter));
I hope it helps.
A state take some time to update. The proper way to log state when it updates, is to use the useEffect hook.
setTimeout attaches the timer and wait for that time, but it will keep the value of variable from the beginning of the timer, witch is 1
import "./App.css";
import React, { useState, useEffect } from "react";
function App() {
const [variable, setVariable] = useState(1);
const handleClick = () => {
setVariable(2);
};
useEffect(() => {
console.log(variable);
}, [variable]);
return <button onClick={handleClick}>Button</button>;
}
export default App;
This is not the case with class components and setState but with
function components and useState
In class components, React keep the state in this.state & then call the Component.render() method whenever its need to update due to a setState or prop change.
Its something like this,
// pseudocode ( Somewhere in React code )
const app = MyClassComponent();
app.render();
// user invoke a callback which trigger a setState,
app.setState(10);
// Then React will replace & call render(),
this.state = 10;
app.render();
Even though you cannot do this.state = 'whatever new value', React does that internally with class components to save the latest state value. Then react can call the render() method and render method will receive the latest state value from this.state
So, if you use a setTimeout in a class component,
setTimeout(() => {
console.log(this.state) // this render the latest value because React replace the value of `this.state` with latest one
}, 2000)
However in functional component, the behaviour is little bit different, Every time when component need to re render, React will call the component again, And you can think the functional components are like the render() method of class components.
// pseudocode ( Somewhere in React code )
// initial render
const app = MyFuctionalComponent();
// state update trigger and need to update. React will call your component again to build the new element tree.
const app2 = MyFunctionalComponent();
The variable value in app is 1 & variable value in app2 is 2.
Note: variable is just a classic variable which returned by a function that hooked to the component ( The value save to the variable is the value return by the hook when the component was rendering so it is not like this.state i.e its hold the value which was there when the component is rendering but not the latest value )
Therefore according to the Clouser, at the time your setTimeout callback invoke ( Which was called from app ) it should log 1.
How you can log the latest value ?
you can use useEffect which getting invoke once a render phase of a component is finished. Since the render phase is completed ( that mean the local state variables holds the new state values ) & variable changed your console log will log the current value.
useEffect(() => {
console.log(variable);
}, [variable])
If you need the behaviour you have in class components, you can try useRef hook. useRef is an object which holds the latest value just like this.state but notice that updating the value of useRef doesn't trigger a state update.
const ref = useRef(0);
const handleClick = () => {
setVariable(2); // still need to setVariable to trigger state update
ref.current = 2 // track the latest state value in ref as well.
setTimeout(() => {
console.log(ref.current); // you will log the latest value
}, 2000);
};

Difference between redux nested selector and non-nested selector

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

React useMemo firing when no change to Redux state detected

I have a state object in Redux and am using useSelector and deepEqual (from fast-deep-equal) to detect changes. Then useMemo on the current state. What's puzzling is that useMemo is being invoked even though the state seemingly hasn't changed.
Here is the structure of my code:
import deepEqual from "fast-deep-equal"
export function useMyState() {
const values = useSelector(
(store) => store.prop1,
(a, b) => { // comparison function arg to useSelector
const equal = deepEqual(a, b)
if (!equal) {
console.log("COMPARE FALSE")
}
return equal
}
)
useEffect(() => {
console.log("MOUNTED")
}, [])
return useMemo(() => {
console.log("VALUES CHANGED", JSON.stringify(values))
return {
values,
method1() {
// do something
}
}
}, [values])
}
I confirm that MOUNTED is only output once, and that VALUES CHANGED is output even when COMPARE FALSE is NOT output. How can this be...?
Maybe if component happens to be rendered as part of parent render it always gets the current object from store (which is not === the previous value) and useSelector compare fn is only used to prevent re-render of the component (in isolation) when state has not changed? If this is the case, what is the best-practice way to prevent useMemo being run when values are deep-equal?
codesandbox: https://codesandbox.io/s/redux-state-change-zdxzs
The issue occurs when there are two useSelector and one is triggered, causing a re-render. The component then thinks the result of the other useSelector has changed even though it has not according to the test in the selector itself (deepEqual).
To reproduce follow above link, click the increment button a few times - component is rendered but no change to todo items. Then click "re-create" button and increment again. Even though items have not changed according to deepEqual the text TODOS CHANGED! is output.
Thanks

useState vs useReducer

useReducer is usually preferable to useState when you have complex state logic that involves multiple sub-values or when the next state depends on the previous one. useReducer also lets you optimize performance for components that trigger deep updates because you can pass dispatch down instead of callbacks.
(quote from https://reactjs.org/docs/hooks-reference.html#usereducer)
I'm interested in the bold part, which states that useReducer should be used instead of useState when being used in contexts.
I tried both variants, but they don't appear to differ.
The way I compared both approaches was as follows:
const [state, updateState] = useState();
const [reducerState, dispatch] = useReducer(myReducerFunction);
I passed each of them once to a context object, which was being consumed in a deeper child (I just ran separate tests, replacing the value by the function that I wanted to test).
<ContextObject.Provider value={updateState // dispatch}>
The child contained these functions
const updateFunction = useContext(ContextObject);
useEffect(
() => {
console.log('effect triggered');
console.log(updateFunction);
},
[updateFunction]
);
In both cases, when the parent rerendered (because of another local state change), the effect never ran, indicating that the update function isn't changed between renders.
Am I reading the bold sentence in the quote wrong? Or is there something I'm overlooking?
useReducer also lets you optimize performance for components that
trigger deep updates because you can pass dispatch down instead of
callbacks.
The above statement is not trying to indicate that the setter returned by useState is being created newly on each update or render. What it means is that when you have a complex logic to update state you simply won't use the setter directly to update state, instead you will write a complex function which in turn would call the setter with updated state something like
const handleStateChange = () => {
// lots of logic to derive updated state
updateState(newState);
}
ContextObject.Provider value={{state, handleStateChange}}>
Now in the above case everytime the parent is re-rendered a new instance of handleStateChange is created causing the Context Consumer to also re-render.
A solution to the above case is to use useCallback and memoize the state updater method and use it. However for this you would need to take care of closure issues associated with using the values within the method.
Hence it is recommended to use useReducer which returns a dispatch method that doesn't change between re-renders and you can have the manipulation logic in the reducers.
Practical observation on useReducer and useState -
UseState:
In my React Native project I've 1 screen containing 25+ different states created using useState.
I'm calling an api in useEffect (componentDidMount) and on getting the response based on some conditions, I'm setting up these 25 states, calling 25 state setter function for each function.
I've put a re-rendering counter and checked my screen is re-rendered 14 times.
re-rendering count likewise :
let count = 0;
export default function Home(props) {
count++;
console.log({count});
//...
// Rest of the code
}
UseReducer :
Then I've moved these 25 states in useReducer states, And used only single action to update these states on API response.
I've observed there is only 2 re-render.
//API calling method:
fetchData()
{
const response = await AuthAxios.getHomeData();
dispatch({type: 'SET_HOME_DATA', data: response.data});
}
//useReducer Code:
const initialStaes = {
state1: null,
state2: null,
//.....More States
state27: null,
state28: null
}
const HomeReducer = (state, action) => {
switch (action.type) {
case 'SET_HOME_DATA': {
return {
...state,
state1: (Data based on conditions),
state2: !(some Conditions ),
//....More states
state27: false
}
}
}
}
Advantage of useReducer in this case :
Using useReducer I've reduced number of re-renders on the screen, hence better performance and smoothness of the App.
Number of lines is reduced in my screen itself. It improved code readablity.
When you need to care about it
If you create a callback on render and pass it to a child component, the props of that child will change. However, when the parent renders, a regular component will rerender (to the virtual dom), even props remain the same. The exception is a classComponent that implements shouldComponentUpdate, and compares props (such as a PureComponent).
This is an optimization, and you should only care about it if rerendering the child component requires significant computation (If you render it to the same screen multiple times, or if it will require a deep or significant rerender).
If this is the case, you should make sure:
Your child is a class component that extends PureComponent
Avoid passing a newly created function as a prop. Instead, pass
dispatch, the setter returned from React.useState or a memoized
customized setter.
Using a memoized customized setter
While I would not recommend building a unique memoized setter for a specific component (there are a few things you need to look out for), you could use a general hook that takes care of implementation for you.
Here is an example of a useObjState hook, which provides an easy API, and which will not cause additional rerenders.
const useObjState = initialObj => {
const [obj, setObj] = React.useState(initialObj);
const memoizedSetObj = React.useMemo(() => {
const helper = {};
Object.keys(initialObj).forEach(key => {
helper[key] = newVal =>
setObj(prevObj => ({ ...prevObj, [key]: newVal }));
});
return helper;
}, []);
return [obj, memoizedSetObj];
};
function App() {
const [user, memoizedSetUser] = useObjState({
id: 1,
name: "ed",
age: null,
});
return (
<NameComp
setter={memoizedSetUser.name}
name={user.name}
/>
);
}
const NameComp = ({name, setter}) => (
<div>
<h1>{name}</h1>
<input
value={name}
onChange={e => setter(e.target.value)}
/>
</div>
)
Demo

How to fetch data by existing redux action with hooks?

I'm trying to understand React Hooks. What I want is to fetch data inside functional component by call redux action with useEffect hooks.
I know that I can pass props to state like
const [todoList] = useState(props.todoList)
But what is the best practice to fetch data by existing redux actions?
In React class component i call this method to fetch data in componentDidMount() and everythink works.
import React, { useEffect } from 'react'
import { connect } from 'react-redux'
import { ITodo } from './types'
import { getTodos } from '../actions/todoActions'
interface IProps {
todoList: Array<ITodo>
getTodos: typeof getTodos
}
const Todos = (props: IProps) => {
useEffect(() => {
props.getTodos()
}, [props.todoList])
return (
<div>
{props.todoList.map((_) => (<div key={_.Id}>{_.Name}</div>))}
</div>
)
}
const mapStateToProps = (state) => ({
todoList: state.todo.todoList
})
const mapDispatchToProps = {
getTodos
}
export default connect(mapStateToProps, mapDispatchToProps)(ProdRoute)
I expected to get list of todos with props and props.getTodos() should call once like in componentDidMount() method. But actualy I get data and getTodos() are called over and over again but should be called once on component mount
Take care that if you pass [props.todoList] to the useEffect you are erroneously forcing a constant refresh because:
useEffect does an instance comparison (===) to know if props.todoList is changed
after the the very first render the props.getTodos() dispatcher is called
when the props.todoList will be updated the component is re-rendered
the useEffect call will receive [props.todoList] as a value to check if it needs to re-run or not
props.todoList is changed (it was empty and now it's valorized) and props.getTodos() is so re-called
redux updates the todoList with the same values but mutating the array reference
the component is re-rendered and the useEffect will check if the [props.todoList] param is been updated... but IT IS BEEN UPDATED because the previous props.todoList is different from the actual props.todoList, even if the content is the same
So, if you need to call the props.getTodos() just once can
use [props.todoList.length] instead of [props.todoList] as the second parameter for the useEffect call
use an empty array [] as the second parameter for the useEffect call (see the docs)

Resources