Why doesn't this child component rerender? - javascript

I'm experimenting with ReactJS and I'm trying to understand how child component rendering is triggered. In ReactJS, if I set up an example like this:
var externalCounterVar = 10
class Counter extends React.Component {
constructor(props){
super(props);
this.state = props;
}
render() {
console.log('rendering counter')
return (
<div> {externalCounterVar} </div>
)
}
}
class Main extends React.Component {
constructor(props){
super(props);
}
handleClick() {
externalCounterVar += 1;
}
rerender(){
this.render();
}
render() {
console.log('rendering');
return (
<div>
<button onClick={this.rerender.bind(this)} />
<Counter counter={externalCounterVar} />
</div>
)
}
}
ReactDOM.render(<Main />, document.getElementById('root'));
I'm not sure I understand why when you "rerender" it calls the render method of Main but not Counter? It seems like it should call both render methods since it's rendering Main and Counter is a child of Main.
So when rerender is called, 'rendering' will print but 'rendering counter' will not.

It looks like you're overlooking one of the main benefits of using React, namely how state works.
You never, ever need to call this.render within a React component
You should never set state dynamically, ie: this.state = ...
You should always use this.setState to set your state.
Rewritten, your code should look something like the following:
const externalCounterVar = 10
class Counter extends React.Component {
render() {
console.log('rendering counter')
return (
<div> {this.props.counter} </div>
)
}
}
class Main extends React.Component {
state = {
counter: externalCounterVar
}
handleClick() {
this.setState({counter: this.state.counter + 1});
}
render() {
console.log('rendering');
return (
<div>
<button onClick={this.handleClick.bind(this)} />
<Counter counter={this.state.counter} />
</div>
)
}
}
By calling this.setState, React automatically knows it needs to rerender your component, and as a result, all child components will also be rerendered.
Hope this helps!

In this case you don't have to use rerender method, also with purpose re-render all child components you need update state with method setState. And also accordingly to this you have to "move state up".
Here my example:
class Counter extends React.Component {
render() {
console.log('rendering counter');
return (<div> {this.props.counter} </div>);
}
}
class Main extends React.Component {
constructor(props){
super(props);
this.state = {counter: props.counter};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(prevState => ({counter: ++prevState.counter}));
}
render() {
console.log('rendering');
return (
<div>
<button onClick={this.handleClick} />
<Counter counter={this.state.counter} />
</div>
);
}
}
var externalCounterVar = 10;
ReactDOM.render(
<Main counter={externalCounterVar} />,
document.getElementById('root')
);

In some situations you can use this.forceUpdate() to call re-render.
But, if you can not do this, do not do.
https://facebook.github.io/react/docs/react-component.html#forceupdate

Related

React trying to assign props to state variable doesn't work

