SweetFeet
SweetFeet

Reputation: 91

changes in state array does not change children through props

Recently, I have been working with a dynamic control of input text boxes, each with their own id's. However, I noticed that if I stored an array of elements and each element was an object with different variables and such, any change in on of those variables in the object would not alert React to update the children components(which receive the state through props). I am trying to control the input according to the documentation, but am confused as to why changes in the parent's state of a normal non array and non object variable will be recognized by the children as a change in props and yet not for normal state arrays. Here is my parent code:

import React, {Component} from 'react';
import Music from './music'
import axios from 'axios';
import update from 'immutability-helper';

class Selection {
    constructor(){
        this.music = '';
        this.beginning = '';
        this.the_end = '';
    }
    setTitle=(title)=>{
        this.music = title;
    }
    setStart=(start)=>{
        this.beginning = start;
    }
    setEnd=(end)=>{
        this.the_end = end;
    }
}

class Practice extends React.Component{
    constructor(props){
        super(props);
        this.state = {
            selections: Array(0),
            test: 0,
        }
        this.removeSong = this.removeSong.bind(this);
    }
    removeSong(index){
        var newArray = this.state.selections.slice();
        newArray.splice(index, 1);
        this.setState({selections: newArray, test: this.state.test+=1});

    }
    addAnotherSong=()=>{
        var newArray = this.state.selections.slice();
        newArray.push(new Selection());
        this.setState({ selections: newArray, test: this.state.test+=1});

    }

    render(){

        return(
            <div>
                <button onClick={() => this.props.practice()}>Log Practice Session</button>
                <h1>{this.props.time}</h1>
                <form >
                        Description: <input type="form" placeholder="How did it go?" name="fname"/><br/>
                </form>
                <button onClick={()=>this.addAnotherSong()}>Add Another Piece</button>
                <button onClick={()=>this.setState({test: this.state.test})}>Will it now Update?</button>
                {
                    this.state.selections.map((child, index) => (
                            this.state.selections[index].music,
                            <Music key={index} number={index} subtract={this.removeSong}
                            Title={this.state.test} Start={this.state.selections[index].beginning}
                            End={this.state.selections[index].the_end} changeTitle={this.state.selections[index].setTitle}
                            changeStart={this.state.selections[index].setStart} changeEnd={this.state.selections[index].setEnd}
                            />

                    ))
                }
            </div>

        );
    }
}

export default Practice;

As you can see, I have an array in the state with objects constructed from the Selection class. Here are the children that won't update unless the change happens in the non array type "this.state.test" prop.

import React, {Component} from 'react';
import InputBox from './input';
class Music extends React.Component{
    constructor(props){
        super(props);
    }
    shouldComponentUpdate(newProps){
        if(this.props !== newProps){
            return true;
        } else{
            console.log("wassup");
            return false;
        }
    }
    render(){
        return(
        <div>{this.props.number}
            <InputBox cValue={this.props.Title} identity={this.props.number} updateInput={this.props.changeTitle} />
            <InputBox cValue={this.props.Start} identity={this.props.number} updateInput={this.props.changeStart} />
            <InputBox cValue={this.props.End} identity={this.props.number} updateInput={this.props.changeEnd} />
            <button onClick={()=> this.props.subtract(this.props.number)}>DELETE{this.props.number}</button>
            {this.props.Title}
        </div>
        )
    }
}
export default Music;

Lastly, here is the children of that child.

import React,{Component} from 'react';

class InputBox extends React.Component{
    constructor(props){
        super(props);
        this.state = { value: '' }
        this.handleChange = this.handleChange.bind(this);
    }
    handleChange(event){
        this.setState({value: event.target.value});
        this.props.updateInput(this.state.value, this.props.identity);
        console.log("test" + this.props.cValue);
    }
    shouldComponentUpdate(newProps){
        if(this.props !== newProps){
            console.log("Updating Input Component");
            return true;
        } else {
            console.log("yo");
            return false;

        }
    }
    render(){

        return(
            <input type="text"onChange={this.handleChange} value={this.props.cValue}/>
        )
    }
}
export default InputBox;

If this is a bad question, please let me know. But, any help is always appreciated. Thankyou

Upvotes: 1

Views: 1119

Answers (1)

elsyr
elsyr

Reputation: 796

You're treating your state as a mutable object - states in React should never be mutated, and a state change should always be a brand new object.

Take, for example, we have just one Selection in state.selections:

[ { music: 'music', beginning: 'beginning', the_end: 'ending' } ]

If the Music component ends up calling setStart('newBeginning'), then we end up with this in our state:

[ { music: 'music', beginning: 'newBeginning', the_end: 'ending' } ]

This looks like everything went well, but React won't actually pick up this change by default because state.selections is still referring to the same array.

To fix it, you're going to have to figure out how to update your array reference while you modify individual items. You've already done this in removeSong and addAnotherSong by calling slice on your current state. Slice returns a new array, so no issues there.

I'd recommend having methods to modify individual Selections in Practice, as opposed to each Selection modifying itself.

Something like this could work:

   class Practice extends React.Component{
        updateSelectionBeginning(index, newBeginning){
            var newArray = this.state.selections.slice();
            newArray[index].beginning = newBeginning;
            this.setState({selections: newArray});
        }
    }

We'd then pass it down to our children via props, and they'd call updateSelectionBeginning(FOO, BAR). That lets us update the state, while also keeping it immutable.

Upvotes: 1

Related Questions