Maximum update depth exceeded when using shortid and react-final-form

Viewed 407

I built up a react-final-form that would render across multiple tabs. I have a Tabs component that tracks the active tab index and displays the appropriate TabPanel.

const App = () => (
  <Styles>
    <Form
      onSubmit={_ => _}
      mutators={{
        ...arrayMutators
      }}
    >
      {({ handleSubmit, submitting, values, form: { mutators } }) => {
        const tabs = [
          {
            tabName: "General",
            tabPanel: <div>I am the general pane</div>
          },
          {
            tabName: "Activity",
            tabPanel: <Activity mutators={mutators} />
          },
          {
            tabName: "Delay reasons",
            tabPanel: <div>I am the delay reasons panel</div>
          }
        ];
        return (
          <form onSubmit={handleSubmit}>
            <Tabs values={tabs} />
          </form>
        );
      }}
    </Form>
  </Styles>
);

When I create the TabPanels I specify a key using shortid.generate() so as to generate a unique value.

const tabPanels = props.values.map((value, index) => (
    // Note that if key is set with shortid, a setState infinite loop
    // error will be thrown
    <TabPanel
      index={index}
      key={shortid.generate()}
      activeTabIndex={activeTabIndex}
    >
      {value.tabPanel}
    </TabPanel>
  ));

The update depth exceeded error indicates the error happened in react-final-form's Field component:

The above error occurred in the <Field> component:
    in Field (at ActivityPanel.tsx:39)
    in div (at ActivityPanel.tsx:37)
    in div (created by styled.div)
    in styled.div (at ActivityPanel.tsx:28)

I'm really flummoxed by this... why would calling shortid.generate() result in this behaviour? I can swap it out with key={new Date().toString()} and the form renders fine.

CodeSandbox showing the issue: https://codesandbox.io/s/react-final-form-field-arrays-ksx15

1 Answers

IMHO it's a result of using not pure function for key generation (when item is an argument). It's unusal situation when on rerender all keyed items are removed/replaced with a new set (the same items with new keys = different items). React probably [not properly] classifies this [situation] as a result of using setState.

key should be a stable 'link to record' to distinguish difference between similar items.

If tab names are unique then simply use them as a key:

const tabPanels = props.values.map((value, index) => (
  <TabPanel
    index={index}
    key={value.tabName}

This way key is connected to specific record ... react will know how to optimally update a list (on insert or delete) without rerendering all items.

Related