Since the reference of options changes when ParentComp re-renders, what are some good methods to prevent SubComp rerendering when its props haven't changed? Example situation:
const ParentComp = ({ uri, someOtherProp }) => {
[...some other state hooks here]
return <SubComp options={{ uri }} someOtherProp={someOtherProp}/>
}
React.memo
If your function component renders the same result given the same
props, you can wrap it in a call to React.memo for a performance boost
in some cases by memoizing the result. This means that React will skip
rendering the component, and reuse the last rendered result.
const ParentComp = ({ uri, someOtherProp }) => {
[...some other state hooks here]
return <SubComp options={{ uri }} someOtherProp={someOtherProp}/>
}
const equalProps = (prevProps, nextProps) => {
/*
return true if passing next props would/should
result in the same output, false otherwise
*/
};
export default memo(ParentComp, equalProps);
NOTE:
This method only exists as a performance optimization. Do not rely on
it to “prevent” a render, as this can lead to bugs.
Another way of doing this to prevent the parent from passing a new reference for the options prop every time it renders. Now you are doing options={{ uri }} but { uri }!=={ uri }. You are creating a new object for options even if uri didn't change.
You could use useMemo for this
const ParentComp = ({ uri, someOtherProp }) => {
const options = React.useMemo(() => ({ uri }), [uri]);
return (
<SubComp
options={options}
someOtherProp={someOtherProp}
/>
);
};
If you have lots of props and just want SubComponent to re render when something changes you can do the following:
const ParentComp = ({ uri, someOtherProp }) => {
const memoProps = React.useMemo(
() => ({
options: { uri },
someOtherProp,
}),
[someOtherProp, uri]
);
return <SubComp {...memoProps} />;
};
This is assuming that SubComp is a either a class that inherits from PureComponent, component wrapped with React Redux connect or a functional component wrapped with React.memo (no need for the compare function). Normal functional components will re render even if props have same reference as previous render.
Related
When it comes to thinking about possibility of props changing, should I only care about parent component updating? Any other possible cases exist for props changing?
I can guess only one case like the code below. I would like to know if other cases exist.
const ParentComponent = () => {
const [message, setMessage] = useState('Hello World');
return (
<>
<button onClick={() => setMessage('Hello Foo')}>Click</button>
<ChildComponent message={message} />
</>
);
};
const ChildComponent = ({ message }: { message: string }) => {
return <div>{message}</div>;
};
Props can change when a component's parent renders the component again with different properties. I think this is mostly an optimization so that no new component needs to be instantiated.
Much has changed with hooks, e.g. componentWillReceiveProps turned into useEffect+useRef (as shown in this other SO answer), but Props are still Read-Only, so only the caller method should update it.
there are a lot of questions related to my issue, but all deal with child components with props. I am not sending any props but the child component is still getting re-rendered which is causing useless load on the db when the getstate function runs.
When i change the sidebar state in the parent, the child re-renders. What is causing the react memo to not work? and how to memoize the chart data to avoid hitting the db everytime the sidebar changes?
function Child() {
const [state, setstate] = useState("")
useEffect(() => {
getState('addressbalance');
},[]);
const getState = async (urlLoc) => {
try {
const response = await fetch(baseURL.concat(`/${urlLoc}`));
const jsonData = await response.json();
setstate(jsonData);
} catch (err) {
console.error(err.message);
}
};
const renderChart = () => {
return <ChartApex graph='Bar' data = {state} height={'95%'} width={'100%'}/>
}
return (
<Explore>
<Card width="90%" height='550px'>
{renderChart()}
</Card>
</Explore>
);
}
export default React.memo(Child)
Parent
<PageContainer changeSidebar={changeSidebar} sidebar={sidebar}>
<Switch>
...
<Route
path="/addressbalance"
component={() => <Child/>}
/>
</Switch>
</PageContainer>
You are using setState in the memoized component, the documentation is explicit on this point :
https://en.reactjs.org/docs/react-api.html#reactmemo
React.memo only checks for prop changes.
If your function component wrapped in React.memo has a useState, useReducer or useContext Hook in its implementation, it will still rerender when state or context change.
I've built a tabs component. Each tab, when clicked, changes the contents of the "main screen". Assume a tab had some back-end call to retrieve data it needs to render, then it makes no sense to have it re-run these calls every time the user clicks another tab and comes back to it. I want to retrieve what was rendered before and display it.
I looked into memo, the big warning says to "not rely on it to “prevent” a render, as this can lead to bugs.", nor does it work. Every time I wrap my component in a memo, the test:
useEffect(() => {
console.log('Rendered');
}, [])
Still runs, telling me that the component re-rendered. Then I thought about memoizing the return itself, so, like:
export const MyComponent = (context) => {
const content = useMemo(() => {
return <></>
}, [context]);
return content;
};
But quickly realized that by the time I reach this useMemo, I'm already in the re-rendering cycle, because there's no way for React to know that MyComponent's useMemo existed in the past, so, again, it re-renders the whole thing. This, in turn made me think that the memoization needs to be done at the level where MyComponent is being rendered, not inside of it but I don't know how to do it.
How can I skip re-renders if my props haven't changed?
Read all the articles, tried all the things but to no avail.
Concisely, here is my component and my latest approach:
export const MyComponent = memo(({ context, className = '', ...props }) => {
..
..
});
The interesting bit here is context. This should almost never change. Its structure is a deeply nested object, however, when I play with memo's second argument, its diff function, what ends up happening if I put a console.log in there, as follows:
const MyComponent = ({ context, className = '', ...props }) => {
};
const areEqual = (prevProps, nextProps) => {
console.log('Did equality check.');
};
export default memo(MyComponent, areEqual);
I will only see "Did equality check." once. No matter what I do, I can't seem to get a memoized component out of memo. This is how MyComponent's parent looks like:
const Parent = ({}) => {
const context = useSelector(); //context comes from the store.
const [selectedTab, setSelectedTab] = useState(false);
const [content, setContent] = useState(null);
useEffect(() => {
switch (selectedTab) {
case 'components':
setContent(<MyComponent context={context} />);
break;
}
}, [selectedTab, context]);
return(<>{content}</>);
};
#daniel-james I think the problem here is we are un-mounting the whole component when we are switching tabs. Instead try memoizing the component in the parent itself. That way your component is memoized only once.
Is there a way to compare previous context value with the current context value in the consumer child component's lifecycle methods?
If there is no way to do that, is there any other workaround or an alternate solution to this?
FYI: I'm using react v16.8.1
Using HOC:
const withContext = Wrapped => props => (
<SomeContext.Consumer>
{value => <Wrapped {...props} contextValue={value}/>}
</SomeContext.Consumer>
);
const MyComponent = withContext(class extends React.Component {
...
componentDidUpdate(prevProps) {
if (prevProps.contextValue !== this.props.contextValue) {
...
}
}
...
});
Using hooks:
function MyComponent(props) {
...
const contextValue = useContext(SomeContext);
const [oldContextValue, saveContextValue] = useState(contextValue);
useEffect(() => {
console.log(oldContextValue, contextValue);
saveContextValue(contextValue);
}, [contextValue]);
...
}
Note that useEffect runs only on the first render and when contextValue changes. So if you don't really need old contextValue for something, you don't need useState.
Usually if you're going to have dynamic context its value will most likely come from a state of a parent component.
So why not detect changes in the state instead of trying to detect changes in the Provider?
Like in the example for dynamic context in the react documentation : https://reactjs.org/docs/context.html#dynamic-context
i have set of components those use central store(i.e. redux store) via container component. these components are tend to be flat simple and reside within HOC component. HOC component utilizes react-redux connect.
class HOComponent extends Component {
const { data1, data2 } = this.props;
render() {
<Component1 data={data1} />
<Component2 data={data2} />
}
}
const selector = (state) => ({
data1 = selectors.data1(state),
data2 = selectors.data2(state),
other = selectors.other(state)
});
above is the selector for child component Component1 Component2 respectively.
Below how is the selectors look like which utilizes reselect.
const alldata = (state) =>
state.alldata;
export other = (state) =>
state.other;
export data1 = createSelector(
[alldata],
(alldata) => {
//lets assume
return alldata.filter(d => d.criteria === data1.criteria);
})
export data2 = createSelector(
[alldata],
(alldata) => {
//lets assume
return alldata.filter(d => d.criteria === data2.criteria);
})
the question is this right usage of the reselect i noticed this is ineffective, since if my HOC other selctor triggered Component1 Component2 also being rerendered anyway. should i be checking each data flow within shouldComponentUpdate method. i thought reselect usage will battle with this issue in first place. did i misunderstand it. is it only prevents recalculation part?
this is continuation of my earlier post
prevent selectors of child components being triggerred in redux