Un/controlled Components

ForwardRef

UseRef

RenderProps

HOCs

Refs and the DOM

by Elizaveta Anatskaya

Controlled Components

import React, { useState } from 'react';
 
const App = () => {
  const [value, setValue] = useState('');
 
  const handleChange = event => setValue(event.target.value);
 
  return (
    <div>
      <label>
        My controlled Input:
        <input type="text" value={value} onChange={handleChange} />
      </label>
 
      <p>
        <strong>Output:</strong> {value}
      </p>
    </div>
  );
};
 
export default App;
value = '';

renders -> ''
value = 'a';

call handleChange = event => setValue('a');


renders -> 'a'
value = 'ab';

call handleChange = event => setValue('ab');


renders -> 'ab'

typing 'a'

typing 'b'

— the state is provided from React

💻

both input field and output paragraph are synchronized by React's state

Uncontrolled Components

import React, { useState } from 'react';
 
const App = () => {
  const [value, setValue] = useState('Hello React');
 
  const handleChange = event => setValue(event.target.value);
 
  return (
    <div>
      <label>
        My still uncontrolled Input:
        <input 
            defaultValue="initial value"
            type="text" 
            onChange={handleChange} 
         />
      </label>
 
      <p>
        <strong>Output:</strong> {value}
      </p>
    </div>
  );
};
 
export default App;

input field receives its value from internal DOM node state

output paragraph receives its value from React's state

— data (source of truth) is handled by the DOM itself

💻

😎 Introducing Refs

textInput = React.createRef();

<input ref={this.textInput} />

const value = this.textInput.current.value;
<input id='textInput' />
const value = document.getElementById('textInput').value;

How do we get DOM element by JS?

Getting DOM element by refs

When to use Refs?

Refs provide a way to access DOM nodes or React elements created in the render method.

💻

  • Managing focus, text selection, or media playback.
  • Triggering imperative animations.
  • Integrating with third-party DOM libraries.
  • Legacy API: String Refs
  • React.createRef()
  • Callback Refs
  • forwardRef()
  • useRef hook
 

Creating Refs

 

React.CreateRef()

class CustomTextInput extends React.Component {
  textInput = React.createRef();
  
  focusTextInput() {
    // Explicitly focus the text input using the raw DOM API
    // Note: we're accessing "current" to get the DOM node
    this.textInput.current.focus();
  }

  render() {
    // tell React that we want to associate the <input> ref
    // with the `textInput` that we created in the constructor
    return (
      <>
        <input
          type="text"
          ref={this.textInput} />
        <button onClick={this.focusTextInput} />
      </>
    );
  }
}

ref is created 

ref is passed to the element

reference to the node becomes accessible at the current attribute of the ref

💻

Callback Refs

ref is created 

ref is passed to the element

reference is accessed

class CustomTextInput extends React.Component {

textInput = null;

setTextInputRef = element => this.textInput = element;
    
focusTextInput = () => this.textInput && this.textInput.focus();
    
//  componentDidMount() {
//    this.focusTextInput();
//  }

  render() {
    return (
      <div>
        <input type="text" ref={this.setTextInputRef} />
        <button onClick={this.focusTextInput} />
      </div>
    );
  }
}

Instead of passing a ref attribute created by createRef(), you pass a function.

The function receives the React component instance or HTML DOM element as its argument, which can be stored and accessed elsewhere.

Invoked before componentDidMount and componentDidUpdate

💻

Adding a Ref to a Class Component

function CustomTextInput(props) {
  return (
    <div>
      <input ref={props.inputRef} />
    </div>
  );
}

class Parent extends React.Component {
  render() {
    return (
      <CustomTextInput
        inputRef={el => this.inputElement = el}
      />
    );
  }
}

💻

 The value of the ref differs depending on the type of the node:

Let's check

  • When the ref attribute is used on an HTML element, the ref created with React.createRef() receives the underlying DOM element as its current property.
     
  • When the ref attribute is used on a custom class component, the ref object receives the mounted instance of the component as its current.
     
  • You may not use the ref attribute with React.createRef() on function components because they don’t have instances

🤔 Wait! What's the difference?

function App() {
  const [count, setCount] = useState(0);
  var ref = React.useRef();

  console.log(ref.current);

  return (
    <div className="App">
      <button 
      	type="button" 
        onClick={() => setCount(count + 1)}
      >
        Click
      </button>
      <input 
      	type="text" 
        ref={ref} 
        defaultValue="Input Value" 
      />
    </div>
  );
  
    /*******************************************
      Prints 'undefined' only first time
      as the reference is just created and is
      yet to be assigned. Prints the
      '<input type="text" value="Input Value"/>' element
      during subsequent updates.
  ******************************************/
}

function App() {
  const [count, setCount] = useState(0);
  let ref = React.createRef();

  console.log(ref.current); // Always prints 'null'

  return (
    <div className="App">
      <button 
      	type="button" 
        onClick={() => setCount(count + 1)}
      >
        Click
      </button>
      <input 
      	type="text" 
        ref={ref} defaultValue="Input Value" 
       />
    </div>
  );
}

useRef()

createRef()

💻

🤨 Why doesn't createRef()  work for functions?

1

In JavaScript, when a function is executed, all the inner declared variables are created at the same time. These variables are destroyed after the execution of the last line of the function.

To deal with these type of issues, ‘useRef’ hook was developed by react developers. Reference created by useRef hook is remembered by react during update, just like state created by useState.

useRef()

— is a technique for automatically passing a ref through a component to one of its children 👫

 

— is remembered by react during an update, just like state created by useState

function TextInputWithFocusButton() {
  const inputEl = useRef(null);
  const onButtonClick = () => {
    // `current` points to the mounted text input element
    inputEl.current.focus();
  };
  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}

