1

I have a very big form - let's call it <MyForm> - consisting of the following:

  • text fields and dropdowns
  • custom selector made of selectable cards
  • table input (for this example 50 rows with each row having inputable text field)
  • cancel button and submit button which will submit all the data inputted above to the API

Following React recommended practice of lifting state up, I would have states for all of them in the big <MyForm> because to enable submission, I should put the states where the submission button is. That means I need to pass the states from <MyForm> to the children components as needed, and having a callback that would update the states in <MyForm>.

But by doing so, even if I split the states into multiple useState or useReducer, because on any state change the parent component would re-render (and hence all child components would be re-rendered), changing any input above would cause re-rendering of the entire <MyForm>. This is especially taxing for the table input, and there is a visible "lag" on UI update after typing the input.

Are there any ways to get around this parent re-rendering, or to improve the performance? The only option I am currently aware of with vanilla React is to do conditional update, but doing that for every components doesn't sound efficient. Also I am aware of the suggestion to "just use redux" but I am looking for how to solve this in the long run from vanilla React perspective because I foresee eventually there will be a big component re-rendering even with redux.

0

2 Answers 2

3

I don't think you can do better than path copying. If you ever written a immutable data structure you'll know that you will have to copy the nodes that you touch as you recursively traverse what in all likelihood is some tree like data structure.

You're React.js application is similar. You have a root state and then the rest is just a big function over that state. When you invalidate the root, everything else must invalidate.

I've experimented with this myself and I haven't arrived at a solution but I'm starting to believe that this is not the problem. It's just a consequence of what is idiomatic React.

First, I have had huge forms with 100+ input fields in them that when you render too much does cause noticeable input lag but I've worked around these issues by making sure the DOM is virtualized. i.e. if an input form is off-screen I don't render it. This DOM virtualization comes with its own caveats but I find it to be worthwhile when you have potentially a lot of components that may render. It's typically not JavaScript or React that is slow, it's the number of mutations that have to take place in the DOM that is slowing everything down.

Secondly, embrace immutability and reference equality. I mostly write function components and use hooks. The React reconciler will opt out of extra work if it can easily tell that there's no change here. If you pass around stable references and primitives that support value equality you can make React just not do a lot of needless render work. You just have to remember that every new object you create and every new function you create is a new reference that require React to do extra work.

// will re-render Button every time
function Component() {
  return <Button onClick={() => alert('ok')} /> 
}

// will not re-render Button every time
function Component() {
  // this is still an additional memory allocation but the
  // onClick function reference is stable across each render
  // so it does not cascade
  const onClick = React.useCallback(() => alert('ok'), [])

  return <Button onClick={onClick} /> 
}

You probably know this but you will have to memorize everything to avoid/minimize render. As long as your render doesn't cascade and cause everything to invalidate you will be fine but it's these cascading render updates that are killing you and you have to memorize stuff to get React to bail on unnecessary work.

That said, if you have +3000 DOM elements, that's just too much for the browser to handle. And you really need to look into virtualization. You can measure this by the way if you look at the performance graph, if you see a hefty doLayout... in DevTools you need to find a way to contain that. CSS has a contain property that can help if you understand what you're doing but if you get this wrong you will break rendering all together. There are ways to make the browser fast but it's something you'll have to experiment a bit. As soon as things get big, the performance problems come creeping and the solution isn't straightforward.

Good luck!

1
  • That makes a lot of sense! Thank you for the explanation!
    – Hans
    Commented Mar 9, 2022 at 3:36
1

I've faced this issue and controlled forms in React have this problem with the re-rendering, and if you have a sufficiently bigger form, you'll see it slows down. I don't think it's a good idea to change state every time a text field changes in a huge form, you can make use of refs to improve the performance.

Let's say you have an input, right now, when user types something, it updates the state and the UI re-renders and then the value in the state reflects in you input. If you use refs, you will only update the value attribute of the input and there won't be any re-render.

There is a package you can try to overcome this issue, react-hook-form(https://react-hook-form.com/). You'll see a significant performance improvement.

1
  • Thanks for the answer! I have used refs before and use a more rudimentary implementation of the package you mentioned. The difficulty starts when (1) I have a complex object / class that holds the values in the form, and (2) the forms have to be completely dynamic based on configuration (i.e. number of rows and columns varies). Definitely a valid solution without the 2 cases I mentioned above :)
    – Hans
    Commented Mar 9, 2022 at 2:38

Not the answer you're looking for? Browse other questions tagged or ask your own question.