msmith1114
msmith1114

Reputation: 3231

React Table w/JSON API hooks?

So I started learning to use React Table (https://github.com/tannerlinsley/react-table) it's pretty nifty. However I am an intermediate at React (I understand the basic lifecycle/props/etc..) but hooks are rather new to me.

I decided to use it in a personal project which was mostly classes. It calls an API (that I wrote in Rails) to return some basic JSON information. I am using axios (https://github.com/axios/axios) to call the API.

From learning react hooks I converted what was originally my componentDidMount() call into a useEffect() call. Below is the code for the Table and the Table Container (it is for an application that deals with sensors hence the names)

Sensor Container:

import React, { useState, useEffect } from "react";
import axios from "axios";
import SensorCard from "./SensorCard";
import SensorTable from "./SensorTable";
import "../Styles/Components/_SensorContainer.css";

function SensorContainer() {
  const [sensors, setSensors] = useState([]);
  const [data, setData] = useState([])

  useEffect(() => {
    // GET sensor list from API
    axios
    .get("http://localhost:3000/api/v1/devices.json")
    .then((response) => {
      // handle success
      console.log(response);
      setSensors(response.data.data)
    })
    .then(() => {
      console.log(sensors)
      console.log(sensors[0].attributes)
      console.log(name)
      console.log(serialNumber)
      console.log(deviceStatus)
      const { name, 'serial-number':serialNumber, 'device-status':deviceStatus} = sensors[0].attributes
      const data = sensors.map(sensor => 
        ({ 
          id: sensor.id, 
          name: name, 
          serialNum: serialNumber,
          status: deviceStatus
        })
      )
    setData(data)
    })
    .catch((error) => {
      console.log(error);
    })
  }, [sensors.length]);





  // const data = React.useMemo(
  //   () => [
  //     {
  //       id: '1',
  //       name: 'TEMP001',
  //       serialNum: 'Temp Sensor',
  //       status: 'Active',
  //     },
  //     {
  //       id: '2',
  //       name: 'TEMP002',
  //       serialNum: 'Temp Sensor',
  //       status: 'Unknown',
  //     },
  //     {
  //       id: '3',
  //       name: 'HUM001',
  //       serialNum: 'Humidity Sensor',
  //       status: 'Active',
  //     },
  //   ],
  //   []
  // )

  const columns = React.useMemo(
    () => [
      {
        Header: 'ID',
        accessor: 'id', // accessor is the "key" in the data
      },
      {
        Header: 'Name',
        accessor: 'name',
      },
      {
        Header: 'Serial Number',
        accessor: 'serialNum',
      },
      {
        Header: 'Status',
        accessor: 'status',
      },
    ],
    []
  )
  return (
    <div>
      <SensorTable columns={columns} data={data} />
    </div>
  )
}

export default SensorContainer;

Sensor Table:

import React from "react";
import {
  useTable,
  useGroupBy,
  useFilters,
  useSortBy,
  useExpanded,
  usePagination,
} from "react-table";
import "../Styles/Components/_SensorTable.css";

function SensorTable({ columns, data }) {
  const {
    getTableProps,
    getTableBodyProps,
    headerGroups,
    rows,
    prepareRow,
  } = useTable({ columns, data })

  // Render the UI for your table
  return (
    <table {...getTableProps()} style={{ border: 'solid 1px blue' }}>
      <thead>
        {headerGroups.map(headerGroup => (
          <tr {...headerGroup.getHeaderGroupProps()}>
            {headerGroup.headers.map(column => (
              <th
                {...column.getHeaderProps()}
                style={{
                  borderBottom: 'solid 3px red',
                  background: 'aliceblue',
                  color: 'black',
                  fontWeight: 'bold',
                }}
              >
                {column.render('Header')}
              </th>
            ))}
          </tr>
        ))}
      </thead>
      <tbody {...getTableBodyProps()}>
        {rows.map(row => {
          prepareRow(row)
          return (
            <tr {...row.getRowProps()}>
              {row.cells.map(cell => {
                return (
                  <td
                    {...cell.getCellProps()}
                    style={{
                      padding: '10px',
                      border: 'solid 1px gray',
                      background: 'papayawhip',
                    }}
                  >
                    {cell.render('Cell')}
                  </td>
                )
              })}
            </tr>
          )
        })}
      </tbody>
    </table>
  )
}

export default SensorTable;

So I am running into a few "design issues" regarding this:

  1. How/Where do people normally set/store the column data and "data" data when using react table. I'd assume just a const for the columns....but where is the data stored? (I'd normally store it in state in a class, but since im trying to use hooks I am unsure)
  2. I feel like my useEffect() is wrong for fetching API data. I also ran into a bug where it constantly kept running. I saw someone suggest to check the length to prevent this....but this feels hacky. What's the correct way to do this?
  3. I can't quite figure out how to map the data how I want to with React.useMemo I can't find any examples either (Apparently react table requires this). Any Advice?

I am just really unsure how to set this up properly. I can't seem to find any examples of people using react table WITH json api fetching either.

Upvotes: 1

Views: 5596

Answers (1)

Lakshmaji
Lakshmaji

Reputation: 1277

Your code is fine except for a few modifications. You can find the final code here

  1. How/Where do people normally set/store the column data and "data" data when using react table. I'd assume just a const for the columns....but where is the data stored? (I'd normally store it in state in a class, but since im trying to use hooks I am unsure)

    Answer

    • It is based on the application requirement.
    • If the data needs to be persisted in the client-side and shared across several modules/components then use redux to store your data.
    • If the data is required (not to be kept for the long run) only when the user visits the screen, then keep the data in the component state.
  2. I feel like my useEffect() is wrong for fetching API data. I also ran into a bug where it constantly kept running. I saw someone suggest to check the length to prevent this....but this feels hacky. What's the correct way to do this?

    Answer:

    • As per your code, there is no requirement/need to pass dependencies in useEffect hook. (This happens because you are trying to maintain the same data in two-component state i.e data and sensors. but there is no need- PS see the attached code in the above link. Have a look at Conditionally filtering an effect
    • If you want to run an effect on mount only once (componentDidMount), you can pass an empty array ([]) as a second argument. So it never re-runs.
  3. I can't quite figure out how to map the data how I want to with React.useMemo I can't find any examples either (Apparently react table requires this). Any Advice?

    Answer:

    • What do you mean by how to map the data?. Want to do some transformation on the response object from API ?. If yes you can use any Javascript built-in methods or you can use 3rd party library like ramda or lodash
    • useMemo is not required in your case as You're not doing any complex operation and your data source is API.
    • Some note mentioned in reactjs documentaion regarding useMemohook

Remember that the function passed to useMemo runs during rendering. Don’t do anything there that you wouldn’t normally do while rendering. For example, side effects belong in useEffect, not useMemo.

Your code is fine and there are no issues, except maintaining the same data in two separate component state variables.

Refer to the lines marked with <---- in below code

  // const [sensors, setSensors] = useState([]); No need to maintain same data in two state variables
  const [data, setData] = useState([])

  useEffect(() => {
    // GET sensor list from API
    axios
    .get("http://localhost:3000/api/v1/devices.json")
    // .then((response) => {  
    //  handle success
    //  console.log(response);
    //  setSensors(response.data.data)
    // }) <------- this is not required
    .then((response) => {
      // ......
      const data = response.data.data.map(sensor => 
        ({ 
          id: sensor.id, 
          name: sensor.name, 
          serialNum: sensor.serialNumber,
          status: sensor.deviceStatus
        })
      )
      setData(data)
    })
    .catch((error) => {
      console.log(error);
    })
//  }, [sensors.length]);
}, []); // <--------- like componentDidMount in classBased components 

Added final code in working codesandbox here

Upvotes: 3

Related Questions