Un/controlled Components
ForwardRef
UseRef
RenderProps
HOCs
Refs and the DOM
by Elizaveta Anatskaya
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
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
💻
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.
💻
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
💻
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
💻
function CustomTextInput(props) {
return (
<div>
<input ref={props.inputRef} />
</div>
);
}
class Parent extends React.Component {
render() {
return (
<CustomTextInput
inputRef={el => this.inputElement = el}
/>
);
}
}
💻
Let's check
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>
);
}
💻
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.
— 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>
</>
);
}
💻
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
— 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>
</>
);
});
💻
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>
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:
— 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
💻
— 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
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)
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.
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 | ❌ | ✅ |