I have two components. A main and a child component.
Let's assume a function is triggered in the main component which cause its state to be mutated.
The state of the main component is passed down to the child component as a prop. The newly updated data in the props of the child component should now be used to to set the state of the child component.
I can't do this on ``componentDidUpdate since it would cause an infinite loop.
On the other hand I wouldn't want to lift the child's state to the main component since most code of it would be useless in the main component.
I hope you can help
You can use getDerivedStateFromProps as mentioned in the React docs:
export default class Child extends Component {
static getDerivedStateFromProps(newProps, currentState) {
return {
value : newProps.value
}
}
render() {
return (
<div>
{/* Your layout */}
</div>
);
}
}
componentDidUpdate takes prevProps as argument componentDidUpdate(prevProps, prevState, snapshot). So to not getting the code in infinite loop, you can compare this.props with prevProps and update the state accordingly.
componentDidUpdate(prevProps) {
if(this.props.data !== prevProps.data) {
// update the new state here this will not cause infinite loop
}
}
For a functional component using hooks.
function Child(props) {
const [whatever, setWhatever] = React.useState(props.whatever);
React.useEffect(() => {
setWhatever(props.whatever);
}. [whatever]);
}
export default Child;
Hope it helps.
Related
I'm trying to learn react and ran into a snag. I'm struggling to update the parent based on the child state. I've managed to pass the child state to the parent by binding the child's state to the same child's prop when invoked by the parent.
Parent.js
import React, { Component, setState } from 'react'
import './Parent.css'
import Child from './Child'
export class Parent extends Component {
constructor(props) {
super(props)
this.state = {
childState: false
}
}
checkState(newState){
console.log(`new state is ${newState}`)
}
render() {
return (
<div class={`parent ${this.state.childState ? 'parent-child-not-clicked' : 'parent-child-clicked'}`}>
<h1>{this.state.childState === true ? 'true' : 'false'}</h1>
{/* <Child changeState={(newState)=>{newState === true ? this.setState(prevState => ({childState: prevState.childState+1})):this.setState(prevState => ({childState: prevState.childState-1}))}}></Child> */}
<Child changeState={(newState) => {console.log(newState)}}></Child>
</div>
)
}
}
export default Parent
Child.js
import React, { Component } from 'react'
import "./Child.css"
export class Child extends Component {
constructor(props) {
super(props)
this.state = {
childState: false
}
this.updateState = this.updateState.bind(this)
}
updateState(){
this.setState({
childState: !this.state.childState
}, () => {return this.state.childState})
}
render() {
return (
<div className="child">
<h1>{`child state is ${this.state.childState}`}</h1>
<div onClick={() => this.props.changeState(this.updateState())}>Click</div>
</div>
)
}
}
export default Child
The console keeps rendering undefined, meaning newState doesn't contain the boolean value true / false. Would appreciate if anyone can point me in the right direction.
Thanks in adavance
this.updateState() doesn't return anything. So nothing is sent to this.props.changeState.
Probably the simplest approach is to remove this.props.changeState from the JSX markup and move it into updateState. Then within updateState define the new state object, update the component's state with it, and pass it to the prop function. Something like this:
updateState(){
const newState = {
childState: !this.state.childState
};
this.setState(newState);
this.props.changeState(newState);
}
Then in the JSX just call updateState (putting less logic inline in the JSX and more in the functions):
<div onClick={this.updateState}>Click</div>
As an aside, while the example shown is clearly a contrived one, tracking the same state in two different places is probably the wrong design. If the parent just needs updates, pass it just the updates that it needs. But if the parent is tracking the state, the child doesn't need to duplicate that effort. You can remove state from the child entirely and just pass it the values it needs, simplifying the whole thing.
I am writing a higher-order component that takes children and then re-renders them based on the state of a context provider.
Consider the following simplified example:
index.js
const ChildElem = () => {
return(
<div/>
)
}
class Example extends React.Component{
render(){
return(
<FocusProvider>
<ChildElem/>
<ChildElem/>
<ChildElem/>
</FocusProvider>
)
}
}
FocusProvider.js
class FocusProvider extends React.Component{
renderChildren = (providerState) => {
//Does nothing with state and simply returns children yet they still re render
return this.props.children
}
render(){
return(
<Provider>
<Subscribe to={[ContextProvider]}>
{provider => this.renderChildren(provider.state)}
</Subscribe>
</Provider>
)
}
}
As you can see from the example the children of FocusProvider are being returned from a function that subscribes to a context.
The problem I am running into is that the children are being re-rendered even though nothing is being changed on them. The only thing that is being changed is the state of the context provider they are subscribed to.
Any advice would be greatly appreciated
You can control whether the component should update or not there is a function of react component class
shouldComponentUpdate ( nextProps, nextState, nextContext ) {
/* compare nextState with your current states properties if you want to update on any basis return true if you want to render the component again */
return true; // will re-render component ,
return false; // do not re-render component even if you change component states properites
}
nextProps contains that prop the new props , and nextState contain new State properties
I am currently grabbing a prop from state and using it on an event listener. i.e.,
import * as React from 'react';
import { getDetails } from './actions';
interface Props {
selecting: boolean;
getDetails(): Action<void>;
}
#connect((state) => ({
selecting: state.items.selecting,
}), {
getDetails,
})
export default class Grid extends React.PureComponent<Props> {
onMouseEnter = () => {
if (!this.props.selecting) {
this.props.getDetails();
}
}
render() {
return (
<div onMouseEnter={this.onMouseEnter} />
);
}
}
However, whenever the selecting property changes, it causes a re-render to my component.
Is there a way to pass a variable from state through connect and NOT have it trigger this update to my component? I want it almost as if it were an instance-bound variable rather than a state variable.
Try overriding the shouldComponentUpdate() lifecycle function. This gives you much more granular control over when your component should or shouldn't re-render (at the cost of added code complexity).
shouldComponentUpdate(nextProps, nextState) {
if(nextProps.someLogic !== this.props.someLogic)
return false; // Don't re-render
return true;
}
Documentation: Here
Use shouldComponentUpdate() to let React know if a component’s output is not affected by the current change in state or props. The default behavior is to re-render on every state change, and in the vast majority of cases you should rely on the default behavior.
I have a react class in which I need to use shouldComponentUpdate(), to prevent an infinite loop between the component and its parent.
I simply check whether a deep clone of nextProps is equal to this.props, and I only update the component if they're not.
So far, so good. (?)
class Child extends Component {
onComponentUpdate = (e) => {
this.props.update(e)
}
shouldComponentUpdate(nextProps, nextState) {
return JSON.stringify(nextProps) !== JSON.stringify(this.props)
}
render() {
return (
<div>
// some code that might trigger onComponentUpdate()
</div>
);
}
}
Now, in my parent component, something happens that makes me want to re-render the child, without specific props changing. What I did now, is changing a counter in state and passing it to the child as a prop. I never do anything with the counter itself, it is merely an indication for the child that props actually changed so that the child should update.
class Parent extends Component {
constructor() {
super()
this.state = { counter: 0 }
}
otherChildChanged = () => {
this.setState({ counter: this.state.counter + 1 })
}
render() {
return (
<div>
<Child
counter={this.state.counter}
update={"some function"}
other={"props"}
>
</Child>
<OtherChild onChange={this.otherChildChanged}>
</OtherChild>
// some code that might trigger onComponentUpdate()
</div>
);
}
}
Is there a better way to do this?
You should pass down the size of your resizable div as a prop to the Child Component. This way, when it changes, JSON.stringify(nextProps) !== JSON.stringify(this.props) will be true and a re-render will occur.
If a component has to behave in a certain way (re-render, for example) depending on something that happens on his Parent, it should be passed to it as a prop.
The render method of this component does use any of the props supplied to the component.
Will the component re-render when the props change regardless?
class MyComponent extends React.Component {
constructor(props) {
super(props);
const { propValue } = props;
// do something with propValue...
}
render () {
return (
<div>foo</div>
);
}
}
Will render be called - yes. Unless you implement shouldComponentUpdate to return false.
Will the DOM be rerendered - no.
Also you might want to take a look at https://babeljs.io/docs/plugins/transform-react-constant-elements/ that hoists static elements up.
In
const Hr = () => {
return <hr className="hr" />;
};
Out
const _ref = <hr className="hr" />;
const Hr = () => {
return _ref;
};
Yes, the component will re-render unless you implement shouldComponentUpdate. You can inherit from PureComponent which uses shallow comparison of prop and state with previous values to determine if component should update or not.
As far as i know react will call the render method in the following scenarios
when your component get mounted initially
when state got changed using this.setState()
when your component receives new props
when this.forceUpdate() get called.
since you didn't implement shouldcomponentUpdate() the render method is going to get called