How to prevent double rerender when I use props, useState and useEffect? - reactjs

For example I have this component
const FooBar = (props) => {
console.log("render")
const [foo, setFoo] = useState(props.foo)
useEffect(() => {
setFoo(props.foo)
}, [props.foo])
return (
<div>
{foo} <button onClick={() => setFoo(x => x + 1)}>component plus</button>
</div>
);
}
And I can change it props like this
const App = () => {
const [foo, setFoo] = useState(1)
return (
<div>
<FooBar foo={foo} />
<button onClick={() => setFoo(x => x + 1)}>parent plus</button>
</div>
);
}
When I click on parent plus button the FooBar component will rerender two times. the first one is from props change and the second one is from setFoo inside useEffect.
How can I prevent second rerender?

I am not exactly sure what your goal with this example would be.
If you want to have two buttons that increment the same counter, than your code currently does not work as intended.
If you click the 'component plus' button several times and afterwards click the 'parent plus' button, your counter will reset to the value when you last clicked the parent button.
In case you want to pass state to child components, you usually do not want to save the passed state as the child components state. That will make it unnecessarily harder to sync up both states.
What you would do, is to pass the state and a function to set that state to the child component.
Your example would then look like this:
const App = () => {
const [foo, setFoo] = React.useState(1);
return (
<div>
<FooBar foo={foo} setFoo={setFoo} />
<button onClick={() => setFoo(x => x + 1)}>parent plus</button>
</div>
);
};
const FooBar = props => {
console.log("render");
return (
<div>
{props.foo} <button onClick={() => props.setFoo(x => x + 1)}>component plus</button>
</div>
);
};

Related

There are two buttons and you need to change them alternately

I have two buttons. I can change its color by clicking on one button. And when you click on another button, change its color as well, and return the old color to the first button. Something like toggle. How can I implement such functionality in a react applicatio.
const [toggle, setToggle] = useState(false);
const toggleIt = () => {
setToggle(!toggle);
};
return (
<div>
<button onClick={toggleIt}>Button1</button>
<button onClick={toggleIt}>Button2</button>
)
somthing like this (codesandbox),
import classNames from "classnames";
import { useCallback, useState } from "react";
import "./styles.css";
export default function App() {
const [toggle, setToggle] = useState(false);
const toggleIt = useCallback(() => {
setToggle((toggle) => !toggle);
}, []);
return (
<div>
<button
onClick={toggleIt}
className={classNames({
"btn-act": toggle
})}
>
Btn A
</button>
<button
onClick={toggleIt}
className={classNames({
"btn-act": !toggle
})}
>
Btn B
</button>
</div>
);
}
const [toggle, setToggle] = useState(false);
const toggleIt = () => {
setToggle(!toggle);
};
return (
<div>
<button onClick={toggleIt} style={toggle ? {color: "blue"} : {color: "red"}}</button>
<button onClick={toggleIt} style={toggle ? {color: "pink"} : {color: "purple"}}</button>
</div>
)
Background
You can use the useEffect() hook to accomplish this feature depending on the button pressed. Just hold two states and flip them each time a different button is pressed, and with those two states you can use two separate functions to handle the onClick()'s.
The useEffect() hook automatically re-renders the component once any of the items in the dependency array at the end change, which will happen depending on the button pressed.
You can also directly set true/false values on your state variables with the second value that returns from useState(), and those state variables will automatically have their states updated without you manually assigning them.
There is very likely a better, more efficient way of doing it, but this is just a general guideline, if you will.
This is the code
const [toggleOne, setToggleOne] = useState(false);
const [toggleTwo, setToggleTwo] = useState(true);
const toggleFirst = () => {
setToggleOne(true);
setToggleTwo(false);
};
const toggleSecond = () => {
setToggleOne(false);
setToggleTwo(true);
};
useEffect(() => {
if (toggleOne) {
// Do something with first button pressed
} else if (toggleTwo) {
// Do something with second button pressed
}
}, [toggleOne, toggleTwo]);
return (
<div>
<button onClick={toggleFirst}>Button1</button>
<button onClick={toggleSecond}>Button2</button>
</div>
);

How to render component onClick in React after passing props?

How do you update the LatestTweetsComponent with the data returned from a fetch call that happens in handleRequest? tweets is updating correctly onClick however the LatestTweetsComponent does not render or update. I may be approaching this incorrectly.
const LatestTweets = () => {
const [tweets, setTweets] = useState(null)
const handleRequest = async () => {
// this hits an api to get tweets and sets the tweets
// is this where the LatestTweetsComponent render is called?
}
return (
<div>
<button onClick={() => handleRequest()}>View Latest Tweets</button>
<LatestTweetsComponent tweets={tweets} />
</div>
)
}
const LatestTweetsComponent = props => {
return (
<div>
{props.map((tweet, index) => {
return <p key={index}>{tweet}</p>
})}
</div>
)
}
export default LatestTweets
i think this is because you are trying to map over "props", while you should be mapping over "props.tweets"
try this :
const LatestTweetsComponent = props => {
return (
<div>
{props.tweets.map((tweet, index) => {
return <p key={index}>{tweet}</p>
})}
</div>
)
}
Use that handleFetch in useEffect. So, whenever your state gets change useeffect will rerender that change.
And use tweets as argument in useEffect

