React'ing with Context, Refs and Hooks
Author: Steve Venzerul
The new Hooks API
Hooks are a new addition in React 16.8. They let you use state and other React features without writing a class.
- React Documentation
Let's unpack that description!
import React, { useState } from 'react';
export default class Button extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0,
};
if (props.handleClick) {
this.handleClick = props.handleClick;
}
}
handleClick = () => {
// this.setState({count: ++count}); // normally a bug!
this.setState(state => {
return { count: ++state.count };
});
};
render() {
return (
<button className="generic-button" onClick={this.handleClick}>
Click count: {this.state.count}
</button>
);
}
}
Traditional Button
export function HookButton() {
let [count, setCount] = useState(0);
const handleClick = () => {
setCount(++count);
};
return (
<button className="generic-button" onClick={handleClick}>
Click count: {count}
</button>
);
}
Button Using Hooks
function getFormattedTime(date) {
return date
.toISOString()
.replace('T', ' ')
.replace('Z', '');
}
export function MultiHookButton() {
let [count, setCount] = useState(0);
let [when, setWhen] = useState(null);
const handleClick = () => {
setCount(++count);
setWhen(new Date());
};
return (
<>
<div>
Last updated: {(when && getFormattedTime(when)) || 'Not updated'}
</div>
<button className="generic-button" onClick={handleClick}>
Click count: {count}
</button>
</>
);
}
Multiple States
useState call order is important!
Hook Rules
The Effect Hook
import React, { useState, useEffect } from 'react';
import * as util from '../util';
let messageLoadingInt;
function loadMessages(cb) {
messageLoadingInt = setInterval(() => {
console.log('Refreshing messages...');
return cb([
{ id: util.monotonic(), content: 'some message', user: 'Dan' },
{ id: util.monotonic(), content: 'another message', user: 'me' },
]);
}, 3000);
}
export default function Messages({ initialMessages = [] }) {
let [messages, setMessages] = useState(initialMessages);
useEffect(() => {
loadMessages(data => {
setMessages(data);
});
return () => {
clearInterval(messageLoadingInt);
};
});
let children = [];
for (let msg of messages) {
children.push(
<li key={msg.id}>
{msg.user}: {msg.content}
</li>
);
}
return (
<>
<div className="message-list-header">Messages</div>
<ul className="message-list">{children}</ul>
</>
);
}
A Message List Component
Important things to remember
- The useEffect function will be run on each render/re-render and during component unmount. Expensive cleanup should be strictly avoided.
- setInterval and setTimeout inside useEffect are really tricky to get right, use Abramov's useInterval if you don't feel comfortable implementing this.
Refs and ForwardRef
In the typical React dataflow, props are the only way that parent components interact with their children. To modify a child, you re-render it with new props. However, there are a few cases where you need to imperatively modify a child outside of the typical dataflow. The child to be modified could be an instance of a React component, or it could be a DOM element. For both of these cases, React provides an escape hatch.
- React Documentation
Use Cases
- Managing focus, text selection, or media playback.
- Triggering imperative animations.
- Integrating with third-party DOM libraries.
- Working with React portals.
- Performing calculations on DOM elements.
class App extends React.Component {
constructor(props) {
super(props);
this.modalInputRef = React.createRef();
this.onClick = this.onClick.bind(this);
}
onClick() {
console.log(this.modalInputRef);
this.modalInputRef.current.value = 'value from parent.';
}
render() {
return (
<div>
<button onClick={this.onClick}>Set focus</button>
<ModalPortal ref={this.modalInputRef} />
</div>
);
}
}
class Portal extends React.Component {
constructor(props) {
super(props);
this.el = document.createElement('div');
}
componentDidMount() {
const root = document.querySelector('#app');
this.el.className = 'portal-x';
root.appendChild(this.el)
}
render() {
return ReactDOM.createPortal(<input type='text' class='modal-input' ref={this.props.inputRef} />, this.el)
}
}
const ModalPortal = React.forwardRef((props, ref) => {
return <Portal inputRef={ref} {...props} />
});
ReactDOM.render(<App />, document.querySelector("#app"))
Fiddle
Contexts
Use Cases
- Passing props through deeply nested component hierarchies.
- Propagating global changes across those hierarchies.
- Light weight substitute for using libraries like Redux, Mobx and friends.
const UserContext = React.createContext({
loggedIn: false,
login: () => {}
});
class App extends React.Component {
constructor(props) {
super(props);
this.login = this.login.bind(this);
this.state = {
loggedIn: false,
login: this.login
};
}
login() {
this.setState({
loggedIn: !this.state.loggedIn
});
}
render() {
return (
<UserContext.Provider value={this.state}>
<Toolbar />
</UserContext.Provider>
);
}
}
function Toolbar(props) {
return (
<div>
<LoginButton />
</div>
);
}
class LoginButton extends React.Component {
login() {
this.context.login();
}
render() {
return (
<div>
<span>User is logged {this.context.loggedIn ? 'In' : 'Out'}</span><br />
<button onClick={this.context.login}>{this.context.loggedIn ? 'Logout' : 'Login'}</button>
</div>
);
}
}
LoginButton.contextType = UserContext;
ReactDOM.render(<App />, document.querySelector("#app"));
Fiddle
React Gotch'ya
class Child extends React.Component {
render() {
let out = [];
const {name, bag} = this.props;
console.log('rendered', name);
return <div>
<h3>-- {name}</h3>
{Object.entries(bag).map(([k, v]) => <div>{k}: {v.toString()}</div>)}
</div>
}
}
class Parent extends React.Component {
constructor(props) {
super(props);
this.update = this.update.bind(this);
this.state = {
child1: {
cp1: 'v1',
cp2: 'v2',
},
child2: {
p1: 'v1',
p2: {}
}
}
}
update() {
this.setState({
child1: {
p1: 'new-val'
}
})
}
render() {
return (<div>
<Child bag={this.state.child1} name='child1' />
<br />
<Child bag={this.state.child2} name='child2' />
<br />
<button onClick={this.update}>Update state</button>
</div>);
}
}
ReactDOM.render(<Parent />, document.querySelector('#app'));
Fiddle
Questions?
Resources
React'ing with Context, Refs and Hooks
By signupskm
React'ing with Context, Refs and Hooks
- 1,051