Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React - mapping data to rows that are formed during a map function

I have a component called Cells which renders with data that is gotten from a flux store. My problem is that I want to render this data to a specific row but because of the way I am rendering the rows (they are dynamic so you can add them to the table), I am struggling to give the rows identifiers which the cell component can render into. I hope that makes sense!

Here is the code:

Cells Component:

import React from 'react';

export default class Cells extends React.Component {
    render() {
        return (
            <td>{this.props.value}</td>
        );
    }
}

Table Component:

    import React from 'react';
    import TableHeader from './TableHeader.jsx';
    import Cells from './Cells.jsx';
    import RowForm from './RowForm.jsx';
    import {createRow} from '../../actions/DALIActions';
    import AppStore from '../../stores/AppStore';

    export default class Table extends React.Component {
        state = {rows: [], cellValues: [], isNew: false, isEditing: false};
        updateState = () => this.setState({cellValues: AppStore.getCellValues()});

        componentWillMount() {
            AppStore.addChangeListener(this.updateState);
        }

        handleAddRowClickEvent = () => {
            let rows = this.state.rows;
            rows.push({isNew: true});
            this.setState({rows: rows});
        };

        handleEdit = (row) => {
            this.setState({isEditing: true});
        };

        editStop = () => {
            this.setState({isEditing: false});
        };

        handleSubmit = (access_token, id, dataEntriesArray) => {
            createRow(access_token, id, dataEntriesArray);
        };

        componentWillUnmount() {
            AppStore.removeChangeListener(this.updateState);
        }

        render() {

            let {rows, cellValues, isNew, isEditing} = this.state;

            let headerArray = AppStore.getTable().columns;

            let cells = this.state.cellValues.map((value, index) => {
                return (
                    <Cells key={index} value={value.contents} />
                );
            });

            return (
                <div>
                    <div className="row" id="table-row">
                        <table className="table table-striped">
                            <thead>
                                <TableHeader />
                            </thead>
                            <tbody>
//////////// This is where the render of the data would happen/////////////
                                {rows.map((row, index) => this.state.isEditing ?
                                    <RowForm formKey={index} key={index} editStop={this.editStop} handleSubmit={this.handleSubmit} /> :
                                    <tr key={index}>
                                        {this.state.cellValues ? cells : null}
                                        <td>
                                            <button className="btn btn-primary" onClick={this.handleEdit.bind(this, row)}><i className="fa fa-pencil"></i>Edit</button>
                                        </td>
                                    </tr>
                                )} 
                   ///////////////End/////////////////
                            </tbody>
                        </table>
                    </div>
                    <div className="row">
                        <div className="col-xs-12 de-button">
                            <button type="button" className="btn btn-success" onClick={this.handleAddRowClickEvent}>Add Row</button>
                        </div>
                    </div>
                </div>
            );
        }
    }

I know this isn't probably the best way to achieve what I want (any tips on that would be appreciated as well), but its what I have to work with at the moment!

Any help would be much appreciated, especially examples!

Thanks for you time!

like image 701
BeeNag Avatar asked Jan 06 '23 13:01

BeeNag


1 Answers

Instead of having one object (rows) that contains row headers, and another object that contains all cells for all rows (cellvalues), I would advise you to put the cell data inside the individual row data in some way, so that your data structure would look something like this:

rows = [
  { rowID: 100, cells: [
      { cellID: 101, value: 'data' },
      { cellID: 102, value: 'data' }
    ]
  },
  { rowID: 200, cells: [
      { cellID: 201, value: 'data' },
      { cellID: 202, value: 'data' }
    ]
  }
]

That way, you pass the cellValues per row, and that allows you to have different cells per row.

Make a separate component for <Row>, which renders:

return 
  <tr>
    {this.props.cells.map( cell => { 
        return <Cell key={cell.cellID} value={cell.value} />
    })}
  </tr>

And change the <tr> bit inside your main render to:

<Row key={row.keyID} cells={row.cells}/>

Finally: it is a bad idea to use the index for keys, as in key={i}. Use a key that uniquely identifies the content of the cell/ row.

UPDATE: A typical situation is that cells or rows are first created in front-end, and only get their database ID after posting to database.
Options to still get unique IDs for the keys are:

  • upon creation of the cell or row in react, give the cell/row a unique ID in the form of a timestamp. After getting back the response from the database, replace the timestamp with the official database key. (this is when you do optimistic updates: show new rows temporarily, until the database gives you the official rows).
  • do not display the row/cell, until you get response back from server with official IDs
  • create a unique key by hashing the content of all cell/row contents (if you are pretty sure that cell/row contents are not likely to be identical)

Hope this helps.

like image 179
wintvelt Avatar answered Jan 09 '23 02:01

wintvelt