I have an example app here. It's a spreadsheet with nine cells. When I type into a cell, you can see that every cell is re-rendered. This is indicated by the border color changing. I prefer if, when I type into a cell, that only that cell re-renders, and the others shouldn't.
Let's look how data flows through my application.
I have the cell component. It takes in data, an onChange handler, and a width. The data is passed directly to a text area as the value. When the text area changes, it triggers the onChange handler.
Next, I have the spreadsheet component. You tell it how many rows and columns you want, and you pass it all the cells data. You also pass it an onCellChange handler.
I'm mapping over the rows, and then I'm internally mapping over the columns. I'm creating a unique ID of the row and column indexes, and I'm using that ID to pass to the cell as its own ID, as well as how to retrieve it from the cells data.
Additionally, when the cell changes, I call onCellChange with the ID of which cell to change, and the new value I want to change it to.
Lastly, I have this app component. It's enhanced with a couple of Recompose higher-order components. I'm passing in three rows, three columns, all my cells data, and when a cell changes, I want to call setCellState.
SetCellState is declared here with the withHandlers higher-order components. It's building upon this withState higher-order component, and I'm storing my state into a prop called cells data.
Whenever I set a cell state, I'm going to call setCells. I'm going to de-structure all of the existing cells data, but then I'm going to set my one new cell to the new value. Now that we have that out of the way, let's go optimize cell.
I'll write a higher-order component called Optimize, and I'm just going to pass it pure. I'm going to go ahead and decorate my cell component with my new Optimize higher-order component. Now, let's refresh and see the behavior.
It still behaves the same. The pure higher-order component is going to check every single prop, pass the cell on every prop change. It'll compare the old props and the new props. If any of them do not pass a shallow equal test, then cell will be re-rendered.
Why, when I type into this top-left cell, do all of the cells still update? Their data is not changing, and their width is not changing. It must be the onChange handler. This is a common problem when trying to take advantage of the pure higher-order component.
You can see here that on every render, we're passing in a new, anonymous function. Let's fix that. I'm just going to pass a reference to onCellChange instead. This reference will never change, but we have to do the work that we were doing somewhere. Let's do the work inside of this Optimize higher-order component.
Now I need to use Compose because I'm going to be using more than one higher-order component. I'll use withHandlers to overwrite the onChange prop that's coming in, with a new onChange prop that I'll define here.
I'll pull off the ID and the original onChange prop. I'll grab the event, and I'll call the original onChange prop with the ID, and the event.target.value, just like I was doing before. Now, let's refresh. This is great. Now, when I type into this top-left field, only the top-left field is updating.
An alternative to pure is onlyUpdateForKeys. You configure this higher-order component by passing it an array of all the keys that you actually care about. In this case, we only care about data, and width, and onChange.
This is nice because it offers some protection. What if the user of your component passes extraneous props? onlyUpdateForKeys protects against that by declaring just the props that you actually care about. If I refresh this, again, you can see it's still working as before.
What if I want to declare some prop types here? I can use the setPropTypes higher-order component. Data is a number, and onChange is a function. This is fine, but we have duplication now.
I'm declaring data with onChange, and I'm declaring data with onChange in two different spots. That's where the onlyUpdateForPropTypes higher-order component comes in. Now, if we refresh, it actually doesn't work.
That's because the prop types need to be declared before this higher-order component can work. It needs to be above the setPropTypes usage. Now, it works, and it's still only updating the cell that I'm typing into.
The last optimization higher-order component is shouldUpdate. This really provides the most granular level of control over render optimizations. It's essentially an alias for the shouldComponentUpdate hook.
However, you'll get both the previous props and the next props. It's your job to return true or false on whether you'd like to update or not. In my case, I only want to update if the previous data is not equal to the next data, or if the previous width is not equal to the next width, or the previous onChange is not equal to the next onChange.
This should also work exactly the same, and it does.