useCallback with onchange function in React JS

I have 2 components. In the parent component I have this:
const Demo = () => {
const [state, setState] = useState(true);
const onChange = useCallback(
(value: string) => {
console.log(value);
},
[],
);
return (
<div className="a">
<button onClick={() => setState(!state)}>sds</button>
<div className="123">
<Bar searchHandler={onChangeSearchHandler} />
</div>
</div>
);
};
In the Bar component I have this:
const Bar = ({ searchHandler }) => {
console.log('bar');
return (
<div>
<input type="text" onChange={(value) => searchHandler(value.target.value)} />
</div>
);
};
Wrapping onChange with useCallback I expect to cache the function and when I click on <button onClick={() => setState(false)}>sds</button> I don't want to render Bar component, but it is triggered. Why Bar component is triggered and how to prevent this with useCallback?
This has nothing to do with the onChange function you're wrapping with useCallback. Bar gets re-rendered because you're changing the state through setState in its parent component. When you change the state in a component all its child components get re-rendered.
You can verify it yourself by trying this:
const Demo = () => {
const [state, setState] = useState(true);
return (
<div className="a">
<button onClick={() => setState(!state)}>sds</button>
<div className="123">
<Bar />
</div>
</div>
);
};
const Bar = ({ searchHandler }) => {
console.log('bar');
return (
<div></div>
);
};
You'll see that the Bar gets re-rerender anyway.
If you want to skip re-rerendring any of the child components, you should memoize them using React.memo when applicable.
Also, you should familiarize yourself with how state in react works and how does it affect the nested components as this is a main concept.
The issue is that you haven't used React.memo on Bar component. The function useCallback works only if you use HOC from memo.
try this, in Bar component create this wrapped component:
const WrappedBar = React.memo(Bar);
and in parent component use this wrapped bar:
const Demo = () => {
const [state, setState] = useState(true);
const onChange = useCallback(
(value: string) => {
console.log(value);
},
[],
);
return (
<div className="a">
<button onClick={() => setState(!state)}>sds</button>
<div className="123">
<WrappedBar searchHandler={onChangeSearchHandler} />
</div>
</div>
);
};

Update child state based on parent state react functional components

