DEV Community

Nwakwoke Patrick Nnaemeka
Nwakwoke Patrick Nnaemeka

Posted on

Using custom hooks in place of "render props"

image extracted from https://www.udemy.com/react-hooks-tutorial

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)

Collapse
 
rokkoo profile image
Alfonso

Nice post! Can you make another example using hooks to get user information (for example) across the other hooks?

Collapse
 
emeka profile image
Nwakwoke Patrick Nnaemeka

If I understand correctly you mean a hook that fetches user's information so that it is available for use across various components?

Collapse
 
rokkoo profile image
Alfonso

Yes, correct, I think it can be useful to understand more hooks with your examples.

Thread Thread
 
emeka profile image
Nwakwoke Patrick Nnaemeka

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

Thread Thread
 
rokkoo profile image
Alfonso

I read your post about useContext and I learned a lot of, great explanation mate!

Thread Thread
 
seanmclem profile image
Seanmclem

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?

Thread Thread
 
emeka profile image
Nwakwoke Patrick Nnaemeka • Edited

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

Collapse
 
elie222 profile image
Elie

Awesome post. Well explained with a simple example.

Collapse
 
toddcoulson profile image
Todd Coulson

well done, well implemented

Collapse
 
evatkautz profile image
Eva Tkautz

Nice one!

Collapse
 
mikevb3 profile image
Miguel Villarreal

Thanks for sharing! it was really useful to grasp hooks