Property 'from' does not exist on type 'IntrinsicAttributes & NavigateProps' - react-router-dom v6 - react-router

I migrated from react-router-dom 5 to version 6 and the Navigate you see below used to be Redirect. The problem is that from from from={from} (the first one) errors saying
Type '{ key: string; from: string; to: any; }' is not assignable to
type 'IntrinsicAttributes & NavigateProps'. Property 'from' does not
exist on type 'IntrinsicAttributes & NavigateProps'.
I don't know if it has to be replaced with something else from v6 of react-router-dom but any help will be greatly appreciated.
const redirects = routeAliases(routes).map(({ from, to }) => {
return <Navigate key={`${from}-${to}`} from={from} to={{ ...location, pathname: to }} />;
});

In react-router-dom v6 to fully replicate the v5's Redirect component with from prop you must render the Navigate component in a Route component.
<Route path={fromPath} element={<Navigate to={toPath} replace />} />
Applied to your code snippet:
const redirects = routeAliases(routes).map(({ from, to }) => (
<Route
key={`${from}-${to}`}
path={from}
element={<Navigate to={{ ...location, pathname: to }} replace />}
/>
));

Related

Why can't I use querystrings in my router?

I want to use the url path with the query string in the router as shown below.
//App.js
<Route
path={"/my_account"}
component={Pages.account}
/>
<Route
exact
path={"/support_main/notice?page=1"}👈 Lik this!!!!!!!!!
component={Pages.support_notice_list}
/>
// link.js
<Link to={"/support_main/notice?page=1"} className="menu-list-link">
<ListItemText
primary={item.text}
className="munu-list-text"
/>
In this case, the page cannot be found.....:(
//App.js
<Route
exact
path={"/support_main/notice"}👈 OK
component={Pages.support_notice_list}
/>
// link.js
<Link to={"/support_main/notice?page=1"} className="menu-list-link">
<ListItemText
primary={item.text}
className="munu-list-text"
/>
In the above case, it works normally.
Is there any way to use query string in router ?
Simply put, it's because RRDv5 deals only in matching the path, not any part of the query search string.
For route path matching, only the "/support_main/notice" is considered. To make this a bit more clear, here is a more conventional way to link to "/support_main/notice" and pass a search string. Here the path and search string are split up and explicitly passed.
<Link
to={{
pathname: "/support_main/notice",
search: "?page=1"
}}
>
To notice
</Link>
When you link to "/support_main/notice?page=1" the search isn't part of the path matching.
Link to-object
to: object
An object that can have any of the following properties:
pathname: A string representing the path to link to.
search: A string representation of query parameters.
hash: A hash to put in the URL, e.g. #a-hash.
state: State to persist to the location.
<Link
to={{
pathname: "/courses",
search: "?sort=name",
hash: "#the-hash",
state: { fromDashboard: true }
}}
/>
Consider the following code:
const Page1MatchPath = () => <h1>Page 1 matched by path</h1>;
const Page1MatchPathAndQuery = () => <h1>Page 1 matched by path and query</h1>;
export default function App() {
return (
<Router>
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<ul>
<li>
<Link to="/support_main/notice?page=1">To notice (string)</Link>
</li>
<li>
<Link
to={{
pathname: "/support_main/notice",
search: "?page=1"
}}
>
To notice (object)
</Link>
</li>
</ul>
<Switch>
<Route
path={"/support_main/notice?page=1"}
component={Page1MatchPathAndQuery}
/>
<Route path={"/support_main/notice"} component={Page1MatchPath} />
</Switch>
</div>
</Router>
);
}
Note that even though the full path and query string is listed first in the Switch that is doesn't match, and is skipped over and matched by path alone. In other words, it's too specific to match "/support_main/notice", and yet doesn't take the query string into consideration for matching.
But why don't query strings work in path prop?
This is very likely due to the way path matching is handled. Under-the-hood RRDv5 path props are strings that path-to-regex#^1.7.0 understands. In this specific case the "?" character is interpreted as an optional parameter.
Optional
Parameters can be suffixed with a question mark (?) to make the
parameter optional. This will also make the prefix optional.
var re = pathToRegexp('/:foo/:bar?', keys)
// keys = [{ name: 'foo', ... }, { name: 'bar', delimiter: '/', optional: true, repeat: false }]
re.exec('/test')
//=> ['/test', 'test', undefined]
re.exec('/test/route')
//=> ['/test', 'test', 'route']
To also more firmly put it, the Usage outright also states in a note:
Please note: The RegExp returned by path-to-regexp is intended for use with pathnames or hostnames. It can not handle the query
strings or fragments of a URL.

react-router-dom 6 upgrade help: All component children of <Routes> must be a <Route> or <React.Fragment>

Our application recently updated to the beta versions of react-router-dom, and things were fine. Then when I try to update to 6.0.2, I get lots of invariant errors about All component children of <Routes> must be a <Route> or <React.Fragment>. This is because we have our routes defined as follows:
Feature.jsx:
export const FeatureRoutes = () => (
<Routes>
<Route path="/" element={<Feature1 />} />
<Route path="/*" element={<NotFound />} />
</Routes>
);
routes.jsx:
export const routes = [
{
path: "feature",
component: FeatureRoutes,
},
/* lots of other routes, defined the same way: <Route> wrapped in a component */
];
App.jsx:
<Routes>
{routes.map((route) => (
<Route key={route.path} path={`${pathPrefix}/${route.path}/*`}>
<route.component />
</Route>
))}
</Routes>
This now results in the error above, because the inner routes (for example FeatureRoutes) are wrapped in a functional component. I've tried returning the literal JSX but then get another error. I'm not sure how to fix this: is the only answer to completely rewrite how we define our routes? We also have some routes that are stored in the back-end and map to custom components - again I'm not sure how I can wrap these now I'm not allowed to have a component between Routes and Route.
Any advice appreciated.
I believe a small refactor will get your app rendering again.
In the routes array rename component to Component so it can be rendered as a React component, i.e. as a properly named React component (PascalCased).
const routes = [
{
path: "feature",
Component: FeatureRoutes
}
/* lots of other routes, defined the same way: <Route> wrapped in a component */
];
When mapping the routes render the Component out on the Route component's element prop as JSX.
<Routes>
{routes.map(({ path, Component }) => (
<Route
key={path}
path={`${pathPrefix}/${path}/*`}
element={<Component />}
/>
))}
</Routes>

how to parse values from JSON.stringified data in a text component sent from a different page

with regard to this tutorial "React Router Native - Passing Data" https://www.youtube.com/watch?v=SdOWxoH3HLg by #benawad user:4272160
I can't figure out how to extract bob or 5 from
{"val1:"bob","val2":5}
from the stringified string data in <Text>JSON.stringify(location.state)}</Text>
when passing data between pages
I've tried to contact #benawad through a comment, I've searched google and here for similar but found nil relevant. I tried a regex unsuccessfully but there has to be a better way anyway...
code is at https://github.com/benawad/react-router-native-example/tree/1_advanced
// Home.js
import React from "react";
import { View, Text, Button } from "react-native";
export default ({ history }) => (
<View>
<Text>This is the home page</Text>
<Button title="change page" onPress={() =>
history.push("/products", {val1: "bob", val2: 5})
/>
</View>
);
// Products.js
import React from "react";
import { View, Text, Button } from "react-native";
export default ({ history, location }) => (
<View>
<Text>Product 1</Text>
<Text>Product 2</Text>
<Text>{JSON.stringify(location.state)}</Text>
<Button title="change page" onPress={() => history.push("/")}/>
</View>
);
I thought about trying to JSON.parse the stringified data. No joy. I tried location.state.val but just got
TypeError: undefined is not an object (evaluating 'location.state.val')
You need to pass state through the history api. Change your Home component to
export default ({ history }) => (
<View>
<Text>This is the home page</Text>
<Button title="change page" onPress={() =>
history.push("/products", {val1: "bob", val2: 5)} />
</View>
);
and then access location.state.val1 and location.state.val2 directly in your Products component.
See https://github.com/benawad/react-router-native-example/blob/1_advanced/ChangePageButton.js#L9 for the similar history line in the tutorial code.
The JSON.stringify used in the example code is just there as an illustration so that the entire location.state can be displayed in a Text element.
You can pass props to history object,
<Button title="change page"
onPress={() => history.push({
pathname: '/',
state: { locationData: JSON.stringify(location.state) } //JSON.stringify needed if you want to pass object otherwise don't use.
})}
/>
In the component which is rendered with / route, you can access the props like,
{JSON.parse(props.location.state.locationData)} //Functional component
{JSON.parse(this.props.location.state.locationData)} //Class based component
Note: JSON.parse needed if object is being passed otherwise no need to use.

How to have react route path one/two/three?

I would like to have something like this
<Route
path="/one/two/three"
render={() => (
<Component/>
)}
exact
/>
But it does not seem to work locally when I wrap it all into a Router. I only see the contents of the Component when it is just /one, but I would like to have /one/two/three as the path (being my root - user lands on that path).
In react-router-v4 you can have below routes only,
<Route path="/one" render={() => (<Component/>)} exact /> //Direct route without any params
or
<Route path="/one/:two" render={() => (<Component/>)} exact /> //Route with params, here `:two` is the parameter to route
If you want to use routes like in your example, then this can be achieve using BrowseRouter basename attribute,
<BrowserRouter basename="/one/two">
<Route path="/three" render={() => (<Component/>)} exact />
</BrowserRouter>
And you Link should be like,
<Link to="/three"/> // renders <a href="/one/two/three">
Few reasons why this did not work for me.
I was missing output: { publicPath: '/' }, in my webpack config.
And weirder thing was that I was using PureComponent instead of Component which totally broke the rending when redirecting to the next step with similar path(x/y/z) or using Link.

React-router and jsx-no-bind

I'm using react-router to do navigation. I have some components that are requiring a login before they can be seen.
Therefore I have this: (from following documentation in react-router)
<Route
{...rest}
render={props =>
isLoggedIn()
? <Component {...props} />
: <Redirect
to={{
pathname: '/login',
state: {from: props.location},
}}
/>}
/>
However, there is a problem with this, since jsx-no-bind disallows arrow functions. What is the correct way around this? Or should this simply be ignored because it for some reason does not suffer the same performance hits?
Official React documentation on render props:
https://reactjs.org/docs/render-props.html
This documentation uses examples like:
<DataProvider render={data => (
<h1>Hello {data.target}</h1>
)}/>
Be aware of caveats as mentioned here:
https://reactjs.org/docs/render-props.html#caveats
This includes the use of PureComponents