In a React component I am using an external library that creates complex components that I modify slightly on render. Thus, in my own component, I use things like element queries and mutation observers to get rendered elements in the DOM and dynamically add my own modifications, depending on the state of the DOM itself (not the external component, since I cannot access its state).
This works great, but I have not been able to figure out how to test this functionality [in Jest]. In particular, I add mutation listeners that add my own small React components based on the HTML that the external component renders, adding a button when the mouse hovers over a list of dynamically-created elements. When I attempt to test this in Jest, none of this logic is performed, and the functionality I have added cannot be tested, as the changes to the DOM do not appear, even with full rendering. In particular, during testing I have found that the queries I am using in the component do not have any values, as they need the external component to fully render the HTML for my own component to observe the changes.
I need a way to test my component in a way such that the external component is rendered, but such that my own component can perform updates afterwards, when its own state changes.
How can I test the functionality of the updates that I perform that are based on element queries if there are not any results to these queries during testing?
Related
Let's say I've triggered an update of a single React component & as we know by default it will trigger the update of all it's children components.
BUT
How does it work in combination with browser's reflow/repaint ?
How does the whole process look like (step by step) after we trigger a single React-component's update?
By default, the child components would be updated as well, however, that only happens in the virtualDOM, and not the actual DOM.
Changes are made to the virtualDOM, React then checks to see which DOM elements/components are changed/updated, and only those are updated in the actual DOM, it's part of the reason why your React app is fast.
You can refer to this thread for more information, though I think they are more so on class component, not functional component.
How can I insert a DOM node using vanilla javascript into a hierarchy of DOM nodes created by react such that React will not remove it when the state changes.
Reason:
I am developing a browser extension, and would like to tightly integrate with an existing site. The existing site uses React under the hood, and I would like to insert a node into the DOM.
Issue:
Inserting the node works, but as soon as some state in the react app changes, it removes the custom node from the DOM.
How can I insert and element which will not be removed?
I don't think you can (but you might be able to work around it).
React's job is, in large part, to reconcile the state of the DOM with the state of the React element tree the code using React gives it. After the code has "rendered" React elements, React "commits" that structure to the DOM, doing a diff between the structure the React element tree describes and the structure that's there: removing things that shouldn't be there, adding things that are missing, and updating things that should be there but need their state updated. When it does that, it will see the element you added, see that it's not supposed to be there, and remove it.
The first workaround that comes to mind for your use case is to add the element and use a MutationObserver to watch for it being removed, adding it back if (when) it is.
I have a react custom component called HomeContent that accepts some props.
To make that animated as per the react-spring documentation I made
const animatedHomeContent = animated(HomeContent);
Then for rendering I did like this:
<animatedHomeContent
id={1}
avatar="O"
image="..."
image_title="Onion"
title=" Medium Sized Onions"
subtitle="November 08, 2020"
desc="Onions from farms of Nasik"
price="74"
quantity="1kg"
/>
But I see the props are not getting passed.
Please tell me how to do it.
It depends on how you want to interact with your component, do you want to use the api or simply supply updated props?
// update by regenerating springProps from some other state
const springProps = useSpring(props)
// update springProps with api
const [springProps, api] = useSpring(() => props)
Updating by regenerating the spring
With the first example, using one of the animated native elements (animated.XXX) you will rerender the animated wrapper every time you want to update the state, because you will pass it a new, updated springProps object every time you update it, which will cause it to rerender. Or... this is only partially true, the individual SpringValue's in the returned object will be the same with both methods so if you pass them as individual props, rerendering would theoretically not be necessary but if you pass them as a whole object (when springProps is a style object for example) it will be a changed object from last render and cause a rerender of the animated wrapper.
When you use a custom element that does not take a ref inside the wrapper instead of one of the native elements, the component will rerender once for every animation frame. This is suboptimal from a performance perspective, but with this strategy, you can use whichever api (prop names) you want for your component, much like you have done.
Here is a sandbox with this solution that works: sandbox
You can observe that the animation works but also that the component rerenders a lot.
Updating via the api
If you want to make it more efficient, and use the api for updating your custom component, you need to adhere to a few rules. When you use the api, react-spring updates the element via a ref on the corresponding DOM element, and therefore, react-spring must understand how to update the element without requiring React to rerender it. This implies:
Your custom element must be able to hold a ref
Your custom element must attach the ref to a DOM element on which you want all animations to take place. Because forwardRef doesn't allow you to add multiple refs, changes cannot take place on multiple DOM elements inside your custom component if you want to wrap it in animated (there are other strategies to solve this, such as NOT wrapping your component in animated and instead use native animated.XXX elements inside your component and pass SpringValues as props).
The property names of your custom component must correspond to the properties you want to update on the DOM element to which the ref its attached. Otherwise react-spring will not understand how to update this element.
Since your element has a lot of custom properties, react-spring won't be able to update your component via the api. It will attempt to do so by simply setting the updated properties on the element to which the ref is attached, but since you need React to map the custom properties to the actual properties on the DOM element, this will fail (no DOM element that I know of have the set of properties that you provide).
As an example of how to accomplish this, here is a sandbox showing the outlines of this strategy: sandbox
Here you can see that the component renders only once and can still be updated. You can also see that the properties used on the custom component wrapped in animated corresponds to props on a div element, enabling react-spring to do exactly what we want (the children of the AnimatedHomeContent is a special prop in React named props.children which react-spring knows how to deal with).
<AnimatedHomeContent style={{ backgroundColor: spring.backgroundColor }}>
{spring.content}
</AnimatedHomeContent>
In the sandbox is also a bad example where other property names are used.
<AnimatedHomeContentBad
backgroundColor={spring.backgroundColor}
content={spring.content}
/>
To get a correct behaviour from this version, React has to process the component to map the input props to the native DOM element props. When updating this version via the api, react-spring does not rerender the component (since it can take a ref) and instead sets the updated properties on the element to which the ref is attached (inspect the element in the console to see for yourself that these props are set after updating). Since the properties are not real properties, nothing updates or animates on the component.
I have been using React from couple of months and React doesn't simply re-rendering a component completely instead it finds the difference and makes those changes. Does Angular 2 does something like this?
And also whenever a change in state is detected does Angular 2 re-render all the components from the root node or does it only re-render those specific components and their sub-tree whose change is detected?
React doesn't simply re-rendering a component completely instead it finds the difference and makes those changes. Does Angular 2 does something like this?
Conceptually yes, it does not re-render entire components.
Angular builds a change detector object for each component/directive. Template bindings (which includes input property bindings) are tracked inside these change detector objects. When change detection runs, by default, each binding is dirty checked for changes. If a change is found, the changed value is propagated to the child component (if an input property changed) or to the DOM. That's it. The entire template/view is not re-rendered. Only the changed values are updated in the DOM. When Angular change detection finishes, the browser notices the DOM changes and updates what we see on the screen.
whenever a change in state is detected does Angular 2 re-render all the components from the root node or does it only re-render those specific components and their sub-tree whose change is detected?
Angular doesn't detect changes to some model/data objects. Rather, it only detects changes to template bindings.
By default, each time change detection runs, it starts from the root component and checks all components for changes, in depth-first order, using those change detector objects. As described above, only template bindings with changes are updated. So, I wouldn't say that Angular ever re-renders a component... it only modifies those parts of the DOM where a template binding changed.
You can configure a component to use the OnPush change detection strategy to limit when that component and its descendants are checked for changes. You can also completely detach() a component from the change detector tree, which means that component and its descendants will not be change detected until you reattach().
Angular is not using virtual DOM as React do. No need for that in context of Angular.
If you have <input> and need to set its value in runtime to something else you don't need to change all DOM around it. You just call setValue() on that element and that's it.
The same applies to any other DOM element. For example if you have this:
<div>{{someVar}}</div>
and Angular detects that someVar was changed it will change content of only that particular <div>.
Angular only renders where it detects changes.
AFAIK there is some room for improvments in *ngFor where it sometimes re-renders too many items when some are added/removed in the middle or the beginning but that is a known issue and will be fixed eventually.
From my comment below
In fact Angular doesn't need re-rendering optimization because it only does anything when bound values change and then it only changes the DOM where it is bound to the changed value. Angular doesn't have a virtual DOM that it needs to mirror to the actual DOM.
Angular2 is using zone.js for onChange rendering. Usually when a change is detected, it will trigger changeDetection that component and all the children, but you also can have control to change that, to force render some things or not render when you don't like angular2 behavior.
Here is a very good talk about how Angular2 change detection works : https://www.youtube.com/watch?v=CUxD91DWkGM
LE: Just to clarify, it will not re-render the component and all the children, it will detect and trigger changes for all of those, but will render only what is necessary.
Is there a way to stop react from removing/changing nodes embedded in a react component.
For example, I have a react component that acts as a container for a non-react component that manages its DOM on its own. Is there a way to mark such components for reactjs, so that it does not modify its DOM?
In my case, I want my react component to be inline-editable by CKeditor, but react always removes/destroys the editor and all the nodes it has added to the DOM, because they were not defined in the react component itself and so it thinks that those elements should not be there.
Any ideas?
If you return false from a shouldComponentUpdate method on your component, then React will step out of the way and the entire reconciliation process will be skipped for that subtree. Of course, this means that you need to manage all DOM mutations yourself in that area and can't take advantage of React.
Take a look at dangerouslySetInnerHTML on https://facebook.github.io/react/tips/dangerously-set-inner-html.html.
This is the method for adding markup that doesn't sticks to React's update methods and also unsupported tags.
This way you can still update your component, while not updating parts of it.