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
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 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.
I'd like to know, how to handle the PropTypes Error when passing a component as a child:
Failed prop type: The prop `value` is marked as required in `ChildComponent`, but its value is `undefined`.
The render works as expected and it's passing the value prop correctly.
I suppose this happens because I am putting the component in the App component's render function without any props.
I am only passing those props to the ChildComponent when the ParentComponent maps over its children (which is the ChildComponent).
See the code: https://codesandbox.io/embed/r70r5z3j9q
Is there a way to prevent this from happening?
How should I be structuring my components?
Am I not supposed to passed components as children?
EDITED: Changed prop "name" to "value". To give it a more generic feel.
I tried to simplify the problem in the code.
I know I could pass the prop directly in App.
The use case would be when the parent is doing calculations and those calculations are supposed to be passed to the child. Without explicitly knowing what these children are.
That's why I'm using it as child in the first place.
You're using cloneElement and you're passing prop to it, not to original element. To fix it, pass props directly:
const App = () => (
<div>
<ParentComponent>
<ChildComponent name="bob" />
</ParentComponent>
</div>
);
You could easily pass component as a prop (not children) to you ParentComponent and render it only after it takes some heavy calculations:
const App = () => (
<div>
<ParentComponent component={ChildrenComponent} />
</div>
);
const ParentComponent extends React.Component {
state = { heavyComputationFinished: false } // initial state
componentDidMount() {
runYourHeavyComputations
.then(() => { this.setState({ heavyComputationsFinished: true }) })
}
render() {
const { component } = this.props
const { heavyComputationsFinished, name } = this.state
// return nothing if heavy computations hasn't been finished
if (!heavyComputationsFinished) { return null }
// we're getting this component (not its rendering call) as a prop
return React.render(component, { name })
}
}
How to initialize state with dynamic key based on props? The props is a data fetched from external source (async). So the props will change when the data is succesfully downloaded. Consider a component like this.
edit: I want to make the state dynamic because I want to generate a dialog (pop up) based on the item that is clicked. the DialogContainer is basically that. visible prop will make that dialog visible, while onHide prop will hide that dialog. I use react-md library.
class SomeComponent extends React.Component {
constructor() {
super();
this.state = {};
// the key and value will be dynamically generated, with a loop on the props
// something like:
for (const item of this.props.data) {
this.state[`dialog-visible-${this.props.item.id}`] = false}
}
}
show(id) {
this.setState({ [`dialog-visible-${id}`]: true });
}
hide(id) {
this.setState({ [`dialog-visible-${id}`]: false });
}
render() {
return (
<div>
{this.props.data.map((item) => {
return (
<div>
<div key={item.id} onClick={this.show(item.id)}>
<h2> Show Dialog on item-{item.id}</h2>
</div>
<DialogContainer
visible={this.state[`dialog-visible-${item.id}`]}
onHide={this.hide(item.id)}
>
<div>
<h1> A Dialog that will pop up </h1>
</div>
</DialogContainer>
</div>
);
})}
</div>
)
}
}
// the data is fetched by other component.
class OtherComponent extends React.Component {
componentDidMount() {
// fetchData come from redux container (mapDispatchToProps)
this.props.fetchData('https://someUrlToFetchJSONData/')
}
}
The data then is shared via Redux.
However, based on my understanding so far, state can be updated based on props with componentWillReceiveProps or the new getDerivedStateFromProps (not on the constructor as above). But, how to do that on either method?
The example here only explains when the state is initialized on the constructor, and call setState on either cWRP or gDSFP. But, I want the key value pair to be initialized dynamically.
Any help/hint will be greatly appreciated. Please do tell if my question is not clear enough.
import React from 'react';
import {connect} from 'react-redux';
import {yourAction} from '../your/action/path';
class YourClass extends React.Component {
state = {};
constructor(props){
super(props);
}
componentDidMount(){
this.props.yourAction()
}
render() {
const {data} = this.props; //your data state from redux is supplied as props.
return (
<div>
{!data ? '' : data.map(item => (
<div>{item}</div>
))}
</div>
)
}
}
function mapStateToProps(state) {
return{
data:state.data //state.data if that is how it is referred to in the redux. Make sure you apply the correct path of state within redux
}
}
export default connect(mapStateToProps, {yourAction})(YourClass)
If you do this, <div>{item}</div> will change as you change the data state. The idea is to just map the redux state to your class props - you don't have to map the props back to the state. The render() automatically listens to changes in props supplied by redux. However, if you do want to somehow know redux state change in events, you can add the following functions.
componentWillReceiveProps(newProps){
console.log(newProps)
}
getDerivedStateFromProps(nextProps, prevState){
console.log(nextProps);
console.log(prevState);
}
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