Let's say we have a component Accordion that has an internal state isOpen, so you can close and open this component.
We now want to have a parent component that also has a state isOpen and has button. In this component, we have 2 times Accordion and we are passing to Accordion isOpen and we want that if the parent changes state isOpen Accordion accept this.
All component are functional components
const Accordion = ({ isOpen: parentIsOpen = false }) => {
const [isOpen, setIsOpen] = useState(parentIsOpen);
const handleSetIsOpen = () => setIsOpen(!isOpen);
return (
<div>
I'm open: {isOpen}
<button onClick={handleSetIsOpen}>toggle isOpen child</button>
</div>
);
};
const MasterComponent = () => {
const [isOpen, setIsOpen] = useState(false);
const handleSetIsOpen = () => setIsOpen(!isOpen);
return (
<div>
<button onClick={handleSetIsOpen}>toggle isOpen parent</button>
<Accordion isOpen={isOpen} />
<Accordion isOpen={isOpen} />
</div>
);
};
In this case above Accordion will take on first render as the initial state parent isOpen prop. In case we press the button toggle isOpen parent we will change the parent state but children will not be updated.
To fix this we can use useEffect
const Accordion = ({ isOpen: parentIsOpen = false }) => {
const [isOpen, setIsOpen] = useState(parentIsOpen);
const handleSetIsOpen = () => setIsOpen(!isOpen);
useEffect(() => {
if (parentIsOpen !== isOpen) {
setIsOpen(parentIsOpen);
}
}, [parentIsOpen]);
return (
<div>
I'm open: {isOpen}
<button onClick={handleSetIsOpen}>toggle isOpen child</button>
</div>
);
};
const MasterComponent = () => {
const [isOpen, setIsOpen] = useState(false);
const handleSetIsOpen = () => setIsOpen(!isOpen);
return (
<div>
<button onClick={handleSetIsOpen}>toggle isOpen parent</button>
<Accordion isOpen={isOpen} />
<Accordion isOpen={isOpen} />
</div>
);
};
in this case, children will be properly updated when a parent changes isOpen state.
There is one issue with this:
"React Hook useEffect has a missing dependency: 'isOpen'. Either include it or remove the dependency array react-hooks/exhaustive-deps"
So how to remove this issue that esLint is complaining and we do not want to put isOpen in this since it will cause bug.
in case we add isOpen into the array like this:
useEffect(() => {
if (parentIsOpen !== isOpen) {
setIsOpen(parentIsOpen);
}
}, [parentIsOpen, isOpen]);
We will have then a situation where we will click on the internal button in accordion and update the internal state then useEffect will run and see that parent has a different state than the child and will immediately set the old state.
So basically you have a loop where the accordion will never be open then.
The question is what is the best way to update the child state based on the parent state?
Please do not suggest to put all-state in parent and pass props without child state. this will not work since both Accordions in this example have to have their own state and be able to open and close in an independent way, but yet if parent says close or open it should listen to that.
Thank you!
Actually I would say this is way to do it
const Accordion = ({ isOpen: parentIsOpen = false }) => {
const [isOpen, setIsOpen] = useState(parentIsOpen);
const handleSetIsOpen = () => setIsOpen(!isOpen);
useEffect(() => {
setIsOpen(parentIsOpen);
}, [parentIsOpen]);
return (
<div>
I'm open: {isOpen}
<button onClick={handleSetIsOpen}>toggle isOpen child</button>
</div>
);
};
const MasterComponent = () => {
const [isOpen, setIsOpen] = useState(false);
const handleSetIsOpen = () => setIsOpen(!isOpen);
return (
<div>
<button onClick={handleSetIsOpen}>toggle isOpen parent</button>
<Accordion isOpen={isOpen} />
<Accordion isOpen={isOpen} />
</div>
);
};
So just remove state check in a child component, let him update the state but since is updated with the same value it will not rerender or do some expensive behavior.
Tested it today and with a check, if states are different or without is the same, react takes care to not trigger rerender if the state that is updated is the same as before.
What you’re saying not to suggest is in fact the solution I would offer… You’ll need state to control isOpen for the parent component. Also, you should have separate methods in the parent that control state for each accordion, passed along to each accordion in props…
Not sure why you want separate state for the child components. I believe something like this would suffice.
const MasterComponent = () => {
const [isOpen, setIsOpen] = useState(false);
const [isOpen1, setIsOpen1] = useState(false);
const [isOpen2, setIsOpen2] = useState(false);
const handleParentClose = () => {
setIsOpen(false);
setIsOpen1(false);
setIsOpen2(false);
}
return (
<div>
<button onClick={handleParentClose}>toggle isOpen parent</button>
<Accordion isOpen={isOpen1} setIsOpen={setIsOpen1} />
<Accordion isOpen={isOpen2} setIsOpen={setIsOpen2} />
</div>
);
};
const Accordion = props => {
return (
<div>
I'm open: {props.isOpen}
<button onClick={props.setIsOpen}>toggle isOpen child</button>
</div>
);
}
This doesn't include code for actual visibility toggle, but the state is there, including state that closes accordions on parent close.

React hook dependent functions as props

After reading through the intro to hooks I have an immediate feeling that it has a performance problem with passing function props.
Consider the following class component, where the function reference is a bound function, so no re-renders happen because of it.
import React from 'react';
class Example extends React.Component {
state = { count: 0 }
onIncrementClicked = () => setState({ count: this.state.count + 1 })
render() {
return (
<div>
<p>You clicked {this.state.count} times</p>
<button onClick={this.onIncrementClicked}>
Click me
</button>
</div>
);
}
}
Now compare it to the hooks-version where we pass a new function on each render to the button. If an <Example /> component renders, there's no way of avoiding the re-rendering of it's <button /> child.
import React, { useState } from 'react';
function Example() {
// Declare a new state variable, which we'll call "count"
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
I know it's a small example, but consider a bigger app where many callbacks are passed around that depend on hooks. How could this be optimised?
How would I avoid re-rendering everything that takes a function prop, that depends on a hook?
You can use useCallback to ensure that event handler doesn't change between renders with the same count value:
const handleClick = useCallback(
() => {
setCount(count + 1)
},
[count],
);
For better optimisation you can store count value as an attribute of the button so you don't need access to this variable inside event handler:
function Example() {
// Declare a new state variable, which we'll call "count"
const [count, setCount] = useState(0);
const handleClick = useCallback(
(e) => setCount(parseInt(e.target.getAttribute('data-count')) + 1),
[]
);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={handleClick} data-count={count}>
Click me
</button>
</div>
);
}
Also check https://reactjs.org/docs/hooks-faq.html#are-hooks-slow-because-of-creating-functions-in-render

Resources