@focus21/hooks
Focus21's React hooks.
Usage
Installation
pnpm add --save @focus21/hooks
Hooks
getUseFetch
Get data using fetch.
Setup example
// utils/useFetch.js
// You must provide a library that implements the fetch API, such as `unfetch`.
// https://github.com/developit/unfetch
import unfetch from "unfetch";
import { getUseFetch } from "@focus21/hooks";
const useFetch = getUseFetch(unfetch);
export default useFetch;
Get request example
// utils/hello.js
import useFetch from "./useFetch";
// Example simple get request hook.
const useGetHello = () =>
useFetch("/hello", {
fetchOnRender: true,
});
export { useGetHello };
// pages/index.jsx
import React, { useEffect } from "react";
import { useGetHello } from "../utils/hello";
const Page = () => {
const getHello = useGetHello();
// Indicate that there was an error.
// You probably want a proper component for showing error messages.
//
// Talk to UX to ask about how to show errors in your app.
//
// Avoid "toaster" errors since they are easy to miss and hard to make accessible.
if (getHello.error) {
return <div>Error!</div>;
}
// Indicate that data is loading.
// You probably want a proper loading indicator component.
//
// Talk to UX to ask about how to indicate loading in your app.
if (!getHello.isLoaded) {
return <div>Loading...</div>;
}
// This is not a good way to display data in a real page, just an example
return <div>Data: {JSON.stringify(getHello.data)}</div>;
};
Set defaults example
// utils/ping
import useFetch from "./useFetch";
const usePostPing = () =>
useFetch("/ping", {
// Any fetch options are supported by "defaults", plus a few of our own helper options.
defaults: {
method: "POST",
},
});
export { usePostPing };
Override defaults example
// utils/foo
import useFetch from "./useFetch";
const useFoo = () => useFetch("/foo");
export { useFoo };
// pages/index.jsx
import React, { useEffect } from "react";
import { useFoo } from "../utils/hello";
const Page = () => {
const foo = useFoo();
// Any of the defaults set by "useFetch" can be overridden on "doFetch".
// The URL itself can also be overwritten if the first argumetn is set.
return (
<div>
<div onClick={() => foo.doFetch(null, { method: "GET" })}>
Click me to get foo.
</div>
<div onClick={() => foo.doFetch(null, { method: "POST" })}>
Click me to post foo.
</div>
</div>
);
};
Payload example
// utils/hello.jsx
import { useEffect, useMemo } from "react";
import useFetch "./useFetch";
// The payload should be memoized (using React's useMemo) for optimal results.
const useGetHelloQuery = (payload) => useFetch("/hello", {
// Use "options.defaults.payload" to automatically encode the query string for GET requests.
// If you wish to encode it yourself, use the URL instead.
// "options.payload" can be used when calling "doFetch" to override this default.
defaults: { payload },
});
const usePostHelloQuery = (payload) => useFetch("/hello", {
defaults: {
method: "POST",
// Use "options.defaults.payload" to automatically encode the body for POST, PUT, and PATCH requests.
// If you wish to encode it yourself, use "options.body" insted.
// "options.payload" can be used when calling "doFetch" to override this default.
payload,
},
});
export { useGetHelloQuery, usePostHelloQuery };
Save last successful data example
// pages/index.jsx
import React, { useEffect, useState } from "react";
import { useGetHello } from "../utils/user";
const Page = () => {
const getHello = useGetHello();
useEffect(() => {
if (!getHello.initialized) {
getHello.doFetch();
}
}, [getHello]);
const [lastData, setLastData] = useState(getHello.data);
useEffect(() => {
if (getHello.data && !getHello.error) {
setLastData(getHello.data);
}
}, [getHello]);
if (!lastData && getHello.error) {
return <div>Error!</div>;
}
if (!getHello.isLoaded) {
return <div>Loading...</div>;
}
return (
<>
<div>Data: {JSON.stringify(lastData)}</div>
{getHello.isFetching ? <div>Loading...</div> : null}
{getHello.error ? <div>Error!</div> : null}
</>
);
};
Shared data and mutations example
Use context to share data and trigger mutations.
// utils/user.js
import React, { createContext, useContext } from "react";
const useGetUser = () => useFetch("/user");
const UserContext = createContext({});
const UserProvider = ({ children }) => {
const getUser = useGetUser();
return (
<UserContext.Provider value={getUser}>{children}</UserContext.Provider>
);
};
const useUser = () => useContext(UserContext);
export { useGetUser, UserContext, UserProvider, useUser };
// pages/_app.jsx
import React from "react";
import { UserProvider } from "../utils/user";
const App = ({ children }) => <UserProvider>{children}</UserProvider>;
export default App;
// pages/index.jsx
import React from "react";
import { useUser } from "../utils/user";
const useGetUser = () => useFetch("/user");
// Add the context to any pages or components which use or mutate it.
const Page = () => {
// The shared user can be referred to with `user.data`, `user.error`, etc.
const user = useUser();
// Refetch the user whenever there is a mutation.
const doMutation = () => {
user.doFetch();
};
// ...
};
useDebounce
Debounce a value.
Example
import React, { useState } from "react";
import { useDebounce } from "@focus21/hooks";
import useGetSearchResults from "./useGetSearchResults";
const Search = () => {
const [search, setSearch] = useState("");
// Debounce every half second
const debouncedSearch = useDebounce(() => search, [search], 500);
const searchResults = useGetSearchResults();
useEffect(() => {
if (debouncedSearch) {
searchResults.doFetch(null, {
payload: {
search: debouncedSearch,
},
});
}
}, [debouncedSearch]);
let results = null;
if (search && searchResults.data) {
results = searchResults.data.map(({ id, name }) => (
<div key={id}>{name}</div>
));
}
return (
<>
<input type="text" value={search} />
{results}
</>
);
};
export default Search;