Problem: the zoom feature in my React app slows to a crawl when I have ~ >20 components rendered. My app needs to be able to support zoom with 1,000s of rendered components.
Current implementation of zoom:
Currently in my app I have a parent component that has a zoom feature which listens for the onWheel event and updates a zoomFactor variable using the useState hook.
Each child of this parent accesses the zoomFactor using useContext, so whenever the zoomFactor changes, the child component receives this change and updates its relevant dimensions (which in my case are an offsetX and width) by multiplying their original offsetX and width by the updated zoomFactor to yield a zoomAdjustedOffsetX and zoomAdjustedWidth.
Finally, these zoomAdjusted values are included in a styles object:
const styles = {
transform: 'translateX(' + zoomAdjustedOffsetX + 'px)',
width: zoomAdjustedWidth + 'px',
}
which is passed inline to the returned component.
I'm pretty sure the performance issue stems from the fact that I'm re-rendering all of these components during every step of the zoom. I don't need to re-render the components though, I just need to update two properties of their CSS during the onWheel event to accurately reflect the updated zoomFactor.
Therefore, my current idea on how to fix the performance issues:
Maintain a ref in my parent component to an object that maps the ID of every child component to a ref of that respective child component.
During the onWheel event, iterate through said map and "manually" update the CSS of each child component with the new zoomFactor.
I'm aware this isn't the proper "React way" of doing things, but I can't think of another way to accomplish my goal of achieving the zoom effect by updating the CSS for each component without slowing my app to a crawl re-rendering everything.
My question: would this approach yield performance benefits? Can you think of any other strategies to manipulate the CSS of 1000's of components during an onWheel event in order to achieve a 'zooming' effect?
Updating width is the killer here. The only really fast CSS properties to update are transform and opacity. Can you achieve what you want using transform: scale instead?
The next thing to look at would be debouncing the event. onWheel fires many times per frame, but you only need to render once per frame.
You might also get a small benefit from setting will-change and contain: layout in CSS.
If it's still slow after all that, you might think about avoiding a React update, like you mentioned. You could achieve this with React-spring instead of rolling your own system.
Related
Is there any way to create a react component having controllable and/or default-uncontrollable scrolling pos?
I created a react component having overflow=scroll/auto.
Everything is done using ref=elmRef trick and manipulating through useEffect.
Is it able not to use ref anyway? I want to make a purely react way.
I feel using ref is like a jquery way. The manipulation is done after actual dom component rendered, and might trigger the second render at startup if the manipulation causing to modify any states. There is a state that the value depend on the scroll position. Rendering dom twice is wasteful performance.
Here the visualization of the component i created:
What I learned about Virtual DOM and its diff algorithm is, that when a change occurs in certain component(or element / and its children as well), it is efficient to reconciliate that particular component and children because other than that, other DOM component will not be changed.
However what I already know is that the time-consuming part of DOM manipulation is the moment of recalculating element's style(like CSS).
If a component in between of many other components changes, such as height style changes or get unmounted, by means of such change, sibling components below will be affected, in which the style of those components should be all re-rendered(relayout or repaint).
Then, doesn't this mean that the object of React - manipulating only the changed part of view by virtual DOM and diff algorithm - would not be achieved?
Am I misunderstanding, or is this normal?
If this is normal(situation that due to the changed component in the middle of other components, other components below also have to be modified), then what is the advantage of React compared to plain DOM manipulating method, other than batch process or declarative method? Is this okay to call it "patch"?
Your concern is that changing the style of a parent component might trigger browser layout of its child components
However, this problem still occurs regardless of whether you are using react's virtual dom or not
The benefit in react's virtual dom, is that it aggregates dom operations and debounces (de-dupes) the redundant operations
This broad optimization usually results in fewer total dom operations than if you "hand-coded" it
In rare circumstances, you might be able to produce a more optimal result without react's virtual dom, by very carefully managing dom operations manually
Going 'manual' is rarely worth the consideration
React 'virtual DOM' is just a javascript object. It is nothing to do with styles and layouts at the time of the reconciliation. After each render React can diff this object with the result of the previous render and only updates the changed attribute of the corresponding DOM element.
Here is an explanatory image from React doc, you can see that the update is well localized.
We are trying to figure out cause of following issue from styled-components project: https://github.com/styled-components/styled-components/issues/389
There were some changes made to refs + setNativeProps that broke animation in one place, assumingly because some animated related information is not being passed down correctly.
Hence the question to understand how createAnimatedComponent alters initial component, whats added? What could cause animation to break if not passed down correctly?
Please provide ideas / detailed answer if you know what could be causing this issue.
UPDATE
Breaking change related to the issue occurred somewhere within this file for reference innerRef passes down ref, isTag function checks if it is a native component.
animatable components can be animated. View, Text, and Image are already provided, and you can create custom ones with createAnimatedComponent. These special components do the magic of binding the animated values to the properties, and do targeted native updates to avoid the cost of the react render and reconciliation process on every frame. They also handle cleanup on unmount so they are safe by default.
https://facebook.github.io/react-native/docs/animated.html
I have an UI built in Flux/React that mimics an OS windows system. User can move, minimize, and resize windows but also drag'n'drop icons between windows. I have main Desktop component which polls LayoutStore and builds children based on layout data.
It turns out however that rerendering the virtual DOM tree with more elements takes long enough to impair app responsiveness sometimes which is bad for transition animations. I already gave up on updating state at 60fps - I just update layout store after user drops an element. I was thinking about ways to limit render calls since I know that most changes to the layout affect child components very selectively - there is low chance that a given layout state change affects more than one particular component.
What I came up with is keeping a dirty or stateVersion variable in LayoutStore for each larger component (i.e. a window) and passing it in props so that each component could check if it was affected very fast without worrying about complexity of state representation (no deep comparisons, immutable copies, etc.) Also I prefer the second way cause I can just bump component's stateVersion each time I mess with its properties and don't need to unset dirty back in the store after emitting change.
Since I'm very new to React - is it a sane approach given my constraints or is there a better standard solution?
Suppose we have two sibling react components called OldContainer and NewContainer. There is a child component inside OldContainer that contains a <video> tag, and the video is currently playing.
The user can now drag the child component (with the video) and drop it in the NewContainer, and they expect the video to keep playing while it's being dragged and after being dropped.
So the video appears to stick to the mouse position, and when dragged and dropped in the new container, it animates to its new position (again, it doesn't get paused).
How would you implement this? Can we implement this in a pure way (in line with the spirit of pure functions)?
Clarification: I could have used some other element instead of a video tag for explaining this problem. A NumberEasing element would be a better example, since it would require the props and state of the component to be preserved during and after the interaction.
Update 1: Code examples obviously would be nice, but what I'm mainly looking for is just a general description of how you would approach this problem in a "functional" way. How do you keep your view code simple and easy to reason about? Who handles the drag-and-drop gesture? How do you model the data that's fed into the views?
Take a look at this library : react-reverse-portal
What is it that you want to preserve? Is it Javascript objects that the component holds as state, or is it state in the DOM (like how long a video has played, or text selection in an input box)?
If it's just Javascript objects as state, you're better of moving the source of that state to another service (something like Flux). That way, it doesn't matter if the component gets recreated because it can be recreated with the state that was there before.
EDIT
The way to keep your view code simple and easy to reason about is to not keep state inside your components. Instead, all data that the component needs should be passed into the component as props. That way, the component is "pure" in that it renders the same output given the same props. That also makes the problem of wanting to reuse a component instance a non-issue, since it doesn't matter when the same input gives the same output.
For drag and drop, I'd suggest looking at: https://github.com/gaearon/react-dnd.
How you model the data you pass to view components is up to you and the needs of your application. The components shouldn't care, they should just expect to get data passed as props, and to render them. But the popular approach to dealing with this is of course Flux, and there are many libraries that implements Flux in different ways.
SECOND EDIT
Regarding if you have a subtree with hundreds of components that you want to move: I'd still start off by making the state external (pure components), and render that tree in a new place. That means that React will probably recreate that entire subtree, which is fine. I wouldn't deviate from that path unless the performance of it turned out to be horrible (just guessing that it might be horrible isn't enough).
If the performance turned out to be horrible, I would wrap that entire subtree in a component that caches the actual DOM tree and reuses it (if it gets passed the same props). But you should only do this when absolutely needed, since it goes against what React tries to do for you.
THIRD EDIT
About gestures: I'd start out with listening to gesture events in componentDidMount, and in the event callback call setState on the component with the coordinates it should have. And then render the component in render with the coordinates given. React won't recreate the component when you call setState but it will re-render it (and diff the output). If the only thing you changed was the coordinates, it should render fast enough.
If that turns out to be too slow, like if the subtree of that component is huge and it becomes a bottleneck to recreate the subtree of vDOM, I'd reposition the DOM node directly in a RAF-loop outside of Reacts control. And I'd also put a huge comment on why that was needed, because it might seem wierd for some other developer later.
Create a new variable using const or var. Put the instance of data using rest spread operator, update the necessary data to pass and send the data to the component without mutating the state of component.
Just like:
const data = {
...this.state.child,
new_data : 'abc'
}