0

This is my custom hook:

function useFetch({url = '', method = 'get', body}) {
  const [data, setData] = useState(null);
  useEffect(() => {
    try {
      (async () => {
        const data = await fetch[method](url, body);
        setData(data);
      })();
    } catch (err) {
      console.log("An error ocurred")
    }
  }, [url, method, body]);
  return [(!data && <LoadingIcon />, data, setData];
}

I want to execute setUserFeedbackMsg("An error ocurred"), which is part of a context component, every time an error ocurrs on any instantiation of the hook. Of course I could do it manually on every component that uses the hook, but I'm wondering if there's a way to condense it all in one place. Thank you!

Guido Glielmi
  • 73
  • 1
  • 5
  • A lot of people integrate context components into (you guessed it) [custom hooks](https://dev.to/alexandprivate/use-react-context-as-a-custom-hook-4ioc) that they can compose into other hooks such as your useFetch. Does that help answer your question? In other words, the answer to "but I'm wondering if there's a way to condense it all in one place" is to use a hook! – Brendan Bond Aug 02 '22 at 00:36
  • I don't know if I'm understading you comment, but my problem is that I want the custom hook to use a setter from a context it doesn't have access to, and it's only a simple call to a single setState() – Guido Glielmi Aug 02 '22 at 00:42
  • I'll try to elaborate in an answer – Brendan Bond Aug 02 '22 at 00:48

1 Answers1

1

Compose your context (global state) into a hook that components and other hooks can access.

// obviously, this has been simplified to help with the concept

// fake fetch
function fakeFetchThatErrs() {
  return new Promise((resolve, reject) => {
    setTimeout(() => reject("error msg from our api!"), 1500);
  });
}


// a custom hook to wrap our state 
function useUserFeedback() {
  const [message, setMessage] = React.useState('');
  

  return {
    message,
    setMessage
  };
}

// our context
const UserFeedbackContext = React.createContext();

// a custom hook to access our context
function useUserFeedbackContext() {
  return React.useContext(UserFeedbackContext);
}

// the context provider component
function UserFeedbackProvider({ children }) {
  const feedback = useUserFeedback();
  return (<UserFeedbackContext.Provider value={feedback}>{children}
  </UserFeedbackContext.Provider>);
}

// your useFetch hook (without the loading component)
function useFetch() {
  const [data, setData] = React.useState(null);
  // here we use our custom hook to "hook" into the context so we can use the setter!
  const { setMessage } = useUserFeedbackContext();
  React.useEffect(() => {
    // changing this because (a) StackOverflow snippets don't support JSX along with async/await and (b) no need to really fetch here, we'll fake it
     fakeFetchThatErrs().then((data) => {
       // write to our data
       setData(data);
     }).catch((err) => {
       console.log("Error:", err)
       // uh oh, error! write to our context 
       setMessage(err);
     });
    
  }, [setMessage]);
  return [data, setData];
}

// our demo app component
function App() {
  const [data] = useFetch();
  // consume our context!
  const { message } = useUserFeedbackContext();
  
  return data ? <div>Success: {data}</div> : message ? <div>Something went wrong: {message}</div> : <div>Fetching...</div>;
   
}

// don't forget to use our provider!
ReactDOM.render(<UserFeedbackProvider><App /></UserFeedbackProvider>, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Here's what's happening here in a nutshell:

  1. We have a state object that we can access via a hook, useUserFeedback.
  2. We create a Context around this state object and wrap that in its own hook, useUserFeedbackContext. This way, we can easily access the state getter and setter in components and, importantly, other hooks!
  3. Thus, we can use the useUserFeedbackContext hook in our useFetch hook to set the message state to an error assuming there is one.
  4. A common question around this (apologies if you already know this) - why can't we just use the useUserFeedback hook in useFetch? The answer is an important concept in React - hooks share behavior, while Contexts share state. If we simply tried to use the useUserFeedback hook, each time we called useFetch would result in a new '' message state object. Using Context, we share one useUserFeedback instantiation across the board.
Brendan Bond
  • 1,737
  • 1
  • 10
  • 8
  • Thank you very much! I thought a function needed to be a children to use the context ^^". In my approach, I just added to the useFetch hook const {showFeedbackMsgModal} = useContext(userFeedbackContext); One little detail is that the try catch needs to be inside the iife in my example :) – Guido Glielmi Aug 02 '22 at 15:30