Sometimes we might need some state in multiple places in our app and for this we can use React's Context API to share the data. For the sake of simplicity and building on previous examples, let's assume that we want to get the state from our useReducer
example in two different locations.
First of all we need to create a way of sharing the state using context.
// ReactContext.res
module type Config = {
type context
let defaultValue: context
}
module Make = (Config: Config) => {
let t = React.createContext(Config.defaultValue)
module Provider = {
let make = React.Context.provider(t)
@obj
external makeProps: (
~value: Config.context,
~children: React.element,
~key: string=?,
unit,
) => {"value": Config.context, "children": React.element} = ""
}
let use = () => React.useContext(t)
}
This might look a bit intimidating at first but bear with me. This new file creates a nice and general way for us to create React contexts using what's called a functor.
By adding this we only need to provide a context
type and a defaultValue
, the values defined in the module type Config
, to create a new context. Here's an example of creating a context that holds a bool
value with the default being false
.
include ReactContext.Make({
type context = bool
let defaultValue = false
})
The include
keyword includes all the parts of the Make
module inReactContext
, which means we now have access to both a <Provider>
and a use
function that calls useContext
.
If we combine the newly created ReactContext
with our state and reducer from the useReducer
example we get this code.
// ValueSettings.res
type state = DisplayValue | HideValue
type action = Toggle
module Context = {
include ReactContext.Make({
type context = (state, action => unit)
let defaultValue = (HideValue, _ => ())
})
}
module Provider = {
@react.component
let make = (~children) => {
let (state, dispatch) = React.useReducer((state, action) => {
switch action {
| Toggle =>
switch state {
| DisplayValue => HideValue
| HideValue => DisplayValue
}
}
}, HideValue)
<Context.Provider value=(state, dispatch)> children </Context.Provider>
}
}
We've moved the state
and action
types as well as the useReducer
. We also define a custom Provider
, instead of using the one from <Context.Provider>
directly, because we want to be able to update the state using our reducer's dispatch
function.
Next, we need to include this provider somewhere above in the component tree from where we want to use it.
// Index.res
@react.component
let make = () => {
<ValueSettings.Provider>
<App />
<AnotherPart />
</ValueSettings.Provider>
}
Finally, we can return to our App.res
from the useReducer
example and modify it to get state and dispatch from the context. Since ReactContext
created a use
hook for us, the easiest way to fetch the state
is to use ValueSettings.Context.use()
which returns a tuple with the state and dispatch.
// App.res
@react.component
let make = () => {
let (state, dispatch) = ValueSettings.Context.use()
<div>
{switch state {
| DisplayValue => React.string("The best value")
| HideValue => React.null
}}
<Button onClick={_ => dispatch(Toggle)}> {React.string("Toggle value")} </Button>
</div>
}
If we only wanted to display a value in <AnotherPart>
we can ignore dispatch
by adding an underscore and pattern match on the state
.
// AnotherPart.res
@react.component
let make = () => {
let (state, _dispatch) = ValueSettings.Context.use()
switch state {
| DisplayValue => React.string("This is another great value")
| HideValue => React.null
}
}
This is the most complicated topic we've covered so far. If you have any questions or ways of clarifying a step feel free to reach out to me on Twitter.
Top comments (1)
when i run this program, i get this error:
React Hook "React.useContext" is called in function "use" which is neither a React function component or a custom React Hook function react-hooks/rules-of-hooks