DEV Community

Rickard Natt och Dag
Rickard Natt och Dag

Posted on • Originally published at willcodefor.beer on

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.

// 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)
}
Enter fullscreen mode Exit fullscreen mode

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
})
Enter fullscreen mode Exit fullscreen mode

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>
  }
}
Enter fullscreen mode Exit fullscreen mode

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>
}
Enter fullscreen mode Exit fullscreen mode

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>
}
Enter fullscreen mode Exit fullscreen mode

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
  }
}
Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
hamzapolito profile image
hamzapolito • Edited

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