How to model 1'000'000'000 cells in React? - reactjs

So I want to display a large table, say 1 billion cells.
The component receives remote updates, each addressing one cell.
Each cell is represented via a div
Here's a naive implementation, which won't be practical, because on each update a large array is created and all cells are updated.
const Table = props => {
const [cells, setCells] = useState(new Array[1_000_000_000])
// ... 'useEffect()' receives remote data and 'setCells()' it.
return cells.map(cell => <div id={cell.id}>{cell.text}</div>)
}
How to implement this efficiently so that performance is top notch?
Ideally, I would like that on each update only 1 array element is updated and only 1 table cell in DOM is updated 🤷‍♂️ Ideally the solution should be O(1) and work great for tables of 10 to 1'000'000'000 cells. Is it possible in React?
By 1'000'000'000 cells I mean a large number of cells that browser can display at once if cells are created and updated with Vanilla JavaScript.
I'm looking not for a library but for a pattern. I want to find out what is the general approach in React for such cases. It is obvious how to do this in Vanilla JavaScript, you just create divs and then access the required div and update its innerText 🤷‍♂️. But how this case can be modeled in React?

For very long lists, "virtualized list" is the typical approach so that only some of the cells are actually mounted and the rest are temporarily rendered with placeholders. A couple libraries that implement this are:
https://react-window.now.sh/
https://bvaughn.github.io/react-virtualized

If you are looking for strictly O(1) then typical React patterns may not suitable since React cannot partially render a component. In other words, the render method of your Table component will need to iterate over the list in O(n). Virtualization of the list could reduce from O(size of list) to O(size of window).
You may be able to workaround this by maintaining a list of React refs of each mounted cell, then calling an update method on the single cell by index to trigger a re-render of a single cell, but this is likely not recommended usage of React.
Another option could be the Vanilla JS approach inside of a stateful React component. You'll want to implement the relevant lifecycle methods for mounting and un-mounting, but the update would be handled in Vanilla JS.
class Table {
constructor(props) {
super(props);
this.container = React.createRef();
}
componentDidMount() {
// Append 1_000_000_000 cells into this.container.current
// Later, modify this.container.current.item(index) text
}
componentWillUnmount() {
// Perform any cleanup
}
render() {
return (
<div ref={this.container} />
)
}
}

Related

Performance issues if MapComponent state is updated

