So I'm trying to build a single page app in react.
What I want: On the page you can visit different pages like normal. On one page (index) i want a button the user can click that expands another component into view with a form. This component or form should be visible on all pages once expanded.
The Problem: The index page loads some data from an api, so when the index component gets mounted, an fetch call is made. But when the user clicks the "Expand form"-Button, the state of the Parent component gets updated as expected, but the children get rerendered which causes the index component to fetch data again, which is not what I want.
What I tried
// Parent Component
const App => props => {
const [composer, setComposer] = useState({
// ...
expanded: false,
});
const expandComposer = event => {
event.preventDefault();
setComposer({
...composer,
expanded: true
});
return(
// ...
<Switch>
// ...
<Route
exact path={'/'}
component={() => (<Index onButtonClick={expandComposer}/>)}
// ....
{composer.expanded && (
<Composer/>
)};
);
};
// Index Component
const Index=> props => {
const [isLoading, setIsLoading] = useState(true);
const [data, setData] = useState([]);
useEffect(()=> {
// load some data
}, []);
if(isLoading) {
// show spinner
} else {
return (
// ...
<button onClick={props.onButtonClick}>Expand Composer</button>
// ...
);
};
};
So with my approach, when the button is clicked, the Index component fetched the data again and the spinner is visible for a short time. But I dont want to remount Index, or at least reload the data if possible
Two problems here. First, React
will by default re render all child components when the parent gets updated. To avoid this behavior you should explicitly define when a component should update. In class based components PureComponent
or shouldComponentUpdate
are the way to go, and in functional components React.memo
is the equivalent to PureComponent
. A PureComponent
will only update when one of it's props
change. So you could implement it like this:
const Index = () =>{/**/}
export default React.memo(Index)
But this won't solve your problem because of the second issue. PureComponent
and React.memo
perform a shallow comparison
in props
, and you are passing an inline function as a prop
which will return false
in every shallow comparison
cause a new instance of the function is created every render.
<Child onClick={() => this.onClick('some param')} />
This will actually create a new function every render, causing the comparison to always return false
. A workaround this is to pass the parameters as a second prop
, like this
<Child onClick={this.onClick} param='some param' />
And inside Child
<button onClick={() => props.onClick(props.param)} />
Now you're not creating any functions on render, just passing a reference of this.onClick
to your child.