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!
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:
useUserFeedback
.useUserFeedbackContext
. This way, we can easily access the state getter and setter in components and, importantly, other hooks!useUserFeedbackContext
hook in our useFetch
hook to set the message state to an error assuming there is one.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.