Author: Steve Venzerul
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>
);
}
}
export function HookButton() {
let [count, setCount] = useState(0);
const handleClick = () => {
setCount(++count);
};
return (
<button className="generic-button" onClick={handleClick}>
Click count: {count}
</button>
);
}
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>
</>
);
}
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>
</>
);
}
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
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"))
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"));
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'));