Search code examples
javascriptreactjsecmascript-6mui-datatable

Child not rerendered when prop set with useState()


I have a situation where I have parent (App) and child (MUIDatatable) component. The child is a datatable which accepts columns prop describing columns structure along with defining custom render function for one of them. In this custom render function I create a button instance for which disabled state should depend on parent's state, on buttonsDisabled value which is set via useState() hook. Up to this point it works fine, but when I also change the columns prop using useState(), the rerender does not trigger anymore when changing buttonsDisabled value. The code looks like this:

  const App = () => {
  const [buttonsDisabled, setButtonsDisabled] = useState(false);

  const tableData = {
    columns: [
      { name: "name", label: "Name", options: {} },
      {
        name: "action",
        label: "Action",
        options: {
          customBodyRender: v => (
            <button disabled={buttonsDisabled}>button</button>
          )
        }
      }
    ],
    data: [["A", null], ["B", null], ["C", null], ["D", null]]
  };

  const btnOnClick = () => {
    setButtonsDisabled(true);
  };

  /***** discussed part of code *****/
  const [columns] = useState(tableData.columns); // if we use this, buttons are not disabled
  //const columns = tableData.columns; // if we use this, buttons are properly disabled
  /**********************************/

  return (
    <div>
      <button onClick={btnOnClick}>DISABLE BUTTONS</button>
      <MUIDataTable title="title" data={tableData.data} columns={columns} />
    </div>
  );
};

It's also available at codesandbox: https://codesandbox.io/s/muidatatables-custom-toolbar-cy4vg

The code that I prepared is a stripped version of my original one so it might not make a lot of sense, but it limits the code to the bare minimum, just to be able to reproduce the problem.

What I can't understand is why does using useState to modify columns value would prevent specified customBodyRender function from being triggered when buttonsDisabled value is changed, whereas setting it via simple assignment works just fine. In both cases it's still the reference to the same array after all. I believe this is some simple concept that I'm missing, but I would appreciate some help in pointing out what's that.


Solution

  • When you create your initial columns state, your customBodyRender function has access to the outer function's buttonsDisabled variable (see: closure), which is defaulted to false. Once initialized, your columns variable is stored in your state, and the buttonsDisabled variable inside customBodyRender will continue to reference the original value from the outer function.

    Would it be possible for you to instead pass buttonsDisabled into your MUIDataTable table component, and perhaps pass it as an argument to the customBodyRender function?

    customBodyRender: (v, disabled) => (
      <button disabled={disabled}>button</button>
    )