3

I am new to react-table and trying to re-render a react-table when data supplied to it changes. Is there anyway to do so? I have tried using useEffect() with no avail.

CLARIFICATION: Usage.js and Updater.js needs to be kept seperated with their functions as shown. I am open to using another approach than global variable if it can help rerender the table when the data value changes.

table.js

import React, {useMemo} from 'react'
import {useTable} from 'react-table'

export const Table = ({ columns, data, noDataComponent, ...rest }) => {
  const tableColumns = useMemo(() => columns, [columns]);
  const { getTableBodyProps, getTableProps, headerGroups, prepareRow, rows } = useTable({ columns: tableColumns, data });

  if (!rows.length) {
    if (noDataComponent) return noDataComponent;
    return <>No data</>;
  }

React.useEffect(() => {}, [data]) //does not re-render table

  return (
    <table {...getTableProps()} {...rest}>
      <thead>
        {headerGroups.map((headerGroup) => (
          <tr {...headerGroup.getHeaderGroupProps()}>
            {headerGroup.headers.map((column) => (
              <th {...column.getHeaderProps()}>{column.render('Header')}</th>
            ))}
          </tr>
        ))}
      </thead>
      <tbody {...getTableBodyProps()}>
        {rows.map((row) => {
          prepareRow(row);
          return (
            <tr {...row.getRowProps()}>
              {row.cells.map((cell) => {
                const { className, style } = cell.column;
                return (
                  <td {...cell.getCellProps({ className, style })}>
                    {cell.render('Cell')}
                  </td>
                );
              })}
            </tr>
          );
        })}
      </tbody>
    </table>
  );
};

globalVariable.js

module.exports = global.config = {
value: { exactval: [] }
}

Usage.js

data is updated by using a GET request in Updater.js and supplied using global variable in globalVariable.js.

import {Table} from './table.js'
...

<Table data={global.config.value.exactval} columns={COLS} />

Updater.js

import './globalVariable.js'

...

function DummyCall() { //Works fine 
    axios.get(URL, headers)
    .then(reponse => {
        global.config.value.exactval.push(reponse.ID[1])
        }
    ).catch(error => {
        console.log(error)
    }) }

...

<button type ="button" onClick={() => DummyCall()} > Test me! </button>

A simple explaination with example code would be greatly appreciated.

EDIT #1: Added how data is updated

EDIT #2: After taking the advice of @Obsidianlab and @Johnny, I used Context API using Johnny's answer (there is an important bug in answer where needs to wrap the .Provider in return(), I am getting the following strange behaviour:

Updater.js

Updated based on @Johnny's steps

import './globalVariable.js'

...

function DummyCall() { //Works fine 
    const updatedData = { ...context.data };

    axios.get(URL, headers)
        .then(reponse => {
            global.config.value.exactval.push(reponse.ID[1])
            updatedData.data.push(reponse.ID[1]);

        }
    context.setData(updatedData)
    ).catch(error => {
        console.log(error)
}) }

...

<button type ="button" onClick={() => DummyCall()} > Test me! </button>

For Usage.js

The following code below works and re-renders:

const context = useContext(TableDataContext) //needs to be there
<Table data={global.config.value.exactval} columns={COLS} />

The following code below does NOT re-render, however console says that the context.data.exactval value is updated.

const context = useContext(TableDataContext);
...
<Table data={context.data.exactval} columns={COLS} />

How can i fix non-render issue related to Context API?

Alex Ham
  • 151
  • 1
  • 12
  • Hello! The `useEffect` hook will not trigger a re-render, actually it will run if the variable in the dependency array updates. So probably you should worry about the `data` prop that you are sending to the component. Could you edit the post with the `Usage.js` file so we can check how you are updating `VALUES` ? – Johnny Baptista Apr 12 '22 at 15:22
  • @JohnnyBaptista Thanks for clarification, I have updated the question to reflect that – Alex Ham Apr 12 '22 at 15:41
  • 1
    Have you tried using the Context api and passing the useState along? You can add that to the dependency list and have it rerender when the state in the context changes. – Obsidianlab Apr 12 '22 at 19:55
  • Hi @Obsidianlab, I have not tried Context API. Would you be so kind to share a sample code on how can I use Context API to achieve the same? – Alex Ham Apr 12 '22 at 20:01
  • 1
    I would suggest the following article. Trust me, Context API is what your looking for. Make sure to wrap the provider somewhere where both components will be children. Doesnt even have to be direct children you can even place the context provider below the App Component and then all components will have access to it. https://blog.logrocket.com/react-context-api-deep-dive-examples/#:~:text=Storing%20and%20accessing%20a%20user%20profile – Obsidianlab Apr 12 '22 at 20:31
  • Hi @Obsidianlab, I have used Context API based on Johnny's step but recieving strange behaviour (see Edit 2), any suggestions on what's going on? – Alex Ham Apr 13 '22 at 08:19
  • Is context.data.exactval the state variable that you set with the setData state function? You need to be sure to use the useState variable in the component. Only Consumer children with those variables will update. Please make sure you pass a useState variable in the context provider aswell. Make a useState for exactval and setExactval and pass those in the provider. Reference: https://stackoverflow.com/questions/50817672/does-new-react-context-api-trigger-re-renders and https://stackoverflow.com/questions/54831399/react-context-api-consumer-does-not-re-render-after-context-changed – Obsidianlab Apr 13 '22 at 10:15
  • `context.data.exactval` is the `useContext` value. I have passed useState for Context Provider as Johnny mentioned. – Alex Ham Apr 13 '22 at 11:03

