kadirahq / flow-router

Carefully Designed Client Side Router for Meteor
MIT License
1.09k stars 195 forks source link

Redirection on unknown defaults #562

Open chmanie opened 8 years ago

chmanie commented 8 years ago

I have a problem that might exist because of a bug in flow-router or on my choice of the correct approach for a particular problem.

Let me explain what I am trying to accomplish:

I am using flow-router in a mantra environment. The react components are populated via corresponding containers that fetch the data.

I set up the following routes:

FlowRouter.route('/', {
  name: 'root',
  action () 
    mount(MainLayoutCtx, {
      content: (<ParentComponent />)
    });
  }
});
FlowRouter.route('/:child1', {
  name: 'root.child1',
  action ({child1}) {
    mount(MainLayoutCtx, {
      content: (<ParentComponent child1={child1} />)
    });
  }
});
FlowRouter.route('/:child1/:child2', {
  name: 'root.child1.child2',
  action ({child1, child2}) {
    mount(MainLayoutCtx, {
      content: (<ParentComponent child1={child1} child2={child2} />)
    });
  }
});

The minimum required set of route parameters child1 and child2. That's why I want to redirect root and root.child1 to root.child1.child2, while populating sensible default parameters. To get those I need data from the DB (or another async source). As I said, this is being done in subcomponents of ParentComponent.

Just to give a vivid example: when slack starts, it first gets all the communities and selects a default community. Then all the channels are fetched and after that a default channel can be selected (it might be just the first in the list or saved via cookie or localStorage). Then the data from the channel itself is being fetched. I don't know if slack preloads all the data before showing anything at all. But I don't want to do that as I want the components/containers to manage their own data themselves.

I read in the Mantra specification:

If you need to redirect upon some condition (for example user is not authorized) use an action instead of route options like FlowRouter’s triggersEnter. Call the action from component or container’s composer function.

So I do the following in the containers (I'll just show the first one here):

import {useDeps, composeWithTracker, composeAll} from 'mantra-core';

export const composer = ({context, child1}, onData) => {
  const {FlowRouter} = context();
  // imagine getData() to be the function that get's the valid data (options) for child1
  getData().then(function(possibleChild1Array) {
    if (!child1) {
      // we landed in a route where param child1 is not given
      return FlowRouter.go('root.child1', {
        child1: possibleChild1Array[0].id
      });
    }
    onData(null, { possibleChild1Array }); // because we want to show a clickable list to make a selection later
  });
};

export default composeAll(
  composeWithTracker(composer),
  useDeps()
)(ParentComponent);

ParentComponent mounts another component that fetches possible data for child2 in the same manner.

Now to my problem:

On a page reload, everything gets picked up fine. But when a click a link like

/validChild1Id/validChild2Id (or do FlowRouter.go('/validChild1Id/validChild2Id'))

I get to an undefined state (both my components are mounted but the addressbar just shows /validChild1Id and I can not change (by click) to /validChild1Id/validChild2Id.

I can solve this by setting a timeout in the subcomponent that handles child2:

if (!child2) {
  setTimeout(function () {
    return FlowRouter.go('root.child1.child2', {
      child1: possibleChild1Array[0].id,
      child2: possibleChild2Array[0].id
    });
  });
}

So apparently if I wait for the next tick I don't end up in the undefined state anymore. This is a bit ugly. So my question is: I am even on the right track here? Maybe there's a completely different approach to do this. Or is it a bug?