SWR is a React hook for data fetching that features a cache for requests. This is generally used to share the response from API calls and deduplicate requests, but SWR is flexible enough to support another use case: shared hook state. 1
Let's look at an example of a useUsername
hook:
const useUsername = () => {
return useState('')
}
const UsernameInput = () => {
const [username, setUsername] = useUsername()
return (
<div>
<input value={username} onChange={setUsername} />
</div>
)
}
const DisplayUsername = () => {
const [username] = useUsername()
return <span>Username: {username}</span>
}
This won't work, because each time we call useUsername
, we receive a new instance of state. Updating the input won't affect what our DisplayUsername
component renders.
Solving with Context
With React context, we typically lift the username state to the highest level and useContext
to read the value in our components:
const UsernameContext = createContext()
const App = () => (
<UsernameProvider>
{/* ... */}
</UsernameProvider>
)
const UsernameProvider = ({ children }) => {
const [username, setUsername] = useState('')
return (
<UsernameContext.Provider value={[username, setUsername]}>
{children}
</UsernameContext.Provider>
)
}
const DisplayUsername = () => {
const [username] = useContext(UsernameContext)
return (
<span>Username: {username}
)
}
This will work, but in big applications you'll end up with a lot of context.
Solving with SWR
We can simulate useState
with SWR by using the mutate
function as our setState
, and the config.initialData
option the initial state. Now when we call mutate
, the updated data will be reflected everywhere the hook is used.
import useSWR from 'swr'
const useUsername = () => {
const { data: username, mutate: setUsername } = useSWR('username', {
initialData: '',
})
return [username, setUsername]
}
It works, no context required. Every useUsername
will share the same state, and calling setUsername
will update the state across all uses of the hook.
useSharedState
We can go one step further and build a shared addition to useState
:
const useSharedState = (key, initial) => {
const { data: state, mutate: setState } = useSWR(key, {
initialData: initial,
})
return [state, setState]
}
Use it like useState
, but pass a key as the first argument:
const [username, setUsername] = useSharedState('username', 'paco')
const [os, setOS] = useSharedState('os', 'macos')
SWR is mainly used to manage remote data requests, but it also provides a powerful cache and API that you can use to share local state between hooks. Kudos to Shu for his excellent work!
- Two components using the same React hook don't share state by default.
- SWR uses a client-side only cache, so your data won't persist between sessions or windows unless you keep external state like
localStorage
, at which point SWR may not be useful.