How does Redux useSelector affect on react component rendering? - reactjs

I don't understand how does my component Word will rerender. I have a redux state keeping my {history: {letters}} state. So the question is: If {letters} are passed into useEffect deps array, will my component Word rerender if {words} property is changed?
`
function Word() {
const { history: {letters, words} } = useAppSelector(state => state)
useEffect(() => {
}, [letters])
return (
<div>
</div>
)
}
`
I expect my component rerender only if letters are changed.

You are maintaining a state using Redux. So, the component re-renders if any state used in the component itself is changed. In your case, your Word component will re-render if letters or words or both got changed. That's how it works.
BTW, your useEffect should only be triggered upon any change in letters only since you have included only letters in its dependency array.
If you want to optimize the performance, you can memorize things using useMemo and useCallback wherever necessary. Pass the dependencies correctly to recalculate them only upon required state changes.

UseSelector will already rerender if the selected part of the state has change. you dont have to add [letters] on useEffect
UseSelector is subscribed to the state and if detects any change, it calls checkForUpdates
subscription.onStateChange = checkForUpdates
and checkForUpdates checks if the reference of the state changed, if it did. it calls forceRender function so your component renders.
github useSelector
useEffect is used to dispatch actions to populate the state before the component first renders so useSelector will have access to newly populated the state

Related

useSelector can not be called inside a callback

I'm very new with redux / toolkit.
I need fetch data using the redux hook useSelector in my react functional component but the data isn't quickly ready so it seems that useSelector will return an empty array at first , then when the data is ready , it will return the popuplated array of data.
At first , i thought useSelector could be asynchronous but it's not. Redux re-render my component each time the data i'm looking for gets its value changed and that's not suitable for my functional needs. In some cases i don't want my component to re-render (espcially when only just an atomic part of the data changes, that strangely re-render all my component as well).
What i tried first is to make my data as part of my component's state so i can decide when i want it to be updated, i made the call to useSelector inside useState as it accepts a callback
const [data, setData] = useState(()=> useSelector(...))
but i got the error :
useSelector can not be called inside a callback
Any suggestions ?
You can use useEffect.
const selectorData = useSelector(...);
const [data, setData] = useState(selectorData);
useEffect(() => {
setData(selectorData)
}, [selectorData])
If you do it like that, component doesn't re-render every time, it will just re-render when your selector data came.

react render state child component vars not updated?

I have a component that is being called, then called again with updated information, but the var topics is not being updated by react. Can someone explain why the first example does not update on the screen, but the second one does? Does it have something to do with the useState call?
App.js calls DisplayQuestions once during initial render, then the page takes in user input and re-renders.
<DisplayQuestionsByTopic topics={topics} />
.....
topics var is NOT updated on screen:
export function DisplayQuestionsByTopic(props) {
console.log(`DisplayQuestionsByTopic`, props.topics)
const [topics, setTopics] = useState(props.topics) //<<<<<<<<
render(<h1>topics.join(',')</h1>)
}
topics var IS updated on screen:
export function DisplayQuestionsByTopic(props) {
console.log(`DisplayQuestionsByTopic`, props.topics)
render(<h1>props.topics.join(',')</h1>) //<<<<<<<<<<<
}
The argument passed to useState is only considered the first time the component renders - that is, on mount. On mount, the prop is put into the topics state. On further renders, changes to the prop don't result in a change to the state because the state has already been initialized to its initial value before.
While you could use useEffect to change the state in the child when the prop changes:
useEffect(() => {
setTopics(props.topics);
}, [props.topics]);
Unless you're using setTopics somewhere, it would make more sense just to render the prop, like you're doing in the final snippet.
If you do need to do setTopics in the child, consider passing down the state setter from the parent instead - that way, all the state is handled in the parent, and you don't have duplicate state holders in different components that need to be synchronized.

How to force an update for a functional component?

