Skip to content

Adding object values to useMemo hook for React

I am attempting to create a react-table component using data that is pulled from a database. From the documentation I have read (https://react-table.tanstack.com/docs/quick-start), it seems like the react-table library uses a useMemo hook to create the data which will be displayed on the table. However, I am having trouble actually adding data to the useMemo hook, since I am not familiar with it.

I have a simple JS object that holds the counts of instances of each category of outages that occur in our database. Once I have the counts, I attempt to pass it to my instance of useMemo, however properties of undefined ‘streamCount’ is returned. I think I am passing the object to useMemo incorrectly. Any help is appreciated.

function Leaderboard(props){
    const data = props.tableData;
    console.log(data); //this is the data that is pulled from our DB and is passed as a prop into the component
    
    let counts = {
      streamCount: 0,
      powerCount: 0,
      internetCount: 0,
      gamingPlatformCount: 0,
      cableCount: 0,
      websiteCount: 0,
    } //the object that holds the number of instances each category occurs in our data

    for(var i = 0; i < data.length; i++){ //checks the data and updates the counts for each category
      switch(data[i].service_type) {
        case "Streaming":
          counts.streamCount += 1;
          break;
        case "Power":
          counts.powerCount+= 1;
          break;
        case "Internet":
          counts.internetCount+= 1;
          break;
        case "Gaming Platform":
          counts.gamingPlatformCount += 1;
          break;
        case "Cable":
          counts.cableCount += 1;
          break;
        case "Website":
          counts.websiteCount += 1;
          break;
        default:
          break;
      }
    }

    console.log(counts) //This returns the correct values of each count when line 41-69 is commented, but returns 0 for all values when those lines are uncommented.

    let outageCounts = React.useMemo(
      (counts) => [
        {
          type: 'Streaming',
          count: counts.streamCount,
        },
        {
          type: 'Power',
          count: counts.powerCount,
        },
        {
          type: 'Internet',
          count: counts.internetCount,
        },
        {
          type: 'GamingPlatform',
          count: counts.gamingPlatformCount,
        },
        {
          type: 'Cable',
          count: counts.cableCount,
        },
        {
          type: 'Website',
          count: counts.websiteCount,
        },
      ],
      []
    );
    
    //this will be updated to have the accessor be 'count' from outageCounts instead of 'service_type' from data when the bug is resolved. For now it is just using data to test to see if the table would render at all.
    const columns = React.useMemo(
        () => [
          {
            Header: 'Service Type',
            accessor: 'service_type',
          },
        ],
        []
    );
    
    const {
        getTableProps,
        getTableBodyProps,
        headerGroups,
        rows,
        prepareRow,
    } = useTable({ columns, data}) //data will eventually be changed to outageCounts
    
    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 Leaderboard;

Answer

The useMemo hook’s callback function doesn’t take any arguments, it simply takes a callback function that returns a value you want, or need, to memoize, and a dependency array.

useMemo

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

Returns a memoized value.

Pass a “create” function and an array of dependencies. useMemo will only recompute the memoized value when one of the dependencies has changed. This optimization helps to avoid expensive calculations on every render.

Move the logic for computing the counts into the useMemo callback and use data (the props value) as the dependency. You can simplify/reduce the code to be more DRY by abstracting the common pattern of mapping the service_type to one of the counts keys and then mapping back but just using the service_type as the counts keys. With this change you can simply use dynamic object properties to update the counts for each outage type. When the counts have been computed create an array of key-value pairs from the object and map this to the array of objects with type and count keys.

const outageCounts = React.useMemo(() => {
  const counts = {
    Streaming: 0,
    Power: 0,
    Internet: 0,
    "Gaming Platform": 0,
    Cable: 0,
    Website: 0
  };

  data.forEach(({ service_type }) => {
    if (Object.hasOwnProperty.call(counts, service_type)) {
      counts[service_type] += 1;
    }
  });

  return Object.entries(counts).map(([type, count]) => ({ type, count }));
}, [data]);

function App({ data = [] }) {
  const outageCounts = React.useMemo(() => {
    const counts = {
      Streaming: 0,
      Power: 0,
      Internet: 0,
      "Gaming Platform": 0,
      Cable: 0,
      Website: 0
    };

    data.forEach(({ service_type }) => {
      if (Object.hasOwnProperty.call(counts, service_type)) {
        counts[service_type] += 1;
      }
    });

    return Object.entries(counts).map(([type, count]) => ({ type, count }));
  }, [data]);

  //console.log({ outageCounts });

  return (
    <div className="App">
      <h1>Outage Counts</h1>
      <ul>
        {outageCounts.map(({ type, count}) => (
          <li key={type}>
            {type}: {count}
          </li>
        ))}
      </ul>
    </div>
  );
}

const service_types = [
  "Streaming",
  "Power",
  "Internet",
  "Gaming Platform",
  "Cable",
  "Website"
];

// Generate "random" outage data
const data = Array.from({ length: Math.floor(Math.random() * 1000) }, () => ({
  service_type: service_types[Math.floor(Math.random() * service_types.length)]
}));

const rootElement = document.getElementById("root");
ReactDOM.render(
  <App data={data} />,
  rootElement
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="root" />