React: Practice of render-hooks pattern
Tags: react, typescript
Published: 2023-05-05 01:51:22

---
title: React: Practice of render-hooks pattern
published: true
description: Explore the render-hooks pattern in React to increase cohesion and simplify code by providing UI components through custom hooks. Learn through a practical example using image file input and output.
tags: react, typescript
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wpwiuk54232nl4wit0xp.png
# published_at: 2023-05-04 23:48 +0000
---

Do you know about the React render-hooks pattern? It is a design pattern introduced in the following article written in Japanese.

[【LINE証券 FrontEnd】コンポーネントをカスタムフックで提供してみた](https://engineering.linecorp.com/ja/blog/line-securities-frontend-3)

The key idea is **"providing components through custom hooks."** Custom hooks are used to separate the logic (ts) from UI components (tsx). By having custom hooks directly provide UI components, the logic of the UI components can be made more cohesive, allowing for the writing of simpler code. I really like it!

In this article, I will introduce the Render hooks pattern using a simple example.

> This is a translation of a previous article I wrote.
[render hooks パターンの素振り](https://zenn.dev/mssknd/articles/1046a44b9d9502)

## Example

I created a component that displays a preview of an image when an image file is specified.

{% embed https://codesandbox.io/embed/awesome-meninsky-3q92v6?fontsize=14&hidenavigation=1&theme=dark %}

We will rewrite this implementation from the usual approach to using the Render hooks pattern.

## Usual implementation

Here's an example of an implementation without using custom hooks.

The input to `<input type="file" />` is handled by `handleInput()`, which converts the image to a data URL.

```tsx
// src/app.tsx
import { useState } from "react";

export default function App() {
  const [value, setValue] = useState<string | null>(null);

  const handleInput = (changeEvent: React.ChangeEvent<HTMLInputElement>) => {
    const file = changeEvent.target.files ? changeEvent.target.files[0] : null;
    if (!file) {
      return;
    }
    const reader = new FileReader();
    reader.onload = (event: ProgressEvent<FileReader>) => {
      if (typeof event?.target?.result === "string") {
        setValue(event.target.result);
      }
    };
    reader.readAsDataURL(file);
  };

  return (
    <div className="App">
      <input type="file" accept="image/*" onChange={handleInput} />
      {value && <img src={value} alt="preview" />}
    </div>
  );
}
```

## Implementation of render hooks pattern applied to `<input>`

First, let's provide `<input type="file" />` from a custom hook. Please be aware that the file extension should be `.tsx`.

```tsx
// src/use-image-file-input.tsx
import { useState } from "react";

export function useInputImageFile() {
  const [imageDataUrl, setImageDataUrl] = useState<string | null>(null);

  const handleInput = (changeEvent: React.ChangeEvent<HTMLInputElement>) => {
    const file = changeEvent.target.files ? changeEvent.target.files[0] : null;
    if (!file) {
      return;
    }
    const reader = new FileReader();
    reader.onload = (event: ProgressEvent<FileReader>) => {
      if (typeof event?.target?.result === "string") {
        setImageDataUrl(event.target.result);
      }
    };
    reader.readAsDataURL(file);
  };

  const imageFileInput = () => 
  <input type="file" accept="image/*" onChange={handleInput} />

  return {
    imageDataUrl,
    imageFileInput,
  }
}
```

Most of the logic for obtaining image files that was written in `app.tsx` has been moved to the custom hook. At this time, `app.tsx` becomes as follows:

```tsx
// src/app.tsx
import { useImageFileInput } from "./use-image-file-input";

export default function App() {
  const { imageDataUrl, ImageFileInput } = useImageFileInput();

  return (
    <div className="App">
      <ImageFileInput />
      {imageDataUrl && <img src={imageDataUrl} alt="preview" />}
    </div>
  );
}
```

The logic has become very simple due to the aggregation in the custom hook. I also like that `<ImageFileInput />` behaves like a regular useState's set function, making it easy to use.

## Implementation of render hooks pattern applied to image preview

Let's also make the preview, which is displayed after obtaining the image, the responsibility of the custom hook. Rename `useImageFileInput` to `useImageFile` and provide the preview component as well.

```diff
  // tsx:src/use-image-file.tsx
  import { useState } from "react";

- export function useImageFileInput() {
+ export function useImageFile() {
    const [imageDataUrl, setImageDataUrl] = useState<string | null>(null);

    const handleInput = (changeEvent: React.ChangeEvent<HTMLInputElement>) => {
        const file = changeEvent.target.files ? changeEvent.target.files[0] : null;
        if (!file) {
        return;
        }
        const reader = new FileReader();
        reader.onload = (event: ProgressEvent<FileReader>) => {
        if (typeof event?.target?.result === "string") {
            setImageDataUrl(event.target.result);
        }
        };
        reader.readAsDataURL(file);
    };

    const ImageFileInput = () => (
        <input type="file" accept="image/*" onChange={handleInput} />
    );

+   const ImagePreview = () =>
+       imageDataUrl ? <img src={imageDataUrl} alt="preview" /> : <></>;

    return {
-       imageDataUrl,
        ImageFileInput,
+       ImagePreview
    };
  }
```

The branching in `app.tsx` has been eliminated, making it even simpler.

```diff
  // tsx:src/app.tsx
- import { useImageFileInput } from "./use-image-file-input";
+ import { useImageFile } from "./use-image-file";

  export default function App() {
-   const { imageDataUrl, ImageFileInput } = useImageFileInput();
+   const { ImageFileInput, ImagePreview } = useImageFile();

    return (
      <div className="App">
        <ImageFileInput />
-       {imageDataUrl && <img src={imageDataUrl} alt="preview" />}
+       <ImagePreview />
      </div>
    );
  }
```

By providing image input and preview output as a custom hook, reusability is increased. For example, if you want to display "No image has been input" in the preview when no image is selected, you can change the definition of the preview within the custom hook and reflect it in all components using it. It seems possible to create easy-to-use custom hooks, such as providing a cancel button for the selected image.

## Conclusion

We have created an example of the render-hooks pattern using image file input and output as the subject matter. Personally, I understand that the core of this pattern is to increase the cohesion of UI components and their related states. I feel that it is a technique that expands the range of expression, and I plan to continue using it at key points in the future.

The contents of the code can be found in the following repository.

{% embed https://github.com/MssKnd/render-hooks-demo %}