I'm trying to assign props that I get from parent component and assign it to state in child component(because I want to manipulate the props data I assign it to state first).
When I log the state variable it comes out as an empty array but when I make a new variable in render and assign props to it and log it. It does show the data I need. Also, when I just log this.props I can definitely see that props holds the data I need.
I've assigned props to state a couple of times before, so I'm not sure what is so different this time for it not to work.
Parent component where I pass props to child:
<ShowAvailableTimeslots onClick={this.getSelectedTimeslot} allTimeSlots={this.state.AvailabletimeSlots} />
Child component where I try to assign props to state:
class ShowAvailableTimeslots extends Component {
constructor(props) {
super(props)
this.state = {
sliceEnd: 5,
sliceStart:0,
selectedSlotValue: "",
timeSlotArr: this.props.allTimeSlots,
// timeSlotSlice: timeSlotArr.slice(this.state.sliceStart, this.state.sliceEnd)
}
}
handleTimeSlotClick = (timeSlot) => {
this.setState({ selectedSlotValue: timeSlot }, () => {
this.props.onClick(this.state.selectedSlotValue)
console.log('time slot value', timeSlot)
});
}
previousSlots =()=>{
var test;
}
forwordSlots =()=>{
var test;
}
render() {
var timeSlotArrRender = this.props.allTimeSlots;
return (
<React.Fragment>
{console.log("state", this.state.timeSlotArr)} // --> doesn't show data
{console.log("props", this.props)} // --> does show data
{console.log("render variable", timeSlotArrRender )} // --> does show data
<button className="button btn" onClick={() => this.previousSlots()} disabled={this.state.sliceStart === 0}>left</button>
{/* {this.state.timeSlotArr.map(timeSlot => <a className="timeslot btn " key={timeSlot} value={timeSlot} onClick={() => this.handleTimeSlotClick(timeSlot)}>{timeSlot}</a>)
} */}
<button className="button btn">right</button>
</React.Fragment>
)
}
}
export default ShowAvailableTimeslots
the constructor is called when the component life cycle begins.
You are passing the this.state.AvailabletimeSlots from the parent and by then the constructor have already been called and the assignment for timeSlotArr is already done, so
timeSlotArr: this.props.allTimeSlots // will not work
You have to get help of life cycle methods or hooks
componentWillReceiveProps(nextProps){
this.setState({timeSlotArr: nextProps.allTimeSlots })
}
According to new changes you have to use
static getDerivedStateFromProps(nextProps, prevState){
return {
timeSlotArr: nextProps.allTimeSlots
};
}
I have it working just fine. https://jsfiddle.net/85zc4Lxb/
class App extends React.Component {
render() {
return (<Child passing="I am being passed to child" />);
}
}
class Child extends React.Component {
constructor(props) {
super(props)
this.state = {
passedProp: this.props.passing,
}
}
render() {
return (
<React.Fragment>
<button>{this.state.passedProp}</button>
</React.Fragment>
)
}
}
ReactDOM.render(
<App />,
document.getElementById('container')
);
I try to recreate the scenario and it work try saving all your files again and then check
parents component
import React, { Component } from "react";
import TestOne from "./Components/TestOne/TestOne";
export class App extends Component {
state = {
x: "x data",
y: "y data",
};
render() {
return (
<div>
<TestOne x={this.state.x} allTimeSlots={this.state.y}/>
</div>
);
}
}
export default App;
Child component
import React, { Component } from "react";
export class TestOne extends Component {
constructor(props) {
super(props);
this.state = {
sliceEnd: 5,
sliceStart: 0,
selectedSlotValue: "",
timeSlotArr: this.props.x,
};
}
render() {
var timeSlotArrRender = this.props.allTimeSlots;
return (
<React.Fragment>
{console.log("state", this.state.timeSlotArr)}
{console.log("props", this.props)}
{console.log("render variable", timeSlotArrRender)}
<button className="button btn">right</button>
</React.Fragment>
);
}
}
export default TestOne;
Result:
I think you are missing this.setState({})

Lifting State Up And Functional Components

