From vanilla React hooks to ad-hok: At a glance
Sep 27, 2019
ad-hok is a library that lets you use React hooks in a functional pipeline style. If you’re unfamiliar with functional pipelines or the flow() helper from lodash/fp, take a look at this introduction first.
We’ll go through examples from the React Hooks at a Glance documentation and look at how they could be written using ad-hok helpers instead of “vanilla hooks”.
Example 1: State Hook
The first example from the React docs just uses the useState() hook:
import React, { useState } from 'react';
function Example() {
// Declare a new state variable, which we'll call "count"
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
With ad-hok, rather than putting our hooks code at the beginning of a function component’s body, we’re going to express the component as a flow():
import React from 'react'
import {flow} from 'lodash/fp'
import {addState} from 'ad-hok'
const Example = flow(
// Declare a new state variable, which we'll call "count"
addState('count', 'setCount', 0),
({count, setCount}) =>
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
)
)
Following the flow of props
The thing that’s “flowing down the pipeline” is a props object. At the beginning of the chain, it’s just the props that got passed to your component. So for example, if we render Example like this:
<Example message="woops" />
then {message: 'woops'} is the value fed into the flow() pipeline.
If we render Example with no props:
<Example />
then an empty object {} is fed into the pipeline.
ad-hok helpers like addState() add additional props to the props object. Here, addState() adds both the state value count and the state setter setCount to the props object. Then the last step in the pipeline uses those props when rendering.
Example #2: Effect Hook
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
// Similar to componentDidMount and componentDidUpdate:
useEffect(() => {
// Update the document title using the browser API
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
This example introduces the useEffect() hook. Notice that the effect depends on the count state variable.
Here’s the ad-hok version:
import React from 'react'
import {flow} from 'lodash/fp'
import {addState, addEffect} from 'ad-hok'
const Example = flow(
addState('count', 'setCount', 0),
addEffect(({count}) => () => {
// Update the document title using the browser API
document.title = `You clicked ${count} times`;
}),
({count, setCount}) => (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
)
)
Instead of referencing count via a closure, we now access count via the props object that gets supplied to the addEffect() callback.
So what?
It may not be obvious yet why this style might be preferable to the “vanilla hooks” version. Notice how each step of the pipeline can be explicit about which props it depends on. Once you get used to the functional style, this can provide a huge benefit in terms of keeping track of things when building components with complex logic and state.
Example #3: Effect with cleanup
import React, { useState, useEffect } from 'react';
function FriendStatus(props) {
const [isOnline, setIsOnline] = useState(null);
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
useEffect(() => {
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
And the ad-hok version:
import React from 'react'
import {flow} from 'lodash/fp'
import {addState, addHandlers, addEffect} from 'ad-hok'
const FriendStatus = flow(
addState('isOnline', 'setIsOnline', null),
addHandlers({
handleStatusChange: ({setIsOnline}) => status => {
setIsOnline(status.isOnline)
}
}),
addEffect(({friend, handleStatusChange}) => () => {
ChatAPI.subscribeToFriendStatus(friend.id, handleStatusChange)
return () => {
ChatAPI.unsubscribeFromFriendStatus(friend.id, handleStatusChange)
}
}),
({isOnline}) => {
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
)
What’s new here?
we use
addHandlers()to add ahandleStatusChange()helper to the props object. Notice howhandleStatusChange()is also able to access its dependencysetIsOnlineThe
FriendStatuscomponent is expecting to be passed afriendprop. Notice how the effect handler is able to accessfriendandhandleStatusChangeas part of the same props object even thoughfriendcame from outside of the component andhandleStatusChangecame from inside the pipelineNotice how the last step of the pipeline only asks for the
isOnlineprop even though it could also ask forfriend,handleStatusChange, andsetIsOnline. This helps us see that when it comes to actually rendering this component, it’s only a function of the currentisOnlinestatus, nothing else
Example #4: Multiple effects
function FriendStatusWithCounter(props) {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
});
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
// ...
Composing multiple effects and state is similar in ad-hok:
const FriendStatusWithCounter = flow(
addState('count', 'setCount', 0),
addEffect(({count}) => () => {
document.title = `You clicked ${count} times`
}),
addState('isOnline', 'setIsOnline', null),
addHandlers({
handleStatusChange: ({setIsOnline}) => status => {
setIsOnline(status.isOnline)
}
}),
addEffect(({friend, handleStatusChange}) => () => {
ChatAPI.subscribeToFriendStatus(friend.id, handleStatusChange)
return () => {
ChatAPI.unsubscribeFromFriendStatus(friend.id, handleStatusChange)
}
}),
// ...
Keep learning!
You can do a lot with addState(), addEffect() and addHandlers(). But just as there are more React hooks, ad-hok has lots of other useful helpers. Explore the ad-hok documentation, and file an issue if you have any questions or need any help :sparkles: