Why are State Updater Functions Faster Than Setting State to Plain Values? - javascript

For context, I have a web app that displays an image in a React-Bootstrap Container component (Arena) that holds an image where users are to look and find specific characters.
Separately, I created a div component (CustomCursor) where the background is set to a magnifying glass SVG image.
The Arena component tracks mouse position through an OnMouseMove handler function (handleMouseMove) and passes those coordinates as props to the CustomCursor component.
Here is my Arena component code:
import { useState, useEffect } from 'react';
import { Container, Spinner } from 'react-bootstrap';
import CustomCursor from '../CustomCursor/CustomCursor';
import Choices from '../Choices/Choices';
import { getImageURL } from '../../helpers/storHelpers';
import './Arena.scss';
export default function Arena(props) {
const [arenaURL, setArenaURL] = useState('');
const [loaded, setLoaded] = useState(false);
const [clicked, setClicked] = useState(false);
const [x, setX] = useState(0);
const [y, setY] = useState(0);
function handleClick(e) {
setClicked(true);
}
function handleMouseMove(e) {
setX(prevState => { return e.clientX });
setY(prevState => { return e.clientY });
}
useEffect(() => {
retreiveArena();
// FUNCTION DEFINITIONS
async function retreiveArena() {
const url = await getImageURL('maps', 'the-garden-of-earthly-delights.jpg');
setArenaURL(url);
setLoaded(true);
}
}, [])
return (
<Container as='main' fluid id='arena' className='d-flex flex-grow-1 justify-content-center align-items-center' onClick={handleClick}>
{!loaded &&
<Spinner animation="border" variant="danger" />
}
{loaded &&
<img src={arenaURL} alt='The Garden of Earthly Delights triptych' className='arena-image' onMouseMove={handleMouseMove} />
}
{clicked &&
<Choices x={x} y={y} />
}
<CustomCursor x={x} y={y} />
</Container>
)
}
Here is my CustomCursor code:
import './CustomCursor.scss';
export default function CustomCursor(props) {
const { x, y } = props;
return (
<div className='custom-cursor' style={{ left: `${x - 64}px`, top: `${y + 50}px` }} />
)
}
When I first created the OnMouseMove handler function I simply set the x and y state values by passing them into their respective state setter functions directly:
function handleMouseMove(e) {
setX(e.clientX);
setY(e.clientY);
}
However, I noticed this was slow and laggy and when I refactored this function to use setter functions instead it was much faster (what I wanted):
function handleMouseMove(e) {
setX(prevState => { return e.clientX });
setY(prevState => { return e.clientY });
}
Before:
After:
Why are using setter functions faster than passing in values directly?

This is interesting. First of all, we need to focus on reacts way of updating state. In the documentation of react https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous There you can see:
React may batch multiple setState() calls into a single update for performance.
Because this.props and this.state may be updated asynchronously, you should not rely on their values for calculating the next state.
For example, this code may fail to update the counter:
// Wrong
this.setState({
counter: this.state.counter + this.props.increment,
});
To fix it, use a second form of setState() that accepts a function rather than an object. That function will receive the previous state as the first argument, and the props at the time the update is applied as the second argument:
// Correct
this.setState((state, props) => ({
counter: state.counter + props.increment
}));
A pretty good article on this is written by Jan Hesters here:
https://medium.com/#jan.hesters/updater-functions-in-reacts-setstate-63c7c162b16a
And more details here:
https://learn.co/lessons/react-updating-state

Related

Toggle component without changing state