I'm having trouble with lifting state up and converting components to functions. What's wrong with my code.
Instructions: 1: Inside the JS section, create a class component named App. Inside its render() method, have it return the Welcome component. In the ReactDOM.render() method, change Welcome to App.
2: Lift the state from the Welcome component to App, so that the state is initialized inside of App's constructor.
3: Convert the Welcome component to a function component that returns the same welcome message as before. You will need to pass the bootcampName property of state from App to the Welcome component. It's up to you whether or not to destructure it.
class App extends Component {
constructor(props) {
super(props);
this.state = {
bootcampName: "Nucamp"
};
}
render() {
return (
<div className="App">
<Welcome {this.state.bootcampName}>;
</div>
);
};
}
function Welcome(props) {
return (
<h1>Welcome to {this.props.bootcampName}!</h1>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
You have some errors there
You need to close the Welcome Component.
You need to name the prop
Destruct the props in because of this.state do not exist there.
Here the Code:
class App extends Component {
constructor(props) {
super(props);
this.state = {
bootcampName: "Nucamp"
};
}
render() {
return (
<div className="App">
{ /**
* you need to close the Welcome Component
* you need to name the prop
*/}
<Welcome bootcampName={this.state.bootcampName}/>;
</div>
);
};
}
// Here destruct props to use it
function Welcome({bootcampName}) {
return (
<h1>Welcome to {bootcampName}!</h1>
);
}
ReactDOM.render(<App />, document.getElementById('root'));
Name the prop:
<div className="App">
<Welcome bootcampName={this.state.bootcampName} />
</div>
When you use functional components, you no longer need to use this. Try doing this instead:
class App extends Component {
constructor(props) {
super(props);
this.state = {
bootcampName: "Nucamp"
};
}
render() {
return (
<div className="App">
<Welcome bootcampName={this.state.bootcampName}>;
</div>
);
}
}
function Welcome({bootcampName}) {
return (
<h1>Welcome to {bootcampName}!</h1>
);
}

Parent state change not updating child props

I'm trying to update a child based on the props provided by it's parent. The way it works right now is that the parent's state contains a variable called 'paused' which is provided to the child like this:
class Parent extends Component {
constructor(props) {
super(props)
this.state = {
history: this.props.history,
paused: false,
}
}
render() {
let paused = this.state.paused
return (
<ChildContainer
graph={
<Child
paused={paused}
/>
}
/>
)
}
Child then uses it like this:
render() {
return (
<div>
{'paused:' + this.props.paused}
</div>
)
}
Paused is a boolean, the usage above is just for testing, since I couldn't get it to update where I want, the behaviour is the same like this.
Paused is being updated in the parent, but not the child.
I've read a lot of questions like this, but I'm at a loss.
Any help would be greatly appreciated.
I can't seem to find a problem with this based on the code you've provided. Here is working proof.
If ChildContainer has any logic that could interfere then I could be wrong, but as is, this works:
class Parent extends React.Component {
constructor(props) {
super(props)
this.state = {
history: this.props.history,
paused: false,
}
}
render() {
let paused = this.state.paused // I agree with #Kuo-hsuan Hsu this is unnecessary
return (
<ChildContainer
toggle={() => this.setState({ paused: !this.state.paused })}
graph={<Child paused={paused}/>}
/>
)
}
}
class ChildContainer extends React.Component {
render() {
return (
<div>
{this.props.graph}
<button onClick={this.props.toggle}>Toggle</button>
</div>
);
}
}
class Child extends React.Component {
render() {
return (
<div>{'paused:' + this.props.paused}</div>
)
}
}
ReactDOM.render(<Parent />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root" />

How to avoid the setState() / render() endless loop when passing data from child to parent?

I am trying to save child-data in the state of the parent, but end up with the endless loop because setState() calls render().
Error message: Maximum update depth exceeded.This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
Relatively new to React, so I can't seem to word the question when googling solutions. I know why the error is occurring, I just don't know how to get around the issue. Is there a specific method I can use that prevents re-rendering?
Here is the parent:
export class ToDoList extends React.Component {
constructor(props){
super(props);
this.state = {
data: null
}
}
myCallback = (dataFromChild) => {
this.setState({data: dataFromChild.toUpperCase()})
}
render() {
return (
<div>
<ToDoItem callbackFromParent={this.myCallback}/>
</div>
);
}
}
The child:
class ToDoItem extends React.Component {
constructor(props){
super(props);
this.state = {
listInfo: 'Doggos'
}
}
render(){
return(
<h1>{this.props.callbackFromParent(this.state.listInfo)}</h1>
);
}
}
Your code is doing exactly that, an endless loop. When your ToDoItem component renders, it calls callbackFromParent which updates the state of ToDoList, causing ToDoList to re-render, subsequently re-rendering the ToDoItem. Since ToDoItem re-renders, it calls callbackFromParent again and so on...
I'd like to ask why you are trying to render the non-value-returning function of callbackFromParent. It doesn't return anything, so it doesn't make sense why you'd want to render it inside of your <h1> tags.
There is a small problem with the code you shared, that you are calling a function from the render() rather than binding it to some event which is making it go into infinite loop...
Is this what you are trying to achieve?
class ToDoList extends React.Component {
toUpper = (dataFromChild) => {
return dataFromChild.toUpperCase();
}
render() {
return (
<div>
<ToDoItem toUpper={this.toUpper}/>
</div>
);
}
}
class ToDoItem extends React.Component {
constructor(props){
super(props);
this.state = {
listInfo: 'Doggos'
}
}
render(){
return(
<h1>{this.props.toUpper(this.state.listInfo)}</h1>
);
}
}
ReactDOM.render(<ToDoList />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
You are getting an endless loop because of the following reasons:
1) You are calling the parent callback on each render.
2) You are saving the uppercased value in the parent state.
When the parent state gets updated, the child gets re-rendered, meaning that it will call the callback again, which will cause to re-render, which calls the callback again etc...
An alternative solution would be to pass the util function down to the child which can then call it once when it re-renders. Since no state in the parent is being updated, the child will not be re-rendered.
If you're trying to save data on parent but want to display it in child, try this:
Here is the parent:
export class ToDoList extends React.Component {
constructor(props){
super(props);
this.state = {
data: null
}
}
myCallback = (dataFromChild) => {
this.setState({data: dataFromChild})
}
render() {
return (
<div>
<ToDoItem callbackFromParent={this.myCallback} data={this.state.data}/>
</div>
);
}
}
The child:
class ToDoItem extends React.Component {
constructor(props){
super(props);
this.state = {
times: 0
}
// Bind Explained below
this.iBeClicked = this.iBeClicked.bind(this);
}
iBeClicked(){
this.setState({times: ++this.props.data});
this.props.callbackFromParent(this.props.data++);
}
render(){
return(
<div className="wrap">
<h1 onClick="iBeClicked">{this.props.data !== null ? this.props.data: 'Nothing' }</h1>
</div>
);
}
}
You use this.method.bind(this) in order to bind Component's this to React callback's execution inside render.

React.js render subcomponents from component

I want to create a reusable component where the DOM structure can be different each time the component is rendered. Let's say I have this
class Comp extends React.Component {
constructor() {
super()
this.state = {
click: null,
}
}
render() {
return(
<div>
{this.props.chidren}
</div>
)
}
handleButton1() {
this.setState({click: 'button1'});
}
handleButton2() {
this.setState({click: 'button2'});
}
}
class SubComp1 extends React.Component {
render() {
return(
<button onClick={() => this.props.handleButton1()}>Button 1</button>
)
}
}
class SubComp2 extends React.Component {
render() {
return (
<button onClick={() => this.props.handleButton2()}>Button 2</button>
)
}
}
ReactDOM.render((
<Comp>
<div id="somediv">
<div id="andanother">
<SubComp1 />
</div>
</div>
<div id="andanotherother">
<SubComp2 />
</div>
</Comp>), document.getElementById('app'))
Currently, the two subcomponents do not have access to their respective handler functions. What's the best way of passing the functions handleButton1 and handleButton2 to the subcomponents assuming that their position in the DOM is dynamic and might change depending on the layout of the page.
I have thought of 2 solutions so far:
Iterating inside the props.children until I find the element of interest then clone it with the property
Using ref and somehow render the subcomponents after the main component has been rendered through the componentDidMount callback.
What are your thoughts on this?
This is a place where using React's Context would be the most straightforward solution.
Another solution would be to use Redux actions, but that would make your component less reusable and more tightly coupled with your application, which you may or may not care about.
Why not do something like this:
class Comp extends React.Component {
constructor() {
super()
this.state = {
click: null,
}
}
render() {
return(
<div>
{this.props.chidren}
</div>
)
}
handleButton(button) {
this.setState({click: button});
}
}
Then in the subcomponents you can do something like
class SubComp1 extends React.Component {
render() {
return(
<button onClick={() => this.props.handleButton('button1')}>Button 1</button>
)
}
}
class SubComp2 extends React.Component {
render() {
return (
<button onClick={() => this.props.handleButton('button2')}>Button 2</button>
)
}
}
One Alternative option which might fit your needs is to build a higher order component, which decorates another component with some additional functionality, below is a quick example of how this may work for you,
The higher order component:
const Comp = ComposedComponent =>
class Comp extends React.Component {
constructor(props) {
super(props)
this.handleButton = this.handleButton.bind(this);
this.state = {
click: null,
}
}
handleButton(button) {
this.setState({click: button});
}
render() {
return(
<ComposedComponent
onClick={this.handleButton}
/>
)
}
}
export default Comp;
The child component:
class SubComp1 extends React.Component {
render() {
return(
<button onClick={() => this.props.onClick('button1')}>Button 1</button>
)
}
}
How to use it:
const ExtendedComp = Comp(SubComp1);
<ExtendedComp />
would this be suitable for your task?

Categories