Reducer with one action creates an error

So I’m setting up a basic redux store, and created my first action for fetching from an API. A simple “has been loaded” action that normalizes some entities by id

type action =
  | EntitiesLoaded(array<Entity.t>)

let reducer = (state: state, action: action) =>
  switch action {
  | EntitiesLoaded(entities) => {
      ...state,
      entities: Array.reduce(entities, state.entities, (map, entity) =>
        map->Map.String.set(entity.id, entity)
      ),
    }
  }

let store = Redux.createStore(reducer, initialState())

The problem is at this stage, this is causing an error when compiled because it (logically) strips away the pattern matching, expecting action to be only of type EntitiesLoaded(). But, redux initializes the store with an “@INIT” action – and in general redux actions typically don’t map 1:1

The compiled JS looks like this:

function reducer(state, action) {
  return {
    entities: Belt_Array.reduce(action._0, state.entities, (function (map, entity) {
      return Belt_MapString.set(map, entity.id, entity);
    }))
  };
}

I’ve tried adding a _ => state case, and also created multiple actions to try to get it to switch, but it all gets stripped out in the end and causes the same error

Here’s an Example on Playground

Question, is there a way to kind of force the compiler to kind of handle the case explicitly? Or is there a better way to do reducers?

An open poly variant seems like a good fit for this problem. Playground

Oh even more fun, if you actually fill it out with other actions, redux will then complain that the action doesn’t have a type property on it.

Might just be easier to create my own little store

Yeah, redux wants the format of the actions to be {type: string, ...}, that will be trickier to overcome

Yeah, there probably is a way to get creative, but honestly I prefer to stay with the simple way.

Just added this to the bottom of my Store module, works fine

let state = ref(initialState())
let listeners = ref([])

let dispatch = action => {
  state.contents = reducer(state.contents, action)
  listeners.contents->Js.Array2.forEach(listener => listener(state.contents))
}

let useSelector = (select: state => 'b) => {
  let (value, setValue) = React.useState(_ => select(state.contents))

  React.useEffect0(() => {
    let onChange = (state: state) => setValue(_ => select(state))
    listeners.contents = listeners.contents->Js.Array2.concat([onChange])
    let unsubscribe = () => {
      listeners.contents = listeners.contents->Js.Array2.filter(i => i !== onChange)
    }

    Some(unsubscribe)
  })

  value
}