14

I'm creating a table in React (I'm new to React), but the CategoryDataCan we live with those or should we use something else? It is not creating cells correctly, i.e. it's creating cells that are not aligned with the <th> that come from the parent and they do not have cell borders at all. It's also giving these warnings:

Warning: validateDOMNesting(...): <div> cannot appear as a child of <tbody>. See Param > tbody > Row > div.
Warning: validateDOMNesting(...): <tr> cannot appear as a child of <div>. See Row > div > tr.
Warning: validateDOMNesting(...): <tr> cannot appear as a child of <div>. See CategoryData > div > tr.

I'm not sure why these warnings are happening and why the table cells(from CategoryData) are not getting aligned and don't have cell borders. What's the correct way to do it?

Code

var Param = React.createClass({

    getInitialState: function() {
        return {
            isLoading: true,
            panelTitle: "",
            data: [],
            categories: []
        }
    },

    updateState: function() {
        var that = this;
        var categories = new Set();
        rest.getParameters('service').then(function (results) {
            for (var i = 0; i < results.data.length; i++) {
                categories.add(results.data[i].category);
            }
            that.setState({
                data: results.data,
                categories: Array.from(categories)
            })
        }).catch(function (err) {
            console.error('rest.getParameters(): ', err);
            that.setState({
                isLoading: true,
                data: [],
                categories: []
            })
        });
    },

    componentDidMount: function() {
        this.updateState();
    },

    render: function() {
      return (
        <Panel className="panel-info" header={<h4 className="panel-title">Service Config</h4>}>
            <div>
                <table className="table table-striped table-bordered table-hover table-condensed table-responsive">
                    <thead>
                        <tr>
                            <th className="col-lg-2 text-center">AMP Name</th>
                            <th className="col-lg-2 text-center">Athena Name</th>
                            <th className="col-lg-2 text-center">Description</th>
                            <th className="col-lg-1 text-center">Default</th>
                            <th className="col-lg-1 text-center">Min Value</th>
                            <th className="col-lg-1 text-center">Max Value</th>
                            <th className="col-lg-1 text-center">Action</th>
                        </tr>
                    </thead>
                    <tbody>
                        {this.state.categories.map((category, index) =>
                            <th colSpan="7" key={index} style={{'textAlign':'left', 'paddingLeft':'5px', 'backgroundColor': '#D3D0CF'}}>{this.state.category}</th>
                            this.state.data.map((row, i) =>
                                if (row.category === category) {
                                    <tr key={i}>
                                        <td className="col-lg-2 text-center">{row.name}</td>
                                        <td className="col-lg-2 text-center">{row.alias}</td>
                                        <td className="col-lg-2 text-center">{row.description}</td>
                                        <td className="col-lg-1 text-center">{row.default_value}</td>
                                        <td className="col-lg-1 text-center">{row.min_value}</td>
                                        <td className="col-lg-1 text-center">{row.max_value}</td>
                                        <td className="col-lg-1 text-center">Action</td>
                                    </tr>
                                }
                            )  
                        )}
                    </tbody>
                </table>
            </div>
        </Panel>
    );
  }
});
Rutvik Bhatt
  • 3,185
  • 1
  • 16
  • 28
