Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

confusion about this React custom hook usage

Tags:

I was looking at some tutorial on React Hooks and in the tutorial the author created a useDropdown hook for rendering reusable dropdowns. The code is like this

import React, { useState } from "react";

const useDropdown = (label, defaultState, options) => {
  const [state, updateState] = useState(defaultState);
  const id = `use-dropdown-${label.replace(" ", "").toLowerCase()}`;
  const Dropdown = () => (
    <label htmlFor={id}>
      {label}
      <select
        id={id}
        value={state}
        onChange={e => updateState(e.target.value)}
        onBlur={e => updateState(e.target.value)}
        disabled={!options.length}
      >
        <option />
        {options.map(item => (
          <option key={item} value={item}>
            {item}
          </option>
        ))}
      </select>
    </label>
  );
  return [state, Dropdown, updateState];
};

export default useDropdown;

and he used this in a component like this

import React, { useState, useEffect } from "react";
import useDropdown from "./useDropdown";

const SomeComponent = () => {
  const [animal, AnimalDropdown] = useDropdown("Animal", "dog", ANIMALS);
  const [breed, BreedDropdown, updateBreed] = useDropdown("Breed", "", breeds);

  return (
    <div className="search-params">
      <form>
        <label htmlFor="location">
          Location
          <input
            id="location"
            value={location}
            placeholder="Location"
            onChange={e => updateLocation(e.target.value)}
          />
        </label>
        <AnimalDropdown />
        <BreedDropdown />
        <button>Submit</button>
      </form>
    </div>
  );
};

export default SomeComponent;

He said this way we can create reusable dropdown components. I was wondering how is this different from defining a plain old Dropdown component and pass props into it. The only difference I can think of in this case is that now we have the ability to get the state and setState in the parent component(i.e. SomeComponent) and read / set the state of the child(i.e. the component output by useDropdown) directly from there. However is this considered an anti-pattern since we are breaking the one way data flow?

like image 745
Joji Avatar asked Jun 04 '20 03:06

Joji


People also ask

What is incorrect about React Hooks?

There are three common reasons you might be seeing it: You might have mismatching versions of React and React DOM. You might be breaking the Rules of Hooks. You might have more than one copy of React in the same app.

What is the use of custom hook in React?

Custom Hooks are a mechanism to reuse stateful logic (such as setting up a subscription and remembering the current value), but every time you use a custom Hook, all state and effects inside of it are fully isolated.

Should I use custom Hooks React?

Why and When To Use Custom Hooks. The main reason to write a custom hook is for code reusability. For example, instead of writing the same code across multiple components that use the same common stateful logic (say a “setState” or localStorage logic), you can put that code inside a custom hook and reuse it.


1 Answers

While there is no hard core restriction on how you should define custom hooks and what logic should the contain, its an anti-pattern to write hooks that return JSX

You should evaluate what benefits each approach gives you and then decide on a particular piece of code

There are a few downsides to using hooks to return JSX

  • When you write a hook that returns JSX component, you are essentially defining the component within the functional component, so on each and every re-render you will be creating a new instance of the component. This will lead to the component being unmounted and mounted again. Which is bad for performance and also buggy if you have stateful login within the component as the state will get reset with every re-render of parent
  • By defining a JSX component within the hook, you are taking away the option of lazy loading your component if the need be.
  • Any performance optimization to the component will require you to make use of useMemo which doesn't give you the flexibility of a custom comparator function like React.memo

The benefit on the other hand is that you have control over the state of the component in the parent. However you can still implement the same logic by using a controlled component approach

import React, { useState } from "react";

const Dropdown = Reat.memo((props) => {
  const { label, value, updateState, options } = props;
  const id = `use-dropdown-${label.replace(" ", "").toLowerCase()}`;
  return (
    <label htmlFor={id}>
      {label}
      <select
        id={id}
        value={value}
        onChange={e => updateState(e.target.value)}
        onBlur={e => updateState(e.target.value)}
        disabled={!options.length}
      >
        <option />
        {options.map(item => (
          <option key={item} value={item}>
            {item}
          </option>
        ))}
      </select>
    </label>
  );
});

export default Dropdown;

and use it as

import React, { useState, useEffect } from "react";
import useDropdown from "./useDropdown";

const SomeComponent = () => {
  const [animal, updateAnimal] = useState("dog");
  const [breed, updateBreed] = useState("");

  return (
    <div className="search-params">
      <form>
        <label htmlFor="location">
          Location
          <input
            id="location"
            value={location}
            placeholder="Location"
            onChange={e => updateLocation(e.target.value)}
          />
        </label>
        <Dropdown label="animal" value={animal} updateState={updateAnimal} options={ANIMALS}/>
        <Dropdown label="breed" value={breed} updateState={updateBreed} options={breeds}/>
        <button>Submit</button>
      </form>
    </div>
  );
};

export default SomeComponent;
like image 185
Shubham Khatri Avatar answered Sep 24 '22 08:09

Shubham Khatri