Skip to content

Instantly share code, notes, and snippets.

@wokalski
Last active October 13, 2017 09:18
Show Gist options
  • Save wokalski/8611844d306e960b8eaf9a7f5adc41f9 to your computer and use it in GitHub Desktop.
Save wokalski/8611844d306e960b8eaf9a7f5adc41f9 to your computer and use it in GitHub Desktop.
Simple state management for navigation

Assumption:

We want a statically typed scene hierarchy in such a way that when we move between nested screens, we pass state/props down and it can be updated. The state should live as close to the subtree as possible.

In other words, the view tree in a navigator looks like this:

       Navigator
    /     |     \
Scene1  Scene2  Scene3

And in the simple optimal scenario (passing props down) we want the state tree look like this:

Navigator
   |
 Scene1
   |
 Scene2
   |
 Scene3

Example:

  • Hierarchy: Main -> Details -> Comments
  • State: type comments = { page: int, comments: array comment };

We navigate from Details to Comments. The imaginary interaction is that after refreshing comments inside the comments view, the content is updated in the Details view which is higher in the hierarchy. So in this case we want to mutate comments in the state which is shared between these two screens.

Solution

Unifying the state and view hierarchy.

Hmm, sounds familiar. Yes, React is all about it. Unfortunately we cannot use react directly because scenes are rendered as siblings.

I think the solution is to partly copy the API of reason-react.

type reduce 'action 'event = ('event => 'action) => 'event => unit;
type screen 'state 'action = {
  render: 'state => reduce 'action => ReasonReact.element,
  reducer: 'action => 'state => 'state,
  renderChild: 'state => reduce 'action => option (screen 'childState 'childAction)
};

let comments ::project ::comments ::updateComments => 
  Navigator.leafScreen
    (fun () => <Comments comments project updateComments);

let details ::project => {
  initialState: fun () => None,
  render: fun state reduce => <Details project comments=state updateComments=(reduce (fun x => UpdateComments x)) />,
  reducer: fun (action: comments) state => switch action { 
    | UpdateComments => {...state, comments: action}
  },
  renderChild: fun state reduce => switch state { 
    | Some x => Some (
      comments ::project comments::x  updateComments::(reduce (fun x => UpdateComments x)
    )  
    | None => None 
  }
};

Ok, the example above sucks and is not typed correctly but you get what's going on here.

In order to render this hierarchy, the navigator recurisvely calls renderChild.

There're no hooks like componentWillMount because adding them would make the implementation harder and closely couple the application to the navigation lib.

Web vs react native

I think it has a great potential in react native but it's applicable to web apps too. You could nest routes this way and then separately provide resolvers if a particular route is accessed directly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment