How to force an update for a functional component? - reactjs

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()} />

Related

How does Redux useSelector affect on react component rendering?

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

Trigger some API once react state empty

I want to trigger An API to do some actions when the value in the react state be empty, Is there a way in react state hook to achieve that?
If you are using a functional component you can use the "useEffect" hook with a proper dependency.
Class base components you might choose (if I understand your situation properly) something like the "componentWillUnmount()" method.
You could have a useEffect hook with the state as a dependency, in-which you can check if the state is empty and act accordingly.
Example:
const [state, setState] = useState([]);
useEffect(() => {
if (state.length !== 0) {
return;
}
// Can make your API call here (preferably call a function that does it)
// and then set the state.
setState(...);
}, [state]);
useEffect is a hook that accepts a callback as the first argument, and a dependency array as the second.
The callback will execute upon re-render whenever any of the dependencies in the array change.
Note: this is relevant only for functional components, for class-based component we have the componentDidUpdate lifecycle hook.

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.

Can't perform a React State update on unMounted child component?

Am getting this warning:
Can't perform a React state update on unmounted component. This is a no-op...
It results from a child component and I can't figure out how to make it go away.
Please note that I have read many other posts about why this happens, and understand the basic issue. However, most solutions suggest cancelling subscriptions in a componentWillUnmount style function (I'm using react hooks)
I don't know if this points to some larger fundamental misunderstanding I have of React,but here is essentially what i have:
import React, { useEffect, useRef } from 'react';
import Picker from 'emoji-picker-react';
const MyTextarea = (props) => {
const onClick = (event, emojiObject) => {
//do stuff...
}
const isMountedRef = useRef(true);
useEffect(() => {
isMountedRef.current = true;
});
useEffect(() => {
return () => {
console.log('will unmount');
isMountedRef.current = false;
}
});
return (
<div>
<textarea></textarea>
{ isMountedRef.current ? (
<Picker onEmojiClick={onClick}/>
):null
}
</div>
);
};
export default MyTextarea;
(tl;dr) Please note:
MyTextarea component has a parent component which is only rendered on a certain route.
Theres also a Menu component that once clicked, changes the route and depending on the situation will either show MyTextarea's parent component or show another component.
This warning happens once I click the Menu to switch off MyTextarea's parent component.
More Context
Other answers on StackOverflow suggest making changes to prevent state updates when a component isn't mounted. In my situation, I cannot do that because I didn't design the Picker component (rendered by MyTextarea). The Warning originates from this <Picker onEmojiClick={onClick}> line but I wouldn't want to modify this off-the-shelf component.
That's explains my attempt to either render the component or not based on the isMountedRef. However this doesn't work either. What happens is the component is either rendered if i set useRef(true), or it's never rendered at all if i set useRef(null) as many have suggested.
I'm not exactly sure what your problem actually is (is it that you can't get rid of the warning or that the <Picker> is either always rendering or never is), but I'll try to address all the problems I see.
Firstly, you shouldn't need to conditionally render the <Picker> depending on whether MyTextArea is mounted or not. Since components only render after mounting, the <Picker> will never render if the component it's in hasn't mounted.
That being said, if you still want to keep track of when the component is mounted, I'd suggest not using hooks, and using componentDidMount and componentWillUnmount with setState() instead. Not only will this make it easier to understand your component's lifecycle, but there are also some problems with the way you're using hooks.
Right now, your useRef(true) will set isMountedRef.current to true when the component is initialized, so it will be true even before its mounted. useRef() is not the same as componentDidMount().
Using 'useEffect()' to switch isMountedRef.current to true when the component is mounted won't work either. While it will fire when the component is mounted, useEffect() is for side effects, not state updates, so it doesn't trigger a re-render, which is why the component never renders when you set useRef(null).
Also, your useEffect() hook will fire every time your component updates, not just when it mounts, and your clean up function (the function being returned) will also fire on every update, not just when it unmounts. So on every update, isMountedRef.current will switch from true to false and back to true. However, none of this matters because the component won't re-render anyways (as explained above).
If you really do need to use useEffect() then you should combine it into one function and use it to update state so that it triggers a re-render:
const [isMounted, setIsMounted] = useState(false); // Create state variables
useEffect(() => {
setIsMounted(true); // The effect and clean up are in one function
return () => {
console.log('will unmount');
setIsMounted(false);
}
}, [] // This prevents firing on every update, w/o it you'll get an infinite loop
);
Lastly, from the code you shared, your component couldn't be causing the warning because there are no state updates anywhere in your code. You should check the picker's repo for issues.
Edit: Seems the warning is caused by your Picker package and there's already an issue for it https://github.com/ealush/emoji-picker-react/issues/142

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!

Resources