I have a component Scroller which I don't control which takes in data as a prop.
This data is a list of objects. Within object, one of the keys takes in a function.
This component has ability where upon clicking on the square, I am meant to show a new component (like a pop up).
The component Scroller which I don't control taking in the data prop.
<Scroller
data={getData(allData)}
/>
This is the data being passed in. content is a list of objects.
const getData = (content) => content.map((c, i) => ({
header: c.header,
customOnClick: (() => {
setClicked(true); // this is the line which resets the scroll
}),
}
));
So this works as intended. Upon clicking, the new pop up content shows. This is due to state change via the setClicked function.
The issue is that this Scroller component has a scroll option. So user could have scrolled pass a a block (0) like following image.
But the moment I click the button to show the popup, it resets the scroll position back to 0 like following. Instead of remaining in position as above.
This scroll reset is the issue.
This is being caused by the call to setClicked function. It doesn't matter if I do anything with it. As long as I call it, it resets.
Showing the popup component is not the issue. The mere call to setClicked is the issue.
Thus wondering if there a way I could toggle showing the pop up component without having to set state?
Or a way to maintain the scroll position without resetting the scroll.
Note that in this instance I am using hooks. It is the same outcome if I use Redux. Please advice.
This is my component which I can control.
import React, { Fragment } from 'react';
import Scroller from 'comp-external-lib';
import PopUpComponent from './PopUpComponent';
const MyComponent = ({data}) => {
const [isClicked, setClicked] = React.useState(false);
const { allData } = data;
const getData = (content) => content.map((c, i) => ({
header: c.header,
customOnClick: c.customOnClick && (() => {
setClicked(true); // this is whats causing the reset for scroll
}),
}
));
return (
<Fragment>
<Scroller
data={getData(allData)}
/>
{
{/* Doesn't matter if this is commented out. The scrolling will still reset due to call to setClicked function */}
{/* isClicked && <PopUpComponent /> */}
}
</Fragment>
);
};
export default MyComponent;
Explanation:
Each time setClick is called, the value of isClicked is changed, which causes MyComponent to be reevaluated. Since allData is initialized inside MyComponent, it will be reinitialized each time MyComponent is reevaluated. Another issue is that the data being sent to Scroller is the result of a function that takes in allData. Each time MyComponent is reevaluated, that function will run again and return a new array instance given the new allData instance. This means that every time MyComponent reevaluates, Scrollbar gets a new instance of data, causing anything that consumes data inside of Scrollbar to also be reevaluated.
Solution:
My suggestion would be to utilize react's useMemo hook (docs: https://reactjs.org/docs/hooks-reference.html#usememo) to 'memoize' the data going into Scroller:
import React from 'react';
import Scroller from 'comp-external-lib';
import PopUpComponent from './PopUpComponent';
const MyComponent = ({data}) => {
const [isClicked, setClicked] = React.useState(false);
const scrollerData = React.useMemo(()=> {
return data.allData.map((c, i) => ({
header: c.header,
customOnClick: c.customOnClick && (() => {
setClicked(true); // this is whats causing the reset for scroll
}),
}
));
},[data])
return (
<>
<Scroller
data={scrollerData}
/>
{
{/* Doesn't matter if this is commented out. The scrolling will still reset due to call to setClicked function */}
{/* isClicked && <PopUpComponent /> */}
}
</>
);
};
export default MyComponent;
Also fun fact, <> is shorthand for React's Fragment
The problem could be that each time you click the component your Scroller gets a different reference of the data and because of that, it calls lifecycle methods that cause your performance issue.
If you will send the same props ( same reference ) to Scroller it should not call any lifecycle method which propably causes your problems.
import React, { Fragment, useMemo, useState } from 'react'
import Scroller from 'comp-external-lib'
import PopUpComponent from './PopUpComponent'
const MyComponent = props => {
const [isClicked, setClicked] = useState(false)
const { allData } = props.data
const getData = content =>
content.map((c, i) => ({
header: c.header,
customOnClick:
c.customOnClick &&
(() => {
setClicked(true)
})
}))
const scrollerData = useMemo(() => getData(allData), [allData])
return (
<Fragment>
<Scroller data={scrollerData} />
{isClicked && <PopUpComponent />}
</Fragment>
)
}
export default MyComponent
You are calling getData on every render cycle and thus causing a reset of the state:
data={getData(allData)}
The solution will be to wrap the getData function with a useCallback hook:
const getData = useCallback((content) => content.map((c, i) => ({
header: c.header,
customOnClick: c.customOnClick && (() => {
setClicked(true); // this is whats causing the reset for scroll
}),
}
)),[]);

React: usePrevious hook only works if there is 1 state in the component. How to make it work if there are multiple states?

