FormidableLabs / redux-little-router

A tiny router for Redux that lets the URL do the talking.
MIT License
1.04k stars 114 forks source link

Example of custom Middleware #239

Closed fpoirier1 closed 6 years ago

fpoirier1 commented 6 years ago

Your custom middleware can intercept this action to dispatch new actions in response to URL changes.

I am trying to wrap my head around this little-router. I can't seem to find any example of a custom middleware implementation that would handle the LOCATION_CHANGED action.

Here is my use case :

  1. The user visits the todo list
  2. The user clicks the edit button of a todo
  3. a PUSH(/todos/{:id}) action is dispatch
  4. My custom middleware catches the LOCATION_CHANGED action
  5. My custom middleware triggers dispatch(editTodo(id))
  6. The action creator 6.1 looks the todo = getState().todos.find(...) list 6.2dispatch({type:'TODO_SELECTED', todo}) 6.3 fetch todo details fetch('/todo/123') (using thunk) 6.4 On fetch, the action creator dispatch({'TODO_DETAILS_SUCCEED'})
  7. The reducers are thin and update the state based on the action parameters.
  8. The <Fragment> components renders accordingly to the state and the containers maps the state to the props.

Does that logic make sens ? Is there a simpler way to avoid using a middleware ?

Using this methodology, most of the dispatch will come from the custom middleware and the containers will simply use the push() function right ?

dpwrussell commented 6 years ago

I am just dashing out the door, but potentially this might help you: https://github.com/FormidableLabs/redux-little-router/issues/184#issuecomment-322487785

Or maybe this. Sorry it's got some of my own business logic in there.

// Middleware to trigger async actions based on the route that is currently being loaded.
export const routeFetch = store => next => action => {
  if (action.type === LOCATION_CHANGED) {

    // Always make sure vocabs are loaded
    store.dispatch(fetchVocabs());

    if (
      action.payload
      && action.payload.params
      && action.payload.params.id
    ) {

      switch(action.payload.route) {

        case '/canonical/:id':
          store.dispatch(fetchCanonical(
            ModelUtils.buildHref('canonical', action.payload.params.id)
          ));
          break;

        case '/batch/:id':
          store.dispatch(fetchBatch(
            ModelUtils.buildHref('batch', action.payload.params.id)
          ));
          break;

        case '/unit/:id':
          store.dispatch(fetchUnit(
            ModelUtils.buildHref('unit', action.payload.params.id)
          ));
          break;

        case '/qc/:id':
          store.dispatch(fetchQc(
            ModelUtils.buildHref('qc', action.payload.params.id)
          ));
          break;
      }
    } else if (
      action.payload
      && action.payload.route === '/search'
      && action.payload.query.q
    ) {
      store.dispatch(fetchSearchResults(action.payload.query.q));
    }
  }

  return next(action);
};

And obviously if the state has not yet loaded, you have to handle that at the React component level. Usually I am preloading everything that could be the users "next" click so that they never have to wait on async for the next page to load. Combine that approach with server side rendering for when they hit a route directly and you have a nice user experience.

fpoirier1 commented 6 years ago

Thanks for your reply, it is really appreciated. Do you only use the middleware logic to fetch data ? Would it be a good place to do something like this ?

...
case '/unit/:id':
    store.dispatch(selectUnit(action.payload.params.id))
    break;
...

// actions.js
export const selectUnit = (id) =>{
    return (dispatch) => {
       dispatch(fetchUnit(
           ModelUtils.buildHref('unit', id)
        ));

        dispatch({type: SELECTED_UNIT, id});
    }
}
dpwrussell commented 6 years ago

If I understand this correctly your action-creator dispatches multiple actions, this is usually fine.

e.g. The one I reference from the unit part of the middleware looks like this.

export const fetchUnit = href => (dispatch, getState) => {
  if (isFetchingHref(href)(getState())) {
    return Promise.resolve();
  }
  dispatch(request(href));

  return api.fetch.unit(href)
    .then(data => {
      dispatch(receiveObjs(href, mergeObjMaps(data)));
      return data;
    })
    .catch(handle404(dispatch)(href));
};
fpoirier1 commented 6 years ago

I guess that answer my question. Thank you so much for your help. I will provided an example of my custom middle soon maybe it will inspire other developers.