A
A
Anton2017-03-29 16:38:11
React
Anton, 2017-03-29 16:38:11

How do state relationships work in react js?

I'm just learning about react and I can't figure out how react operates on state. This component renders a simple table with each row corresponding to one node. When you click on the edit button in one of the rows, the corresponding node is copied to the selected_node, and another row is shown with an input that changes the selected_node. But for some reason, after the change, when canceling editing (erasing selected_node), the original state.nodes is already changed, and after pressing cancel, the initial table becomes changed.
In short - how to make it so that when editing one selected_node, the general state.nodes does not change

import React from 'react';

export default class TestPage extends React.Component {
constructor(props) {
    super(props);

    this.state = {
        nodes: [
            {id: 1, content: "qwerty"},
            {id: 2, content: "abcdef"}
        ],
        selected_node: {}
    };
}
cancelEdit() {
    this.setState({selected_node: {}});
}
selectNode(node) {
    this.setState({selected_node: node});
}
handleContent(event) {
    let selected_node = this.state.selected_node;
    selected_node.content = event.target.value;
    this.setState({selected_node: selected_node});
}
render() {
    let self = this;

    return (
        <div>
            <table>
                <thead>
                    <th>id</th>
                    <th>content</th>
                    <th>edit</th>
                </thead>
                <tbody>
                {this.state.nodes.map(function(node, i) {
                    if(node.id == self.state.selected_node.id) {
                        return <tr key={i}>
                            <td>{node.id}</td>
                            <td><input
                                value={self.state.selected_node.content}
                                onChange={self.handleContent.bind(self)}
                            />
                            </td>
                            <td>
                                <button>save</button>
                                <button onClick={self.cancelEdit.bind(self)}>cancel</button>
                            </td>
                        </tr>
                    } else {
                        return <tr key={i}>
                            <td>{node.id}</td>
                            <td>{node.content}</td>
                            <td><button onClick={self.selectNode.bind(self, node)}>edit</button></td>
                        </tr>;
                    }
                })}
                </tbody>
            </table>
        </div>
    );
}
};

Answer the question

In order to leave comments, you need to log in

1 answer(s)
V
Vlad Feninets, 2017-03-31
@jezzit

You in selectNode assign not the contents of the object, but a link to it, and then you make changes to the object using this link.
so if you do something like this:

selectNode(node) {
  let nodeNew = Object.assign({}, node)
    this.setState({selected_node: nodeNew});
}

then everything will be ok, but in general, doing this is not at all comme il faut, passing the instance itself as a parameter .. in extreme cases, it's better to use "ref".
but in general, here you can do without copying, just change the flag of the object itself, like "edit" and, depending on its value, render what you need ..
something like this:
import React, { Component } from 'react'


export default class TestPage extends Component {
constructor(props) {
    super(props);

    this.input = null
    this.state = {
        nodes: [
            { id: 1, content: "qwerty", edit: false },
            { id: 2, content: "abcdef", edit: false }
        ],
        inputVal: ''
    };
}

toggleEdit(id, isEdit) {
    this.setState({
    	...this.state,
    	nodes: this.state.nodes.map(n => {
    		if( n.id === id ) {
    			n.edit = isEdit
    		}
    		return n
    	})
    })
}  

handleContent(event) {
  ....
}

render() {
    return (
        <div>
            <table>
                <thead>
                    <th>id</th>
                    <th>content</th>
                    <th>edit</th>
                </thead>
                <tbody>

                {this.state.nodes.map((node) => {
                    if(node.edit) {
                        return <tr key={node.id}>
                            <td>{node.id}</td>

                            <td><input
                            	onChange={this.handleContent.bind(this)}
                            	ref={(input) => this.input = input}
                                value={this.state.inputVal}
                            />
                            </td>
                            <td>
                                <button type="submit">save</button>
                                <button onClick={() => this.toggleEdit(node.id, false)}>cancel</button>
                            </td>
                        </tr>
                    } else {
                        return <tr key={node.id}>
                            <td>{node.id}</td>
                            <td>{node.content}</td>
                            <td><button onClick={() => this.toggleEdit(node.id, true)}>edit</button></td>
                        </tr>;
                    }
                })}

                </tbody>
            </table>
        </div>
    );
}
};

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question