Say I have a react hook component
const Child = (props) => {
useEffect(()=>{
// fetch data
...
});
return (
//Displays fetched data
)
}
Then it is used in the parent component
class Parent extends Component {
render () {
... <Child ... /> ...
}
}
My question is, if Parent rerenders when its state changes (but irrelevant to Child), would it cause the Child component to rerender and ultimately fetching data again?
If Child2 has a dependency array, would it suffer the same problem?
const Child2 = (props) => {
let [data, setData] = useState();
useEffect(()=>{
// fetch data
ajax(...).then((result)=>setData(result));
}, [data]);
return (
//Displays fetched data
)
}
Yes, the standard behavior in react is that when a component renders, its children render too. To improve performance you can skip some renders with shouldComponentUpdate on a class component or React.memo on a functional component, but this is a performance optimization, not something you should rely on for skipping effects.
If you're fetching data in a component, you often only want to do it on mount, or only when certain relevant data changes. This is controlled with useEffect's dependency array. To run it only on mount, provide an empty array []. The component will still rerender when the parent component changes, but its effect will not rerun.
Related
If a component uses some data from a custom hook and if the custom hook also contains data (local/global) which is not consumed by the above component, then the component re-renders even when the non-dependent data changes (references for non-primitive types). Is there a way to prevent this?
Example code:
useTest.hook.js
export default function useTest() {
const {
consumedData, nonConsumedData
} = useSelector(state => state.Data);
return {
consumedData,
nonConsumedData
}
}
Component.js
export default function Component() {
const {consumedData} = useTest();
return <div>{consumedData}</div>;
}
Here Component re-renders when nonConsumedData changes ( or even when state.Data changes ). nonConsumedData might be consumed by some other component. Is there a way to stop the re-render in such cases?
You can use memoization to avoid re-rendering components (if re-rendering affects performances).
With function based components, just use React.memo()
const MyComponent = React.memo(function MyComponent(props) {
/* render using props */
});
More information here:
https://reactjs.org/docs/react-api.html#reactmemo
I have a parent Component with a state variable that gets changed by one of its child components upon interaction. The parent then also contains some more components based on the data in the state variable.
The problem is that the child component rerenders when the state of its parent changes because the reference to the setState function changes. But when I use useCallback (as suggested here), the state of my parent just does not update at all.
This is my current setup:
function ArtistGraphContainer() {
const [artistPopUps, setArtistPopUps] = useState([])
const addArtistPopUp = useCallback(
(artistGeniusId, xPos, yPos) => {
setArtistPopUps([{artistGeniusId, xPos, yPos}].concat(artistPopUps))
},
[],
)
return (
<div className='artist-graph-container'>
<ArtistGraph addArtistPopUp={addArtistPopUp} key={1}></ArtistGraph>
{artistPopUps.map((popUp) => {
<ArtistPopUp
artistGeniusId={popUp.artistGeniusId}
xPos={popUp.xPos}
yPos={popUp.yPos}
></ArtistPopUp>
})}
</div>
)
}
And the Child Component:
function ArtistGraph({addArtistPopUp}) {
// querying data
if(records) {
// wrangling data
const events = {
doubleClick: function(event) {
handleNodeClick(event)
}
}
return (
<div className='artist-graph'>
<Graph
graph={graph}
options={options}
events={events}
key={uniqueId()}
>
</Graph>
</div>
)
}
else{
return(<CircularProgress></CircularProgress>)
}
}
function areEqual(prevProps, nextProps) {
return true
}
export default React.memo(ArtistGraph, areEqual)
In any other case the rerendering of the Child component wouldn't be such a problem but sadly it causes the Graph to redraw.
So how do I manage to update the state of my parent Component without the Graph being redrawn?
Thanks in advance!
A few things, the child may be rerendering, but it's not for your stated reason. setState functions are guaranteed in their identity, they don't change just because of a rerender. That's why it's safe to exclude them from dependency arrays in useEffect, useMemo, and useCallback. If you want further evidence of this, you can check out this sandbox I set up: https://codesandbox.io/s/funny-carson-sip5x
In my example, you'll see that the parent components state is changed when you click the child's button, but that the console log that would fire if the child was rerendering is not logging.
Given the above, I'd back away from the usCallback approach you are using now. I'd say it's anti-pattern. As a word of warning though, your useCallback was missing a required dependency, artistPopUp.
From there it is hard to say what is causing your component to rerender because your examples are missing key information like where the graphs, options, or records values are coming from. One thing that could lead to unexpected rerenders is if you are causing full mounts and dismounts of the parent or child component at some point.
A last note, you definitely do not need to pass that second argument to React.memo.
I am creating a react application using hooks but I'm getting a weird behavior on my code, I'm not going to put all my code here but I will give you an example what it's going on.
const Child = props => {
const {data} = props;
const [myData, setMyData] = useState(data);
const items => myData.map( r => <li> r </li> );
return ( <ul> { items } </ul> );
}
const Parent = () => {
return (<div>
<Child data={ [1, 2, 3] }
</div> );
}
I am making changes to the array that the Parent component sends to the Child component.
When I add a new element to the array, the Child component re-render so MyData is equals to the array ( this makes sense because the Child components is re-render by the props change ).
If I delete an element from the array the Child component is re-render but myData doesn't change and the element I deleted from the array is still there in myData.
Why the useState sets the array to myData when I add elements to the array but when I delete elements it seems like this doesn't work,even though the Child component is re-render.
I know it seems a little dumb, you can ask me, why you don't just use the props value on the Child component instead of a state value, well the idea is on the Child component there is a search control so I can make some kind of searching over MyData and not over the props value ( but maybe there is another way ).
I think the issue is how you're using props.
When Child renders, it gets props.data from Parent. It then copies data to its component state as the variable myData.
From here on, changes to props can trigger the Child to re-render, but myData won't be redefined again via useState. That happens only once.
If you're writing code that does hot-reloading (you save the file and the application reloads in the browser), it might seem like changing the props sent to Child updates myData, but that's not happening in a production environment.
My suggestion: if you're going to fork props into the state of Child, think of props as an initial value, and not something that Parent can update.
The React docs explain why this is an anti-pattern:
The problem is that it’s both unnecessary (you can use this.props.color directly instead), and creates bugs (updates to the color prop won’t be reflected in the state).
Only use this pattern if you intentionally want to ignore prop updates.
React constructor docs
useEffect(() => {
setMyData(data)
},[data])
your data will be load to state only once. but you can handle changes on data by parent component with useEffect, useEffect will updates when data will updated
there is full code
const Child = props => {
const {data} = props;
const [myData, setMyData] = useState(data);
useEffect(() => {
setMyData(data)
},[data])
const items => myData.map( r => <li> r </li> );
return ( <ul> { items } </ul> );
}
const Parent = () => {
return (<div>
<Child data={ [1, 2, 3] }
</div> );
}
you can read about React use effect
I have two pages (page1 and page2) with a SimpleTable component.
I have a page with a table for apples:
render() {
const props = this.props
return (
<Page {...props}>
<SampleTable requestData={this.getApples} columns={[<columns for apples>]} />
</Page>
)
}
And a page with a table for tomatoes:
render() {
const props = this.props
return (
<Page {...props}>
<SampleTable requestData={this.getTomatoes} columns={[<columns for tomatoes>]} />
</Page>
)
}
For reasons unknown to me, this particular child (SampleTable) is not being unmounted / mounted when I transition from page1 to page2 or vice-versa.
It is strange, since all the other children in all the other pages are being mounted / unmounted, even when I am using the same component for the children. So, clearly, I do not understand how / when does React decide to mount / unmount, or when does it decide to reuse a component. But I digress.
I will accept this fact: React will reuse children whenever it pleases.
My problem is that I am using Sampletable.componentDidMount to request data:
componentDidMount() {
console.log('SampleTable did mount. Requesting data ...')
this.props.requestData(state.pageSize, state.page, state.sorted, state.filtered).then(res => {
this.setState({
data: res.rows,
pages: res.pages,
loading: false,
})
})
}
But the function to request the data is provided by the parent, via props. I have the following problem:
on initial rendering, page1 provides a requestData=getApples via props to a SampleTable child.
SampleTable mounts and requests the data, using the getApples method provided by page1
I move to page2
page2 provides a different requestData=getTomatoes method to a SampleTable child
somehow Reacts decides that it can reuse the original component for this child and, since it is already mounted, componentDidMount is not called
and therefore, the new getTomatoes method is not called, and no data suitable for the tomatoes table is requested.
What am I doing wrong?
How can I ensure that the change in props is triggering a data reload?
You should try using a unique key attribute on your SampleData component while calling it, so that react knows it a different instance of your component and re-render it accordingly.
You can find more about keys here: https://reactjs.org/docs/lists-and-keys.html
I have a react app that ties into localStorage of the browser. On the startup of the app, the localStorage is populated with all the data that is needed to run the app. This data is pulled with AJAX from XML files and constructed to form a localStorageObject that the web app can use as its "database" of information to pull content from...
At the moment, The main component's state is set to the localstorage. So essentially I have the following:
constructor(props) {
super(props);
this.state = {
courseData : JSON.parse(localStorage.getItem("storageID"));,
}
}
The state contains an object that is the entirety of the localStorage. Now I have many children components, who also have children components themselves. Some are components that just need to render once, while others are going to need to rerender with interaction from the user.
After reading, it seems there are many ways to implement a solution. I could have all the components have state, but that's not needed. I could just have the main component have state, and no other component have state. And whenever the state of the main component changes, the props will be based down and reupdated.
Is there a specific method that is best?
This method works, but.
First of all, localStorage calls should be on a componentDidMount function. Otherwise, it wouldn't work on a server-side-rendering case.
Secondly, I'd implement all the initial data fetching on a parent function and then pass down data to the root of react tree:
const localStorageData = localStorage.getItem('some_data')
ReactDom.render(
document.getElementById('my-element'),
<MyComponent
localStorageData={localStorageData}
/>
)
if have many children components it will be difficult to manage state because of deep nesting.
I would recommend using Higher Order Component for your local storage implementation And Pass it down to children. Here How I would do it:
import React from 'react';
var HigherOrderComponent = (Component) =>
class extends React.Component {
state={locStorage:{}}
componentDidMount(){
this.setState({locStorage:window.localStorage.getItem("data")})
}
render() {
return (
<Component
locStorage={this.state.locStorage}
/>
)
}
};
export default HigherOrderComponent;
import HigherOrderComponent from './HigherOrderComponent'
const ChildComponent = ({locStorage}) => {
console.log(locStorage)
return (
<div>
</div>
);
};
export default HigherOrderComponent(ChildComponent);