Skip to content

Instantly share code, notes, and snippets.

@bmingles
Last active October 5, 2022 01:31
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 bmingles/d9a8e7fbe58727128c1b9b82a39a4abb to your computer and use it in GitHub Desktop.
Save bmingles/d9a8e7fbe58727128c1b9b82a39a4abb to your computer and use it in GitHub Desktop.
MobX with custom React context hook
import React from 'react'
/**
* Create a React Context that requires a value be provided explicitly (aka. no
* default value). Returns the Context Provider + a useContext hook that
* encapsulates access to the Context.
*/
export function createNamedContext<TValue>(name: string) {
const Context = React.createContext<TValue | null>(null)
function useContext(): TValue {
const value = React.useContext(Context)
if (value === null) {
throw new Error(`Context value is null for '${name}'.`)
}
return value
}
return {
Provider: Context.Provider as React.Context<TValue>['Provider'],
useContext,
}
}
import React from 'react'
import { observer } from 'mobx-react-lite'
const MyComponent: React.FC = () => {
const { name, age, loadAge, loadName } = useSomeService()
React.useEffect(() => {
loadAge()
void loadName()
}, [loadName])
// Any changes to `name` or `age` in the service will cause React to re-render
// this component. Mobx handles the subscription to the `name` and `age`
// observables, but you just treat it like it is a string prop
return (
<div>
{name} {age}
</div>
)
}
// This is what makes the component subscribe to mobx observables
export default observer(MyComponent)
import { makeAutoObservable, runInAction } from 'mobx'
import { fetchName } from './api'
class SomeService {
constructor() {
// This makes methods actions and properties observable
makeAutoObservable(this)
}
age = 0
// synchronous action
loadAge = () => {
this.age = 4
}
name = ''
// async action
loadName = async () => {
const name = await fetchName()
// This is the one mobx API thing you have to use with async to tell the
// framework something is changing. You don't have to use it for change
// in syncrhonous methods
runInAction(() => {
this.name = name
})
}
}
const { Provider, useContext } = createNamedContext<SomeService>('SomeService')
export {
Provider as SomeServiceContextProvider,
useContext as useSomeService
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment