setState cause Invariant Violation addComponentAsRefTo - javascript

I'm using react-rails with Fluxxor and React. My component is fully functional as long as I stay on the same page.
However, if I change the page by clicking on an other link and come back to my component, when I try to use setState on it, It throw the error :
Invariant Violation: addComponentAsRefTo(...): Only a ReactOwner can have refs. This usually means that you're trying to add a ref to a component that doesn't have an owner (that is, was not created inside of another component's `render` method). Try rendering this component inside of a new top-level component which will hold the ref.
My actual code can be found here. The problem seems to be the setState method here. Maybe the refs attribut of my Chosen component can't be re-render ? May it be caused by Turbolink ?

I think it's a problem in the implementation of the Chosen component.
When it's given new props, React re-renders it and puts new nodes in the page. However, $.fn.chosen has already been instantiated, and it's attached to DOM nodes that aren't in the page anymore. I suspect that, somewhere along the way, references to old nodes and components are being preserved, even after they're unmounted.
I had the same problem using select2 with React. I found Ryan Florence's guide to jQuery + React to be very helpful:
We need a way to stop rendering with React, do the jQuery dialog work, and then start rendering with React again. Some people call these "Portals". You open a portal for React to skip over a bit of old-school DOM stuff, and then keep going on the other side.
The big trick is rendering nothing and then calling React.renderComponent inside a component.
var Dialog = React.createClass({
render: function() {
// don't render anything, this is where we open the portal
return <div/>;
},
componentDidMount: function() {
var node = this.getDOMNode();
// do the old-school stuff
var dialog = $(node).dialog().data('ui-dialog');
// start a new React render tree with our node and the children
// passed in from above, this is the other side of the portal.
React.renderComponent(<div>{this.props.children}</div>, node):
}
});
source https://github.com/ryanflorence/react-training/blob/gh-pages/lessons/05-wrapping-dom-libs.md

Related

Prevent unnecessary re-renders of components when using useContext with React

I've seen a few questions related to this topic, but none that tackle the issue head-on in a pure way. useContext is a great tool for minimizing prop-drilling and centralizing key data needed across your app; however, it comes at a cost that I'm trying to minimize.
The closest issue to the one I'm describing here was asked two years ago here. The question didn't gain a lot of traction and the only answer basically says call the context in a parent container and pass values down through props (defeats the purpose of context) or use Redux. Maybe that's the only way, but I wanted to bring the question back to the collective to see if there is a better answer that's not dependent on external libraries.
I've set up a code sandbox here to illustrate the issue. Re-renders are console logged out to make seeing them easier.
In short, when a state changes in context, every app accessing data from that context re-renders, even if it's not utilizing the state data that changed (because it's all ultimately passed through the value object). React.Memo does not work on values accessed from context the same way it does for properties.
For example, in the code sandbox linked above, in App.js if you comment out <AppContainerWithContext /> and load <AppContainer />, there are two states managed in the container, one called titleText and the other called paragraphText. titleText is passed as a prop to component called TitleText and paragraphText is passed to a component called ParagraphText. Both components are wrapped in React.memo(). There are two buttons called in the AppContainer and each has a function that changes the text back and forth based on the value of separate boolean states.
Here is the function that toggles the titleText, the one for paragraph text is the same logic:
const changeTitleHandler = useCallback(() => {
const title = listTitleToggle ? "Title B" : "Title A";
setListTitleToggle((pV) => !pV)
setTitleText(title);
}, [listTitleToggle]);
Since the titleText component and paragraphText components are wrapped with React.useMemo, they only re-render when the corresponding value passed to them changes. Perfect.
Now in App.js if you comment out the <AppContainer /> component and enable the <AppContainerWithContext /> component, the rendered output and result of button clicks is identical; however, the states that change and are rendered on the screen are now managed by AppContext (called contextTitleText and contextParagraphText and passed to the TitleText component and ParagraphText component via useContext.
Now, if you click on the button to toggle the title, the ParagraphText component re-renders too, even though it doesn't use the contextTitleText state. My understanding of why this happens is because the value object changes when the contextTitleText is updated, causing any component accessing that value object through useContext to re-render.
My question is this:
Is there a way to utilize useContext without causing re-renders on all components accessing the context. In the example above, can we utilize useContext to manage the contextTitleText and the contextParagraphText but only re-render the components where the state from context being accessed changes?

how to emulate messages/events with react useState and useContext?

I'm creating a react app with useState and useContext for state management. So far this worked like a charm, but now I've come across a feature that needs something like an event:
Let's say there is a ContentPage which renders a lot of content pieces. The user can scroll through this and read the content.
And there's also a BookmarkPage. Clicking on a bookmark opens the ContentPage and scrolls to the corresponding piece of content.
This scrolling to content is a one-time action. Ideally, I would like to have an event listener in my ContentPage that consumes ScrollTo(item) events. But react pretty much prevents all use of events. DOM events can't be caught in the virtual dom and it's not possible to create custom synthetic events.
Also, the command "open up content piece XYZ" can come from many parts in the component tree (the example doesn't completely fit what I'm trying to implement). An event that just bubbles up the tree wouldn't solve the problem.
So I guess the react way is to somehow represent this event with the app state?
I have a workaround solution but it's hacky and has a problem (which is why I'm posting this question):
export interface MessageQueue{
messages: number[],
push:(num: number)=>void,
pop:()=>number
}
const defaultMessageQueue{
messages:[],
push: (num:number) => {throw new Error("don't use default");},
pop: () => {throw new Error("don't use default");}
}
export const MessageQueueContext = React.createContext<MessageQueue>(defaultMessageQueue);
In the component I'm providing this with:
const [messages, setmessages] = useState<number[]>([]);
//...
<MessageQueueContext.Provider value={{
messages: messages,
push:(num:number)=>{
setmessages([...messages, num]);
},
pop:()=>{
if(messages.length==0)return;
const message = messages[-1];
setmessages([...messages.slice(0, -1)]);
return message;
}
}}>
Now any component that needs to send or receive messages can use the Context.
Pushing a message works as expected. The Context changes and all components that use it re-render.
But popping a message also changes the context and also causes a re-render. This second re-render is wasted since there is no reason to do it.
Is there a clean way to implement actions/messages/events in a codebase that does state management with useState and useContext?
Since you're using routing in Ionic's router (React-Router), and you navigate between two pages, you can use the URL to pass params to the page:
Define the route to have an optional path param. Something like content-page/:section?
In the ContentPage, get the param (section) using React Router's useParams. Create a useEffect with section as the only changing dependency only. On first render (or if section changes) the scroll code would be called.
const { section } = useParams();
useEffect(() => {
// the code to jump to the section
}, [section]);
I am not sure why can't you use document.dispatchEvent(new CustomEvent()) with an associated eventListener.
Also if it's a matter of scrolling you can scrollIntoView using refs

