Skip to content

Instantly share code, notes, and snippets.

@highlander08
Forked from pjchender/UserList.tsx
Created July 21, 2022 05:35
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save highlander08/66199a57a451724fda5b839287b97680 to your computer and use it in GitHub Desktop.
Save highlander08/66199a57a451724fda5b839287b97680 to your computer and use it in GitHub Desktop.
Recoil Pattern inspired from useContext
import { useCallback } from 'react';
import { selector, atom, useRecoilState } from 'recoil';
/**
* APIs
**/
export enum STATE {
HAS_VALUE = 'hasValue',
HAS_ERROR = 'hasError',
LOADING = 'loading',
RESET = 'reset',
}
type User = {
id: number;
name: string;
username: string;
email: string;
};
export const apiFetchUsers = async (): Promise<User[]> => {
console.log('[state/users] fetchUsers');
const resp = await fetch('https://jsonplaceholder.typicode.com/users');
await sleep(1000);
const data = await resp.json();
return data;
};
// STEP 1: define the default state
type UserStateType = {
state: `${STATE}`;
data: User[];
};
export const usersState = atom<UserStateType>({
key: 'users',
default: {
state: STATE.LOADING,
data: [],
},
});
// STEP 2-1: create a custom hook
export const useUsers = () => {
const [users, setUsers] = useRecoilState(usersState);
// STEP 2-2 put async function or state manipulation here
const fetchUsers = useCallback(async () => {
setUsers({
...users,
state: STATE.LOADING,
});
const data = await apiFetchUsers();
setUsers({
state: STATE.HAS_VALUE,
data,
});
}, [setUsers]);
const removeUser = useCallback(
(userId: number) => {
console.log('[useGetUsers] removeUser', userId);
setUsers({
...users,
state: STATE.LOADING,
});
const filteredData = users.data.filter((user) => user.id !== userId);
setUsers({
data: filteredData,
state: STATE.HAS_VALUE,
});
},
[users]
);
const resetUser = useCallback(() => {
setUsers({
state: STATE.RESET,
data: [],
});
}, [setUsers]);
// STEP 2-3 return state and actions
return { users, actions: { fetchUsers, removeUser, resetUser } };
};
// STEP 3: define derived state if needed
type CurrentUserSelectorType = {
state: `${STATE}`;
data?: User;
};
export const currentUserSelect = selector<CurrentUserSelectorType>({
key: 'currentUser',
get: ({ get }) => {
const users = get(usersState);
const { state } = users;
switch (state) {
case STATE.HAS_VALUE: {
const [currentUser] = users.data;
return {
state,
data: currentUser,
};
}
case STATE.RESET:
case STATE.LOADING:
case STATE.HAS_ERROR: {
return {
state,
};
}
}
},
});
// other utilities
export function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
import { STATE, useUsers } from '@/state/user';
import { useEffect } from 'react';
const UserList = () => {
// STEP 4: useUser to get data from state and actions
const { users, actions } = useUsers();
const { removeUser, fetchUsers, resetUser } = actions;
useEffect(() => {
// if you want to get the newest users every time, otherwise the cache will be used
fetchUsers();
return () => {
// if you want to reset user when the component is unmounted
return resetUser();
};
}, []);
return (
<div>
<button type="button" onClick={() => fetchUsers()}>
fetch users
</button>
<div>{users.state === STATE.LOADING && <p>loading...</p>}</div>
<ul>
{users.state === STATE.HAS_VALUE &&
users.data.map((user) => (
<li key={user.id}>
{user.name}
<button
type="button"
onClick={() => {
removeUser(user.id);
}}
>
X
</button>
</li>
))}
</ul>
</div>
);
};
export default UserList;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment