Home Writing

til / ReScript: Using useContext in rescript-react

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.

 1// ReactContext.res
 2module type Config = {
 3  type context
 4  let defaultValue: context
 5}
 6
 7module Make = (Config: Config) => {
 8  let t = React.createContext(Config.defaultValue)
 9
10  module Provider = {
11    let make = React.Context.provider(t)
12
13    @obj
14    external makeProps: (
15      ~value: Config.context,
16      ~children: React.element,
17      ~key: string=?,
18      unit,
19    ) => {"value": Config.context, "children": React.element} = ""
20  }
21
22  let use = () => React.useContext(t)
23}

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.

1include ReactContext.Make({
2  type context = bool
3  let defaultValue = false
4})

The include keyword includes all the parts of the Make module in ReactContext, 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.

 1// ValueSettings.res
 2type state = DisplayValue | HideValue
 3
 4type action = Toggle
 5
 6module Context = {
 7  include ReactContext.Make({
 8    type context = (state, action => unit)
 9    let defaultValue = (HideValue, _ => ())
10  })
11}
12
13module Provider = {
14  @react.component
15  let make = (~children) => {
16    let (state, dispatch) = React.useReducer((state, action) => {
17      switch action {
18      | Toggle =>
19        switch state {
20        | DisplayValue => HideValue
21        | HideValue => DisplayValue
22        }
23      }
24    }, HideValue)
25
26    <Context.Provider value=(state, dispatch)> children </Context.Provider>
27  }
28}

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.

1// Index.res
2@react.component
3let make = () => {
4  <ValueSettings.Provider>
5    <App />
6    <AnotherPart />
7  </ValueSettings.Provider>
8}

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.

 1// App.res
 2@react.component
 3let make = () => {
 4  let (state, dispatch) = ValueSettings.Context.use()
 5
 6  <div>
 7    {switch state {
 8    | DisplayValue => React.string("The best value")
 9    | HideValue => React.null
10    }}
11    <Button onClick={_ => dispatch(Toggle)}> {React.string("Toggle value")} </Button>
12  </div>
13}

If we only wanted to display a value in <AnotherPart> we can ignore dispatch by adding an underscore and pattern match on the state.

 1// AnotherPart.res
 2@react.component
 3let make = () => {
 4  let (state, _dispatch) = ValueSettings.Context.use()
 5
 6  switch state {
 7  | DisplayValue => React.string("This is another great value")
 8  | HideValue => React.null
 9  }
10}

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.


  • Loading next post...
  • Loading previous post...