Shindig: React Nav View Controller

Chet Corcos
4 min readNov 24, 2015

--

The iOS UINavigationController is a crucial component in any quality mobile app. As discussed in the previous two articles, there we some challenges implementing a generic NavVC React component due to the fact that React data flow tree doesn’t always align with the DOM tree. This is the case with setting the title and the back button in the navbar in a typical tab-nav style app. So in the rest of this article, I’m going to finish up what I started.

Two articles ago in Shindig: React Tab View Controller, I gave an example of a simple NavVC component.

NavVC = createView
mixins: [InstanceMixin]
getInitialState: ->
transition: @props.instance.state.transition or 'navvc-appear'
stack: @props.instance.state.stack or [{
sceneRoute: @props.rootScene
instance: {}
}]
push: (route) ->
nextStack = React.addons.update @state.stack,
$push:[{
sceneRoute: route
instance: {}
}]
@setState
stack: nextStack
transition: 'navvc-push'
pop: ->
if @state.stack.length is 1
console.warn("Don't' pop off the root!")
else
last = @state.stack.length — 1
nextStack = React.addons.update @state.stack,
$splice:[[last, 1]]
@setState
stack: nextStack
transition: 'navvc-pop'
popFront: ->
@setState
stack: [@state.stack[0]]
transition: 'navvc-pop
save: ->
state: @state
render: ->
{sceneRoute, instance} = @state.stack[@state.stack.length — 1]
pop = @pop if @state.stack.length > 1
popFront = @popFront if @state.stack.length > 1
div
className: @props.className
Transition
transitionName: @state.transition
@props.renderScene(
sceneRoute
instance
@push
pop
popFront
)

The issue was that the back button can only be set within each view. And in the previous article Shindig: React Proxy Component, I explained that the issue with this is that the navbar will now animate with the view content rather than stay stationary and that doesn’t look good. This is why I invented the Proxy component.

Since you might not always know what kind of component you want to render from the NavVC, I’ve modified the Proxy component to accept a transform function which transforms the pop function into a button suitable for my app layout. The point of this is really just to make the NavVC more generic.

Anyways, this is how you use the NavVC when you want to render a back button outside the view content.

App = createView
componentWillMount: ->
@PopProxy = createProxy ({pop, popFront}) ->
# transform the pop function into a button
if pop
button
className: 'pop'
onClick: pop
'<'
renderScene: (route, instance, pop, popFront) ->
Views[route]({instance, push})
render: ->
div
className: 'app'
PopProxy()
NavVC
instance: instance
rootScene: name
renderScene: @renderScene
# make the NavVC aware of the proxy
popProxy: @PopProxy

And the only code we need to add to NavVC is just two lines to handle rendering to the proxies if they exist.

NavVC = createView
# ...
render: ->
{sceneRoute, instance} = @state.stack[@state.stack.length — 1]
pop = @pop if @state.stack.length > 1
popFront = @popFront if @state.stack.length > 1
@props.pushProxy?.render(@push) # HERE
@props.popProxy?.render({pop, popFront}) # HERE
div
className: @props.className
Transition
transitionName: @state.transition
@props.renderScene(
sceneRoute
instance
@push
pop
popFront
)

And that’s it! Pretty simple and these Proxy component are incredibly useful. Suppose you want to set the title of the of the page based on the current view thats being rendered in the NavVC? Easy.

App = createView
componentWillMount: ->
@PopProxy = createProxy ({pop, popFront}) ->
# transform the pop function into a button
if pop
button
className: 'pop'
onClick: pop
'<'
@TitleProxy = createProxy (title) ->
span
className: 'title'
title
renderScene: (route, instance, pop, popFront) ->
Views[route]({instance, push, titleProxy:@TitleProxy})
render: ->
div
className: 'app'
div
className: 'navbar'
PopProxy()
TitleProxy()
NavVC
instance: instance
rootScene: name
renderScene: @renderScene
# make the NavVC aware of the proxy
popProxy: @PopProxy

Tab-Nav Apps and Routing

Now that I’ve covered TabVC and NavVC, I think its a good time to about routing. Its not so obvious at first, but traditional routing in your browser does not work for these types of apps. When you have multiple tabs, each with their own navigation controller, then you are maintaining multiple sets of histories. When you switch tabs and hit back, you’d expect to pop off a view from the current tab — you wouldn’t expect to go back to the previous tab.

Browsers maintain a linear history and thus, we have to keep track of the navigation state for each tab within the app. Routing is maintained within the app and within the TabVC and NavVC. But we need some way of getting back to where we were — some way of sharing links with friends.

My solution was to come up with a dead simple router that is responsible only for the entry point to my program. After that, views would update url in the browser, but the router was no longer used in the control flow of the app. For this reason, I created a dead simple client-only router ccorcos:client-router. Here’s an example of how I’m using it in Shindig.

# only use the router as an entry point to the app
start = do ->
called = false
(f) ->
unless called
called = true
f()
Router.route 'feed', start (route) ->
React.render App
initialRoute: route
instance: ReactUI.instance
, document.body
# ...

The point of the router is just to tell the app where to start. After that, all the views and state are handled internally. Each view sets the url in the browser so users can get back to the same view, and the route passed to renderScene in the NavVC is the same format as the route passed from the router which makes everything work very nicely together.

Push/Pop Animations

The push and pop animations for the NavVC are up to you! The NavVC uses a React.addons.CSSTransitionGroup component to wrap the views with transitionName of “navvc-push” and “navvc-pop” so you can do whatever transitions you want so have at it! The possibilities are endless.

I have some suggestions about how you write these animations though. I’ve configured a lot of animations in this app, and I’ve figured out a away to keep your Stylus / SCSS nice and dry. But thats the topic of the next article.

--

--