2 Answers2

1

So, from my understanding, you are updating a global variable created in a js file ./globalVariable.js. I haven't seen the file, but I believe these variables are outside the scope of what react can "keep an eye on".

So I suggest you to create a state for your data in the Usage.js, and then update it. Example:

Usage.js

import { Table } from './table.js';
...
const [data, setData] = useState(initialData); //define your inital data like your global variables

async function DummyCall() {
  try {
    const response = await axios.get(URL, headers);
    if(response.status === 200) {
       const updatedData = { ...data };
       updatedData.config.value.exactval.push(reponse.ID[1]);
       setData(updatedData)
    }

  } catch(error) {
    console.log(error);
  }
}
...
<button type ="button" onClick={() => DummyCall()} > Test me! </button>

Edit #1

Since you cannot merge both files or export the function, i suggest you tu use ContextAPI

TableDataContext.js

import { createContext } from 'react'

const TableDataContext = createContext();

const TableDataProvider = ({ children }) => {
  const [data, setData] = useState(initialData); //define your inital 

  <TableDataContext.Provider value={{ data, setData }}>
    {children}
  </TableDataContext.Provider>
}

export { TableDataContext, TableDataProvider };

Now you have to wrap your components taht need to consume the TableDataContext with the provider

If you want in your whole app, for example:

import { TableDataProvider } from 'path/to/context/TableDataContext'

<TableDataProvider>
  <App/>
</TableDataProvider>

This way, you can setData in DummyCall by importing from context:

Updater.js

import { useContext } from 'react';
import { TableDataContext } from 'path/to/context/TableDataContext'
....
const context = useContext(TableDataContext);

async function DummyCall() { 
   //...code
   // on setData
   const updatedData = { ...context.data };
   updatedData.config.value.exactval.push(reponse.ID[1]);
   context.setData(updatedData)
}

Finally just import the data from context in your table.js, and you will not need to pass by props

You can learn more about the ContextAPI on React Docs

Hope it works

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Johnny Baptista
  • 131
  • 1
  • 4
  • Hi @Johnny - the issue is that `Updater.js` and `Usage.js`cannot be merged as you are you suggesting - that is why I using the `globalVariable` to keep an eye on the value and to check for updates using it. Clarified the question – Alex Ham Apr 12 '22 at 19:34
  • 1
    oh, sure. so i suggest you to use the context api. i will edit the reply – Johnny Baptista Apr 12 '22 at 20:23
  • Thanks @Johnny - if possible, can you please share the sample code on how to achieve this in Context API – Alex Ham Apr 12 '22 at 20:27
  • Hi @Johnny, I am recieving strange behaviour (see Edit 2), any suggestions on what's going on with it? Thank you so much for the help. Will accept answer if strange behaviour stops with regards to Context API – Alex Ham Apr 13 '22 at 08:21
  • Reading more about [react-table useTable docs](https://react-table.tanstack.com/docs/api/useTable), `columns` and `data` options `"must be memoized"`. Could you try memoizing `data` as you did with `tableColumns` in `table.js`? – Johnny Baptista Apr 13 '22 at 20:17
  • 1
    Hi @Johnny, the issue was fixed by using an always true `filter` on the array that was return by `useContext` value – Alex Ham Apr 14 '22 at 03:53
0

I guess you cannot rerender the component untill you are just supplying static variables to the elements. For your component to re-render when they changes,you have to store that values to the state and pass that state in the dependency array of useEffect. Linke:

const [value,setValue] = useState({})
.....
//and in useEffect

useEffect(() => {}, [value]) 

try this one out,may be it will work.

Alan66
  • 11
  • 5