💻

👯‍♀️ Are there any more differences?

useRef can persist a value for a full lifetime of the component.

Note that the component will not rerender when the current value of useRef changes, if you want that effect, use useState hook instead

the difference is that createRef will return a new ref on every render while useRef will return the same ref each time

Forwarding Refs

— is a technique for automatically passing a ref through a component to one of its children 👫

— allows to define internally what element the ref will point at 👈

ref = React.createRef();

 <UncontrolledComponentWithForwardRef
          handleButtonClick={this.makeUppercase}
          ref={this.ref}
        />
        
const UncontrolledComponentWithForwardRef = React.forwardRef((props, ref) => {
  console.log('refs', ref);
  return (
    <>
      <p>Uncontrolled Component With ForwardRef;</p>
      <input ref={ref} />
      <button onClick={props.handleButtonClick}>Uppercase</button>
    </>
  );
});

💻

Rules:

 

  • Don’t overuse refs
  • Abolish string refs
  • Use callback refs when you have to dynamically set them
    • When in a class component, use createRef in all other cases
    • When in a function component, use useRef in all other cases
  • Use forwardRef when you need access to a child ref
    • Use Hooks to empower your function component
    • If the child ref must not be a function component, then use a custom method to trigger focus programmatically from the parent (remember you will get a component instance, not a DOM element)

Children in JSX

 props.children — content between opening tag a closing tags

<MyComponent>Hello world!</MyComponent>

String Literals

JSX Children

const MyContainer = (props) => 
	<div className="callout">{props.children}</div>;


<MyContainer>
  <MyFirstComponent />
  <MySecondComponent />
  <div>Some text</div>
</MyContainer>

Functions as Children

const MyContainer = (props) => 
	<div className="container">{props.children}</div>;


<MyContainer>
	{() => console.log('I am a function child')}
</MyContainer>

Booleans, Null, and Undefined Are Ignored

false, null, undefined, and true are valid children.

They simply don’t render. 

<div />

<div></div>

<div>{false}</div>

<div>{null}</div>

<div>{undefined}</div>

<div>{true}</div>
<div>
  {showHeader && <Header />}
  <Content />
</div>

Some “falsy” values, such as the 0 number, are still rendered by React:

This can be useful to conditionally render React elements:

<div>
  {props.messages.length &&
    <MessageList messages={props.messages} />
  }
</div>

To fix this, make sure that the expression before && is always boolean:

<div>
  {props.messages.length > 0 &&
    <MessageList messages={props.messages} />
  }
</div>

These JSX expressions will all render to the same thing:

Render Props

 — helps to share code between React components

const DataProvider = props => 
    props.children(props.firstName + " " + props.lastName)
 

const ConsumingComp = () => (
    <DataProvider firstName="First" lastName= "Last" render={fullName => (
        <h1>Hello {fullName}</h1>
    )}/>
)

/* renders:
<h1>Hello First Last</h1>
*/

— is a function prop that a component uses to know what to render

💻

👍 Render props advantages

👎 Render props disadvantages

  • Reuse code across components when using ES6 classes.
  • The lowest level of indirection - it’s clear which component is called and the state is isolated.
  • No naming collision issues for props, state and class methods.
  • No need to deal with boiler code and hoisting static methods.
  • There could also be minor memory issues when defining a closure for every render.
  • Another small annoyance is the render props callback is not so neat in JSX as it needs to be wrapped in an expression. Rendering the result of an HOC does look cleaner.

HOCs

— is an advanced technique in React for reusing component logic.

HOCs are not part of the React API

const withDataProvider = (Wrapped, {firstName, lastName }) =>
  class Random extends React.Component {
    fullName = firstname + " " + lastName

    render = () => {
      return <Wrapped fullName={this.fullName} {...this.props}/>;
    }
  };

const ConsumingComp = props => <h1>Hello {props.fullName}</h1>;

const Comp = withDataProvider(ConsumingComp, {firstName: "First", lastName: "Last"});

<Comp />

/* renders:
<h1>Hello First Last</h1>
*/

💻

— is a function that takes a component and returns a new component

Composing HOCs

Recompose is a React utility belt for function components and higher-order components.

compose — helps to compose multiple HOCs into a single HOC 

const ButtonWithTrack = withStateTimes(withHandlerClick(withDisplayTrack(Button)));

withDisplayTrack(Button) => component  => withHandlerClick => component => withStateTimes => component => (ButtonWithTrack).

import { compose } from "recompose";

...

const ButtonWithTrack = compose(
  withStateTimes,
  withHandlerClick,
  withDisplayTrack
)(Button)

👍 HOC's advantages

👎 HOC's disadvantages

  • It provides a way to reuse code when using ES6 classes. No longer have method name clashing if two HOC implement the same one.
  • It is easy to make small reusable units of code, thereby supporting the single responsibility principle.
  • Apply multiple HOCs to one component by composing them.
  • They don't protect us from collisions between prop names , so we don't know that HOC gives us that value
  • HOCs restrict the composition since it will be a static composition
  • HOCs are evaluated at compile time.

Usage

Whenever you have some wrapping logic you want to apply to a component, use HOC, because it's a better solution for separating the concerns in this case.

Whenever you want to customize sections parts of a component, use render props, because you don't want to separate these concerns from the actual render, you need them to co-locate.

👩🏻‍💻

Thanks for your attention!

🙌

Let's compare

feature uncontrolled controlled
one-time value retrieval (e.g. on submit)
allows validation control
enforcing input format
accepts its current value as a prop
uses a ref for their current values
data is controlled by the parent component
data is controlled by the DOM itself
maintains its internal states

Lecture 3

By Elizabeth Anatskaya