Search code examples
reactjsreactjs-flux

React - Accessing updated values from nested components


I have a table with rows and each cell within a row can be edited. There is a button associated with each row that will submit all the cells in that row to a database. What I would like to know is how I can access the new values, if the data in the cell has been changed, from the component that the submit event is actually occurring on?

Here is how I am building the table at the moment:

Main Table:

import React from 'react';
import TableWithDataHeader from './TableWithDataHeader.jsx';
import Row from './Row.jsx';
import AppStore from '../../stores/AppStore';

export default class Table extends React.Component {

    handleSubmitEvent = (e) => {
        e.preventDefault();
        /////What do I do here to get the updated values???////
    };

    render() {
        return (
            <div>
                <div className="row">
                    <table className="table table-striped table-bordered">
                        <thead>
                            <TableWithDataHeader />
                        </thead>
                        <Row data={AppStore.getCells().historycells} handleSubmitEvent={this.handleSubmitEvent} />
                    </table>
                </div>
            </div>
        );
    }
}

Row Component:

import React from 'react';
import TableBody from './TableBody.jsx';
import AppStore from '../../stores/AppStore';

export default class Row extends React.Component {
    state = {data: this.props.data};

    handleSubmitEvent = (e) => {
        this.props.handleSubmitEvent(e);
    };

    render() {

        let {data} = this.state;

        return (
            <tbody>
                <tr id={AppStore.getRowId()}>
                    {this.state.data.map(cell => {
                        return (
                            <TableBody key={cell.id} cell={cell.contents} />
                        );
                    })}
                    <td className="table-button">
                        <button type="submit" className="btn btn-primary" onClick={this.handleSubmitEvent.bind(this)}>Save Row</button>
                    </td>
                </tr>
            </tbody>
        );
    }
}

Cell Component:

import React from 'react';
import EditableCells from './EditableCells.jsx';

export default class TableBody extends React.Component {

    render() {
        return (
            <EditableCells data={this.props.cell} />
        );
    }
}

Edit Cell Component:

import React from 'react';

export default class EditableCells extends React.Component {
    state = {isEditMode: false, data: ""};

    componentWillMount() {
        this.setState({
            isEditMode: this.props.isEditMode,
            data: this.props.data
        });
    }

    handleEditCell = (e) => {
        this.setState({isEditMode: true});
    };

    handleKeyDown = (e) => {
        switch (e.keyCode) {
            case 13: //Enter
            this.setState({isEditMode: false});
            break;
        }
    };

    handleChange = (e) => {
        this.setState({data: e.target.value});
    };

    render() {

        let {isEditMode, data} = this.state;

        let cellHtml;

        if (this.state.isEditMode) {
            cellHtml = <input type="text" className="form-control" value={this.state.data} onKeyDown={this.handleKeyDown} onChange={this.handleChange} />
        } else {
            cellHtml = <div onClick={this.handleEditCell}>{this.state.data}</div>
        }
        return (
            <td className="text-center">{cellHtml}</td>
        );
    }
}

Any help on how I would be able to do this would be very helpful, and, of course, much appreciated!

Thanks for your time


Solution

  • Try to keep you application state at the highest most level in the component tree. The easy way out in this case is to let your Table component handle the data state, and also supply event handlers. Pass these down as props to the Row, and further down as needed. Proof of concept: https://jsfiddle.net/dannyjolie/4jfxeag8/1/

    The general rule of thumb is to never use component state at all, and leverage all change in application change to the very top most component, but input fields is sometimes an exception from the rule as you may not want to modify the application state while typing.

    The basics of it:

    export default class Table extends React.Component {
      constructor(props) {
        super(props);
        this.state.cells = ['Foo', 'Bar']; // Fetch something from your store, or better, pass the cells as props from parent component
        this.handleSubmitEvent = this.handleSubmitEvent.bind(this);
        this.handleChangeEvent = this.handleChangeEvent.bind(this);
      }
      handleSubmitEvent () {
        // Up to date cells are stored in this.state
      }
      handleChangeEvent (value, cell) {
        // All values are available in this.state, do with that as you wish :)
      }
      render () {
        ....
        <Row handleSubmitEvent={this.handleSubmitEvent} 
             handleChangeEvent={this.handleChangeEvent}
             data={this.state.cells} />
        ....
      }
    }