The docs suggests the folllowing to get previous state:
function Counter() {
const [count, setCount] = useState(0);
const prevCount = usePrevious(count);
return <h1>Now: {count}, before: {prevCount}</h1>;
}
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}
Per my understanding, this works fine only if there is exactly one state in the component. However consider the following where there are multiple states:
import "./styles.css";
import React, { useState, useEffect, useRef, useContext } from "react";
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}
export default function App() {
const [count, setCount] = useState(0);
const [foo, setFoo] = useState(0);
const prevCount = usePrevious(count);
return (
<div>
<button onClick={() => setFoo(f => f+1)}> Update foo </button>
<h1>Now: {count}, before: {prevCount}</h1>
</div>);
}
Sandbox: https://codesandbox.io/s/little-feather-wow4m
When a different state (foo) is updated, the usePrevious hook returns the latest value for count, as opposed to the previous one).
Is there a way to reliably get the previous value for a state/prop when there are multiple states?
I don't think this is the right approach.
How about a custom hook that sets up the state and returns a custom setter function that handles this logic for you.
function useStateWithPrevious(initial) {
const [value, setValue] = useState(initial)
const [prev, setPrev] = useState(initial)
function setValueAndPrev(newValue) {
if (newValue === value) return // optional, depends on the logic you want.
setPrev(value)
setValue(newValue)
}
return [prev, value, setValueAndPrev]
}
Which you would use like:
function MyComponent() {
const [prevCount, count, setCount] = useStateWithPrevious(0)
}
I was able to create an altered version of your hook that does seem to work:
function usePrevious(value) {
const ref = useRef([undefined, undefined]);
if (ref.current[0] !== value) {
ref.current = [value, ref.current[0]];
}
return ref.current[1];
}
Playground here.
Keeping track of only the previous value of some state is not a very common use case. Hence there's no need to overthink it or try to "trick" React with refs to achieve a slightly shorter syntax. There's almost always a few ways to get the same result in a more straightforward and maintainable manner.
React's docs also stress that this suggested approach is only for edge cases.
This is rarely needed and is usually a sign you have some duplicate or redundant state.
If the previous count is de facto part of the application state (it's used in rendering just like the current count), it's counter productive to not just store it as such. Once it's in state, it's just a matter of making state updates in event listeners update all parts of the state in one go, making it inherently safe with React's concurrent features.
Method 1: Set multiple state variables at the same time
Just create an additional state variable for the old value, and make your handler set both values.
const initialCount = 0;
function App() {
const [count, setCount] = useState(initialCount);
const [prevCount, setPrevCount] = useState(initialCount);
return <>
<button
onClick={() => {
// You can memoize this callback if your app needs it.
setCount(count + 1);
setPrevCount(count);
}}
>Increment</button>
<span>Current: {count} </span>
<span>Previous: {prevCount} </span>
</>
}
You can almost always do this instead, it offers the same functionality as usePrevious and obviously will never lead to the application using the wrong combination of values. In fact, because of batched state updates since React 18 there's no performance penalty in calling 2 setters in one event handler.
Using a hook like usePrevious doesn't really bring any overall benefits. Clearly both the current and the previous value are pieces of state your application needs for rendering. They can both use the same simple and readable syntax. Just because usePrevious is shorter doesn't mean it's easier to maintain.
Method 2: useReducer
If you want to avoid the 2 function calls in the event listener, you can use useReducer to encapsulate your state. This hook is particularly well suited for updates of complex but closely related state. It guarantees the application state transitions to a new valid state in one go.
const initialState = { count: 0, prevCount: 0, foo: 'bar' };
function countReducer(state, action) {
switch (action.type) {
case: 'INCREMENT':
return {
...state,
count: state.count + 1,
prevCount: state.count,
};
case 'DO_SOMETHING_ELSE':
// This has no effect on the prevCount state.
return {
...state,
foo: payload.foo,
}
}
return state;
}
function App() {
const [
{ count, prevCount },
dispatch
] = useReducer(countReducer, initialState)
return <>
<button
onClick={() => {
dispatch({ type: 'INCREMENT' });
}}
>Increment</button>
<button
onClick={() => {
dispatch({
type: 'DO_SOMETHING_ELSE',
payload: { foo: `last update: ${prevCount} to ${count`} },
);
}}
>Do foo</button>
<span>Current: {count} </span>
<span>Previous: {prevCount} </span>
</>
}

How to prevent unwanted re-renders to stop another API call in react js?

Here is a parent component (Cart) and a child component (CartItem).
in the parent, there is a button that counts on the state that changes by the checkAvailability function which passed to the child via props,
import {useState} from "react";
import CartItem from "./CartItem";
const Cart = ({cart}) => {
const [available, setAvailable] = useState(true);
const checkAvailability = (check) => {
setAvailable(check)
}
return (
<>
{cart.items.map((item) => (
item.is_valid &&
<CartItem
key={item.id}
checkAvailability={checkAvailability}
/>
))}
<button disabled={available} >Click Me!</button>
</>
)
}
export default Cart;
in the child component, an API call returns true or false called by useEffect.
import {useState, useEffect} from "react";
const CartItem = ({checkAvailability}) => {
const [newData, setNewData] = useState(null);
const handleCheck = async () => {
const data = await api.call();
setNewData(data)
if(newData.available === false) {
checkAvailability(false)
} else if(newData.available === true) {
checkAvailability(true)
}
};
useEffect(() => {
handleCheck();
}, []);
return (
<div> Item </div>
)
};
export default CartItem;
issue:
every time the components mount, the API call in the child returns a value, that value gets passed to the parent by the checkAvailability function as a prop, which changes the state in the parent, when the state changes a re-render happen which restarts the circle infinitely.
the main thing is the button gets disabled when the API call returns a {false} value. if this way won't do the job, is there another way of doing it?.
what is the solution?.
Even if you resolved the current issue of infinite re-renders, you'll still have multiple API requests if there are multiple <CartItem/> components.
A better approach will be to move the API call to the parent. This will ensure it's called once regardless of the number of cart items it has. If the cart items need to know the value of available, then pass it to them.
const CartItem = ({ available }) => {
return <div> Item </div>
}

How to create a static function within a functional React component?

I am attempting to add and remove an event listener within a functional React component. The listener is added fine but is not removed when asked to be. I believe the issue is that the function I am referencing handlemousemove is recreated every component render and so when removeEventListener attempts to remove it, it's not the same function reference as when addEventListener added it.
I tried moving handlemousemove out of the component but it required access to the setState hooks generated in the component.
const handleMouseMove = e => {
setYOffset(e.clientY-280)
setXOffset(e.clientX-350)
}
const followMouse = () => {
if (isFollowingMouse){
setIsFollowingMouse(false)
document.removeEventListener("mousemove", handleMouseMove)
} else {
setIsFollowingMouse(true)
document.addEventListener("mousemove", handleMouseMove)
}
}
...
<button name="mouse" onClick={followMouse}>
Follow Mouse
</button>
All branches of execution are hit here but document.removeEventListener("mousemove", handleMouseMove) doesn't actually remove the event listener.
Is there a way to have a "static method" within a functional component? Is that even the issue here?
Here's a link to code sandbox with the whole code: https://codesandbox.io/s/pzrwh
The old way to do it was with render props, but now that hooks have arrived this is a better solution
const MyComponent = (props) => {
const [isFollowingMouse, setIsFollowingMouse] = React.useState(false);
const [xOffset, setXOffset] = React.useState(0);
const [yOffset, setYOffset] = React.useState(0);
const handleMouseMove = e => {
if (isFollowingMouse) {
setYOffset(e.clientY-28);
setXOffset(e.clientX-35);
}
};
const followMouse = () => {
setIsFollowingMouse(!isFollowingMouse);
}
const styles = {
'cat': {
'backgroundColor': 'red',
'height': '20px',
'position': 'absolute',
'left': xOffset,
'top': yOffset,
'width': '20px',
'display': isFollowingMouse ? 'block' : 'none'
}
};
return (
<div style={{ 'height': '100%' }} onMouseMove={handleMouseMove}>
<div style={ styles.cat }>C</div>
<button name="mouse" onClick={followMouse}>
Follow Mouse
</button>
</div>
)
}
ReactDOM.render(<MyComponent />, document.getElementById('root'));
html,
body,
#root {
height: 100%;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.9.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js"></script>
<div id="root"></div>
I think your description of the issue is spot on. A quick fix is to define the variable handleMouseMove outside of your App function - essentially making the variable static and not recreated every render.
Then, within the body of the function, only assign the handleMouseMove variable if it's currently unassigned, and set it back to null when you set isFollowingMouse to false.
With React 16.7 you can use Hooks to do this:
import React, { useCallback, useEffect, useState } from 'react';
const DraggedComponent = React.memo(
props => {
const [isFollowingMouse, setIsFollowingMouse] = useState(false);
const [xOffset, setXOffset] = useState(0);
const [yOffset, setYOffset] = useState(0);
const handleMouseMove = useCallback(
e => {
if (isFollowingMouse) {
setYOffset(e.clientY-28);
setXOffset(e.clientX-35);
}
}, [isFollowingMouse, setYOffset, setXOffset]
);
useEffect(
() => {
document.addEventListener('mousemove', handleMouseMove);
return () => document.removeEventListener('mousemove', handleMouseMove);
},
[handleKeyDown]
);
const followMouse = () => setIsFollowingMouse(!isFollowingMouse);
return (
<div onMouseMove={handleMouseMove}>
<div>C</div>
<button name="mouse" onClick={followMouse}>
Follow Mouse
</button>
</div>
)
}
);
ReactDOM.render(<DraggedComponent />, document.getElementById('root'));
In this example React.memo() ensures that the component is only redrawn if state or properties change. Similar useCallback() will cache the event listener for the mousemove event, such that this will not be recreated only if isFollowingMouse, setYOffset or setXOffset change, instead of every rerender. useEffect will be called once the component is created, and once every time the handleMouseMove callback changes. Furthermore it returns a function, which is automatically called if the component is destroyed or the parameter handleKeyDown changes.

How to force a functional React component to render?

I have a function component, and I want to force it to re-render.
How can I do so?
Since there's no instance this, I cannot call this.forceUpdate().
🎉 You can now, using React hooks
Using react hooks, you can now call useState() in your function component.
useState() will return an array of 2 things:
A value, representing the current state.
Its setter. Use it to update the value.
Updating the value by its setter will force your function component to re-render,
just like forceUpdate does:
import React, { useState } from 'react';
//create your forceUpdate hook
function useForceUpdate(){
const [value, setValue] = useState(0); // integer state
return () => setValue(value => value + 1); // update state to force render
// A function that increment 👆🏻 the previous state like here
// is better than directly setting `setValue(value + 1)`
}
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>
);
}
You can find a demo here.
The component above uses a custom hook function (useForceUpdate) which uses the react state hook useState. It increments the component's state's value and thus tells React to re-render the component.
EDIT
In an old version of this answer, the snippet used a boolean value, and toggled it in forceUpdate(). Now that I've edited my answer, the snippet use a number rather than a boolean.
Why ? (you would ask me)
Because once it happened to me that my forceUpdate() was called twice subsequently from 2 different events, and thus it was reseting the boolean value at its original state, and the component never rendered.
This is because in the useState's setter (setValue here), React compare the previous state with the new one, and render only if the state is different.
Update react v16.8 (16 Feb 2019 realease)
Since react 16.8 released with hooks, function components have the ability to hold persistent state. With that ability you can now mimic a forceUpdate:
function App() {
const [, updateState] = React.useState();
const forceUpdate = React.useCallback(() => updateState({}), []);
console.log("render");
return (
<div>
<button onClick={forceUpdate}>Force Render</button>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.1/umd/react-dom.production.min.js"></script>
<div id="root"/>
Note that this approach should be re-considered and in most cases when you need to force an update you probably doing something wrong.
Before react 16.8.0
No you can't, State-Less function components are just normal functions that returns jsx, you don't have any access to the React life cycle methods as you are not extending from the React.Component.
Think of function-component as the render method part of the class components.
Official FAQ now recommends this way if you really need to do it:
const [ignored, forceUpdate] = useReducer(x => x + 1, 0);
function handleClick() {
forceUpdate();
}
Simplest way 👌
if you want to force a re-render, add a dummy state you can change to initiate a re-render.
const [rerender, setRerender] = useState(false);
...
setRerender(!rerender); //whenever you want to re-render
And this will ensure a re-render, And you can call setRerender(!rerender) anywhere, whenever you want :)
I used a third party library called
use-force-update
to force render my react functional components. Worked like charm.
Just use import the package in your project and use like this.
import useForceUpdate from 'use-force-update';
const MyButton = () => {
const forceUpdate = useForceUpdate();
const handleClick = () => {
alert('I will re-render now.');
forceUpdate();
};
return <button onClick={handleClick} />;
};
Best approach - no excess variables re-created on each render:
const forceUpdateReducer = (i) => i + 1
export const useForceUpdate = () => {
const [, forceUpdate] = useReducer(forceUpdateReducer, 0)
return forceUpdate
}
Usage:
const forceUpdate = useForceUpdate()
forceUpdate()
If you already have a state inside the function component and you don't want to alter it and requires a re-render you could fake a state update which will, in turn, re-render the component
const [items,setItems] = useState({
name:'Your Name',
status: 'Idle'
})
const reRender = () =>{
setItems((state) => [...state])
}
this will keep the state as it was and will make react into thinking the state has been updated
This can be done without explicitly using hooks provided you add a prop to your component and a state to the stateless component's parent component:
const ParentComponent = props => {
const [updateNow, setUpdateNow] = useState(true)
const updateFunc = () => {
setUpdateNow(!updateNow)
}
const MyComponent = props => {
return (<div> .... </div>)
}
const MyButtonComponent = props => {
return (<div> <input type="button" onClick={props.updateFunc} />.... </div>)
}
return (
<div>
<MyComponent updateMe={updateNow} />
<MyButtonComponent updateFunc={updateFunc}/>
</div>
)
}
The accepted answer is good.
Just to make it easier to understand.
Example component:
export default function MyComponent(props) {
const [updateView, setUpdateView] = useState(0);
return (
<>
<span style={{ display: "none" }}>{updateView}</span>
</>
);
}
To force re-rendering call the code below:
setUpdateView((updateView) => ++updateView);
None of these gave me a satisfactory answer so in the end I got what I wanted with the key prop, useRef and some random id generator like shortid.
Basically, I wanted some chat application to play itself out the first time someone opens the app. So, I needed full control over when and what the answers are updated with the ease of async await.
Example code:
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// ... your JSX functional component, import shortid somewhere
const [render, rerender] = useState(shortid.generate())
const messageList = useRef([
new Message({id: 1, message: "Hi, let's get started!"})
])
useEffect(()=>{
async function _ () {
await sleep(500)
messageList.current.push(new Message({id: 1, message: "What's your name?"}))
// ... more stuff
// now trigger the update
rerender(shortid.generate())
}
_()
}, [])
// only the component with the right render key will update itself, the others will stay as is and won't rerender.
return <div key={render}>{messageList.current}</div>
In fact this also allowed me to roll something like a chat message with a rolling .
const waitChat = async (ms) => {
let text = "."
for (let i = 0; i < ms; i += 200) {
if (messageList.current[messageList.current.length - 1].id === 100) {
messageList.current = messageList.current.filter(({id}) => id !== 100)
}
messageList.current.push(new Message({
id: 100,
message: text
}))
if (text.length === 3) {
text = "."
} else {
text += "."
}
rerender(shortid.generate())
await sleep(200)
}
if (messageList.current[messageList.current.length - 1].id === 100) {
messageList.current = messageList.current.filter(({id}) => id !== 100)
}
}
If you are using functional components with version < 16.8. One workaround would be to directly call the same function like
import React from 'react';
function MyComponent() {
const forceUpdate = MyComponent();
return (
<div>
<button onClick={forceUpdate}>
Click to re-render
</button>
</div>
);
}
But this will break if you were passing some prop to it. In my case i just passed the same props which I received to rerender function.
For me just updating the state didn't work. I am using a library with components and it looks like I can't force the component to update.
My approach is extending the ones above with conditional rendering. In my case, I want to resize my component when a value is changed.
//hook to force updating the component on specific change
const useUpdateOnChange = (change: unknown): boolean => {
const [update, setUpdate] = useState(false);
useEffect(() => {
setUpdate(!update);
}, [change]);
useEffect(() => {
if (!update) setUpdate(true);
}, [update]);
return update;
};
const MyComponent = () => {
const [myState, setMyState] = useState();
const update = useUpdateOnChange(myState);
...
return (
<div>
... ...
{update && <LibraryComponent />}
</div>
);
};
You need to pass the value you want to track for change. The hook returns boolean which should be used for conditional rendering.
When the change value triggers the useEffect update goes to false which hides the component. After that the second useEffect is triggered and update goes true which makes the component visible again and this results in updating (resizing in my case).

Categories

Resources