0

I'm using the datatables.net library with React to generate nice-looking tables. To get a working example I hard-coded a variable dataSet and sent it as a prop to the component like this:

import React from "react"
import { TblFunc } from "./TblFunc"

export default function AppFunc() {
  const dataSet = [
    ["site1", "device1"],
    ["site1", "device2"],
    ["site1", "device3"],
    ["site1", "device4"],
    ["site1", "device5"],
    ["site1", "device6"],
    ["site1", "device7"],
    ["site1", "device8"],
    ["site1", "device9"],
    ["site2", "device1"],
    ["site2", "device2"],
    ["site2", "device3"],
    ["site2", "device4"],
    ["site2", "device5"],
    ["site2", "device6"],
    ["site2", "device7"],
    ["site2", "device8"]
  ]

  return (
    <div>
      <TblFunc data={dataSet} />
    </div>
  )
}

This generates this output:enter image description here

Now, instead of hard coding the dataSet variable, I want to generate it automatically by calling an API. I need to create an Array of Arrays object to mimic dataSet. The API returns an object like this:

{
    "site1": [
      "device-a01",
      "device-a02",
      "device-b01",
      "device-b02"
    ],
    "site2": [
      "device-a01",
      "device-a02",
      "device-b01",
      "device-b02",
      "device-c01",
      "device-c02"
    ]
}

Here is what I have tried:

import React from "react"
import { TblFunc } from "./TblFunc"

export default function AppFunc() {

  const [tableData, setTableData] = React.useState([])

  function buildTableData(site, device) {
    const hubValue = []
   
    hubValue.push(site)
    hubValue.push(device)
    
    return hubValue

  }

  React.useEffect(() => {
    async function getData() {
      const res = await fetch("https://api.com/api/devices/")
      const data = await res.json()
      
      Object.entries(data).forEach(([site, devices]) => {
        const _tableData = devices.map(device => buildTableData(site, device))
        console.log(_tableData)

        setTableData(_tableData)

      })
      
    }
    getData()
    
  },[])

  return (
    <div>
      {/* <TblFunc data={dataSet} /> // THIS WORKS */}
      <TblFunc data={tableData} />
      {JSON.stringify(tableData)}
    </div>
  )
}

When printing tableData to the screen I get this:

[["site1","device01"],["site1","device02"],["site1","device03"],["site1","device04"],["site1","device05"],["site1","device06"]["site1","device07"]]

I believe I have generated an Array of Array's object but for some reason when I pass it to the <TableFunc /> component it does not populate the table. This leads me to believe that tableData is not matching the dataSet object.

This is what <TableFunc /> looks like:

import React, { useEffect, useRef } from "react"
import $ from 'jquery'

export function TblFunc(props) {

$.DataTable = require('datatables.net')
const tableRef = useRef()

useEffect(() => {
    // console.log(tableRef.current)
    const table = $(tableRef.current).DataTable(
        {
            data: props.data,
                columns: [
                    { title: "Site"},
                    { title: "Device"}
                ],
                destroy: true  // I think some clean up is happening here
        }
    )
    // Extra step to do extra clean-up.
    return function() {
        console.log("Table destroyed")
        table.destroy()
    }
},[])

    return (
        <div>
            <table className="display" width="100%" ref={ tableRef }></table>
        </div>
        
    )
}
6
  • 2
    You're using forEach and setting the state at the end of each iteration, so you will only ever have the last entry refactored. You should instead refactor the full Object.entries array, and then set state. Commented May 16, 2022 at 22:00
  • I think @pilchard is probably right. But also, I'd like to add - use the browser's debugger and step through execution. In Chrome, choose View -> Developer -> Developer Tools, click source pane, find your file and set a breakpoint. Alternatively, add a "debugger;" statement in your JS code. Stepping through will make it much easier to tell what is going on with your logic. Commented May 16, 2022 at 22:05
  • @pilchard, i see what you're saying, but even with only the last entry being written to tableData have i generated the tableData object correctly as an Array of Arrays? I can't even get the last entry to display in the table. Commented May 16, 2022 at 22:08
  • It looks like because you initially pass an empty array the table doesn't update when you finally populate the array in the useEffect. A quick fix is to conditionally render the first render {!!tableData.length && <TblFunc data={tableData} />}, but you'll want to troubleshoot the actual rerender issue. (I would recommend using a React ready table library rather than one that relies on jQuery and direct DOM manipulation) Here's a quick sandbox Commented May 16, 2022 at 22:35
  • The above comment is accurate, but I overlooked your second useEffect in the TblFunc component. Simply add props.data to the dependency array. useEffect(() => { const table = $(tableRef.current).DataTable({ ...}, [props.data]); Updated sandbox Commented May 16, 2022 at 22:40

1 Answer 1

1

The table update relies on the jquery code in the useEffect in your TblFunc component, but you have passed an empty dependency array so the table is never rebuilt after the first render. Simply add props.data to the dependency array and the table should update as expected after the fetch completes in the parent's useEffect.

useEffect(() => {
  const table = $(tableRef.current).DataTable(
    {
      data: props.data,
      columns: [
        { title: "Site" },
        { title: "Device" }
      ],
      destroy: true  // I think some clean up is happening here
    }
  )

  return function () {
    console.log("Table destroyed")
    table.destroy()
  }
}, [props.data])
//  ^^^^^^^^^^ add relevant dependency to trigger rerender

Secondly, you're using forEach and setting the state at the end of each iteration, so you will only ever have the last entry refactored. You should instead refactor the full Object.entries array, and then set state.

React.useEffect(() => {
  async function getData() {
    const res = await fetch("https://api.com/api/devices/")
    const data = await res.json()

    const _tableData = Object.entries(data).flatMap(([site, devices]) =>
      devices.map((device) => [site, device])
    );

    setTableData(_tableData);
  }
  getData();
}, []);

The above uses a streamlined refactoring method using flatMap() with a nested map() on the inner devices array.

const data = { "site1": ["device-a01", "device-a02", "device-b01", "device-b02"], "site2": ["device-a01", "device-a02", "device-b01", "device-b02", "device-c01", "device-c02"] };

const _tableData = Object.entries(data).flatMap(([site, devices]) =>
  devices.map((device) => [site, device])
);

console.log(_tableData);

Example sandbox.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.