React Hooks
Introducing Hooks
Hooks are a new addition in React 16.8. They let you use some React features that before were not possible without writing a class. Some of these features are: state, life cycle methods (componentDidMount, componentWillReceiveProps, etc), context, pure component, and refs.
Also, let you connect to a fresh new API where you can write your own hooks functions that let you “hook into” React state and lifecycle features from function components.
No need for classes...
Why Using a Hook ?
-
Reduce code implementation: Pass from having a class based to only have a functional based component. This also means a reduction of bytes from bundle size.
-
Logic is now shareable: Writing custom hooks we can now share logic between components easily than before.
-
Easier life cycle methods: React life cycle methods are easier to reason especially if you are new to React, fewer methods to memorize.
-
Performance gains: The gains are minimal but still React perform better with hooks than with class components.
-
Classes confuse both people and machines: You have to understand how this works in JavaScript, which is very different from how it works in most languages. You have to remember to bind the event handlers etc.
Also, classes don’t minify very well, and they make hot reloading flaky and unreliable.
Adding Hooks to React
At React Conf 2018, Sophie Alpert and Dan Abramov introduced Hooks, followed by Ryan Florence demonstrating how to refactor an application to use them
However, there are some catches
-
⚠️ You should call hooks at the top level of the render function.
These means don’t call Hooks inside loops, conditions, or nested functions. Instead, always use Hooks at the top level of your React function. By following this rule, you ensure that Hooks are called in the same order each time a component renders. -
⚠️ You can only use hooks in React Function Components, and in Custom Hooks
It means that you Don’t call Hooks from regular JavaScript functions.
-
⚠️ There aren’t hook primitives for componentDidCatch or getSnapshotBeforeUpdate
The React team says they are on their way. For the componentDidCatch use case, you could create an Error Boundary component. -
✅ ESLint Plugin
There is an ESLint plugin called eslint-plugin-react-hooks that enforces these rules.
Class features vs. Hooks equivalents: State
import React, { useState } from 'react'
function Example() {
// Declare a new state, we can have more than one
const [count, setCount] = useState(0) // Initial state
const [user, setUser] = useState({ name: 'Joni' lastName: 'del Valle' })
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Change Count
</button>
<p>User: {user.name} {user.lastName}</p>
<button onClick={() => setUser({...user, name: 'Ariel' })}>
Change User
</button>
</div>
);
}
export default Example
State
//Class
import React, { Component } from 'react'
class CounterButton extends Component {
constructor() {
super()
this.state = {
count: 0
}
}
render() {
return <button onClick={() => this.setState({ count: this.state.count + 1 })}>
{ this.state.count }
</button>
}
}
export default CounterButton
//Hooks
import React, { useState } from 'react'
const CounterButton = props => {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>
{ count }
</button>
}
export default CounterButton
ComponentDidUpdate
//Class
import React, { Component } from 'react'
class ClassComponent extends Component {
componentDidUpdate() {
console.log('Just updated...')
}
render() {
return <div>Class Component</div>
}
}
export default ClassComponent
//Hooks
import React, { useEffect } from 'react'
const FunctionalComponent = props => {
useEffect(() => {
console.log('Just updated...')
})
return <div>Functional Component </div>
}
export default FunctionalComponent
ComponentDidMount
//Class
import React, { Component } from 'react'
class ClassComponent extends Component {
componentDidMount() {
console.log('I just mounted!')
}
render() {
return <div>Class Component</div>
}
}
export default ClassComponent
//Hooks
import React, { useEffect } from 'react'
const FunctionalComponent = props => {
useEffect(() => {
console.log('I just mounted!')
}, [])
return <div>Functional Component </div>
}
export default FunctionalComponent
ComponentWillUnmount
//Class
import React, { Component } from 'react'
class ClassComponent extends Component {
componentWillUnmount() {
console.log('I am unmounting!')
}
render() {
return <div>Class Component</div>
}
}
export default ClassComponent
//Hooks
import React, { useEffect } from 'react'
const FunctionalComponent = props => {
useEffect(() => {
return () => console.log('I am unmounting')
}, [])
return <div>Functional Component </div>
}
export default FunctionalComponent
ComponentWillReceiveProps
//Class
import React, { Component } from 'react'
class ClassComponent extends Component {
componentWillReceiveProps(nextProps) {
if (nextProps.count !== this.props.count) {
console.log('count changed', nextProps.count)
}
}
render() {
return <div>Class Component</div>
}
}
export default ClassComponent
//Hooks
import React, { useEffect } from 'react'
const FunctionalComponent = props => {
useEffect(() => {
console.log('count changed', props.count);
}, [props.count])
return <div>Functional Component </div>
}
export default FunctionalComponent
DOM Refs
import React, { Component } from 'react' //Class
class ClassComponent extends Component {
constructor() {
super()
this.inputRef = null
}
render() {
return <>
<input ref={inputRef => { this.inputRef = inputRef }} />
<button onClick={() => this.inputRef.focus()}>
Focus the input
</button>
</>
}
}
export default ClassComponent
import React, { useRef } from 'react' //Hooks
const FunctionalComponent = props => {
const inputRef = useRef()
return <>
<input ref={inputRef} />
<button onClick={() => inputRef.current.focus()}>
Focus the input
</button>
</>
}
export default FunctionalComponent
ShouldComponentUpdate
import React, { Component } from 'react' //Class
class ClassComponent extends Component {
shouldComponentUpdate(nextProps) {
return nextProps.count !== this.props.count
}
render() {
return <div>Class Component</div>
}
}
export default ClassComponent
import React, { memo } from 'react' //Hooks
const FunctionalComponent = props => {
return <div>Functional Component</div>
}
const MyFunctionalComponent = memo(
FunctionalComponent,
(prevProps, nextProps) => nextProps.count !== prevProps.count
)
export default MyFunctionalComponent
We gonna use memo for this one, while this is not a Hook, it’s still part of the class-to-functional-component migration plan
this.myVar
const Timer = (props) => {
const intervalRef = useRef();
useEffect(() => {
const id = setInterval(() => {
// ...
});
intervalRef.current = id;
return () => {
clearInterval(intervalRef.current);
};
});
}
useRef has another cool usage besides DOM refs, it is also a generic container whose current property is mutable and can hold any value, similar to an instance property on a class
Previous state\props
const Counter = props => {
const [count, setCount] = useState(0);
const prevCountRef = useRef();
useEffect(() => {
prevCountRef.current = count;
});
const prevCount = prevCountRef.current;
return <h1>Now: {count}, before: {prevCount}</h1>;
}
Some lifecycle method, like componentDidUpdate, provide the previous state and props. If you really need the previous values for your Hooks, this can be imitated the following way (using yet again our good friend – useRef)
More...
Thank you
Next Step...
React Hooks
By Jonatan del Valle
React Hooks
- 620