I am not sure if this is an issue of react-leaflet-markercluster, react-leaflet, leaflet, react, or my code.
I have a map with several thousand markers and I am using react-leaflet-markercluster for marker clustering. If I need to update a global state of MapComponent, there is 1-3 seconds delay when this change is reflected.
I created a codesandox with 5000 markers and you can see there 2 use cases with performance issues:
1.) MapComponent is inside react-reflex element, that allows resizing panel and propagates new dimensions (width, height) to MapComponent. If width and height are changed, mapRef.invalidateSize() is called to update map dimensions. Resizing is extremely slow.
2.) If user clicks on Marker, global state selected is updated. It is a list of clicked marker ids. Map calls fitBounds method to focus on clicked marker and also marker icon is changed. There is around 1 second delay.
In my project, if I need to change a MapComponent state, it takes 2-3 seconds in dev mode when changes are reflected and it is just a single rerender of MapComponent and its elements (markers).
I took a look at Chrome performance profile and it seems like most time is spent in internal React methods.
It is possible to fix this by preventing rerendering using memo, which is similar to shouldComponentUpdate, but it makes whole code base too complicated. preferCanvas option doesn't change anything. I am wondering what is a good way to fix these issues.
The main problem I identified in your code is that you re-render the whole set of marker components. If you memoize the generation of those, you achieve a good performance boost; instead of running the .map in JSX, you can store all the components in a const; this way, the .map won't run on every render.
from this
...
<MarkerClusterGroup>
{markers.map((marker, i) => {
...
to something like this
const markerComponents = React.useMemo(() => {
return markers.map((marker) => {
return (
<MarkerContainer .../>
);
});
}, [markers, onMarkerClick]);
return (
<>
<MarkerClusterGroup>{markerComponents}</MarkerClusterGroup>
</>
);
The second refactor I tried is changing the way you select a marker. Instead of determining the selected prop from the selected array for each marker, I put a selected field on every marker object and update it when selecting a marker. Also, I add the position to the onClickHandler args to avoid looking for that in the markers array.
There are some other tweaks I don't explain here so please check my codesandbox version.
https://codesandbox.io/s/dreamy-andras-tfl67?file=/src/App.js

Is it possible to save components state when they are stored in an array manipulated?

I'm trying to create a stepper form
I store my steps in an array of json with a proprety component ({typeOfComponent, component, key})
It works wells, but:
Everytime i slice my array, like when i move up/down a step or add a new step between two steps.
I lose the states inside my component.
I tried to use memo, i don't understand why it's only when an item position my composent is recreate. Is it possible like a pointer in C to store only his "adress"
the code sandbox exemple =>
https://codesandbox.io/s/infallible-maxwell-zkwbm?file=/src/App.js
In my real projet, the button ADD is a button for chosing the new step type
Is there any solution for manipulates my steps without losing the user data inside ?
Thanks for your help
React is re-mounting the components inside of this every re-render probably due to a variety of reasons. I couldn't get it to work as is, but by lifting the state up from your components, it will work.
You'd likely need to lift the state up anyway because the data isn't where you need it to be to make any use of your form when the user is done with it.
In order to lift the state up, I added the current value to the steps array:
function addNext(step, index) {
componentKey++;
setSteps(prevState => {
let newState = [...prevState];
step = 1;
newState.splice(index + 1, 0, {
stepNumber: step,
component: getStepContent(step, componentKey),
value: getDefaultValue(step),
key: componentKey
});
return newState;
});
}
I also made sure your getStepContent just returned the component rather than a node so you can render it like this:
<step.component
value={step.value}
onChange={handleChange}
data-index={i}
/>
There are definitely a lot of ways to optimize this if you start running into performance issues, of course.
https://codesandbox.io/s/beautiful-river-2jltr?file=/src/App.js

How to optimize the re-rendering of large amounts of child components?

I'm a beginner when it comes to react.js and I'm building a component that contains a large number of changing items.
TLDR:
I have a Parent component that contains many Child components (think > 1000) that change their state very quickly. However the state of the child components needs to be known in the parent component - therefore I lifted the state of all children to the parent component. Since all child components are rendered every time the state in the parent changes, the performance is pretty bad. A single changing pixel can take more than 200ms to update. Implementing shouldComponentUpdate on the Child component is still too slow. Do you have general advice how to handle such a case?
As a specific example of my issue I created an "graphics editor" example with a PixelGrid component consisting of 32 by 32 Pixel components:
JS Fiddle of example
When the onMouseDown or onMouseEnter event is called on the Pixel component, the event is passed up to the parent PixelGrid component through prop callbacks, and the corresponding state (PixelGrid.state.pixels[i].color) is changed. Keep in mind that the PixelGrid component is supposed to be able to access all pixel values for further functionality, so keeping state in Pixel itself is really not an option, I think. But this means, that the whole PixelGrid component needs to be re-rendered when a single pixel changes. This is obviously very slow. I implemented shouldComponentUpdate on the Pixel component to speed things up a little, but this is still not very fast, since every Pixel is tested for changes.
My first reaction was to manually change the pixel's inline CSS in the DOM through React refs and not keep the pixel state in this.state.pixels, but in this.pixels, so a state change doesn't cause re-rendering, but it seems like a bad to maintain the visual representation "manually".
So, how would you implement such a functionality with React?
Use React.memo to prevent the child components from rendering when the parent renders but the child props don't change.
Example (random guess at what your Pixel component looks like):
const Pixel = React.memo(({x, y, color, ...rest}) =>
<div style={{
width: 1,
height: 1,
x,
y,
backgroundColor: color
}}
{...rest}
/>)
Now keep in mind if you are passing functions into Pixel they also need to be memoized. For instance, doing this is incorrect:
const Parent = () => {
// the callback gets redefined whenever Parent rerenders, causing the React.memo to still update
return <Pixel onClick={() => {}} />
}
instead you would need to do
const Parent = () => {
const cb = useCallback(() => {}, []);
return <Pixel onClick={cb} />
}
You're right to raise state to the parent so that it can control the data. Your Fiddle cannot be optimised because you are mutating state on line 82.
setPixelColor(row, col, color){
const {pixels} = this.state;
pixels[row * this.props.cols + col].color = color; // mutates state
this.setState({pixels: pixels});
}
When you run this.state.pixels[n].color = color, you're re-assigning a (nested) property on an item in the array in state. This is a form of mutation.
To avoid this, you can spread a copy of the state into a new variable and mutate that:
setPixelColor(row, col, color){
const newPixels = [...this.state.pixels]; // makes a fresh copy
newPixels[row * this.props.cols + col].color = color; // mutate at your leisure
this.setState({pixels: newPixels});
}
Implemented properly, it should not concern you whether "the whole PixelGrid component needs to be re-rendered when a single pixel changes." This is what needs to happen for your pixels to change colour. React's diffing algorithm is designed to update the minimum no. of elements as necessary. In your case, React will update only the style property on the relevant elements. (See React docs: Reconcilliation)
However, React cannot diff properly if it's not sure exactly what has changed, e.g if elements do not have unique keys, or if the element type changes (e.g. from <div> to <p>).
In your Fiddle, you map over the array, then calculate index for each one, and set that as they key.
Even though you are not re-ordering the elements in your Fiddle example, you use let instead of const, and mention that this is not the whole code. If your real code employs index, or the current row or column, as a key, but the order changes, then React will unmount and remount every child. That would certainly affect your performance.
(You don't need to calculate the index, by the way, as it is available as the second param in .map, see MDN).
I'd recommend adding a unique id property to each initialised pixel object, which is set in the parent, and does not change. As you're not using data with uuids, perhaps their inital position:
pixels.push({
col: col,
row: row,
color: 'white',
id: `row${row}-col${col}`; // only set once
});
I've made a quick fiddle with the above changes in one to demonstrate that this works: https://jsfiddle.net/bethylogism/18ezpoqc/4/
Again, the React docs on Reconcilliation are very good, I highly recommend for anyone trying to optimise for whom memoisation is not working: https://reactjs.org/docs/reconciliation.html

How to control a non-React component (BokehJS) in React?

Backstory
I want to include a BokehJS plot in my React component. The process for this is to render <div id="my_plot_id" className="bk-root"/> and call window.Bokeh.embed.embed_item(plotData, 'my_plot_id') which injects needed HTML into the DOM.
Because I want to control the BokehJS plot using the React component's state (i.e replace the plot with new generated plot data), I don't want to just call embed_item() in componentDidMount(). I've instead placed embed_item() in render() and added some code to remove child nodes of the container div prior to this call.
Problem
My React component renders 3 times on page load and although by the final render I have only one plot displayed, there is a brief moment (I think between the 2nd and 3rd/final render) where I see two plots.
Code
render()
{
let plotNode = document.getElementById('my_plot_id');
console.log(plotNode && plotNode.childElementCount);
while (plotNode && plotNode.firstChild) {
//remove any children
plotNode.removeChild(plotNode.firstChild);
}
const { plotData } = this.state;
window.Bokeh.embed.embed_item(plotData, 'my_plot_id');
return(
<div id="my_plot_id" className="bk-root"/>
)
}
In console I see:
null
0
2
Question
So it seems embed_item executes twice before the my_plot_id children are correctly detected.
Why is this happening and how can I resolve it? While the triple render may not be performance optimized I believe my component should be able to re-render as often as it needs to (within reason) without visual glitching like this, so I haven't focused my thought on ways to prevent re-rendering.
Interaction with DOM elements should never happen inside the render method. You should initiate the library on the element using the lifecycle method componentDidMount, update it based on props using componentDidUpdate and destroy it using componentWillUnmount.
The official React documentation has an example using jQuery which shows you the gist of how to handle other dom libraries.
At start plotNode is unable to reach 'my_plot_id'.
You can render null at start, when plotData is unavailable.
You can use componentDidUpdate().
In this case I would try shouldComponentUpdate() - update DOM node and return false to avoid rerendering.

Extremely slow React list render

We are experiencing some frustrating issues with React.
We have a form, consisting from a search form and a search result list.
As seen below in the code. filter and content.
Whenever the user types in the search field, there is a debounce and a call to a rest service.
The result then populates the search result (content)
Even with as little as 15 items in the list, this is insanely slow.
it takes about 300 ms per update.
In production mode, there is no issue. only in dev mode.
Also, removing propTypes makes it much faster but still slow.
We can see that the ContactSearchResultLayout is being rendered 3 times per keystroke, while it really just should care about the result of the rest call.
What are our best bets here?
is the container component kind of pattern here wrong for our usecase, does it mean that if something in the SearchPageLayout props changes, the entire list will also be re-rendered?
We have a version that pretty much bypass React and just render item by item as they come in from the service call.
Which is super fast, but on the other hand, much less maintainable.
Is there some idiomatic way to make this work with React?
<SearchPageLayout
filter={
<ContactSearchForm
allTeams={allTeams}
allAreasOfExp={allAreasOfExp}
allResponsiblePeople={allResponsiblePeople}
allTags={allTags}
detailed={props.searchFormExpanded}
onSearchFieldBlur={() => props.setSearchFormExpanded(false)}
onSearchFieldFocus={() => props.setSearchFormExpanded(true)}
/>
}
content={
<ContactSearchResultLayout //<-- this rerenders too often
items={items.map(item => (
<ContactCard
key={item.PersonId}
areasOfExpertise={item.AreasOfExperise}
cellPhone={item.CellPhone}
city={item.City}
One reason for this as I see it, is that items is the result of a map operation and thus, causes a new array of components to be generated.
But how do we bypass this?
Thoughts?
Anonymous function will get rendered each time.
I'll create another method for creating the items:
getItems() {
return (
items.map(item => (
<ContactCard
key={item.PersonId}
areasOfExpertise={item.AreasOfExperise}
cellPhone={item.CellPhone}
city={item.City}
/>
)
)
}
<ContactSearchResultLayout
items={this.getItems()}
/>
How to check if props change and if you should re-render the code:
you can use react "shouldComponentUpdate"
https://reactjs.org/docs/react-component.html#shouldcomponentupdate
componentWillUpdate(nextProps, nextState) {
//here you can compare your current state and props
// with the next state and props
// be sure to return boolean to decide to render or not
}

Resources