14

I'm getting to learn React. Some guys of different sites tells everyone that using refs is a bad practice (yep, using them at all).

What's the real deal with it? Is it something bad that I will attach it to, for example, child component (so I can access inner stuff)?

Thanks!

Krzysztof Borowy
  • 538
  • 1
  • 5
  • 21
  • 3
    Yeah, it breaks encapsulation. Why would you access your child component through a ref? You should only be speaking between components through props. If it gets messy, add Redux to help manage state. – ZekeDroid Nov 12 '16 at 22:38
  • @ZekeDroid Mostly to do some sort of non-data-driven activity. "Refresh" is the most common one. The alternative would be to use some sort of synthetic state to pass along to children (like `hasToRefresh` or somesuch). Not ideal either, to say the least. – ZenMaster Nov 12 '16 at 23:43
  • @ZenMaster Having to refresh IS data driven. You would only refresh after some state change therefore you should not be accessing the DOM directly for it. As soon as you refresh something manually your state is no longer controlled and you must start reading the DOM to know things (ie, did it finish refreshing?) – ZekeDroid Nov 12 '16 at 23:49
  • @ZekeDroid Absolutely not. There just might be a "refresh" button on the parent. At which point - there is a need to refresh the children. – ZenMaster Nov 13 '16 at 00:20
  • 1
    I like your point of view, honestly. I hate taking things for face value and even though in this case I've experienced it first-hand, I'd love to hear more. Would you mind answering the question and show an example where you think it would be acceptable to use refs? – ZekeDroid Nov 13 '16 at 01:01
  • @ZenMaster - whats a use case for a button that forces a re-render that changes the UI without a change to props/state? – skav Nov 13 '16 at 03:07
  • @ZekeDroid See below... – ZenMaster Nov 13 '16 at 16:31

2 Answers2

17

React requires you to think the react way and refs are kind of a backdoor to the DOM that you almost never need to use. To simplify drastically, the react way of thinking is that once state changes, you re-render all the components of your UI that depend on that state. React will take care of making sure only the right bits of the DOM are updated, making the whole thing efficient and hiding the DOM from you (kinda).

For example, if your component hosts an HTMLInputElement, in React you'll wire up an event handler to track changes to the input element. Whenever the user types a character, the event handler will fire and in your handler you'll update your state with the new value of the input element. The change to the state triggers the hosting component to re-render itself, including the input element with the new value.

Here's what I mean

import React from 'react';
import ReactDOM from 'react-dom';

class Example extends React.Component {

    state = {
      inputValue: ""
    }

    handleChange = (e) => {
      this.setState({
        inputValue: e.target.value
      })
    }

    render() {
        const { inputValue } = this.state
        return ( 
          <div>
            /**.. lots of awesome ui **/
            /** an input element **/
            <input value={inputValue} onChange={this.handleChange}} />
            /** ... some more awesome ui **/
          </div>
       )
  }
}


ReactDOM.render( <Example />, document.getElementById("app") );
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>


<div id="app">
</div>

Notice how anytime the input value changes, the handler gets called, setState gets called and the componet will re-render itself in full.

Its generally bad practice to think about refs because you might get tempted to just use refs and and do things say the JQuery way, which is not the intention of the React architecture/mindset.

The best way to really understand it better is to build more React apps & components.

Alex Cory
  • 10,635
  • 10
  • 52
  • 62
skav
  • 1,400
  • 10
  • 16
  • Hey, thanks for answer. I understand this react way of thinking, I do go with building Containers + Components way. However, few days ago I came up to this problem - I had a Container component that has other component as a children. This children-component has 2 radio buttons and 2 buttons (+ and -). So, Container (parent) has to check which radio buttons is checked and which button (+ or -). So I thought that best way to do this is by putting ref on the child, so I can track on radio buttons. – Krzysztof Borowy Nov 14 '16 at 08:42
  • @skav: hey if I run your code its throwing error...can you fix it :( –  May 17 '17 at 14:16
  • I had a similar instance in using react with a dynamic form generator. Basically the workflow was that I make an API call and get back a list of fields depending on the user interaction.I believe I could make a specific form for each view, but were talking about 30-40 different form views something I just don't see as feasible as far as maintenance goes. I found that using 1 ref on the form was good enough as I could then parse the form and get all the values. I am unsure if this is still "correct" but I would argue that it's a valid use case. – Brett Reinhard Feb 22 '18 at 00:06
  • 1
    It's very important that in the above case the input element will not be replaced at the DOM level because React detects that it's in sync with the state. So, it keeps focus, cursor position, open autocompletion etc. – Dávid Horváth Apr 05 '18 at 21:31
3

Hmm... Not sure it qualifies as an answer, but it became too long-form for a comment.

Imagine you have a Dashboard that contains widgets showing various states of a system. Each widget has its own data source and its own controls. Perhaps they are even refreshed from time to time. However, when user wants to see an updated view of the system there is a "Refresh" button at Dashboard level. Implementing such a button is not trivial.

If you are in a Redux application - you'd have a choice - "faking" dispatch('refresh') for all the children. To decouple it, each widget, upon loading registers an action, so that parent simply goes through all actions and fires them, when imperative refresh is needed.

In a non Redux/Flux system, or in more complex/dynamic scenarios, this may not be possible or may not be as straightforward. It then may be better, complexity wise, to expose refresh method on all widgets and then access it from the parent (or, rather, owner):

class WidgetA extends React.Component {
    refresh() {
        console.log('WidgetA refreshed');
    }

    render() {
      return (
        <h3>WidgetA</h3>
      );
    }
}  

class WidgetB extends React.Component {
    refresh() {
        console.log('WidgetB refreshed');
    }

    render() {
      return (
        <h3>WidgetB</h3>
      );
    }
}  

class Dashboard extends React.Component {
    constructor() {
        super();

        this.onRefresh = this.handleRefresh.bind(this);
        this.onRegister = this.handleRegister.bind(this);
        this.widgets = [];
    }

    handleRegister(widget) {
        this.widgets.push(widget);
    }

    handleRefresh() {
        this.widgets.forEach((widget) => {
            widget.refresh();
        });
    }

    render() {
        return (
            <div>
                <button onClick={this.onRefresh}>Refresh</button>
                <hr />
                <WidgetA ref={this.onRegister} />
                <WidgetB ref={this.onRegister} />
            </div>
        );
    }
}

Something like that, with less verbosity, of course.

As side note, I upvoted @skav answer and think that these scenarios should be avoided. This is an exception.

CodePen Example

ZenMaster
  • 12,363
  • 5
  • 36
  • 59
  • Interesting example. I can see how, without redux, this would be the way to go. With redux it'd be like you said, as simple as making sure each widget's reducer case a case for something like `REFRESH_ALL`, triggered only by the high level Dashboard. But yeah, good last point about trying to avoid refs when posible – ZekeDroid Nov 14 '16 at 00:50
  • I am not sure if it necessary for all children to dispatch a "refresh" action. I think the parent can dispatch the "refresh" action and each child component reducer can respond to the "refresh" action and return the necessary state. – David Blay Jul 14 '17 at 17:41
  • @dblay These are separately developed and perhaps delivered (pacakge-wise) widgets.Having their parent do the refresh "for them" would be a bit problematic as it would have to intimately know what action is the one to send (and their may be a complex thunk-net of actions that constitutes refresh). – ZenMaster Jul 15 '17 at 16:55