What constitutes an appropriate use of ref in React - javascript

Can someone explain how ref is used in React? I understand it's a shortcut that sort of defeats the purpose of the react DOM, but I don't know how or why exactly. I'm trying to determine whether something I'm trying to accomplish represents one of the rare cases where you should use ref
I want a custom bootstrap alert I can show from any of these pages, without using JQuery (I have one working with JQuery already)
I have a router that switches between pages, each containing a Layout component with a Page component inside (based on this)
Like:
render() {
return (<Layout ref={layout => (this.layout = layout)}>
<WhateverPage
session={this.session}
otherExampleProp={"something"}
showAlert={this.showAlert}/>
</Layout>);
}
showAlert(type, text, hasTimeout, timeoutMs) {
this.layout.alert.showAlert(type, text, hasTimeout, timeoutMs);
}
I can think of three solutions:
Use ref, which as I only partially understand defeats the purpose of react components to some extent, but I'm not sure how exactly...
Use ref, but to a lesser extent, by placing the alert component in each Layout before the Page component (so no need for a ref to ).
Create a component and a function on each page, using the page's state to control the alert, so it would be basically the same as creating a unique alert for each page, which also defeats the purpose of a component...
The example most people give when explaining what to use ref for involves focus() - is this similar? Intuitively it feels like I should use ref, but I also know that theoretically you shouldn't, but I want to understand why, because there are exceptions and for all I know this may count.
Similarly, I want to create a confirm component to replace the native JS confirm() (since it might be deprecated soon), and this approach (using ref) also makes this WAY easier than creating a component for each page, since I can pass any function as a parameter to the confirm component for it to execute on an OK button press (also gives me the option of including icons, titles, custom buttons, etc).
Existing examples and libraries all seem to use method 3 (or they're simpler, and not not really analogous).
Is ref ok to use here? Is it wrong? Why? Am I overthinking this?

Yes, you are "misusing" ref here because you're trying to build around how React is intended to be used.
ref is mainly for accessing the actual rendered DOM element - maybe to focus it, read input, get dimensions, whatever. Generally speaking though you should us ref as a "read only" feature - use it to get info about the rendered DOM but don't use it as part of a process to bypass render() or inject elements into the DOM.
What you should do is create a reusable component for your Alert. Make it flexible enough that it can accept arbitrary settings like color, text, duration, callback functions for accept/cancel/clear, etc. Then you can just render it somewhere, maybe like this:
<MyAlert
title="foo"
text="bar"
duration={5}
confirmCallback={someFunction}
cancelCallback={anotherFunction}
/>
Remember that components are a way to render and interact with state, and that's exactly what you're trying to do with your Alert. There is some kind of notification, it has content and controls for doing some action(s), and all of that should live somewhere in your app state hierarchy. There is absolutely no reason to resort to refin this situation.

To complement the jered answer:
If you plan to use the alert component in all the pages then instead of placing an alert component inside each page you can create a high order component named like PageWithAlert (I would say just Page) that includes the alert component for each page.
You might want to take a look at this https://facebook.github.io/react/docs/higher-order-components.html

Related

Does React (hooks) require the use of states in order to do its virtual dom diff magic?

This always puzzled me. If I said "Thank you React, your state hooks are awesome, but I'm just gonna do my direct dom manipulation here", would react still do the virtual dom comparison in order to update only that specific item? would I still benefit from the virtual dom 'situation'?
Is there any difference between using react to directly manipulate dom without states, and using a standard HTML file with imported vanilla js code?
For clarity, here's an example,
Let's say I have function printHellol() triggered by a button 'click' in my JSX. The function targets the ID of an element and changes the text content.
I couldn't find the answer anywhere. Thanks!
Anything that his held in state becomes part of an object that react renders as a detached element from the dom. It's in essence creating a separate environment for all states. Anything that is used outside of state can be considered to be part of the direct dom object, causing the page to be rerendered when updates occur. In other words you would need to use the react specific state if you want to access the virtual dom specific environment.

How can I make my children components inherits a parent class method/state?

I am building a React application and I feel like I have a beginner question, but I can't seem to figure out an answer to it.
Basically, I am building a comparator for credit cards/savings accounts/etc. The pages are the same: I have an header, some filters, a sorting selection and finally "rows" to compare the result. I built this once but I need it 3 times. In this component, there are methods that I will reuse such as the call to get the financial institutions and I can reuse the call to get the differents options. However, I don't want to duplicate my code.
This brought me two options: I can either get these functions, put them in another file and import them or I can make other components inherit from one parent component.
The first option is great, but my methods change the state. If it is external to the component, it won't change the state. (and I don't want to do like var = x() for each call).
The second option would be perfect in other circumstances, but I heard that inheriting in React is bad. However, I feel like this is the only way I can reuse the methods to change the state.
Can you help me figure this out?
(I can give code since I am building this for a company and I can't reveal the code to everyone)

Call method from sibling component using ReactJS

I have one ReactJS App which I reduced to the minimum as possible on the diagram below:
Side note: On this App I use Redux to manage state changes.
This App contains:
Component: UploadScreen with an image holder and a button. When that button is clicked, the user gets displayed a Popup Window which let him to pick an image from his device file system. Then that image is displayed on the image holder.
Component: AuxWidget which is a totally different component (needs to be separate) which also contains a button that when it is clicked it should popup the Select File window. I was thinking in something like triggering the click event of the first button.
Any idea on how to achieve that?
First I though about using Redux but I think that's not a too good idea because even though you can send messages with it from one component to another, that causes a render update and I don't want that.
Also, I was thinking on using jQuery but that's not the best approach when it comes to ReactJS.
Also, I thought about using the attribute: ref="foo" to get a reference to the other component but I think that's normally done when you want the interaction to be between parent and child components.
Also, I was thinking about EventEmmitter but I don't know if that's the best approach on this case (I'm using Redux to manage the state changes between components).
One of the best ways I can suggest using RxJS, you can create a Subject and pass it to your components. In one component you will need to subscribe to it and whenever you will call next on your subject from the second component, the other will be notified, so you can trigger open popup. You can even create your own implementation for this in case you don't want to add new library to your project.
The upload window could be triggered when a certain state in the app changes. The relevant state on the app could be changed from different places, like from AuxWidget and UploadScreen. That way they are not coupled with the upload window. They merely call a function that is passed to them and that function changes the state on the app and it will display the window.
If you have a shared component between two unrelated component I think it is best to lift that common component and let its state sit on a higher level.
If I understand things correctly, your primary concern is code-reuse as opposed to wanting to call a sibling method. Basically, you want a SelectFilePopup component that can be re-used (open/closed) cleanly. I think React Portals could be a good solution for this. I found a good example (https://github.com/Assortment/react-modal-component/blob/master/src/components/Modal.js) of how a Modal can be isolated into a component and be called anywhere in the codebase.
The usage of the Modal looks like this (copied and slightly modified from App.js in the github project above)
import Modal from './components/Modal';
<Modal><div>Click me to open Modal</div></Modal>
And the Modal component implementation (simplified)
render() {
return (
<Fragment>
<ModalTrigger
onOpen={this.onOpen}
/>
{isOpen &&
<ModalContent/>
}
</Fragment>
)
}
By default the Modal component shows a trigger (i.e button) when isOpen state is false. Once clicked, and isOpen switches to true, the ModalContent (i.e can be the FilePickerPopup) is dynamically created and attached to document body. You can check out the source code for more details. I think its a very clean solution to modals. So in your case, your code could end up looking something like this
UploadScreen.js
import FileSelectPopup from './components/FileSelectPopup';
<FileSelectPopup>{Upload Image}</FileSelectPopup>
AuxWidget.js
import FileSelectPopup from './components/FileSelectPopup';
<FileSelectPopup>{Upload Image or some other text}</FileSelectPopup>
So basically, AuxWidget doesn't even need to know about where the FileSelectPopup is located at. It's an independent component that can be called anywhere. The caveat is that the Modal implementation in the project I linked to is not a singleton (although it can be modified to be one). So if AuxWidget and UploadScreen are visible to the user at the same time, clicking both Upload Image buttons will create two instances of the Popup.
I would define the function in the parent component and pass it to both children as props

React.js: Why is there no componentDidRender event?

I have just started using React, and a couple of times I have thought to myself: "Why is there no componentDidRender event?".
Say that I have a component that renders a table to the DOM, and I want to use bootstrap-sortable on this table to allow the user to sort on whatever column he wants. In the case of bootstrap-sortable you need to run $.boostrapSortable() after the table is drawn, in order to initialize the plugin.
As I see it, there are two handlers on a React component that would be logical to consider to use for this purpose:
componentDidMount: This does not work because the DOM does not seem to be updated at this point of the execution.
componentDidUpdate: This could possibly work, but it does not fire on the initial render.
I am not saying that React is actually missing a componentDidRender function, because I assume that there is a perfectly logical explanation as to why it is not there. I am just asking if someone could explain why such a function is not present, and what would be the "React way" to handle a case like the one above.
In componentDidMount you can do: this.getDOMNode() to get a reference to the underlying DOM for that component. So if you do want to use your mounted component with jQuery you can do:
componentDidMount: function() {
$(this.getDOMNode());
}
http://facebook.github.io/react/docs/working-with-the-browser.html
Here's a fiddle which shows jQuery acting on the DOM node of a react component:
http://jsfiddle.net/sa5e88ys/1/
As you can see, it adds a border to the div as expected. If you're still having problems I guess it could be with the plugin you're using rather than jQuery or react?
Although there's no componentDidRender, you can make a method with the desired behavior and call it in both componentDidMount (which is only called after the first render) and componentDidUpdate (which is called after every render but the first).
Also, this is the preferred way of getting a ref to a DOM node from within the component:
https://facebook.github.io/react/docs/refs-and-the-dom.html

Using JQuery plugins that transform the DOM in React Components?

Some JQuery plugins don't just add behavior to DOM nodes, but change them. For example, Bootstrap Switch turns
<input type="checkbox" name="my-checkbox" checked>
into something like
<div class="bootstrap-switch bootstrap-switch-wrapper bootstrap-switch-on bootstrap-switch-large bootstrap-switch-animate">
<div class="bootstrap-switch-container">
<span class="bootstrap-switch-handle-on bootstrap-switch-primary">ON</span>
<label class="bootstrap-switch-label"> </label>
<span class="bootstrap-switch-handle-off bootstrap-switch-default">OFF</span>
<input type="checkbox" name="download-version" checked="" data-size="large" data-on-text="3" data-off-text="2.0.1">
</div>
</div>
with
$("[name='my-checkbox']").bootstrapSwitch();
Which doesn't jive with React:
Uncaught Error: Invariant Violation: findComponentRoot(..., .0): Unable to find
element. This probably means the DOM was unexpectedly mutated (e.g., by the
browser), usually due to forgetting a <tbody> when using tables or nesting <p> or
<a> tags. ...<omitted>...`.
Is there a recommended technique for incorporating these plugins into React components? Or do they fundamentally break the assumptions of React and cannot work with it?
No, react will react (haha) badly to anything that modifies its own component dom structure outside of react. This is something you don't ever want to do. The recommended solution would be to replicate the functionality of whatever you're trying to do with a jquery or similar plugin, in react.
Having said that, there is a reasonable way to do this for specific instances where you just can't do without it, but it essentially means wrapping some non-react dom inside react.
Example:
var Example = React.createClass({
componentDidMount: function() {
var $checkboxContainer = $(this.refs.checkboxContainer.getDOMNode());
var $checkbox = $('<input />').prop('type', 'checkbox');
$checkboxContainer.append($checkbox);
$checkbox.bootstrapSwitch({});
},
render: function() {
return (
<div>
<div ref="checkboxContainer"></div>
</div>
)
}
});
Now of course you are rendering a component with a nested div. The nested when mounted to the dom for the first time that nested div will get a checkbox appended to it by jquery, which will then also execute our jquery plugin on it.
This particular example component has little point to it, however you can see how this might integrate into a more complex component while still allowing you to re-render and react to state changes etc. You just lose the ability to directly react to events/modify things inside of the checkbox in question which as far as react is concerned, doesn't exist.
Now with the above example if you were to have some react logic to add/remove the nested div, you'd have to have the same logic around that div being inserted be responsible for re-inserting the checkbox and re-initializing it with the jquery plugin. However because react only modifies the dom when needed, this inserted dom content wont be removed unless you do something that modifies the container div in a way that causes it to be removed/re-rendered to the dom. This means you can still access all of the events within react for that container div etc.
You could also make use of the componentDidMount function in react to bind events or callbacks to specific interactions on the checkbox itself. Just make sure to unbind them correctly in componentWillUnmount or wherever it makes sense to do so in the component lifecycle in your specific case.
In this great ryanflorence's tutorial you'll get an idea on how to do this:
Wrapping DOM Libs
Methodology
DOM libs usually manipulate the DOM
React tries to re-render and finds
a different DOM than it had last time and freaks out
We hide the DOM
manipulation from React by breaking the rendering tree and then
reconnecting around the DOM the lib manipulates.
Consumers of our
component can stay in React-land.
Sure, there is such a technique. We're doing these things all the time.
You create React component to wrap jQuery plugin.
Inside of your render(), you return an empty <div ref="placeholder" />
In your componentDidMount method, you retrieve this element by its ref, and initialize your jQuery plugin there.
In your componentWillUnmount, you clean it up. Calling 'destroy', or anything else required to avoid memory leaks.
That's it. Fortunately, it's completely safe to modify DOM in this way in React.
If you want this plugin to react on props changes, things get a bit more tricky. You need to override other lifecycle methods, like componentWillReceiveProps, check whenever props actually changed, and call corresponding plugin methods. I can explain in more details, if you will have specific questions, overall topic is too broad for the comment.
This is more of a philosophical question
React was created to optimize DOM manipulations and has a lot of wiring behind the scenes to do so when a component's state changes via setState
Doing so will cause said wiring to traverse its virtual DOM to find the nodes that need to be updated
If you must use React, whether to try to keep a level of consistency in your coding, your best bet is to apply the JQuery DOM manipulation inside the componentDidMount like so...
componentDidMount(){
this.node = $("#"+this.props.id); // Keep a reference to the node
this.chart = this.node.easyPieChart(); // Apply JQuery transformation and keep a reference
this.percentTitle = this.chart.find(".percent"); // Keep a reference to the title
}
Having done so, on whatever your "refresh" method is, do NOT make any calls to setState, instead, call whatever update method your JQuery component may have, like so...
componentWillMount(){
this.interval = setInterval(this._doRefresh.bind(this), 1500);
}
_doRefresh( percentage ){
// Note how setState is NOT being called here
percentage = percentage || Math.floor (Math.random() * 100) // simulate since we're not getting it yet
this.chart.data('easyPieChart').update(percentage); // call easyPieChart's update
this.percentTitle.text(percentage);
}
At this point, if you're asking why use React at all, well, in my case, this component is an item in a list of other React components and was used to maintain consistency throughout the application... You may have a similar dilemma
If, unlike me, you are unlucky enough that your component doesn't have an update method, and you can't make one, it might be time to rethink the approach altogether

Categories