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
Related
I have a small question.
Let's imagine I have component A which holds , after component A does it's job I render component B. I would like that same DOM element (textarea) would be reused in component B.
The reason is if new textarea is rendered in component B it loses focus as it's just new DOM element. It's like after component A lifetame take textarea element from it and just put it in component B instead of rendering new one.
Sample APP
https://jsfiddle.net/remdex/v67gqyLa/1/#&togetherjs=aiRvTGhRK2
class AComponent extends React.Component {
render() {
return ( <textarea>A Component</textarea> )
}
}
class BComponent extends React.Component {
render() {
return ( <textarea>Should be A Component text</textarea> )
}
}
class ABComponent extends React.Component {
constructor(props) {
super(props)
this.state = {'component' : 'A'};
}
render() {
return (
<div><button onClick={(e) => this.setState({component:'B'})}>Switch to B Component</button>
{this.state.component == 'A' && <AComponent/>}
{this.state.component == 'B' && <BComponent/>}
</div>
)
}
}
ReactDOM.render(<ABComponent />, document.querySelector("#app"))
In your sandbox example, ComponentA and ComponentB are redundant. You can create ComponentA and ComponentB as a class if they are using same element and operate them with ComponentAB. You can change your ComponentAB like:
class A {
handle(input) {
// Do your A job here and return result
console.log("Handler A is running");
};
}
class B {
handle(input) {
// Do your B job here and return result
console.log("Handler B is running");
};
}
class ABComponent extends React.Component {
currentHandler = new A();
handleClick = () => {
this.currentHandler = new B();
};
handleChange = (event) => {
// Handle the input with current handler
var result = this.currentHandler.handle(event.target.value);
// If you want you can use result to cahnge something in view
// this.setState({value: result});
}
render() {
return (
<div>
<button onClick={this.handleClick}>
Switch to B Component
</button>
<textarea onChange={this.handleChange}>Text Area used between A class and B class</textarea>
</div>
)
}
}
I also edit the codebox example. You can find it here.
This can be achieved using a ref. ABComponent passes a ref to BComponent to attach to the textarea. When the state of ABComponent updates to component = 'B', then the ref is used to set focus. Use a ref passed to AComponent to grab its textarea value before it's unmounted, then set the value of the textarea in B to it.
import React, { Component, createRef } from "react";
...
class AComponent extends Component {
render() {
const { textareaRef } = this.props;
return <textarea ref={textareaRef} defaultValue="A Component" />;
}
}
class BComponent extends Component {
render() {
const { textareaRef } = this.props;
return <textarea ref={textareaRef} defaultValue="Should be A Component text" />;
}
}
class ABComponent extends Component {
state = { component: "A" };
refA = createRef();
refB = createRef();
componentDidUpdate(prevProps, prevState) {
const { component, content } = this.state;
if (prevState.component !== component) {
if (component === "B") {
this.refB.current.focus();
this.refB.current.value = content;
}
}
}
render() {
return (
<div>
<button
onClick={e =>
this.setState({ component: "B", content: this.refA.current.value })
}
>
Switch to B Component
</button>
{this.state.component === "A" && <AComponent textareaRef={this.refA} />}
{this.state.component === "B" && <BComponent textareaRef={this.refB} />}
</div>
);
}
}
How do I call a child component function from the parent component? I've tried using refs but I can't get it to work. I get errors like, Cannot read property 'handleFilterByClass' of undefined.
Path: Parent Component
export default class StudentPage extends React.Component {
constructor(props) {
super(props);
this.state = {
};
}
newStudentUserCreated() {
console.log('newStudentUserCreated1');
this.refs.studentTable.handleTableUpdate();
}
render() {
return (
<div>
<StudentTable
studentUserProfiles={this.props.studentUserProfiles}
ref={this.studentTable}
/>
</div>
);
}
}
Path: StudentTable
export default class StudentTable extends React.Component {
constructor(props) {
super(props);
this.state = {
studentUserProfiles: props.studentUserProfiles,
};
this.handleTableUpdate = this.handleTableUpdate.bind(this);
}
handleTableUpdate = () => (event) => {
// Do stuff
}
render() {
return (
<div>
// stuff
</div>
);
}
}
UPDATE
Path StudentContainer
export default StudentContainer = withTracker(() => {
const addStudentContainerHandle = Meteor.subscribe('companyAdmin.addStudentContainer.userProfiles');
const loadingaddStudentContainerHandle = !addStudentContainerHandle.ready();
const studentUserProfiles = UserProfiles.find({ student: { $exists: true } }, { sort: { lastName: 1, firstName: 1 } }).fetch();
const studentUserProfilesExist = !loadingaddStudentContainerHandle && !!studentUserProfiles;
return {
studentUserProfiles: studentUserProfilesExist ? studentUserProfiles : [],
};
})(StudentPage);
My design here is: component (Child 1) creates a new studentProfile. Parent component is notified ... which then tells component (Child 2) to run a function to update the state of the table data.
I'm paraphrasing the OP's comment here but it seems the basic idea is for a child component to update a sibling child.
One solution is to use refs.
In this solution we have the Parent pass a function to ChildOne via props. When ChildOne calls this function the Parent then via a ref calls ChildTwo's updateTable function.
Docs: https://reactjs.org/docs/refs-and-the-dom.html
Demo (open console to view result): https://codesandbox.io/s/9102103xjo
class Parent extends React.Component {
constructor(props) {
super(props);
this.childTwo = React.createRef();
}
newUserCreated = () => {
this.childTwo.current.updateTable();
};
render() {
return (
<div className="App">
<ChildOne newUserCreated={this.newUserCreated} />
<ChildTwo ref={this.childTwo} />
</div>
);
}
}
class ChildOne extends React.Component {
handleSubmit = () => {
this.props.newUserCreated();
};
render() {
return <button onClick={this.handleSubmit}>Submit</button>;
}
}
class ChildTwo extends React.Component {
updateTable() {
console.log("Update Table");
}
render() {
return <div />;
}
}
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);
}
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.
I have a react component(parent) that has as state another react component(child)
The parent passes down is't state as props to the child.
But if I do setState on the passed down property, it does not update in the child.How do I make such that a change in state is reflected in the child?
See code:
class Child extends React.Component {
constructor(props) {
super(props)
}
render() {
return (
<div>
{this.props.x}
</div>
)
}
}
class Parent extends React.Component {
constructor(props) {
super(props)
this.state = {x: 1, intervalID: 0, currentScreen: <Child x={0} />}
}
componentDidMount() {
let self = this
let intervalID = setInterval(function() {
self.setState({x: self.state.x+1})
}, 1000)
self.setState({intervalID: intervalID, currentScreen: <Child x={self.state.x} />})
}
render() {
return (
<div>
{this.state.currentScreen}
</div>
)
}
}
ReactDOM.render(<Parent />, document.getElementById('app'))
Below code is working.
import React from 'react';
import ReactDOM from 'react-dom';
class Child extends React.Component {
render() {
return (
<div>
{this.props.x}
</div>
)
}
}
class Parent extends React.Component {
constructor(props) {
super(props)
this.state = {x: 1, intervalID: 0, currentScreen: <Child x={0} />}
}
componentDidMount() {
let intervalID = setInterval(() => {
const x = this.state.x + 1;
this.setState({
x: x,
currentScreen: <Child x={x} />
});
}, 1000)
this.setState({intervalID: intervalID, currentScreen: <Child x={this.state.x} />})
}
render() {
return (
<div>
{this.state.currentScreen}
</div>
)
}
}
ReactDOM.render(<Parent />, document.getElementById('root'))
Your child component is not updating because for the lifecycle of parent component componentDidMount is only called once when it is being mounted.
If you need to update your state on regular interval you can do something like :
import React, { Component } from 'react';
import { render } from 'react-dom';
class Child extends Component {
constructor(props) {
super(props)
}
render() {
return (
<div>
{this.props.x}
</div>
)
}
}
class Parent extends Component {
constructor(props) {
super(props)
this.state = { x: 1, intervalID: 0 }
}
componentDidMount() {
let self = this
let intervalID = setInterval(function () {
self.setState({ x: self.state.x + 1 })
}, 1000)
self.setState({ intervalID: intervalID })
}
render() {
return (
<div>
<Child x={this.state.x}/>
</div>
)
}
}
render(<Parent />, document.getElementById('root'))
for your case, just so when setState is done, it will call render again and it will pass the latest value of x to the child component.
You can check out live working example on stackblitz
It is a bad practise to maintain JSX in the state. Move all your JSX into the render() and use state variables to manage the state as shown below (For brevity only the Parent component code is shown).
Further instead of doing let self=this use the arrow function for clarity.
Note that you need to use the updater function when setting the state if your new state depends on the previous state. This is because React does batch updates for state. More information can be found in the official documentation.
class Parent extends React.Component {
constructor(props) {
super(props)
this.state = { x: 1 }
}
componentDidMount() {
setInterval(() => {
this.setState((prevState, props) => {
return { x: prevState.x + 1 };
});
},3000)
}
render() {
return (
<div>
<Child x={this.state.x} />
</div>
)
}
}
ReactDOM.render(<Parent />, document.getElementById('root'))
Above function will update the value of x every 3 seconds. Below is a working example
https://codesandbox.io/s/2677zoo4p