React Child components in an Array not updating on prop change - javascript

I would like to update all child components in an array using a prop passed down from the state of a parent component. A basic example is shown below. Each child component in the array is stateless and has a prop value which is determined by the state of the parent component. However, when the parent components state changes, the child components do not re-render with the change. How can I make the child components re-render when the parents state changes? Thanks!
import React from 'react';
import ReactDOM from 'react-dom';
class Child extends React.Component {
render(){
return (
<p>
<button onClick = {(e) => this.props.onClick(e)}>
click me
</button>
{this.props.test}
</p>
)
};
}
class Parent extends React.Component{
constructor(props){
super(props);
this.state = {msg: 'hello', child_array: []};
this.handleClick = this.handleClick.bind(this);
}
handleClick(e){
e.preventDefault();
const msg = this.state.msg == 'hello' ? 'bye' : 'hello';
this.setState({msg: msg});
}
componentDidMount(){
let store = [];
for (var i = 0; i < 5; i++){
store.push(<Child test = {this.state.msg} key = {i} onClick = {this.handleClick}/>);
}
this.setState({child_array: store});
}
render(){
return(
<div>
{this.state.child_array}
</div>
)
}
}
ReactDOM.render(<Parent />, document.getElementById('root'));

As mentioned in comments
It won’t re render again because you are generating chil components in componentDidMount and this method gets called only once per the component after first render. So when your callback fires the child_array will be empty
Instead what you can do is remove componentDidMount method code and do that in render as like below. In the below case it will render every time the onclick fires in child component
render(){
const store = [];
for (var i = 0; i < 5; i++){
store.push(<Child test = {this.state.msg} key = {i} onClick = {this.handleClick}/>);
}
return(
<div>
{store}
</div>
)

The problem is that you're rendering the child components (and thus baking in the value of this.state.msg) in the parent's componentDidMount() method. You need to render them in the parent's render() method instead.

componentWillReceiveProps will work in your case, everytime you will get props child component will re-render.
class Child extends React.Component {
constructor(props) {
super(props);
let initialData = (
<p>
<button onClick = {(e) => self.props.onClick(e)}>
click me
</button>
{nextProps.test}
</p>
);
this.state = {data: initialData };
}
componentWillReceiveProps(nextProps) {
let self = this;
let updatedHtml = (
<p>
<button onClick = {(e) => self.props.onClick(e)}>
click me
</button>
{nextProps.test}
</p>
);
this.setState({data: updatedHtml})
}
render(){
return (
{data}
)
};
}

Related

Can we pass props from Parent to Child component in React, and set the props as state of child component?

const Parent=()=>{
return(
)
}
const Child=({data})=>{
return (
{data}
)
}
Can I set the props in Child component to its state?
Yes you can. Something like:
class Child extends Component {
constructor(props){
const {myProp} = props;
this.state = {myNumber: myProp}
render(){
const {myNumber} = this.state;
return <div>{myNumber}</div>
}
class Parent extends Component {
render () {
return <Child myProp={5} />
}
}
BUT: if your "parent" component refreshes, so does the child and the state is reverted back to the value set by the parent, and all the state changes you did on the child are lost.
Yeah here made a code sandbox example showing how to do it: https://codesandbox.io/s/stack-pass-props-fgq67n
Child component looks like this
const Input = ({ string }) => {
const [value, setValue] = useState(string);
return <input value={value} onChange={(e) => setValue(e.target.value)} />;
};
Parent Component:
export default function App() {
return (
<div className="App">
<Input string="default" />
</div>
);
}
Sometimes if the parent reloads the passed props may reset to defaults
There is a concept in React known as controlled and uncontrolled components, When your component only have props and no state inside itself then it a controlled component, that is controlled by the parent component.
Also, it is not advisable to convert the props to the state of the child component, if you want to change the value of the props, just pass an additional method as a props which will be called by the Child to update the value of the props, let show you with an example
const Parent = () => {
const [number, setNumber] = useState(0);
const updateNumberHandler = (numberToUpdate) => setNumber(numberToUpdate)
return <Child number={number} onUpdateNumber={updateNumberHandler} />
}
const Child = (props) => {
const { number, onUpdateNumber } = props;
return <button onClick={() => onUpdateNumber(number + 1)}>Updated Number: {number}</button>
}
Here you can see I am passing two props one value and one method to update that value, when the onUpdateNumber method is called the value on the parent is updated so it gets re-render and also the child gets re-render with the updated number value.

Troubles with state

I'm just started to learn react, and i have a question
Well, i can impact on state from one component to another. But can i do it in reverse?
Here's what i mean:
import React from 'react';
import Butt from './Button';
class Checkbox extends React.Component {
constructor(props) {
super();
}
render() {
return (
<div>
<Butt arg={13} />
</div>
);
}
}
export default Checkbox;
import React from 'react';
class Butt extends React.Component {
constructor(props) {
super();
this.state = {
s1: props.arg,
};
}
add = () => {
let val = this.state.s1;
val++;
this.setState({ s1: val });
};
render() {
return (
<div>
<label>
<label>
<button onClick={this.add}>add</button>
<div>{this.state.s1}</div>
</label>
</label>
</div>
);
}
}
export default Butt;
Sorry for my silly question. Thanks in advance :)
I am not sure about your question, but in react, there is a one-way flow (from parent to child) for transferring information (props, states, or ...). If you want to have access to states everywhere or set them in each direction you should use Redux or context or any other state management.
You're updating the Butt state from inside Butt so this will work fine. It won't change the value of this.props.arg though, if that's what you're asking.
Props are always non-mutable.
What you can do is have two components share the state of their parent...
class Parent extends React.Component {
state = {
val = 0
}
render () {
return (
<>
<Child1
val={this.state.val}
onChange={newVal => this.setState({ val: newVal })}
/>
<Child2
val={this.state.val}
onChange={newVal => this.setState({ val: newVal })}
/>
</>
)
}
}
Then inside the child components pass the updated value to onChange...
class Child1 extends React.Component {
handleChange() {
this.props.onChange(this.props.val + 1)
}
render() {
return (
<Button onClick={() => this.handleChange()}>
Update value
</Button>
)
}
}
This way you're just passing a new value from Child to Parent and letting Parent decide what to do with it.
Whether Child1 or Child2 sends the new value, both children will get updated when Parent calls this.setState({ val: newVal }) and changes this.state.val.

How to re-render a component when it changes its props async?

I am using React and Redux in a project I’ve two components a parent and a child.
The parent component passes some props to the child when the child component receives those props it calls some action which change some of the props (async) that the parent passed to its child. Now my redux-store shows that the data has successfully stored in it. But my child component doesn’t re-render itself.
Parent Component:
class Parent extends React.Component {
getChilds(){
let child = [];
let i = 0;
for (let keys in this.props.home.data) {
child[i] = (
<Child title={keys} data={this.props.home.data[keys]} key={keys} />
);
i++;
if (i === 6) break;
}
return Rows;
}
render(){
return (
<div>
<h1>I am gonna call my child </h1>
{this.getChilds()}
</div>)
}
}
Child Component:
class Child extends React.Component {
// Here I'm not sure which lifecycle method to use to i'm gonna use
// componentDidMount
componentDidMount(){
if(this.props.data.items.length === 0){
// calling an action to fill this.props.data.items array with data
this.props.getData(this.props.data.id);
}
}
getGrandSong(){
let grandSons = [];
if(this.props.data.items.length > 0){
grandSons = this.props.data.items.map( item => <GrandSon item={item}
/>);
}
return grandSons;
}
render(){
return (
<div>
<h1> I am the child component and I will call my own child </h1>
{this.getGrandSon()}
</div>
)
}
Props are updated when I check the props in react-dev tool.
You need the data to re-render whenever the prop changes, so you can call it on the render function and implement shouldComponentUpdate() to deeply compare props and call render().
So, in this case:
class Child extends React.Component {
componentDidMount(){
if(this.props.data.items.length === 0){
// calling an action to fill this.props.data.items array with data
this.props.getData(this.props.data.id);
}
}
shouldComponentUpdate(nextProps){
if(this.props.data.items.length === nextProps.data.items.length){
for(let i=0; i< nextProps.data.items ; i ++){
if(nextProps.data.items[i] !== this.props.data.items[i]) return true;
}
}
return true;
}
render(){
const getGrandSong = () => {
let grandSons = [];
if(this.props.data.items.length > 0){
grandSons = this.props.data.items.map( item => <GrandSon item={item} />);
}
return grandSons;
}
return (
<div>
<h1> I am the child component and I will call my own child </h1>
{getGrandSon()}
</div>
)
}

Child component not updating on state change

I am trying to pass a value to child component yet the value doesn't not update as the parent component value changes. Here is my work
Child component:
class Test extends Component {
constructor(props) {
super(props)
this.state = {
data: this.props.data,
}
}
render() {
return(
<div>{this.state.data}</div>
)
}
}
export default Test;
Parent js file
constructor(props) {
super(props)
this.state = {
val: 0,
}
this.addVal = this.addVal.bind(this)
}
addVal() {
let val = this.state.val
val = val + 1
console.log(val)
this.setState({
val
})
}
render() {
return(
<div>
<Button onClick={this.addVal}> add </Button>
<Test data={this.state.val} />
</div>
)
}
The value gets updated on the parent component however, the child component does not get the updated value. What am I missing?
The constructor() only runs once in the start. When the parent is updated it sends new props to the Test but it doesnot change state.data to props.data after the component is rendered first time
You are printing this.state variable which is not updated. Instead you should print value from this.props
render(){
return(
<div>{this.props.data}</div>
)
}
If you want to detect the new props you can use compnentWillRecieveProps()
componentWillRecieveProps(nextProps){
this.setState({data:nextProps.data})
}
state is unnecessary in the child component. It should be enough to render this.props.data since that is what you are passing to the child from the parent.

Render a component from another class using function

So, I have a class like this:
class Blah extends Component {
constructor(props) {
super(props);
}
handleComponent = (event) => {
let divid = event.target.getAttribute('id');
if (divid === 'col') {
// I want to render component by this condition
} else if (divid === 'ro') {
// or I want to render component by this condition
} else {
//or I want to render component by this condition
}
};
render() {
const { classes } = this.props;
return (
<div>
<div id = 'col' onClick={this.handleComponent}>Sheep</div>
<div id = 'ro' onClick={this.handleComponent}>Cow</div>
<div id = 'ball' onClick={this.handleComponent}>Dog</div>
{ I want to render my component here after click }
</div>
);
}
}
I have another class written on top of this:
class Flow extends Component {
constructor(props){
super(props);
};
render() {
return(
<div style={{background:'somecolor'...blah blah}}>Clap</div>
);
}
}
And I am Passing this by:
var foo = withStyles(styles)(Flow)
I have tried returning components but I am not getting anywhere.
I can use ternary operator but it still will render only one of two but I have three component have three design for each of them.
I want to render one of them to render on some condition as stated above.
If I use states that for toggle that will too have two components for render. Don't go on the code, this is made up, So any Ideas ? Fragments ? Any help will be appreciated. Thank you.
To render component by condition simply use switch statement.
In my example we use state to store current active component.
renderMyComponent method takes care of rendering one of three possible components.
handleChange method changes current state and triggers new render of App component.
This example use class properties plugin.
renderMyComponent = () => {
means autobind and is the same as using in constuctor method
this.renderMyComponent = this.renderMyComponent.bind(this);
Working example:
const ComponentOne = () => <div>Hi, i am component one</div>;
const ComponentTwo = () => <div>Hi, i am component two</div>;
const ComponentThree = () => <div>Hi, i am component three</div>;
class App extends React.Component {
state = { current: 0 }
renderMyComponent = () => {
// Our switch conditional render
switch(this.state.current) {
case 0:
return <ComponentOne />;
case 1:
return <ComponentTwo />;
case 2:
return <ComponentThree />;
default:
return null;
}
}
handleChange = (event) => {
// We are looking for data-trigger attribute
// In this example we expect type number but trigger holds string
// That's why we 'cast' to a number using Number()
const current = Number(event.target.dataset.trigger);
// Sets new state of current component and triggers new render
this.setState({ current })
}
render() {
return (
<div>
<div>
Pick component to render
<button
type="button"
data-trigger="0"
onClick={this.handleChange}
>
Render 1
</button>
<button
type="button"
data-trigger="1"
onClick={this.handleChange}
>
Render 2
</button>
<button
type="button"
data-trigger="2"
onClick={this.handleChange}
>
Render 3
</button>
</div>
{this.renderMyComponent()}
</div>
)
}
}
ReactDOM.render(<App />, 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>
Reviewing your code:
You don't need constructor here.
...
constructor(props){
super(props);
}
...

Categories

Resources