React/Flux implementation technique is unclear for when a parent component needs to pull strings on child component

I have a situation which isn't too contrived, and I'm having trouble implementing it using the React best practices. In particular it produces this error:
Uncaught Error: Invariant Violation: setProps(...): You called setProps on a component with a parent. This is an anti-pattern since props will get reactively updated when rendered. Instead, change the owner's render method to pass the correct value as props to the component where it is created.
The situation is like this. The parent contains a child component. The parent has event handlers for UI and for the behavior to work, something inside the child component needs to render its HTML with a CSS change to the height style. Therein lies the wrinkle, usually the information flows upward or stays put, but here I need to change something in the child.
Parent component (Widget) renders this:
<div class="Widget">
<div class="WidgetGrabBar" onMouseDown={this.handleMouseDown}>
<WidgetDetails heightProp={this.props.detailsHeight} />
</div>
And elsewhere in Widget I've got
componentDidMount: function() {
document.addEventListener('mousemove', this.handleMouseMove);
document.addEventListener('mouseup', this.handleMouseUp);
},
componentDidUnmount: function() {
document.removeEventListener('mousemove', this.handleMouseMove);
document.removeEventListener('mouseup', this.handleMouseUp);
},
<...>
handleMouseDown: function(e) {
e.preventDefault();
this.props.actuallyDragging = true;
},
handleMouseUp: function(e) {
this.props.actuallyDragging = false;
},
handleMouseMove: function(e) {
e.preventDefault();
if (this.props.actuallyDragging) {
// update the prop! I need to send an urgent package of information to my child!! jQuery or findDOMElement() followed by DOM traversal is forbidden!!!
this.setProps({
detailsHeight: this.props.detailsHeight + e.deltaY
});
}
},
And I had WidgetDetails' render() render something like:
<div class="WidgetDetails" style={height: this.props.heightProp}>
{detail_items_move_along_nothing_to_see_here}
</div>
I figured that rolling out the jQuery to grab .WidgetDetails to fiddle with its style attr is the wrong thing, the non-React way to go about it. The real anti-pattern.
But now I'm being told that I can't change my props. Or I have to throw out everything including the bathwater in order to have new props. I'm not doing that; my props contain the contents of the detail items. Maybe it is expensive to make another entirely new copy of this.
I'm trying to let React participate in this rendering work to put the new height in. How am I supposed to even do this? Is this error basically enforcing that Props are supposed to be immutable now? The error is telling me that I have to involve this height even farther up on the component chain. I can conceivably do so with a callback from up above, but this feels very wrong. I need to pass information downward, not upward.
Maybe I'm supposed to use state. But changing state forces Widget, the parent component to render. That is not what I desire. Only one singular place in the DOM needs to re-render, that is the child component's div's style attr.
There are two approaches. Either
call handlers on the parent. Then Pass the new props to the child via props. If I recall correctly, that's the approach the react hello world tutorial takes.
Mutate state in the view via setState.
In your case, it seems that approach 2 really makes sense. You are basically trying to track view data.
Never, by the way, update state directly. Use setState. The whole point of reacts virtual dom is that it's optimized for spurious updates, so you will be fine. There is also the life cycle method componentShouldUpdate in case you want finer control.
For completeness I should add that there's a third way of using a global store. That's what react flux adds. But again, in your case that's probably over kill.

