One of the sweet but sometimes difficult to figure out part of React is reusing stateful logic across various components. Instead of rewriting a certain stateful logic whenever we need it, we would all love to write this logic just once and then reuse it in whatever components need it. A common pattern that makes this possible is "render props".
A component with a render prop takes a function that returns a React element and calls it instead of implementing its own render logic. This component can be termed the 'Container Component' while the React element or component we are returning can be termed a 'presentation Component'.
// example 1
<Container render={prop => (
<Presentation {...props} />
)} />
// example 2
<Container children={prop => (
<Presentation {...props} />
)} />
// example 3
<Container>
{props => (
<Presentation {...props} />
)}
</Container>
The three examples above implement the render props pattern, where 'Container' is our container Component that renders a presentation component...well, literally. We can put whatever stateful logic we need to reuse in the Container component, and the results along with an 'updating function' if needed can be passed down to any other component it renders. That's "render props" in a nutshell.
What is the alternative?
What if instead of having the container, we have a custom hook that implements this logic and returns the result with an 'updating function'. By 'updating function' I mean a function that updates the state in the container or the result from our hook. How we can implement this is the exact reason we are here. Let's make use of a "cat and mouse" example I found in the official React documentation for render props. We will take a look at the "render props" example and try to refactor it to use a custom hook.
Render Props Example
If we have a component that listens to the mouse movement and sets the pointer position in the state as shown below:
class Mouse extends React.Component {
constructor(props) {
super(props);
this.handleMouseMove = this.handleMouseMove.bind(this);
this.state = { x: 0, y: 0 };
}
handleMouseMove(event) {
this.setState({
x: event.clientX,
y: event.clientY
});
}
render() {
return (
<div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}>
{/*
Instead of providing a static representation of what <Mouse> renders,
use the `render` prop to dynamically determine what to render.
*/}
{this.props.render(this.state)}
</div>
);
}
}
Any component that needs to render elements based on the position of the mouse can be rendered by our mouse component. Let's define a Cat component that renders the image of a cat chasing the mouse pointer.
class Cat extends React.Component {
render() {
const mouse = this.props.mouse;
return (
<img src="/cat.jpg" style={{ position: 'absolute', left: mouse.x, top:
mouse.y }} />
);
}
}
We don't need to rewrite the logic for getting the pointer position but rather we can extend this logic from the Mouse component like this:
class MouseTracker extends React.Component {
render() {
return (
<div>
<h1>Move the mouse around!</h1>
<Mouse render={mouse => (
<Cat mouse={mouse} />
)}/>
</div>
);
}
}
This will render the Cat component passing down the position of the mouse as a prop. We can reuse the logic in as many components as we need to.
The hook alternative
We are going to get rid of our 'Mouse' component and instead, create a hook to implement our mouse logic.
export function useMouse(initialValue = {x:0, y:0}) {
const [position, setPosition] = useState(initialValue);
const handleMouseMove = (event) => {
setPosition({
x: event.clientX,
y: event.clientY
});
}
return [position, handleMouseMove];
}
We have just defined a hook called useMouse. It's a convention that the function name should start with 'use' so that people know it is a hook. Our useMouse hook returns the position of the mouse and a function to update that position. Let's see how we can use this in our Cat component.
function Cat() {
const [position, setMousePosition] = useMouse();
return (
<div style={{ height: '100%' }} onMouseMove={setMousePosition}>
<img src="/cat.jpg" style={{ position: 'absolute', left: position.x, top:
position.y }} />
);
</div>
}
What word comes to mind, simple?..neat?..concise? Maybe all three. Any component that needs to get the mouse position as it moves can use this hook.
Using this pattern improves the readability and maintainability of complex react code and it will also help to prevent having very large and deeply nested component trees. We can reuse auth status, user information and even form handling logic by creating custom hooks. They can also be used in place of HOCs(Higher Order Components) in React.
Top comments (11)
Nice post! Can you make another example using hooks to get user information (for example) across the other hooks?
If I understand correctly you mean a hook that fetches user's information so that it is available for use across various components?
Yes, correct, I think it can be useful to understand more hooks with your examples.
Alright, I will do something and send you a sandbox link, in the meantime, you should check my other article on useContext. That will also be helpful
I read your post about useContext and I learned a lot of, great explanation mate!
What was that sandbox link? I'd love to see a custom hook Sharing a consistent State across components. I feel like you can only do it with context, no?
If by consistent you mean a global state, then you can only do that with context or any store that holds global state. Just create a hook that consumes your store
Awesome post. Well explained with a simple example.
well done, well implemented
Nice one!
Thanks for sharing! it was really useful to grasp hooks