Skip to content

Instantly share code, notes, and snippets.

@Neo42
Last active January 8, 2022 14:20
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 Neo42/1ee4e06af4330fd961470ea8ae9d4a26 to your computer and use it in GitHub Desktop.
Save Neo42/1ee4e06af4330fd961470ea8ae9d4a26 to your computer and use it in GitHub Desktop.
React Patterns: context module functions
// Context Module Functions
import * as React from 'react'
import {dequal} from 'dequal'
import * as userClient from '../user-client'
import {useAuth} from '../auth-context'
const UserContext = React.createContext()
UserContext.displayName = 'UserContext'
function userReducer(state, action) {
switch (action.type) {
case 'start update': {
return {
...state,
user: {...state.user, ...action.updates},
status: 'pending',
storedUser: state.user,
}
}
case 'finish update': {
return {
...state,
user: action.updatedUser,
status: 'resolved',
storedUser: null,
error: null,
}
}
case 'fail update': {
return {
...state,
status: 'rejected',
error: action.error,
user: state.storedUser,
storedUser: null,
}
}
case 'reset': {
return {
...state,
status: null,
error: null,
}
}
default: {
throw new Error(`Unhandled action type: ${action.type}`)
}
}
}
// wrap user state into a provider function with a value
function UserProvider({children}) {
const {user} = useAuth()
const [state, dispatch] = React.useReducer(userReducer, {
status: null,
error: null,
storedUser: user,
user,
})
const value = [state, dispatch]
return <UserContext.Provider value={value}>{children}</UserContext.Provider>
}
// guard the context with a condition to tell if it's used within a provider
function useUser() {
const context = React.useContext(UserContext)
if (context === undefined) {
throw new Error(`useUser must be used within a UserProvider`)
}
return context
}
async function updateUser(dispatch, user, updates) {
dispatch({type: 'start update', updates})
try {
const updatedUser = await userClient.updateUser(user, updates)
dispatch({type: 'finish update', updatedUser})
return updatedUser
} catch (error) {
dispatch({type: 'fail update', error})
return Promise.reject(error)
}
}
function UserSettings() {
const [{user, status, error}, userDispatch] = useUser()
const isPending = status === 'pending'
const isRejected = status === 'rejected'
const [formState, setFormState] = React.useState(user)
const isChanged = !dequal(user, formState)
function handleChange(e) {
setFormState({...formState, [e.target.name]: e.target.value})
}
function handleSubmit(event) {
event.preventDefault()
updateUser(userDispatch, user, formState).catch(() => {
/* ignore the error */
})
}
return (
<form onSubmit={handleSubmit}>
<div style={{marginBottom: 12}}>
<label style={{display: 'block'}} htmlFor="username">
Username
</label>
<input
id="username"
name="username"
disabled
readOnly
value={formState.username}
style={{width: '100%'}}
/>
</div>
<div style={{marginBottom: 12}}>
<label style={{display: 'block'}} htmlFor="tagline">
Tagline
</label>
<input
id="tagline"
name="tagline"
value={formState.tagline}
onChange={handleChange}
style={{width: '100%'}}
/>
</div>
<div style={{marginBottom: 12}}>
<label style={{display: 'block'}} htmlFor="bio">
Biography
</label>
<textarea
id="bio"
name="bio"
value={formState.bio}
onChange={handleChange}
style={{width: '100%'}}
/>
</div>
<div>
<button
type="button"
onClick={() => {
setFormState(user)
userDispatch({type: 'reset'})
}}
disabled={!isChanged || isPending}
>
Reset
</button>
<button
type="submit"
disabled={(!isChanged && !isRejected) || isPending}
>
{isPending
? '...'
: isRejected
? '✖ Try again'
: isChanged
? 'Submit'
: '✔'}
</button>
{isRejected ? <pre style={{color: 'red'}}>{error.message}</pre> : null}
</div>
</form>
)
}
function UserDataDisplay() {
const [{user}] = useUser()
return <pre>{JSON.stringify(user, null, 2)}</pre>
}
function App() {
return (
<div
style={{
minHeight: 350,
width: 300,
backgroundColor: '#ddd',
borderRadius: 4,
padding: 10,
}}
>
// use the provider here
<UserProvider>
<UserSettings />
<UserDataDisplay />
</UserProvider>
</div>
)
}
export default App
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment