Open joaosoares opened 7 years ago
IMO, the reducer state cannot be the same with the router object received from withRouter
.
With withRouter
, it provides props for a single route. However, the proposed ConnectedRoute
can be applied to multiple routes.
We might provide additional key
prop to ConnectedRoute
like this:
import { ConnectedRouter, ConnectedRoute } from 'connected-react-router'
...
<ConnectedRouter history={history}>
<ConnectedRoute key="parent" path='/example/:username' render={({match}) => (
<div>
{match.params.username}
<ConnectedRoute key="child" path={`${match.url}/:item`} render={({match}) => (
{match.params.item}
)} />
</div>
)} />
</ConnectedRouter>
Then, if the URL is /example/supasate/pencil
, the reducer state might be like this:
{
history: {
location: {
path: '/example/somevalue'
}
},
matches: {
parent: {
params: {
username: 'supasate',
},
},
child: {
params: {
item: 'pencil',
},
}
}
}
However, the mapStateToProps
needs to know the key name which makes it tight coupling with the route. So, I don't think it's the right way to go.
If you have any idea, feel free to discuss :)
That's true. Maybe we could connect the <Switch />
components, since they match exclusively with a route?
The key property would still have to be set, but the coupling would happen on a higher-level, as there are usually fewer switches than routes and they're less likely to change dynamically.
<ConnectedRouter history={history}>
<ConnectedSwitch key="mainnav">
<Route path='/example/:username' render={({match}) => (
<div>
{match.params.username}
<Route path={`${match.url}/:item`} render={({match}) => (
{match.params.item}
)} />
</div>
)} />
<Route path ='/other/:id' component={OtherComponent}/>
</ConnectedSwitch>
</ConnectedRouter>
The state for /example/supasate
could be
{
/* ... */
matches: {
mainnav: {
params: {
username: 'supasate',
},
}
}
}
And the state for /other/someid
would be
{
/* ... */
matches: {
mainnav: {
params: {
id: 'someid',
},
}
}
}
As for the child route, it'd only be connected if it's enclosed on its own <Switch />
Yes, the coupling still exists. A component needs to know that it will be wrapped in which key of ConnectedSwitch
.
Compared to connecting a route directly with ConnectedRoute
in the previous comment, I prefer ConnectedRoute
to ConnectedSwitch
because it's quite straightforward about mapping one-to-one of the match
we need and the state in redux store. Anyway, I still don't like how coupling it is.
We may keep this issue open and see other ideas if there is the same demand on this feature.
I agree this still needs some thought. For now, the workaround I found was to create a static route config and use the matchPath
function provided by React Router to avoid parsing the current path manually.
@joaosoares could you also share the way you are doing with matchPath
? I am using createMatchSelector
in react-router-redux
and wondering if there is a same method in connected-react-router
.
Try this:
import { matchPath } from 'react-router-dom';
const match = matchPath(
router.location.pathname, // like: /course/123
{ path: '/course/:id' }
);
const id = match.params.id; // get: 123
Any headway on this issue? It seems like a major limitation to not have access to the match properties in redux (for use in thunks)
I'm also suprised this isn't a basic feature. For now I'm using matchPath
.
Whats the point of having the key prop? Can't we just merge the state?
{
history: {
location: {
path: '/example/somevalue'
}
},
matches: {
params: {
username: 'supasate',
item: 'pencil'
}
}
}
@jorgenbs @trungdq88 you can do this way too:
import { createMatchSelector } from 'connected-react-router';
const matchSelector = createMatchSelector({ path: '/course/:id })
const match = matchSelector(state) // like: /course/123
const id = match.params.id; // get: 123
@brneto awesome thanks !
+1 lets get match params into state
+1 for match params into state
My thumb up to @brneto, but can't do
import { createMatchSelector } from 'connected-react-router';
in TypeScript envirement
so I used
import {createMatchSelector} from 'connected-react-router/esm/';
Maybe wrong but works
Anyway, @brneto , I also +1 for match params into state, because otherwise it is impossible to create reusable component with nested routes:
const Topics = () => {
const match = useStore(state => ❓matchSelector❓(state));
/* 🤔 how can I define
const matchSelector = createMatchSelector({ path: '....' })
if I even didn't know at which path my re**usable** component will appear
*/
return (
<div>
<h2>Topics</h2>
<Link to={`${match.url}/details`}>Show Details</Link>
</ul>
<Route path={`${match.path}/:id`} component={TopicDetails} />
</div>);
}
It is very elegant and convenient to use:
import useReactRouter from 'use-react-router';
const { history, location, match } = useReactRouter();
for those who already use react hooks, but it will be better to have same ability in connected-react-router
@jorgenbs @trungdq88 you can do this way too:
import { createMatchSelector } from 'connected-react-router'; const matchSelector = createMatchSelector({ path: '/course/:id }) const match = matchSelector(state) // like: /course/123 const id = match.params.id; // get: 123
The problem of this method, you must know exact path matcher. If you have two paths in one epic that loads box, like:
@joaosoares did your technique evolved? BTW , I guess the following apply to connected-react-router too.
Source: https://github.com/reactjs/react-router-redux#how-do-i-access-router-state-in-a-container-component
You should not read the location state directly from the Redux store. This is because React Router operates asynchronously (to handle things such as dynamically-loaded components) and your component tree may not yet be updated in sync with your Redux state. You should rely on the props passed by React Router, as they are only updated after it has processed all asynchronous code.
My two cents would be... we want Route Paired Data Fetching, as in: internal/external url change -> saga/thunk -> fetch data -> notify reducers
So this boils down to... if we can't get the params from Redux:
<Provider store={store}>
<ConnectedRouter history={history} >
<Route path={["/","/employee/:id", "/all","/employee/:id/all"]} exact component={App}/>
</ConnectedRouter>
</Provider>
Then in App.js you alert thunk or sagas sending them a LOCATION_SYNC action with location and match like:
dispatch({type: 'LOCATION_SYNC', location: props.location, match: props.match})
thunk/sagas will compare the route and params, verify that the correct employee has already been fetched, and if not go fetch it.
Two lines of code -> go to redux development tools -> see LOCATION_SYNC there 💃
Note that all internal app navigation should be done with dispatch(push(url)). In some cases you may want to... after changing something internally... change the url without navigating.
Other than that, it would be great if connected-react-router guys talk with react first router guys and see if they can merge together or at least interact.
Here is some discussion about possibilities with a low usage library https://medium.com/faceyspacey/redux-first-router-data-fetching-solving-the-80-use-case-for-async-middleware-14529606c262
here a library to quickly match many routes to pick a winner https://www.npmjs.com/package/route-recognizer
I don't know if it helps anyone, but I'm using custom route that dispatches an action when it's being accessed:
import React from "react";
import { Route } from "react-router";
import { ReactReduxContext } from 'react-redux'
export const CustomRoute = ({ component: Component, computedMatch, ...rest }) => (
<Route {...rest} render={props => {
// custom logic here, if needed
return <ReactReduxContext.Consumer>
{({ store }) => {
store.dispatch({ type: "ROUTE_CHANGED", url: computedMatch.url, path: computedMatch.path, params: computedMatch.params })
return <Component {...props} />;
}}
</ReactReduxContext.Consumer>;
}
} />
);
And I use it later as follows:
<ConnectedRouter history={history}>
<Switch>
<CustomRoute exact path='/login' component={LoginPage} />
// ...
</Switch>
</ConnectedRouter>
It dispatches an action called ROUTE_CHANGED with url, path and params from match object that i can use in any reducer/saga/component.
@kjeske that's an elegant solution
for my version
And then I can set in index.js something that notifies in any route, listing all my routes like
<ConnectedRouter history={history}>
<Switch>
<CustomRoute
path={[
"/",
"/results",
"/results/:id",
"/pets/:id",
"/all",
"/pets/:id/all",
"/doctors"
]}
exact
component={App}
/>
</Switch>
</ConnectedRouter>
`
The ConnectedRouter sends its own syncing to redux but in a incomplete fashion :_(
do we have some updates or estimates when match
or params
will be available in reducer or in thunk?
It doesn't seem like it is possible to access the match object in the reducer. Should there be a way to connect a specific Route so that its state is saved to the reducer as well? Something like
that would generate the state
This would basically make the reducer state be the same as the Router object received from the
withRouter
connector.