Skip to content

Instantly share code, notes, and snippets.

@jonleopard
Created November 17, 2021 14:57
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 jonleopard/0924dda2706454edf498d329404d5590 to your computer and use it in GitHub Desktop.
Save jonleopard/0924dda2706454edf498d329404d5590 to your computer and use it in GitHub Desktop.
// ./src/utils/api-client.js
import {queryCache} from 'react-query'
import * as auth from 'auth-provider'
const apiURL = process.env.REACT_APP_API_URL
async function client(
endpoint,
{data, token, headers: customHeaders, ...customConfig} = {},
) {
const config = {
method: data ? 'POST' : 'GET',
body: data ? JSON.stringify(data) : undefined,
headers: {
Authorization: token ? `Bearer ${token}` : undefined,
'Content-Type': data ? 'application/json' : undefined,
...customHeaders,
},
...customConfig,
}
return window.fetch(`${apiURL}/${endpoint}`, config).then(async response => {
if (response.status === 401) {
queryCache.clear()
await auth.logout()
// refresh the page for them
window.location.assign(window.location)
return Promise.reject({message: 'Please re-authenticate.'})
}
const data = await response.json()
if (response.ok) {
return data
} else {
return Promise.reject(data)
}
})
}
export {client}
// ./src/utils/auth-provider.js
// pretend this is firebase, netlify, or auth0's code.
// you shouldn't have to implement something like this in your own app
const localStorageKey = '__auth_provider_token__'
async function getToken() {
// if we were a real auth provider, this is where we would make a request
// to retrieve the user's token. (It's a bit more complicated than that...
// but you're probably not an auth provider so you don't need to worry about it).
return window.localStorage.getItem(localStorageKey)
}
function handleUserResponse({user}) {
console.log(user)
window.localStorage.setItem(localStorageKey, user.token)
return user
}
function login({username, password}) {
return client('login', {username, password}).then(handleUserResponse)
}
function register({username, password}) {
return client('register', {username, password}).then(handleUserResponse)
}
async function logout() {
window.localStorage.removeItem(localStorageKey)
}
// an auth provider wouldn't use your client, they'd have their own
// so that's why we're not just re-using the client
const authURL = process.env.REACT_APP_AUTH_URL
async function client(endpoint, data) {
const config = {
method: 'POST',
body: JSON.stringify(data),
headers: {'Content-Type': 'application/json'},
}
return window.fetch(`${authURL}/${endpoint}`, config).then(async response => {
const data = await response.json()
if (response.ok) {
return data
} else {
return Promise.reject(data)
}
})
}
export {getToken, login, register, logout, localStorageKey}
// ./src/utils/hooks.js
import * as React from 'react'
function useSafeDispatch(dispatch) {
const mounted = React.useRef(false)
React.useLayoutEffect(() => {
mounted.current = true
return () => (mounted.current = false)
}, [])
return React.useCallback(
(...args) => (mounted.current ? dispatch(...args) : void 0),
[dispatch],
)
}
// Example usage:
// const {data, error, status, run} = useAsync()
// React.useEffect(() => {
// run(fetchPokemon(pokemonName))
// }, [pokemonName, run])
const defaultInitialState = {status: 'idle', data: null, error: null}
function useAsync(initialState) {
const initialStateRef = React.useRef({
...defaultInitialState,
...initialState,
})
const [{status, data, error}, setState] = React.useReducer(
(s, a) => ({...s, ...a}),
initialStateRef.current,
)
const safeSetState = useSafeDispatch(setState)
const setData = React.useCallback(
data => safeSetState({data, status: 'resolved'}),
[safeSetState],
)
const setError = React.useCallback(
error => safeSetState({error, status: 'rejected'}),
[safeSetState],
)
const reset = React.useCallback(() => safeSetState(initialStateRef.current), [
safeSetState,
])
const run = React.useCallback(
promise => {
if (!promise || !promise.then) {
throw new Error(
`The argument passed to useAsync().run must be a promise. Maybe a function that's passed isn't returning anything?`,
)
}
safeSetState({status: 'pending'})
return promise.then(
data => {
setData(data)
return data
},
error => {
setError(error)
return error
},
)
},
[safeSetState, setData, setError],
)
return {
// using the same names that react-query uses for convenience
isIdle: status === 'idle',
isLoading: status === 'pending',
isError: status === 'rejected',
isSuccess: status === 'resolved',
setData,
setError,
error,
status,
data,
run,
reset,
}
}
export {useAsync}
// ./src/context/index.js
import * as React from 'react'
import {BrowserRouter as Router} from 'react-router-dom'
import {ReactQueryConfigProvider} from 'react-query'
import {AuthProvider} from './auth-context'
const queryConfig = {
queries: {
useErrorBoundary: true,
refetchOnWindowFocus: false,
retry(failureCount, error) {
if (error.status === 404) return false
else if (failureCount < 2) return true
else return false
},
},
}
function AppProviders({children}) {
return (
<ReactQueryConfigProvider config={queryConfig}>
<Router>
<AuthProvider>{children}</AuthProvider>
</Router>
</ReactQueryConfigProvider>
)
}
export {AppProviders}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment