How to make counter of renders the child component in parent?
I have 2 components Widget (parent) and Message(child). I passed counter from child to parent and trying to set getting value from child set to state. And I getting err: Maximum update depth exceeded.
There is child component Message:
import React, { Component } from "react";
export default class Message extends React.Component {
constructor(props) {
super(props);
this.changeColor = this.changeColor.bind(this);
this.changeCount = this.changeCount.bind(this);
this.state = { h: 0, counter: 0 };
}
changeColor = () => {
this.setState(state => ({
h: Math.random()
}));
};
changeCount = () => {
this.setState(state => ({
counter: ++state.counter
}));
};
componentDidUpdate(prevProps) {
this.props.getColor(this.color);
this.changeCount();
this.props.getCount(this.state.counter);
}
render() {
const { children } = this.props;
const { s, l, a } = this.props.color;
this.color = `hsla(${this.state.h}, ${s}%, ${l}%, ${a})`;
return (
<p
className="Message"
onClick={this.changeColor}
style={{ color: this.color }}
>
{children}
</p>
);
}
}
There is parent component:
import React, { Component } from "react";
import Message from "./Message/Message";
export default class Widget extends React.Component {
constructor(props) {
super(props);
this.state = {
color: {
s: 30,
l: 60,
a: 1
},
counter: 0
};
}
getCount = count => this.setState(state => ({
counter: state.counter
}));
getColor = color => {
console.log(`the color is ${color}`);
};
render() {
const counter = this.state.counter;
return (
<div>
<Message
getColor={this.getColor}
getCount={this.getCount}
color={this.state.color}
>
{undefined || `Hello World!`}
</Message>
{counter}
</div>
);
}
}
What I do wrong?
The answer by #Yossi counts total renders of all component instances. This solution counts how many renderes and re-renders an individual component has done.
For counting component instance renders
import { useRef } from "react";
export const Counter = props => {
const renderCounter = useRef(0);
renderCounter.current = renderCounter.current + 1;
return <h1>Renders: {renderCounter.current}, {props.message}</h1>;
};
export default class Message extends React.Component {
constructor() {
this.counter = 0;
}
render() {
this.counter++;
........
}
}
In order to count the number of renders, I am adding a static variable to all my components, and incrementing it within render().
For Class components:
import React, { Component } from 'react';
import { View, Text } from 'react-native';
let renderCount = 0;
export class SampleClass extends Component {
render() {
if (__DEV__) {
renderCount += 1;
console.log(`${this.constructor.name}. renderCount: `, renderCount);
}
return (
<View>
<Text>bla</Text>
</View>
)
}
}
For functional Components:
import React from 'react';
import { View, Text } from 'react-native';
let renderCount = 0;
export function SampleFunctional() {
if (__DEV__) {
renderCount += 1;
console.log(`${SampleFunctional.name}. renderCount: `, renderCount);
}
return (
<View>
<Text>bla</Text>
</View>
)
}
The componentDidUpdate is calling this.changeCount() which calls this.setState() everytime after the component updated, which ofcourse runs infinitely and throws the error.
componentDidUpdate(prevProps) {
this.props.getColor(this.color);
// Add a if-clause here if you really want to call `this.changeCount()` here
// For example: (I used Lodash here to compare, you might need to import it)
if (!_.isEqual(prevProps.color, this.props.color) {
this.changeCount();
}
this.props.getCount(this.state.counter);
}
Related
I am learning React js. I need to rerender one of the child components from the parent component. One way is I can use setState for the matrix but the entire matrix which is parent component will be rerendered instead I want to rerender only one child component. This have added by code below.
Child.js
import React from 'react';
class Child extends React.Component {
constructor(props){
super(props);
this.state = {
text : ""
};
}
updateParent(text) {
if(text) {
this.setState({text : text});
}
}
render() {
return(
<div>Child {this.state.text}</div>
);
}
}
export default Child;
Parent.js
import React from 'react';
import Child from './Child'
class Parent extends React.Component {
constructor(props){
super(props);
this.state = {
table : [[<Child key={11}/>, <Child key={12}/>, <Child key={13}/>],
[<Child key={21}/>, <Child key={22}/>, <Child key={23}/>]],
i : 0,
j : 0
};
}
componentDidMount() {
this.timerID1 = setInterval(() => this.updateTable(), 1000);
}
updateTable() {
//this.state.table[this.state.i][this.state.j].updateParent("");
this.state.j++;
if( this.state.j % 3 == 0) {
this.state.i++;
this.state.i %= 2;
}
//this.state.table[this.state.i][this.state.j].updateParent("*");
// or tempTable[i][j] = <Child key={ij} text={"*"}/>; this.setState({table: tempTable});
this.state.j++;
}
createTable() {
let table = []
for(let i = 0; i < 2; i++) {
table.push( <div key={i} style={{display:"flex"}}>{this.state.table[0]}</div> )
}
return table;
}
render() {
return(
<div>{this.createTable()}</div>
);
}
}
export default Parent;
Don't store Child component instances in state, instead render them dynamically
You can implement Child as a PureComponent so that if no props or state change for it, it doesn't re-render
Do not mutate state directly like you do this.state.j++ and so on. Use setState
Parent.js
export default class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {
table: this.createTableData(3),
i: 0,
j: 0
};
}
createTableData(size) {
const arr = new Array(size);
for (var i = 0; i < size; i++) {
arr[i] = new Array(size).fill("");
}
return arr;
}
componentDidMount() {
this.timerID1 = setInterval(() => this.updateTable(), 1000);
}
componentWillUnmount() {
clearInterval(this.timerID1);
}
updateTable() {
let { i, j, table } = this.state;
j++;
if (j % 3 == 0) {
i++;
i %= 2;
}
const newTable = table.map((tr, row) => {
return tr.map((td, col) => {
if (row == i && col == j) {
return "*";
} else {
return "";
}
});
});
j++;
this.setState({
table: newTable,
i,
j
});
}
createTable() {
return this.state.table.map((row, i) => {
return (
<div className="row">
{row.map((col, j) => {
return <Child key={`${i + 1}${j + 1}`} text={col} />;
})}
</div>
);
});
}
render() {
return <div>{this.createTable()}</div>;
}
}
Child.js
class Child extends React.PureComponent {
render() {
console.log("child rerender", this.props.text);
return <div>Child {this.props.text} </div>;
}
}
working demo
NOTE: The demo only contains the display and performance optimization logic along with the architecture, The logic to update the indexes i, j needs to be done by you in updateTable method.
If you look at the demo, only the cell you whose value changed from "" to "*" and vice versa will re-render, the rest will not
you can rerender child component without rendering parent component
by using ref to call child function from parent and update child component
Parent.js
import React from "react";
import Table from "./Table";
import Child from "./Child";
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {};
this.table = [
[<Child key={11} />, <Child key={12} />, <Child key={13} />],
[<Child key={21} />, <Child key={22} />, <Child key={23} />],
];
this.i = 0;
this.j = 0;
}
componentDidMount() {
this.timerID1 = setInterval(() => this.updateTable(), 1000);
}
updateTable() {
this.j++;
if (this.j % 3 == 0) {
this.i++;
this.i %= 2;
}
this.j++;
this.table[0].push(
<Child key={21} />,
<Child key={22} />,
<Child key={23} />
);
this.refs.table.updateTable(this.table[0]);
}
componentDidUpdate() {
console.log("component rerender");
}
render() {
return (
<div>
<h1>Parent Component</h1>
<Table ref="table" />
</div>
);
}
}
export default Parent;
Child.js
import React from 'react';
class Child extends React.Component {
constructor(props){
super(props);
this.state = {
text : ""
};
}
updateParent(text) {
if(text) {
this.setState({text : text});
}
}
render() {
return(
<div>Child {this.state.text}</div>
);
}
}
export default Child;
Table.js
import React, { Component } from "react";
class table extends Component {
state = {
table: [],
};
updateTable = (table) => {
this.setState({ table });
};
render() {
const { table } = this.state;
return (
<div>
{table.map((tableItem, i) => {
return <div key={i} style={{ display: "flex" }}>{tableItem}</div>;
})}
</div>
);
}
}
export default table;
componentDidUpdate will give log if rerendering is happen
Note: I did not use state in parent component. if you want to use parent component state then you have to stop rerendering by using shouldComponentUpdate lifecycle
I have a state counter in my main App.js class. Also I have a Countdown.js, which updates the counter of his parent class every time he has finished. But i get an Error, when the timer finished once. Also, state counter jumps from 0 to 2 and not from 0 to 1...
Warning: Cannot update during an existing state transition (such as within `render`).
How can i get rid of this error? Or do you have a solution how to count++, when the timer is finished?
My class App.js:
import React from "react"
import "./App.css"
import Countdown from "./Countdown.js"
class App extends React.Component {
constructor() {
super();
this.state = {
counter: 0
};
this.count = this.count.bind(this);
}
count() {
this.setState(prevState => ({
count: prevState.counter++
}));
}
render() {
return (
<div className="window">
<p>{this.state.counter}</p>
<Countdown count={this.count} />
</div>
);
}
}
export default App
My Countdown.js
import React from "react";
import CountDown from "react-countdown";
class CountdownQuestion extends React.Component {
constructor() {
super();
this.state = {
time: 3000
};
}
render() {
const renderer = ({ seconds, completed }) => {
if (completed) {
this.props.count();
return <h2>Zeit abgelaufen</h2>;
} else {
return <h3>{seconds}</h3>;
}
};
return (
<CountDown date={Date.now() + this.state.time} renderer={renderer} />
);
}
}
export default CountdownQuestion;
Well, it's exactly like the error says. You can't update state (like in your count() function) during a render. You're probably better of using the onComplete hook.
class CountdownQuestion extends React.Component {
constructor() {
super();
this.state = {
time: 3000
};
}
render() {
// Removing this.props.count() from this function also keeps it more clean and focussed on the rendering.
const renderer = ({ seconds, completed }) => {
if (completed) {
return <h2>Zeit abgelaufen</h2>;
} else {
return <h3>{seconds}</h3>;
}
};
return (
<CountDown
date={Date.now() + this.state.time}
onComplete={this.props.count} // <-- This will trigger the count function when the countdown completes.
renderer={renderer}
/>
);
}
}
I'am getting props from child in getCount function. And set it prop into state. Than i try set it in component and get infinity loop. How can i fix that?
There is code of parent component:
import React, { Component } from "react";
import Message from "./Message/Message";
export default class Widget extends React.Component {
constructor(props) {
super(props);
this.state = {
color: {
s: 30,
l: 60,
a: 1
},
counter: 0
};
}
getCount = count => this.setState(state => ({
counter: count
}));
getColor = color => {
console.log(`the color is ${color}`);
};
render() {
const counter = this.state.counter;
return (
<div>
<Message
getColor={this.getColor}
getCount={this.getCount}
color={this.state.color}
>
{undefined || `Hello World!`}
</Message>
{counter}
</div>
);
}
}
child:
import React, { Component } from "react";
export default class Message extends React.Component {
constructor(props) {
super(props);
this.changeColor = this.changeColor.bind(this);
this.state = { h: 0 };
this.counter = 0;
}
changeColor = () => {
this.setState(state => ({
h: Math.random()
}));
};
componentDidUpdate(prevProps) {
this.props.getColor(this.color);
this.props.getCount(this.counter);
}
render() {
this.counter++;
const { children } = this.props;
const { s, l, a } = this.props.color;
this.color = `hsla(${this.state.h}, ${s}%, ${l}%, ${a})`;
return (
<p
className="Message"
onClick={this.changeColor}
style={{ color: this.color }}
>
{children}
</p>
);
}
}
The problem lies in your Message component.
You are using getCount() inside your componentDidUpdate() method. This causes your parent to re-render, and in turn your Message component to re-render. Each re-render triggers another re-render and the loop never stops.
You probably want to add a check to only run the function if the props have changed. Something like:
componentDidUpdate(prevProps) {
if(prevProps.color !== this.props.color) {
this.props.getColor(this.color);
this.props.getCount(this.counter);
}
}
This will keep the functionality you need, but prevent, not only the infinity-loop, but also unnecessary updates.
Builder Action positionRComponent not called. Am I doing something wrong? Check out the commentLine inside moveBox function in the BuildView.js
Expecting output: to be printed in console.
Position R Component
Below are the code snippets of BuildView.js and builder-actions.js.
BuildView.js
import React, {PropTypes} from 'react';
import BuilderStore from '../stores/builder-store';
import BuilderActions from '../actions/builder-actions'
import update from 'react/lib/update';
import ItemTypes from './ItemTypes';
import RComponent from './RComponent';
import { DropTarget } from 'react-dnd';
import HTML5Backend from 'react-dnd-html5-backend';
function getViewRComponents(){
return( {components: BuilderStore.getViewRComponents()})
}
const rComponentTarget = {
drop(props, monitor, component) {
const item = monitor.getItem();
const delta = monitor.getDifferenceFromInitialOffset();
const left = Math.round(item.left + delta.x);
const top = Math.round(item.top + delta.y);
component.moveBox(item.id, left, top);
}
};
const wrapper = {
border: '1px solid grey'
}
function collect(connect, monitor){
return ({
connectDropTarget: connect.dropTarget()
})
}
class BuildView extends React.Component{
constructor(props){
super(props);
this.state = getViewRComponents();
this._onChange = this._onChange.bind(this);
}
moveBox(id, left, top) {
this.setState(update(this.state, {
components: {
[id]: {
$merge: {
left: left,
top: top
}
}
}
}));
//CALLING HERE>>> Not getting called
BuilderActions.positionRComponent.bind(null, this.state.components[id]);
}
componentWillMount(){
BuilderStore.addChangeListener(this._onChange)
}
render(){
const { hideComponentOnDrag, connectDropTarget } = this.props;
let components = this.state.components.map( (component, index) => {
return(<RComponent
key={index}
id={index}
left={component.left}
top={component.top}
hideComponentOnDrag={hideComponentOnDrag}>
{component.name}
</RComponent>);
})
return connectDropTarget(
<div>
{components}
</div>
);
}
_onChange(){
this.setState(getViewRComponents());
}
componentWillUnMount(){
BuilderStore.removeChangeListener(this._onChange())
}
}
BuildView.propTypes = {
hideComponentOnDrag: PropTypes.bool.isRequired,
connectDropTarget: PropTypes.func.isRequired
};
export default DropTarget(ItemTypes.RCOMPONENT,rComponentTarget, collect )(BuildView);
builder-actions.js
import BuilderConstants from '../constants/builder-constants';
import {dispatch, register} from '../dispatchers/builder-dispatcher';
export default {
addRComponent(component) {
console.log("Add R Component")
dispatch({
actionType: BuilderConstants.ADD_RCOMPONENT, component
})
},
removeRComponent(component){
dispatch({
actionType: BuilderConstants.REMOVE_RCOMPONENT, component
})
},
positionRComponent(component){
console.log("Position R Component");
dispatch({
actionType: BuilderConstants.POSITION_RCOMPONENT, component
})
}
}
Use call or execute the returned function from bind:
var f = BuilderActions.positionRComponent.bind(null, this.state.components[id])
f()
or:
BuilderActions.positionRComponent.call(null, this.state.components[id]);
The difference is bind doesn't execute but returns a new function with the argument list passed into the new function.
call basically does a bind then executes, apply is similar but takes an array of arguments.
Hope it helps.
If I have a simple react component that records a click count for a button and on each click records a new history state without changing the URL. When the user clicks back how do I restore the state to as it was?
I can do as it is here using the native JavaScript history object, but it fails when the user transitions back to the first state and back from a different component into the last state of this one.
I suspect that there is a better to do this using react-router (1.0)?
import React, { Component } from 'react';
export default class Foo extends Component {
state = {
clickCount: 0,
};
componentWillMount() {
window.onpopstate = (event) => {
if (event.state.clickCount) {
this.setState({ clickCount: event.state.clickCount });
}
};
}
onClick() {
const newClickCount = this.state.clickCount + 1;
const newState = { clickCount: newClickCount };
this.setState(newState);
history.pushState(newState, '');
}
render() {
return (
<div>
<button onClick={this.onClick.bind(this)}>Click me</button>
<div>Clicked {this.state.clickCount} times</div>
</div>
);
}
}
localStorage or even cookies are options, but probably not the best way. You should store the count in a database, this way you can set the initial state in your constructor to the last value saved in the database.
Another option, if you only need to persist the count on the client-side(and not in a database) is using a closure.
// CountStore.js
var CountStore = (function() {
var count = 0;
var incrementCount = function() {
count += 1;
return count;
};
var getCount = function() {
return count;
};
return {
incrementCount: incrementCount,
getCount: getCount
}
})();
export default CountStore;
So your code would change to the below.
import React, { Component } from 'react';
import CountStore from './CountStore';
export default class Foo extends Component {
state = {
clickCount: CountStore.getCount()
};
componentWillMount() {
window.onpopstate = (event) => {
if (event.state.clickCount) {
this.setState({ clickCount: event.state.clickCount });
}
};
}
onClick() {
const newClickCount = CountStore.incrementCount();
const newState = { clickCount: newClickCount };
this.setState(newState);
}
render() {
return (
<div>
<button onClick={this.onClick.bind(this)}>Click me</button>
<div>Clicked {this.state.clickCount} times</div>
</div>
);
}
}
There may be a cleaner way of using react-router, but this is an option.
An example:
import React, {Component} from "react";
import {NavLink} from "react-router-dom";
interface Props {
}
interface State {
count: number
}
export default class About extends Component<Props, State> {
UNSAFE_componentWillMount(): void {
this.setState(Object.getPrototypeOf(this).constructor.STATE || {});
}
componentWillUnmount(): void {
Object.getPrototypeOf(this).constructor.STATE = this.state;
}
constructor(props: Props) {
super(props);
this.state = {count: 0}
}
render() {
const {count} = this.state;
return <div style={{width: "100%", height: "100%", display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "space-evenly", fontSize: "2em"}}>
<span>Count: {count}</span>
<button onClick={() => this.setState({count: count + 1})}>PLUS ONE</button>
<NavLink to="/">Redirect to HOME</NavLink>
</div>
}
}