fridays / next-routes

Universal dynamic routes for Next.js
MIT License
2.47k stars 230 forks source link

Interoperability with redux #90

Closed breeny closed 1 year ago

breeny commented 7 years ago

Is there a way with the inbuilt link/router componentry to track/invoke these as actions so that redux-dev-tools can replay not only state data changes but all route changes?

I assume something would be possible with a BYO link/router, but was wondering if it's possible out of the box?

mrwillis commented 7 years ago

I'm also interested in this. It would be helpful to link up redux state with route. Currently you have to keep both in sync yourself if you want to keep track of which route you are on. From what I understand, I don't think it is that easy though seeing that react-redux-router exists as a repo. Then we also have to think about SSR.

Just thinking out loud, I think what we could do is make something like a <ConnectedRouter> a la react-redux-router v5 that would be nested under your redux Provider, taking that store. Then attach a router middleware to the store like react-redux-router. From there, maybe you can use the hooks here like

routeChangeComplete(url)

and then store.push(urlChange(url)).

breeny commented 7 years ago

@mrwillis - one thing I was having a look at was abstracting out from the Link component and creating one within the application that dispatches actions for route changes, then using redux to refer to the Router instance to push the changes.

The benefit of this would be that whatever the value the store had for the URL, would be rendered by the router. I don't think it would be too difficult (however may have some odd edge cases with parameters etc. and making that work cleanly), very similar to react-redux-router.

In the longer term though, would be nice to be able to hook next-routes into the store and have that happen auto-magically.

mrwillis commented 7 years ago

@breeny Ok so you're saying like extend Link with additional functionality? I believe this is what @fridays did in his implementation. If you see he is effectively wrapping the next.js Router and Link. We could just add some hooks there that dispatch actions for route changes that are already completed like you said. I agree with this! We could make a reducer like react-redux-router that listens for these actions and updates the store with the correct route. Is this what you're thinking?

// From fridays' index.js in this repo
class Routes {
  constructor ({
    Link = NextLink,
    Router = NextRouter,
    Store = null
  } = {}) {
    this.routes = []
    this.Link = this.getLink(Link)
    this.Router = this.getRouter(Router)
    this.store = Store
    this.Router.onRouteChangeComplete = (url) => {
      dispatch(ROUTE_CHANGE)
    }
  }
// ...
}
// in routerReducer.js

const routerReducer = (state = '', action) => {
  switch (action.type) {
    case ROUTE_CHANGE:
      return { action.url }

}

When you say magically hook next-routes into the store and have that happen auto-magically, isn't this what we are doing? :) Effectively the consumer would just maybe do something like this:

In routes.js,

/* eslint-disable no-multi-assign */
const nextRoutes = require('next-routes');
const initStore = require('../../store/initStore')
const store = initStore() // where initStore is memoized to re-use the store on the client and recreate every time on the server with the caveat that even if we are on the server, we cannot make 2 stores.

const routes = module.exports = nextRoutes(store);

routes.add('my-route', '/my-route/:slug');

Sorry for the inaccuracy

breeny commented 7 years ago

@mrwillis I might be misunderstanding your implementation but isn't

this.Router.onRouteChangeComplete = (url) => {
  dispatch(ROUTE_CHANGE)
}

only reacting and storing the new URL in state, rather than actually pushing the url to the router?

By comparison I was thinking of something like this (assume that the Router class is connected to state and would call onRouteChange)

class Routes {
  constructor ({
    Link = NextLink,
    Router = NextRouter,
    Store = null
  } = {}) {
    this.routes = []
    this.Link = this.getLink(Link)
    this.Router = this.getRouter(Router)
    this.store = Store
    this.onRouteChange = ({ url, props }) => {
      this.Router.push(url, props);
    }
  }
// ...
}

In this case, by pushing a new path with queries to state, the router responds to the state change directly, giving us the ability to re-play those state changes in redux-dev-tools (as well as all the logging benefits etc. that comes with that).

Forgive me if I misunderstood what you meant above.

breeny commented 7 years ago

Another option as well would be to replicate the functionality as per react-router-redux here, so that it's possible to dispatch an action that triggers state to change, then the middleware updates the Router.

https://github.com/reactjs/react-router-redux/blob/master/src/sync.js for reference