4

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!

BeeNag
  • 1,764
  • 8
  • 25
  • 42

2 Answers2

9

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.

Mr Lister
  • 45,515
  • 15
  • 108
  • 150
wintvelt
  • 13,855
  • 3
  • 38
  • 43
  • Sorry for the late response! My problem is that the ids for the row and cell values aren't gotten until the data for a particular row is submitted to my database. So once the row itself is saved, the row gets an id and each cell value is given one as well. Would the best way to assign them be to store those values and then update the state of rows to be something like you have suggested? – BeeNag Mar 04 '16 at 09:15
  • Updated answer to address this – wintvelt Mar 04 '16 at 14:16
  • Yeah I ended up doing that and it works now, so thanks! – BeeNag Mar 04 '16 at 14:26
0

You need to pass row to cells render method :

  {rows.map((row, index) => this.state.isEditing ?
       <RowForm formKey={index} key={index} editStop={this.editStop} handleSubmit={this.handleSubmit} /> :
       <tr key={index}>
         {(row.cellValues || []).map((value, index) => {
            return (
                <Cells key={index} value={value.contents} />
          )})
         }
         <td>
           <button className="btn btn-primary" onClick={this.handleEdit.bind(this, row)}><i className="fa fa-pencil"></i>Edit</button>
         </td>
       </tr>
  )} 

Hope this help!

Nour Sammour
  • 2,822
  • 1
  • 20
  • 19
  • Using a mapping index in keys is a bad idea, and should really be avoided. – wintvelt Mar 03 '16 at 17:55
  • @wintvelt yes i know, but this what he has in his question and i tried to solve his issue with minimuim changes – Nour Sammour Mar 03 '16 at 18:22
  • Thanks for your answer. When I tried with your answer it still only saves the latest entered data to a row so if I enter data into one row, it is the same for all rows, and if I change it the data in all the rows changes to what I most recently put in. – BeeNag Mar 04 '16 at 09:19
  • I understand where you are going but I get an error of 'cannot read map of undefined' as in the first instance cellValues is unpopulated do to there being no data in it. – BeeNag Mar 04 '16 at 10:17
  • If you can post the server response, so i can help more – Nour Sammour Mar 04 '16 at 10:18
  • When the table is initiated there is no server response, as the data is only sent to the server after the row is created and data has been entered and saved to it. The error I am getting at the moment is: "Uncaught TypeError: Cannot read property 'map' of undefined" because cellValues starts off as an empty array and is only updated after data has been stored in the database and then accessed by my store. – BeeNag Mar 04 '16 at 10:33
  • I edited my answer again :) there is something wrong in your structure, the cells should be inside row you can not have rows = [], cells = [] it should be `rows= [{cellValues:[cell1,cell2]}, {cellValues:[cell1,cell2]} ]` – Nour Sammour Mar 04 '16 at 10:52
  • Yeah I know its a problem with how I'm creating my rows I think. Ideally I would want it so that the row updates with the cell content but at the moment I create a row by push an arbitrary value to it – BeeNag Mar 04 '16 at 11:05