ssss
  • 551
  • 3
  • 5
  • 14
  • 2
    Your `tr`'s are enclosed in `div` tags. – Frank Fajardo Jul 26 '17 at 23:46
  • 2
    You can't have a `
    ` inside of a ``, unless it is a direct child of either ``. You may wish to re-work your `var Row` to not render `
    ` as the outer containing element :)
    ` or ``. Yours are a child of `
    – Obsidian Age Jul 26 '17 at 23:46
  • @ObsidianAge, but how to re-work? – ssss Jul 26 '17 at 23:52
  • 1
    ...SImply remove the `
    ` from your `Row`. If you'd like to have a `
    `, it's probably looking to go in `Panel`. It depends on what you want the final structure to be. But yes, it's certainly invalid to return `
    ...` in your row :)
    – Obsidian Age Jul 26 '17 at 23:56
  • @ObsidianAge, i was thinking to remove `CategoryData`, but im not sure what i should add in `Row`'s render.. – ssss Jul 26 '17 at 23:56
  • @ObsidianAge, @FrankFajardo, could you guy look at the edit? I removed the other two child components, but now `th` is not letting execute the nested `map` – ssss Jul 27 '17 at 00:09
  • https://stackoverflow.com/a/25647392/6836839 – btzr Jul 27 '17 at 01:00
  • Possible duplicate of [How do I wrap a React component that returns multiple table rows and avoid the " cannot appear as a child of
    " error?](https://stackoverflow.com/questions/37982842/how-do-i-wrap-a-react-component-that-returns-multiple-table-rows-and-avoid-the)
    – btzr Jul 27 '17 at 22:41
  • As of March 2019, your question looks confusing, because your code no longer includes the `Row` and `CategoryData` components that were causing the original errors. The issue with the the current code is that you're rendering `` elements outside of the ,`` elements. `` should always be a child of ``. – carpiediem Mar 05 '19 at 12:01

2 Answers2

5

I would change the 'th' to a 'tr' because I'm pretty sure react will give you a warning if you add 'th' inside 'tbody'

let finalList = []
this.state.categories.forEach( (cat, index) => {
   finalList.push(<tr...>{this.state.category}</tr>)
   this.state.data.forEach( (row, index) => {
      if(row.category === cat){
         finalList.push(
             <tr key={i}>
                 <td className="col-lg-2 text-center">{row.name}</td>
                 <td className="col-lg-2 text-center">{row.alias}</td>
                 <td className="col-lg-2 text-center">{row.description}</td>
                 <td className="col-lg-1 text-center">{row.default_value}</td>
                 <td className="col-lg-1 text-center">{row.min_value}</td>
                 <td className="col-lg-1 text-center">{row.max_value}</td>
                 <td className="col-lg-1 text-center">Action</td>
             </tr>
          )
      }
   })
})

Word of warning I would avoid using tables checkout css grids their a lot more flexible and pretty well supported

4

EDIT: From version 16.0.0 onwards in react, you could make use of React.Fragment to return multiple elements from render

<tbody>
    {
        this.state.categories.map((category, index) => {
            var innerData = this.state.data.map((row, i) => {
                if (row.category === category) {
                    return (
                        <tr key={i}>
                            <td className="col-lg-2 text-center">{row.name}</td>
                            <td className="col-lg-2 text-center">{row.alias}</td>
                            <td className="col-lg-2 text-center">{row.description}</td>
                            <td className="col-lg-1 text-center">{row.default_value}</td>
                            <td className="col-lg-1 text-center">{row.min_value}</td>
                            <td className="col-lg-1 text-center">{row.max_value}</td>
                            <td className="col-lg-1 text-center">Action</td>
                        </tr>
                    )
                }
                return null
            })

            return (
              <React.Fragment>
                <th colSpan="7" key={index} style={{
                    'textAlign': 'left',
                    'paddingLeft': '5px',
                    'backgroundColor': '#D3D0CF'
                }}>{this.state.category}</th>,
                {innerData}
              </React.Fragment>    
            )
        })
    }
    </tbody>

Before v16

With the help of JSX syntactic sugar it is possible to return multiple elements from within a component, by writing them as comma separated elements in an array like

<tbody>
{
    this.state.categories.map((category, index) => {
        var innerData = this.state.data.map((row, i) => {
            if (row.category === category) {
                return (
                    <tr key={i}>
                        <td className="col-lg-2 text-center">{row.name}</td>
                        <td className="col-lg-2 text-center">{row.alias}</td>
                        <td className="col-lg-2 text-center">{row.description}</td>
                        <td className="col-lg-1 text-center">{row.default_value}</td>
                        <td className="col-lg-1 text-center">{row.min_value}</td>
                        <td className="col-lg-1 text-center">{row.max_value}</td>
                        <td className="col-lg-1 text-center">Action</td>
                    </tr>
                )
            }
            return null
        })

        return ([
            <th colSpan="7" key={index} style={{
                'textAlign': 'left',
                'paddingLeft': '5px',
                'backgroundColor': '#D3D0CF'
            }}>{this.state.category}</th>,
            [...innerData]
        ])
    })
}
</tbody>

Also when you make use of if statements within a map function, you need to have them outside of the return statement, now if you do {this.state.categories.map((category, index) => <tr>... it means that whatever is after the arrow is considered to be part of the return and hence you inner map's if statement will give you an error.

There is an issue on react github page for returning multiple elements. Read through it for more details.

Shubham Khatri
  • 270,417
  • 55
  • 406
  • 400