IWI
IWI

Reputation: 1608

React returning a function in a custom hook, whose internal hook returns an object

I recent upgraded my use-debounce react package. the breaking change was that the hook returned an object instead of an array. I am unable to update the hook so that it works with the new change. I have created a codesandbox to demonstrate the issue, where setting the state fails, as the setter returned from the hook isnt configured right. For the purpose of the sandbox, i threw the hook component inside the main component so all the info would be in one spot.

The error is setState is not a function

Heres the code from the sandbox if you dont feel like looking

const Input = () => {
  // hook that would normally be in a seperate component
  const useDebouncedState = (
    initialState,
    durationInMs = 200,
    options = {}
  ) => {
    const [internalState, setInternalState] = useState(initialState);
    const debouncedSetter = useDebouncedCallback(
      () => debouncedSetter.callback(setInternalState),
      durationInMs,
      options
    );
    return [internalState, debouncedSetter];
  };

  // this would be set in the main components
  const [searchText, setSearchText] = useDebouncedState("", 200, {
    maxWait: 1000
  });

  // this is where i set
  return (
    <>
      <input type="text" onChange={(e) => setSearchText(e.target.value)} />
      <h1>{searchText}</h1>
    </>
  );
};

const rootElement = document.getElementById("root");
ReactDOM.render(<Input />, rootElement);

Upvotes: 1

Views: 411

Answers (2)

IWI
IWI

Reputation: 1608

Thanks to all who posted, it led me to my answer. the working code is

import React, { useState } from "react";
import ReactDOM from "react-dom";
import useDebouncedCallback from "use-debounce/lib/useDebouncedCallback";

const Input = () => {
  // hook that would normally be in a seperate component
  const useDebouncedState = (
    initialState,
    durationInMs = 200,
    options = {}
  ) => {
    const [internalState, setInternalState] = useState(initialState);
    const debouncedSetter = useDebouncedCallback(
      setInternalState,
      durationInMs,
      options
    );
    return [internalState, debouncedSetter];
  };

  // this would be set in the main components
  const [searchText, setSearchText] = useDebouncedState("", 800, {
    maxWait: 1000
  });

  // this is where i set
  return (
    <>
      <input
        type="text"
        onChange={(e) => setSearchText.callback(e.target.value)}
      />
      <h1>{searchText}</h1>
    </>
  );
};

const rootElement = document.getElementById("root");
ReactDOM.render(<Input />, rootElement);

Upvotes: 1

Dennis Vash
Dennis Vash

Reputation: 53874

The problem is with this code:

const debouncedSetter = useDebouncedCallback(
  // debouncedSetter in this scope is undefined, but linter doesn't catch it
  () => debouncedSetter(setInternalState),
  durationInMs,
  options
);

debouncedSetter is undefined since you never declared it, therefore due to closures it will call callback() on undefined which causes a runtime error.

If you change your code to the next snippet, you will notice the linting warnings:

const useDebouncedState = (initialState, durationInMs = 200, options = {}) => {
  const [internalState, setInternalState] = useState(initialState);
  const callback = useDebouncedCallback(
    // 'debouncedSetter' is not defined
    () => debouncedSetter(setInternalState),
    durationInMs,
    options
  );
  return [internalState, callback];
};

Upvotes: 2

Related Questions