Updating a React component with new data

In a legacy web app, a React component was introduced, let's call it CustomComponent.
updateView(newData) {
React.render(<CustomComponent data={newData} />, document.querySelector('.custom-component-1'));
}
This works, however for some reason the actual DOM node .custom-component-1 seems to be emptied every time React.render is called on it, defeating the purpose of using React to achieve minimal DOM updates because each time it replaces the contents.
What am I doing wrong?
When you use React.render, React doesn't test the existing virtual DOM against the previous virtual DOM -- it simply re-renders the component.
What you're referring to -- render only when state changes -- occurs on a render method within a component.
Example:
// always renders
React.render(<CustomComponent />, document.querySelector('.custom-component'));
// renders only when state changes
var CustomComponent = React.createClass({
render() {
I re-render only if my state changes
}
});

Is my React component being recreated instead of updating?

I am trying to combine Angular and React.js. I have an work example project here I have seen a couple of ways to bring the Angular and React.js together. One of the methods I have seen is to create a directive and create the React component in the link function. For example in the first part of the project to generate the React version(in red) I am using
.directive('reactElementRepeater', function() {
return {
link: function(scope, element) {
var update_react = function(oldVal, newVal){ //Called every time one of the two values change
React.renderComponent(Demo_Element({
numberOfElements: scope.myModel.numberOfElem,
numberInElements: scope.myModel.numberInElem
}), element[0]);
}
scope.$watch('myModel.numberOfElem.length', update_react);
scope.$watch('myModel.numberInElem', update_react);
}
}
});
What I want and what should happen in a React enabled application is for something in the model to be updated, then that update is sent through React and it will alter the DOM as little as possible to reflect that change. It looks like that instead of updating a bit of the DOM this will Create a new React component each time with renderComponent.
React.renderComponent() instantiates the root component, starts the
framework, and injects the markup into a raw DOM element, provided as
the second argument.
Is it actually recreating the elements each time? If that is the case is there a way to alter this so that doesn't happen?
Just to be clear I know about ngReact, I just want to know other ways to speed up Angular with React.
Yes this is fine, it's not mounting the component multiple times.
When you call React.renderComponent() the second argument is the element which react should render the component to. So react notices if you are rendering the same component to a dom element that already contains a mounted instance of the component, and does not re-mount the entire component, it just updates the properties of it instead.
You can see this in action if you make a component with componentDidMount function defined. You'll notice that componentDidMount will only execute the first time renderComponent gets called. And afterwards, subsequent calls to renderComponent on the same target dom element will not call it because the component is already mounted. Likewise getDefaultState and getDefaultProps also only get called on the first renderComponent call.
If you're asking will the render function of the component be called every time the answer is yes. But this is how react works, you want the render function to get called because props might have changed. You can block it from being called by using shouldComponentUpdate (http://facebook.github.io/react/docs/component-specs.html#updating-shouldcomponentupdate) and returning false. However react developers recommend you don't use this to block render calls unless you have specific performance problems - most of the time it should be fine to just let the render call execute as it wont cause any slow dom updates unless things have actually changed.

Categories