I'm trying to use the new Context API in my app and it looks like every time I update the context, it re-renders any component connected to it regardless. I have a sandbox demo setup to see code and working issue. When you type in the input - the buttons context is rendered and vice-versa. My original thinking was that if you type in the input, only the input context would be printed out.
DEMO
Is this how it works or am I missing something?
Thanks,
Spencer
The way I avoid re-rendering with react context API:
First I write my component as pure functional component:
const MyComponent = React.memo(({
somePropFromContext,
otherPropFromContext,
someRegularPropNotFromContext
}) => {
... // regular component logic
return(
... // regular component return
)
});
Then I write a function to select props from context:
function select(){
const { someSelector, otherSelector } = useContext(MyContext);
return {
somePropFromContext: someSelector,
otherPropFromContext: otherSelector,
}
}
I have my connect HOC wrote:
function connect(WrappedComponent, select){
return function(props){
const selectors = select();
return <WrappedComponent {...selectors} {...props}/>
}
}
All together
import connect from 'path/to/connect'
const MyComponent ... //previous code
function select() ... //previous code
export default connect(MyComponent, select)
Usage
<MyComponent someRegularPropNotFromContext={something} />
Demo
Demo on codesandbox
Conclusion
MyComponent will re-render only if the specifics props from context updates with a new value, if the value is the same, it will not re-render. Also it avoid re-rendering on any other value from context that is not used inside MyComponent. The code inside select will execute every time the context updates, but as it does nothing, its ok, since no re-rendering of MyComponent is wasted.
That is the expected behaviour. Components as consumers re-renders when their provider data updates. Further more, shouldComponentUpdate hooks do not work on Consumers.
Quoting React's content API:
All Consumers that are descendants of a Provider will re-render whenever the Provider’s value prop changes. The propagation from Provider to its descendant Consumers is not subject to the shouldComponentUpdate method, so the Consumer is updated even when an ancestor component bails out of the update.
For more info check here
Related
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'm having a really hard time to figure out what's happening when there is nothing being used to trigger re-render the component.
Events.js Component renders twice when I remove the useState() from the Event.js it renders once, but I need to keep it. when I use useEffect() inside Event components, renders fourth time.
I just kept the dummy data to give you to fill the emptiness and tried to remove React.memo, nothing happens. the problem is with the Event.js component I believe. I'm also using the Context API, but forth time rendering is too much.
useEffect inside App.js is getting some value from the localStorage, I can't access that direct 'cause the value is undefined by default
sandbox code here: https://codesandbox.io/s/event-manager-reactjs-nbz8z?file=/src/Pages/Events/Events.js
The Events.js file is located on /Pages/Events/Events.js
example code is below
Event.js ( child component )
function Events() {
// Sate Managing
const [allEvents, setAllEvents] = React.useState(null);
console.log('Rendering EventsJs', allEvents);
React.useEffect(() => {
setAllEvents(['apple', 'banana']);
}, []);
return (
<div className="events">
{ console.log('Event Rendered.js =>') }
</div>
)
}
export default React.memo(Events, (prevProps, nextProps) => {
return true;
} );
App.js ( parent component )
import { BrowserRouter, Route, Redirect } from 'react-router-dom';
function App() {
const [userId, setUserId] = React.useState(null);
React.useEffect(() => {
setUserId(1);
}, []);
// Login
return (
<BrowserRouter>
<Navigation />
<Route path='/events' component={Events} />
{console.log('App Rendered')}
</BrowserRouter>
);
}
export default App;
Error:
Your app is working fine. It is rendering as it should. As we know:
A React component re-renders whenever its props or state change.
And react component lifecycle order is:
Initial props/state --> render --> DOM update --> mounted
props/state changed --> render --> DOM update --> updated ... so on
In the example below, it is rendering 2 times and that's correct:
First one (first console.log) is due to initial render with state as []
Second one (second console.log) is due to state change (caused by useEffect) to ['apple', 'banana']
function Events() {
const [allEvents, setAllEvents] = React.useState([]);
console.log('Event Rendered', allEvents);
useEffect(() => {
setAllEvents(['apple', 'banana']);
}, []);
return <>Events</>;
}
About using React.memo:
React.memo only checks for props changes. If your function component wrapped in React.memo has a useState or useContext Hook in its implementation, it will still rerender when state or context change.
You can not skip re-render using React.memo due to change in state. You can only optimize to skip re-rendering caused by change in props.
But in the example above, you don't have props passed from the parent component, the only props passed to Events are those passed by react-router i.e. route props. So, there is no need to use React.memo.
Here is sandbox, check the console.logs. You will see only 3 logs: "App render", "Event render with initial state", "Event render with new state".
EDIT:
If we remove StrictMode from index.html, and add below console.logs in components:
App.js --> console.log('App rendered')
Evenets.js --> console.log('Event rendered', allEvents, isLoading) // (allEvents and isLoading are state variables here)
And go to http://localhost:3000, we see 1 log:
App Rendered
Now click on "Events", we see 3 logs:
1: Event Rendered, [], true
2: Event Rendered, [{}, ... 54 items], true
3: Event Rendered, [{}, ... 54 items], false
which is correct behavior (refer lifecycles order written above):
1st log: render with initial state ([], true)
2nd log: render with new allEvents (54 items) and old isLoading (true)
3rd log: render with old allEvents (54 items) and new isLoading (false)
Below are the right questions to ask now:
Question1:
Why 2nd and 3rd render (log) are separate, should not they be batched (merged) and applied together as they are written in the same function?
fetch('url').then(() => {
// ... code here
setAllEvents([...events])
setLoading(false)
})
Answer:
No, they will not be batched in above code. As explained by Dan Abramov:
This is implementation detail and may change in future versions.
In current release, they will be batched together if you are inside a React event handler. React batches all setStates done during a React event handler, and applies them just before exiting its own browser event handler.
With current version, several setStates outside of event handlers (e.g. in network responses) will not be batched. So you would get two re-renders in that case.
There exists a temporary API to force batching. If you write ReactDOM.unstable_batchedUpdates(() => { this.fn1(); }); then both calls will be batched. But we expect to remove this API in the future and instead batch everything by default.
So, you can write (inside fetch's then), if you want, it will save 1 render:
ReactDOM.unstable_batchedUpdates(() => {
setAllEvents([...events])
setLoading(false)
})
Question2:
What's React event handler in above quote?
Answer: foo in example below. These 2 set states will be batched.
const foo = () => {
setAllEvents([
{ _id: '5ede5af03915bc469a9d598e', title: 'jfklsd', },
])
setLoading(false)
}
<button onClick={foo}>CLICK</button>
Question3:
Does it update HTML DOM as many times as it renders (prints console.log)?
Answer: No. React compares calculated virtual DOMs before updating real DOM, so only those changes are applied to real DOM which are required to update the UI.
Question4:
Why was rendering doubled when we use StrictMode?
Answer: Yes, StrictMode will intentionally double invoke "render" and some other lifecycle methods to detect side-effects. Strict mode checks are run in development mode only; they do not impact the production build.
Well actually this is caused by your usage of React.memo, its second parameter is called areEqual, and you pass in () => false, so you are basically telling React that the props are always changing. Therefore whenever App rerenders, Events rerenders too.
You should let React.memo check for prop changes. By passing () => false you are actually telling that its props always change (they are never equal).
export default React.memo(Events);
Here's a working example.
how to force render our component when props changes?
how to force render parent component when child component's props changes?
i searched a lot but all solutions are deprecated in new React version.
in my any pages (Route exact path="/post/:id" component={post}) for example (siteUrl/post/1) i am getting (:id) from props (this.props.match.params) and that works.
but when i am in single page component (Post) and this Route (siteUrl/post/1) when i am passing new Props to this component (:id).
props will changes but single component and parent component Will not re render...
You may be using componentDidMount method.
componentDidMount() is invoked immediately after a component is
mounted (inserted into the tree). Initialization that requires DOM
nodes should go here. If you need to load data from a remote endpoint,
this is a good place to instantiate the network request.
but you need to use componentDidUpdate.
componentDidUpdate() is invoked immediately after updating occurs.
This method is not called for the initial render.
You can also use state and other React features without writing a class.
read more: https://reactjs.org/docs/hooks-effect.html
To make both parent and child re-render you need to path prop from parent to it's child.
// parent using
<Parent someProp={{someVal}} />
// parent render:
render() {
const { someProp } = this.props
<Child someProp={{someProp}} />
}
this will surely re-render both components, unless you stated another logic in componentShouldUpdate
in your case Router looks like a parent for Parent so you should only path :id as a prop.
Make sure Router is at the top level, right under the App
Important is ,that you initialise the someVal of the child first in the constructor
public static getDerivedStateFromProps(
nextProps,
nextState
) {
let { someVal } = nextProps;
if (nextState.someVal !== someVal) {
return {
initialVal,
someVal: someVal
};
}
return null;
}
After it will rerender on prop changes because the state changes
I have a parent component which is a flat list which contains a header HeaderComponent. This HeaderComponent is a custom component that I have created which contains 2 child components of its own. Whenever i refresh the list, I am passing a boolean to the HeaderComponent as props which get passed onto its own children, I am doing this so I can check if each component needs to fetch new data or not. The problem is that whenever the parent refreshes and sets a new state the constructors of the child components get called everytime. Shouldn't the constructor be called only the first time the parent initializes and then all further calls involve calling the shouldComponentUpdate method of the children in order to see if it needs an update or not.
Parent component
_renderHeader = () => {
return <HeaderComponent Items={this.state.Data} refresh={this.state.refresh}/>;
};
render() {
console.log("TAG_RENDER render called " + this.state.refresh);
return (
<FlatList
refreshing={this.state.refresh}
onRefresh={() => {
console.log("onRefresh");
this.setState({
refresh: true
}, () => {
this._fetchData();
});
}}
......
ListHeaderComponent={() => this._renderHeader()}
.......
/>
);
}
Header Component
export default class HeaderComponent extends React.Component {
constructor(props) {
super(props);
console.debug("HeaderComponent");
}
render() {
return (
<MainHeader Items={this.props.Items}/>
<SubHeader refresh={this.props.refresh}/>
);
}
}
The constructor of MainHeader and Subheader gets called whenever the parent component refreshes. Does this mean that it is creating new child components each time it refreshes because I can see the render of the children also being called multiple times.
Control your index.js file. If you see <React.StrictMode>, you should change to <>. This is solved my problem.
It should be like:
ReactDOM.render(
<>
<App/>
</>,
document.getElementById('root')
);
As correctly stated in the one of the answers , removing the strict mode fixes the issue. Coming to why it does that, its because the strict mode intentionally calls the 'render' method twice in order to detect potential problems.
React works in two phases:render and commit. Render phase checks and determines the new changes to be applied. And commit phase applies it.
Render phase lifecycle includes methods like : constructor, UNSAFE_componentWillMount,UNSAFE_componentWillReceiveProps, ...,render and few more.
The render phase is time consuming and is often broken into pieces to free up the browser. Render phase might be called multiple times before the commit phase(usually very fast).
Since the render phase methods are called more than once, its important that none of those method have any problems or side effects.
Thus just in order to highlight the possible side effects to make them easy to spot, react explicitly double invoke the render phase methods.
You can read more about this on :https://reactjs.org/docs/strict-mode.html#detecting-unexpected-side-effects :)
Strict mode can’t automatically detect side effects for you, but it can help you spot them by making them a little more deterministic. This is done by intentionally double-invoking the following functions:
Class component constructor, render, and shouldComponentUpdate
methods
Class component static getDerivedStateFromProps method
Function component bodies
State updater functions (the first argument to setState)
Functions passed to useState, useMemo, or useReducer
https://reactjs.org/docs/strict-mode.html
As stated in the site,
Note:
This only applies to development mode. Lifecycles will not be double-invoked in production mode.
TL;DR Given the following example code:
ReactDOM.render(<MyComponent prop1={someVar} />, someDomNode);
Is it possible to manually pass React context into the instance of MyComponent?
I know this sounds like a weird question given React's nature, but the use case is that I'm mixing React with Semantic UI (SUI) and this specific case is lazy-loading the contents of a SUI tooltip (the contents of the tooltip is a React component using the same code pattern as above) when the tooltip first displays. So it's not a React component being implicitly created by another React component, which seems to break context chain.
I'm wondering if I can manually keep the context chain going rather than having components that need to look for certain data in context AND props.
React version: 0.14.8
No. Before react 0.14 there was method React.withContext, but it was removed.
However you can do it by creating HoC component with context, it would be something like:
import React from 'react';
function createContextProvider(context){
class ContextProvider extends React.Component {
getChildContext() {
return context;
}
render() {
return this.props.children;
}
}
ContextProvider.childContextTypes = {};
Object.keys(context).forEach(key => {
ContextProvider.childContextTypes[key] = React.PropTypes.any.isRequired;
});
return ContextProvider;
}
And use it as following:
const ContextProvider = createContextProvider(context);
ReactDOM.render(
<ContextProvider>
<MyComponent prop1={someVar} />
</ContextProvider>,
someDomNode
);
In React 15 and earlier you can use ReactDOM.unstable_renderSubtreeIntoContainer instead of ReactDOM.render. The first argument is the component who's context you want to propagate (generally this)
In React 16 and later there's the "Portal" API: https://reactjs.org/docs/portals.html