I'm learning redux and want to find out how useSelector updates a component, because the component doesn't have its own state.
I understand that useSelector() subscribes the component to the store, and when the store is updated, the component also updates.
Class components have this.forceUpdate(), but functional components don't have it.
How does one force-update a functional component?
You can simply do this
Add a dummy state that you can change to reliably initiate a re-render.
const [rerender, setRerender] = useState(false);
...
//And whenever you want to re-render, you can do this
setRerender(!rerender);
And this will re-render the component, since components always re-render on state change
The react-redux package relies on the rendering engine of react/react-dom to trigger the re-render of a given component that uses the useSelector hook.
If you take a look at the source of useSelector you can notice the use of useReducer:
const [, forceRender] = useReducer((s) => s + 1, 0)
As the name (forceRender) implies, redux uses this to trigger a re-render by react.
With v8 of react-redux the implementation of this mechanism changes but still relies on react-hooks for the re-render.
If you are curious how React handles re-renders, take a look at this excellent SO answer. It provides a great entry on the implementation details of how react-hooks are associated with the calling component.
I don't repeat Ryan here, but to sum it up:
The renderer keeps a reference to the component that is currently rendered. All hooks being executed during this render (no matter how deeply nested in custom-hooks they are) ultimately belong to this component.
So, the useReducer is associated with the component within which you called useSelector.
The dispatch function of useReducer triggers a re-render of this component (React either calls the render() method of a class-component or executes the function body of a functional component).
If you are curious how react-redux determines when it should force this re-render (by utilizing useReducer), take another look at the source code of useSelector.
Redux uses the subscriber-pattern to get notified of updates to the state. If the root-state of redux is updated the following things happen:
useSelector hooks in your application re-run their selector function
This re-selected state is compared to the previously selected state (by default via === comparison). The second argument to useSelector can be a comparison function to change this behavior
If the re-selected state differs from the previously selected state, a re-render is triggered via the useReducer hook.
The subscriber pattern is very react-like but potentially helps save many re-renders. Calling several useSelector hooks is cheap when compared with re-renders.
First of all, I want to mention that you don't need to do a force update when you use useSelector hook. Rerender will happen automatically whenever the selected state value will be updated.
But if you need to force update the functional component you can use this approach.
import React, { useState } from 'react';
//create your forceUpdate hook
function useForceUpdate(){
const [value, setValue] = useState(0); // integer state
return () => setValue(value => ++value); // update the state to force render
}
function MyComponent() {
// call your hook here
const forceUpdate = useForceUpdate();
return (
<div>
{/*Clicking on the button will force to re-render like force update does */}
<button onClick={forceUpdate}>
Click to re-render
</button>
</div>
);
}
I highly recommend avoiding the use of this hack, in 99% of issues you can resolve them without force update. But in any case, it's good to know that there is such a possibility in the functional component exists too.
Maybe something like this could help you:
In a Class Component you could pass a property like the one below...
<Element onSomethingHappen={
()=>{
if(shouldComponentUpdate())
this.forceUpdate();
}}/>
In the function component you can call the updater like this one:
function FunctionComponent(props){
//When you need it you can update like this one...
props.onSomethingHappen();
// Here you are ;) let me know if this helps you
}
Continuing on other answers, to keep your code clean you can create a dummy state and then set it in your own forceUpdate function:
const [helper, setHelper] = useState(false);
function forceUpdate(){
setHelper(!helper);
}
Now you can just call forceUpdate() in the rest of your code:
<div onClick={() => forceUpdate()} />

React Hooks - prevent re-render, but act on the new props

I have a component that displays a web map. I need to pass data in and out of the map component, but I never need to re-render it. I do all my work on the map outside of react - working directly with the mapping library.
Using functional components and hooks, how can I pass props in, act on them, and get data out of this component without rerendering? I've tried a combo of useEffect() and memo(), but they end up canceling out each other's use:
I have useEffect watching for changes of the relevant props and memo blocking renders, but since memo blocks renders, and useEffect only fires after a render, I have no way of handling prop updates in this component.
const Map = memo(props => {
useEffect(() => {
// run this on props update
console.log(props.selectedImg);
}, [props.selectedImg]);
//... map lib specific code using props
return <div id="mapDiv" />;
},
//prevent all rerenders using memo..
() => true
);
This doesn't work because memo prevents re-render and so useEffect is never fired even though props are updated.
How can I use hooks to work with props while entirely preventing this component from re-rendering? Perhaps this structure is not correct at all.
Thanks for the help!

How do I force a child component to rerender when given new props values?

I have tried this pattern.
ParentComponent
...
render(
return <ChildComponent newProps="newPropsValue />)
ChildComponent
...
ComponentWillReceiveProps{
this.setState({"propsKey": "newPropsValue"})
}
As far as I understand the initial component rendering is triggered by the props change, and as setState is asynchronous (for some reason), the rendering with the new state update is not done on the first pass.
However what I don't understand is why when it finally decides to update the state, it doesn't rerender the component. I thought state changes that are caused by setState always trigger a rerender.
So in the end I have a component that uselessly rerenders before the state is actually changed, and then does nothing when/if(?) the state is updated. I don't understand this behaviour at all.
setState will trigger componentUdpate -> componentWillUpdate -> render. props change will trigger componentWillReceiveProps before this chain. You can have a look here at this image about React lifecycle. You can see the different how React behave on props and state.
So:
However what I don't understand is why when it finally decides to update the state, it doesn't re-render the component.
Updating state by setState will trigger the render function (re-render). And props also trigger render as well.
Following your code:
componentWillReceiveProps:
this.props.newProps="newPropsValue"
this.state.propsKey="newPropsValue"
render: as above, nothing change.
If any event of childComponent setting propsKey by setState (onClick, onChange ...). Assuming setState({propsKey: "anotherValue"}). Then render will be triggered again with this.state.propsKey="anotherValue and this.props.newProps="newPropsValue"
Now let's update your childComponent's props within parentComponent, assuming newProps="latestPropsValue":
Before componentWillReceiveProps:
this.props.newProps="latestPropsValue"
this.state.propsKey="anotherValue"
After componentWillReceiveProps:
this.props.newProps="latestPropsValue"
this.state.propsKey="latestPropsValue"
How do I force a child component to rerender when given new props values?
If your render is using state then setState inside render. And if you are using props inside render, it also being updated accordingly
I have found a nice solution using key attribute. If we changed key property of a child component or some portion of React Component, it will re-render entirely. It will use when you need to re-render some portion of React Component or re-render a child component depending on props or state. Here is a example. I will re-render the full component.
import React, { useState, useEffect } from "react";
import { PrEditInput } from "./shared";
const BucketInput = ({ bucketPrice = [], handleBucketsUpdate, mood }) => {
const data = Array.isArray(bucketPrice) ? bucketPrice : [];
const [state, setState] = useState(Date.now());
useEffect(() => {
setState(Date.now());
}, [mood, bucketPrice]);
return (
<span key={state}>
{data.map((item) => (
<PrEditInput
key={item.id}
label={item?.bucket?.name}
name={item.bucketId}
defaultValue={item.price}
onChange={handleBucketsUpdate}
mood={mood}
/>
))}
</span>
);
};
export default BucketInput;

Resources