0

I've just started working with React, and wrote a couple of HOCs. Then I read that Functional Components and hooks are the suggested way of moving forward, so I decided to rewrite my HOCs.

A common pattern I used was:

class MyButton(Component) {
...
  handleClick = () => {
    setState({busy: True}, () => {
      fetchAPI().then(
        (results) => {
          setState({results: results, errs: [], busy: False});
        },
        (err) => {
          setState({errs: err, busy: False});
        });
    });
  };

  render() {
    return(
      <Button disabled={this.state.busy} onClick={this.handleClick} />
    )
  }
}

If I understand correctly, when I click the button, it would disable it, render the disabled button, then fetch things from the api, then set the state with the results (and enable the button), then render the enabled button.

This would prevent people from clicking the button 10 times, hitting the api 10 times before the first is finished.

How do I do this with functional components and hooks? My naive implementation would be this, but it seems incorrect.

MyButton = (props) => {
  const [results, setResults] = useState([]);
  const [errs, setErrs] = useState([]);
  const [busy, setBusy] = useState(False);
  const handleClick = () => {
    setBusy(true);
    fetchAPI().then(
      (results) => {
        setResults(results);
        setErrs([]);
        setBusy(False);
      },
      (err) => {
        setErrs(errs);
        setBusy(False);
      });
    );
  }
  return (
    <Button disabled={busy} onClick={handleClick} />
  )
}

Is there a better way?

vstumpf
  • 7
  • 2
  • 4

2 Answers2

0

One naive way is to write your fetchApi in a useEffect hook and your button toggles when to activate this effect like the following. You could also check out react-query library to do this

useEffect(()=>{
    const loadApi = () => {
        setBusy(true);
        fetchAPI().then(
        (results) => {
            setResults(results);
            setErrs([]);
            setBusy(False);
        },
        (err) => {
            setErrs(errs);
            setBusy(False);
        });
        );
    } 
    if (toggle) {
      loadApi();
      setToggle(false)
    }
},[toggle])

const handleOnClick = () => {
    setToggle(true)
}

weiklr
  • 159
  • 1
  • 7
0

I don't see anything wrong with your implementation. It should work. But if you are looking for a better way, you can consider using a reducer and the useReducer hook.

Define a reducer for all your states, including error, loading and loaded states. In loading state set disabled to be false. In loaded state you would have disabled true. In your fetchApi function, you can dispatch the various actions. Before API call, dispatch a loading state and after the results are back, a loaded state with the payload containing your results. You can then use the reducer in your component with a useReducer hook which will get you the state and the dispatch. Use the state.disabled to disable the button.

Helpful SO discussion around useReducer Good article on how to use useReducer for API calls

Priyankar Kumar
  • 437
  • 3
  • 11