Search code examples
reactjssetstate

Passing the state to the state in another component - React


I wanted to create the effect of editing the name of an item from the list. To do this, I passed the current name of the list item to the component where I have an input form and set state in this component to

state = {
        nodeName: this.props.nodeName
    }

In turn, I passed nodeName to input value. However, it turned out that assigning propertie to nodeName is not working because this.props.nodeName is empty. When I call in the render function - console.log (this.props.nodeName), the name shows up correctly

Here is the class where I pass my list item name(nodeName={this.state.activeNodeName}) to EditNodeModal component:

import {treeActions} from '../_actions'
import { connect } from 'react-redux';
import Node from './Node/Node'
import './style.css'
import ContextMenu from './ContextMenu'
import {AddNodeModal} from './Modals/AddNodeModal'
import { EditNodeModal } from './Modals/EditNodeModal';

class TreePage extends Component {

    state = {
        activeNode: 0,
        activeNodeName: '',
        showContextMenu: false,
        top:0,
        left:0,
        showAddNodeModal: false,
        showEditNodeModal: false
      }

    componentDidMount(){
      //document.addEventListener('click', this.hideContextMenu.bind(this))
      this.props.getTree()
    }
    componentWillUnmount(){
      //document.removeEventListener('click',this.hideContextMenu.bind(this))
    }
    hideContextMenu(){
      this.setState({showContextMenu: false})
    }
    setActiveNode(id) {   
        this.setState({activeNode: id})
    }
    handleOnContextMenu(event, id, name){
        event.preventDefault()

        this.setState({activeNode: id, activeNodeName: name, showContextMenu: true, left: event.clientX, top: event.clientY})   
    }
    toggleShowAddNodeModal() {
      this.setState({showAddNodeModal: !this.state.showAddNodeModal})
    }
    toggleShowEditNodeModal() {
      this.setState({showEditNodeModal: !this.state.showEditNodeModal})
    }

    renderSubNodes(subNodes) {
        const {activeNode} = this.state
        return (
          <ul>
            {subNodes.map((node) => (
                <React.Fragment>
                    <li>
                        <div onContextMenu={(event) => this.handleOnContextMenu(event, node.nodeId, node.name)} className={activeNode===node.nodeId?"bgSelected":""} key={node.nodeId} onClick={() => this.setActiveNode(node.nodeId)}>
                            <Node name={node.name}/>
                        </div>
                        {node.subNodes.length > 0 && this.renderSubNodes(node.subNodes)}
                    </li>
                </React.Fragment>
            ))}
          </ul>
        );
      }

    render() {
        const tree2 = this.props.tree.items;

        return(
            <React.Fragment>
                {tree2 && this.renderSubNodes(tree2)}
                {this.state.showContextMenu? <ContextMenu  showAddNodeModal={() => this.toggleShowAddNodeModal()} showEditNodeModal={() => this.toggleShowEditNodeModal()} left={this.state.left} top={this.state.top}/>:''}

                <AddNodeModal
                  show={this.state.showAddNodeModal} 
                  onHide={() => this.toggleShowAddNodeModal()}
                  parentNodeId={this.state.activeNode}
                />

                <EditNodeModal
                  show={this.state.showEditNodeModal}
                  onHide={() => this.toggleShowEditNodeModal()}
                  nodeId={this.state.activeNode}
                  nodeName={this.state.activeNodeName}
                />
            </React.Fragment>
        )
    }
}
function mapState(state){


    return state;
}
const actionCreators = {
    getTree: treeActions.getTree
}

const connectedApp = connect(mapState, actionCreators)(TreePage)
export {connectedApp as TreePage}

And my EditNodeModal component:

import React,{Component} from 'react'
import {Modal, Button, Form} from 'react-bootstrap'
import {nodeActions} from '../../_actions'
import { connect } from 'react-redux';

class EditNodeModal extends Component {

    state = {
        nodeName: this.props.nodeName
    }

    handleChange({target}) {
        this.setState({nodeName: target.value})
    }
    editNode() {
        const {editNode} = this.props
        editNode(this.props.nodeId, this.state.nodeName)
        this.hideAndClearName()
    }
    hideAndClearName() {
        this.props.onHide()
        this.setState({nodeName:''})
    }

