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?
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
Lecture 3
- 266