Created
November 17, 2021 14:57
-
-
Save jonleopard/0924dda2706454edf498d329404d5590 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ./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} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ./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} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ./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} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ./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