    render() {
        console.log(this.props.nodeName)
    return (
        <Modal
            onHide={() => this.hideAndClearName()}
            show={this.props.show}
            size="md"
            aria-labelledby="contained-modal-title-vcenter"
            centered
        >
        <Modal.Header closeButton>
            <Modal.Title id="contained-modal-title-vcenter">
                Edytuj nazwę
            </Modal.Title>
        </Modal.Header>
        <Modal.Body>
            <Form.Control value={this.state.nodeName} onChange={(event) => this.handleChange(event)} type="text" placeholder="Nazwa" />
        </Modal.Body>
        <Modal.Footer>
            <Button onClick={() => this.editNode()} variant="success">Edytuj</Button>
            <Button onClick={() => this.hideAndClearName()}>Zamknij</Button>
        </Modal.Footer>
        </Modal>
    );
    }
}

function mapState(state){

  return state;
}
const actionCreators = {
  editNode: nodeActions.editNode,

}

const connectedApp = connect(mapState, actionCreators)(EditNodeModal)
export {connectedApp as EditNodeModal}

I tried set state in the componentDidMount function but it didn't help.

Here is the whole code - https://codesandbox.io/s/long-http-jvt9c


Solution

  • Your example doesn't use the correct way to define the state in class-based components. Please check out the example below. state should be defined as part of the class constructor.

    Treepage Component

    import React, { Component } from "react";
    import { treeActions } from "../_actions";
    import { connect } from "react-redux";
    import Node from "./Node/Node";
    import "./style.css";
    import ContextMenu from "./ContextMenu";
    import { AddNodeModal } from "./Modals/AddNodeModal";
    import { EditNodeModal } from "./Modals/EditNodeModal";
    
    class TreePage extends Component {
      // Added this line
      constructor(props) {
        super(props);
        this.state = {
          activeNode: 0,
          activeNodeName: "",
          showContextMenu: false,
          top: 0,
          left: 0,
          showAddNodeModal: false,
          showEditNodeModal: false
        };
    
        // You have not bound your event handlers
        // Added event handler bindings
        this.toggleShowAddNodeModal = this.toggleShowAddNodeModal.bind(this);
        this.toggleShowEditNodeModal = this.toggleShowEditNodeModal.bind(this);
        this.handleOnContextMenu = this.handleOnContextMenu.bind(this);
      }
    
      componentDidMount() {
        document.addEventListener("click", this.hideContextMenu.bind(this));
        this.props.getTree();
      }
    
      componentWillUnmount() {
        document.removeEventListener("click", this.hideContextMenu.bind(this));
      }
    
      hideContextMenu() {
        this.setState({ showContextMenu: false });
      }
    
      setActiveNode(id) {
        this.setState({ activeNode: id });
      }
    
      handleOnContextMenu(event, id, name) {
        event.preventDefault();
    
        this.setState({
          activeNode: id,
          activeNodeName: name,
          showContextMenu: true,
          left: event.clientX,
          top: event.clientY
        });
      }
    
      toggleShowAddNodeModal() {
        this.setState({ showAddNodeModal: !this.state.showAddNodeModal });
      }
    
      toggleShowEditNodeModal() {
        this.setState({ showEditNodeModal: !this.state.showEditNodeModal });
      }
    
      renderSubNodes(subNodes) {
        const { activeNode } = this.state;
        return (
          <ul>
            {subNodes.map(node => (
              <React.Fragment>
                <li>
                  <div
                    onContextMenu={event =>
                      this.handleOnContextMenu(event, node.nodeId, node.name)
                    }
                    className={activeNode === node.nodeId ? "bgSelected" : ""}
                    key={node.nodeId}
                    onClick={() => this.setActiveNode(node.nodeId)}
                  >
                    <Node name={node.name} />
                  </div>
                  {node.subNodes.length > 0 && this.renderSubNodes(node.subNodes)}
                </li>
              </React.Fragment>
            ))}
          </ul>
        );
      }
    
      render() {
        const tree2 = this.props.tree.items;
        const tree = [
          {
            nodeId: 1,
            name: "node1",
            subNodes: [
              {
                nodeId: 4,
                name: "node1-1",
                subNodes: [],
                subLeaves: []
              },
              {
                nodeId: 5,
                name: "node1-2",
                subNodes: [],
                subLeaves: []
              }
            ],
            subLeaves: []
          },
          {
            nodeId: 2,
            name: "node2",
            subNodes: [],
            subLeaves: []
          },
          {
            nodeId: 3,
            name: "node3",
            subNodes: [
              {
                nodeId: 6,
                name: "node3-1",
                subNodes: [
                  {
                    nodeId: 7,
                    name: "node3-1-1",
                    subNodes: [],
                    subLeaves: []
                  },
                  {
                    nodeId: 8,
                    name: "node3-1-2",
                    subNodes: [],
                    subLeaves: []
                  }
                ],
                subLeaves: []
              },
              {
                nodeId: 9,
                name: "node3-2",
                subNodes: [],
                subLeaves: []
              }
            ],
            subLeaves: []
          }
        ];
        return (
          <React.Fragment>
            {this.renderSubNodes(tree)}
            {this.state.showContextMenu ? (
              <ContextMenu
                showAddNodeModal={() => this.toggleShowAddNodeModal()}
                showEditNodeModal={() => this.toggleShowEditNodeModal()}
                left={this.state.left}
                top={this.state.top}
              />
            ) : (
              ""
            )}
    
            {/*  Conditionally rendering so that the component mounts only when needed*/}
            {this.state.showAddNodeModal && (
              <AddNodeModal
                show={this.state.showAddNodeModal}
                onHide={() => this.toggleShowAddNodeModal()}
                parentNodeId={this.state.activeNode}
              />
            )}
    
            {/*  Conditionally rendering so that the component mounts only when needed*/}
            {this.state.showEditNodeModal && (
              <EditNodeModal
                show={this.state.showEditNodeModal}
                onHide={() => this.toggleShowEditNodeModal()}
                nodeId={this.state.activeNode}
                nodeName={this.state.activeNodeName}
              />
            )}
          </React.Fragment>
        );
      }
    }
    function mapState(state) {
      return state;
    }
    const actionCreators = {
      getTree: treeActions.getTree
    };
    
    const connectedApp = connect(
      mapState,
      actionCreators
    )(TreePage);
    export { connectedApp as TreePage };
    

    NodeModal Component

    import React, { Component } from "react";
    import { Modal, Button, Form } from "react-bootstrap";
    import { nodeActions } from "../../_actions";
    import { connect } from "react-redux";
    
    class EditNodeModal extends Component {
      constructor(props) {
        super(props);
    
        this.state = {
          nodeName: this.props.nodeName
        };
    
        this.handleChange = this.handleChange.bind(this);
        this.hideAndClearName = this.hideAndClearName.bind(this);
        this.editNode = this.editNode.bind(this);
      }
    
      handleChange({ target }) {
        this.setState({ nodeName: target.value });
      }
    
      editNode() {
        const { editNode } = this.props;
        editNode(this.props.nodeId, this.state.nodeName);
        this.hideAndClearName();
      }
    
      hideAndClearName() {
        this.props.onHide();
        this.setState({ nodeName: "" });
      }
    
      render() {
        // console.log(this.props.nodeName);
        return (
          <Modal
            onHide={() => this.hideAndClearName()}
            show={this.props.show}
            size="md"
            aria-labelledby="contained-modal-title-vcenter"
            centered
          >
            <Modal.Header closeButton>
              <Modal.Title id="contained-modal-title-vcenter">
                Edit Name
              </Modal.Title>
            </Modal.Header>
            <Modal.Body>
              <Form.Control
                value={this.state.nodeName}
                onChange={event => this.handleChange(event)}
                type="text"
                placeholder="Nazwa"
              />
            </Modal.Body>
            <Modal.Footer>
              <Button onClick={() => this.editNode()} variant="success">
                Edit
              </Button>
              <Button onClick={() => this.hideAndClearName()}>Close</Button>
            </Modal.Footer>
          </Modal>
        );
      }
    }
    
    function mapState(state) {
      return state;
    }
    const actionCreators = {
      editNode: nodeActions.editNode
    };
    
    const connectedApp = connect(
      mapState,
      actionCreators
    )(EditNodeModal);
    export { connectedApp as EditNodeModal };