Uncaught TypeError: match is undefined - javascript

import books from '../books'
function BookScreen({ match }) {
const book = books.find((b) => b._id === match.params.id)
return (
<div>
{book}
</div>
)
}
export default BookScreen
I keep getting the error match is undefined. I saw a tutorial with similar code, but it seemed fine when put to the test. Any clue what might be the issue?

If match prop is undefined this could be from a few things.
react-router-dom v5
If using RRDv5 if the match prop is undefined this means that BookScreen isn't receiving the route props that are injected when a component is rendered on the Route component's component prop, or the render or children prop functions. Note that using children the route will match and render regardless, see route render methods for details.
Ensure that one of the following is implemented to access the match object:
Render BookScreen on the component prop of a Route
<Route path="...." component={BookScreen} />
Render BookScreen on the render or children prop function of a Route and pass the route props through
<Route path="...." render={props => <BookScreen {...props} />} />
or
<Route path="...." children={props => <BookScreen {...props} />} />
Decorate BookScreen with the withRouter Higher Order Component to have the route props injected
import { withRouter } from 'react-router-dom';
function BookScreen = ({ match }) {
const book = books.find((b) => b._id === match.params.id)
return (
<div>
{book}
</div>
);
};
export default withRouter(BookScreen);
Since BookScreen is a React function component, import and use the useParams hook to access the route match params
import { useParams } from 'react-router-dom';
...
function BookScreen() {
const { id } = useParams();
const book = books.find((b) => b._id === id)
return (
<div>
{book}
</div>
);
}
react-router-dom v6
If using RRDv6 then there is no match prop. Gone are the route props. Here only the useParams and other hooks exist, so use them.
import { useParams } from 'react-router-dom';
...
function BookScreen() {
const { id } = useParams();
const book = books.find((b) => b._id === id)
return (
<div>
{book}
</div>
);
}
If you've other class components and you're using RRDv6 and you don't want to convert them to function components, then you'll need to create a custom withRouter component. See my answer here with details.

I guess you rendered BookScreen component before match is initialized. This is why match is undefined when BookScreen rendered. Render it conditionally like below.
return (
... other components
{match && <BookScreen match={match}/>}
)
Also, I leave useful site which tells you some good ways to do that. :D
https://www.digitalocean.com/community/tutorials/7-ways-to-implement-conditional-rendering-in-react-applications
I hope you find an answer :D

Related

Unable to get properties off navlink [duplicate]

This question already has answers here:
How to pass data from a page to another page using react router
(5 answers)
Closed 4 months ago.
I'm teaching myself React (using v18.1.0) and I'm struggling to understand why I am getting an undefined object off the properties I'm passing to a component through a NavLink using react-router-dom v.6.3.0.
I have a component that has a NavLink that is created when a certain variable (let's call it "var1") is not null, like so:
[...]
{
var1 != null
?
<NavLink className="Menu-Button" to={{pathname : "/Component1"}} state = {{ props : var1 }}>ButtonContent</NavLink>
: <p/>
}
[...]
The component being routed to (let's call it "Component1") looks like this:
import React from 'react';
import {useLocation} from 'react-router-dom';
const Component1= () => {
const location = useLocation();
const { props } = location;
console.log(props);
return(
[...]
)
};
export default Component1;
The output of that console.log is undefined. I've also tried using props.location instead of useLocation(), but I get the same issue. Where am I going wrong?
EDIT:
Including route config in App.js as requested by #Nick Vu:
N.B. Toolbar is a component that acts as a header / navigator
[all the imports]
const App = () => {
return (
<BrowserRouter>
<Toolbar />
<Routes>
[all the existing routes that work fine]
<Route exact path='/Component1' element={<Component1 /> } />
</Routes>
</BrowserRouter>
);
}
export default App;
Your NavLink seems good, but according to your setup, in Component1, you should access props values from NavLink by state.
import React from 'react';
import {useLocation} from 'react-router-dom';
const Component1= () => {
const location = useLocation();
const props = location?.state?.props; //use `?` to avoid errors from missing states
console.log(props)
return(
[...]
)
};
export default Component1;
Sandbox link
This was embarassing but, changing
state = {{ props : var1 }}
to
state={{props : var1}}
has resolved the issue.

How to call a Component which name is passed as props in React?

i'm new at React. Basically i want to call different components based on the string passed as props. I've component named Device that have as prop the name of a particular device and in Device i want to call another components which has as name the props. Something like this: <DeviceName/>
This is my code:
App.js
<Device name = {devName} />
Device.js
import DeviceMark from ./devices/DeviceMark
function DeviceName({devName}) {
const DevName = devName;
return (
<Router>
<Switch>
<Route path = "/" exact><DevName /></Route>
</Switch>
</Router>
)
}
Imagine that in this case DevName will replace DeviceMark
When you really need to convert string component names to actual components while rendering, I can suggest following way
import DeviceMark from ./devices/DeviceMark
function DeviceName({devName}) {
const mappingComponents = {
foo: FooComponent,
bar: BarComponent,
deviceMark: DeviceMark
};
const ComponentToRender = mappingComponents[devName || 'foo'];
return (
<Router>
<Switch>
<Route path = "/" exact><ComponentToRender></ComponentToRender></Route>
</Switch>
</Router>
)
}
More details in official doc - https://reactjs.org/docs/jsx-in-depth.html
I guess you need to use React.createElement. It allows to create React element from string (if it's a basic html tag) or from function (if it's a React component).
import React from 'react'
import ReactDOM from 'react-dom'
const DeviceName = () => {
return 'sub component'
}
const Device = ({subDev}) => {
return (
<div>
{ React.createElement(subDev) }
</div>
)
}
function App() {
return <Device subDev={DeviceName} />
}
ReactDOM.render(<App />, document.getElementById('root'))
It's a little example. I have a main component Device. It accepts a sub-component via a prop and I pass sub-component to createElement. It does not matter if sub-component whether basic html tag or React component.
It's codesandbox example.

How can I define the route for the following schema in react router dom

I'm trying to make the page for the following route:
/password?token=479wasc8-8ffe-47a6-fatw-624e9d2c323a&user=e238bc4c-cf79-4cc3-b4a5-8fe7ewrta54a9w8a5
My solution to that initially was like the following:
<Route exact path='/password?token=:token&user=:user' component={Password}/>
But I guess I'm missing something important here. I tried various sources, but didn't find anything close to my problem.
The Password component can make use of the useLocation hook to read the query string:
<Route path='/password' component={Password} />
import { useLocation } from 'react-router-dom'
const useQuery = () => {
return new URLSearchParams(useLocation().search)
}
const Password = () => {
const query = useQuery()
return (
<div>
<p>{query.get('token')}</p>
<p>{query.get('user')}</p>
</div>
)
}
Here's the example on the react router website

Pass dynamic url to React Router

I use react-router-dom version ^5.1.2 and try to pass dynamic property. My root component looks like this:
export const RegisterRoutes: React.FunctionComponent<RouteComponentProps> = ({
match,
}: RouteComponentProps) => {
const root = match.url;
return (
<Switch>
<Route exact path={`${root}/success`} component={RegisterSuccess} />
<Route exact path={`${root}/success/email`} component={RegisterEmail} />
<Route exact path={`${root}/confirmation/email/:code`} component={RegisterEmailConfirmation} />
<Redirect to={root} />
</Switch>
);
};
Here is a child RegisterEmailConfirmation component:
export const RegisterEmailConfirmation: React.FunctionComponent<RouteComponentProps> = ({ match }) => {
console.log(match.params.code) // expected 'code' property from the url
return (
<SomeContent />
/>
);
I have an url from the BE with dynamic 'code' parameter, it looks like this: register/confirmation/email?code=fjds#dfF. How can I render RegisterEmailConfirmation component and read 'code' property inside? What's wrong with my code?
As you are using function component you could use useParams hook in your component. See from useParams documentation:
useParams returns an object of key/value pairs of URL parameters. Use it to access match.params of the current <Route>.
Try as:
export const RegisterEmailConfirmation: React.FunctionComponent<RouteComponentProps> = () => {
let { code } = useParams();
console.log(code);
return <>
{ /* your implementation */ }
</>
}
Also you can import as:
import { useParams } from "react-router-dom";
I hope this helps!

React Router v4 Nested match params not accessible at root level

Test Case
https://codesandbox.io/s/rr00y9w2wm
Steps to reproduce
Click on Topics
Click on Rendering with React
OR
Go to https://rr00y9w2wm.codesandbox.io/topics/rendering
Expected Behavior
match.params.topicId should be identical from both the parent Topics component should be the same as match.params.topicId when accessed within the Topic component
Actual Behavior
match.params.topicId when accessed within the Topic component is undefined
match.params.topicId when accessed within the Topics component is rendering
I understand from this closed issue that this is not necessarily a bug.
This requirement is super common among users who want to create a run in the mill web application where a component Topics at a parent level needs to access the match.params.paramId where paramId is a URL param that matches a nested (child) component Topic:
const Topic = ({ match }) => (
<div>
<h2>Topic ID param from Topic Components</h2>
<h3>{match.params.topicId}</h3>
</div>
);
const Topics = ({ match }) => (
<div>
<h2>Topics</h2>
<h3>{match.params.topicId || "undefined"}</h3>
<Route path={`${match.url}/:topicId`} component={Topic} />
...
</div>
);
In a generic sense, Topics could be a Drawer or Navigation Menu component and Topic could be any child component, like it is in the application I'm developing. The child component has it's own :topicId param which has it's own (let's say) <Route path="sections/:sectionId" component={Section} /> Route/Component.
Even more painful, the Navigation Menu needn't have a one-to-one relationship with the component tree. Sometimes the items at the root level of the menu (say Topics, Sections etc.) might correspond to a nested structure (Sections is only rendered under a Topic, /topics/:topicId/sections/:sectionId though it has its own normalized list that is available to the user under the title Sections in the Navigation Bar).
Therefore, when Sections is clicked, it should be highlighted, and not both Sections and Topics.
With the sectionId or sections path unavailable to the Navigation Bar component which is at the Root level of the application, it becomes necessary to write hacks like this for such a commonplace use case.
I am not an expert at all at React Router, so if anyone can venture a proper elegant solution to this use case, I would consider this to be a fruitful endeavor. And by elegant, I mean
Uses match and not history.location.pathname
Does not involve hacky approaches like manually parsing the window.location.xxx
Doesn't use this.props.location.pathname
Does not use third party libraries like path-to-regexp
Does not use query params
Other hacks/partial solutions/related questions:
React Router v4 - How to get current route?
React Router v4 global no match to nested route childs
TIA!
React-router doesn't give you the match params of any of the matched children Route , rather it gives you the params based on the current match. So if you have your Routes setup like
<Route path='/topic' component={Topics} />
and in Topics component you have a Route like
<Route path=`${match.url}/:topicId` component={Topic} />
Now if your url is /topic/topic1 which matched the inner Route but for the Topics component, the matched Route is still, /topic and hence has no params in it, which makes sense.
If you want to fetch params of the children Route matched in the topics component, you would need to make use of matchPath utility provided by React-router and test against the child route whose params you want to obtain
import { matchPath } from 'react-router'
render(){
const {users, flags, location } = this.props;
const match = matchPath(location.pathname, {
path: '/topic/:topicId',
exact: true,
strict: false
})
if(match) {
console.log(match.params.topicId);
}
return (
<div>
<Route exact path="/topic/:topicId" component={Topic} />
</div>
)
}
EDIT:
One method to get all the params at any level is to make use of context and update the params as and when they match in the context Provider.
You would need to create a wrapper around Route for it to work correctly, A typical example would look like
RouteWrapper.jsx
import React from "react";
import _ from "lodash";
import { matchPath } from "react-router-dom";
import { ParamContext } from "./ParamsContext";
import { withRouter, Route } from "react-router-dom";
class CustomRoute extends React.Component {
getMatchParams = props => {
const { location, path, exact, strict } = props || this.props;
const match = matchPath(location.pathname, {
path,
exact,
strict
});
if (match) {
console.log(match.params);
return match.params;
}
return {};
};
componentDidMount() {
const { updateParams } = this.props;
updateParams(this.getMatchParams());
}
componentDidUpdate(prevProps) {
const { updateParams, match } = this.props;
const currentParams = this.getMatchParams();
const prevParams = this.getMatchParams(prevProps);
if (!_.isEqual(currentParams, prevParams)) {
updateParams(match.params);
}
}
componentWillUnmount() {
const { updateParams } = this.props;
const matchParams = this.getMatchParams();
Object.keys(matchParams).forEach(k => (matchParams[k] = undefined));
updateParams(matchParams);
}
render() {
return <Route {...this.props} />;
}
}
const RouteWithRouter = withRouter(CustomRoute);
export default props => (
<ParamContext.Consumer>
{({ updateParams }) => {
return <RouteWithRouter updateParams={updateParams} {...props} />;
}}
</ParamContext.Consumer>
);
ParamsProvider.jsx
import React from "react";
import { ParamContext } from "./ParamsContext";
export default class ParamsProvider extends React.Component {
state = {
allParams: {}
};
updateParams = params => {
console.log({ params: JSON.stringify(params) });
this.setState(prevProps => ({
allParams: {
...prevProps.allParams,
...params
}
}));
};
render() {
return (
<ParamContext.Provider
value={{
allParams: this.state.allParams,
updateParams: this.updateParams
}}
>
{this.props.children}
</ParamContext.Provider>
);
}
}
Index.js
ReactDOM.render(
<BrowserRouter>
<ParamsProvider>
<App />
</ParamsProvider>
</BrowserRouter>,
document.getElementById("root")
);
Working DEMO
Try utilizing query parameters ? to allow the parent and child to access the current selected topic. Unfortunately, you will need to use the module qs because react-router-dom doesn't automatically parse queries (react-router v3 does).
Working example: https://codesandbox.io/s/my1ljx40r9
URL is structured like a concatenated string:
topic?topic=props-v-state
Then you would add to the query with &:
/topics/topic?topic=optimization&category=pure-components&subcategory=shouldComponentUpdate
✔ Uses match for Route URL handling
✔ Doesn't use this.props.location.pathname (uses this.props.location.search)
✔ Uses qs to parse location.search
✔ Does not involve hacky approaches
Topics.js
import React from "react";
import { Link, Route } from "react-router-dom";
import qs from "qs";
import Topic from "./Topic";
export default ({ match, location }) => {
const { topic } = qs.parse(location.search, {
ignoreQueryPrefix: true
});
return (
<div>
<h2>Topics</h2>
<ul>
<li>
<Link to={`${match.url}/topic?topic=rendering`}>
Rendering with React
</Link>
</li>
<li>
<Link to={`${match.url}/topic?topic=components`}>Components</Link>
</li>
<li>
<Link to={`${match.url}/topic?topic=props-v-state`}>
Props v. State
</Link>
</li>
</ul>
<h2>
Topic ID param from Topic<strong>s</strong> Components
</h2>
<h3>{topic && topic}</h3>
<Route
path={`${match.url}/:topicId`}
render={props => <Topic {...props} topic={topic} />}
/>
<Route
exact
path={match.url}
render={() => <h3>Please select a topic.</h3>}
/>
</div>
);
};
Another approach would be to create a HOC that stores params to state and children update the parent's state when its params have changed.
URL is structured like a folder tree: /topics/rendering/optimization/pure-components/shouldComponentUpdate
Working example: https://codesandbox.io/s/9joknpm9jy
✔ Uses match for Route URL handling
✔ Doesn't use this.props.location.pathname
✔ Uses lodash for object to object comparison
✔ Does not involve hacky approaches
Topics.js
import map from "lodash/map";
import React, { Fragment, Component } from "react";
import NestedRoutes from "./NestedRoutes";
import Links from "./Links";
import createPath from "./createPath";
export default class Topics extends Component {
state = {
params: "",
paths: []
};
componentDidMount = () => {
const urlPaths = [
this.props.match.url,
":topicId",
":subcategory",
":item",
":lifecycles"
];
this.setState({ paths: createPath(urlPaths) });
};
handleUrlChange = params => this.setState({ params });
showParams = params =>
!params
? null
: map(params, name => <Fragment key={name}>{name} </Fragment>);
render = () => (
<div>
<h2>Topics</h2>
<Links match={this.props.match} />
<h2>
Topic ID param from Topic<strong>s</strong> Components
</h2>
<h3>{this.state.params && this.showParams(this.state.params)}</h3>
<NestedRoutes
handleUrlChange={this.handleUrlChange}
match={this.props.match}
paths={this.state.paths}
showParams={this.showParams}
/>
</div>
);
}
NestedRoutes.js
import map from "lodash/map";
import React, { Fragment } from "react";
import { Route } from "react-router-dom";
import Topic from "./Topic";
export default ({ handleUrlChange, match, paths, showParams }) => (
<Fragment>
{map(paths, path => (
<Route
exact
key={path}
path={path}
render={props => (
<Topic
{...props}
handleUrlChange={handleUrlChange}
showParams={showParams}
/>
)}
/>
))}
<Route
exact
path={match.url}
render={() => <h3>Please select a topic.</h3>}
/>
</Fragment>
);
If you have a known set of child routes then you can use something like this:
Import {BrowserRouter as Router, Route, Switch } from 'react-router-dom'
<Router>
<Route path={`${baseUrl}/home/:expectedTag?/:expectedEvent?`} component={Parent} />
</Router>
const Parent = (props) => {
return (
<div >
<Switch>
<Route path={`${baseUrl}/home/summary`} component={ChildOne} />
<Route
path={`${baseUrl}/home/:activeTag/:activeEvent?/:activeIndex?`}
component={ChildTwo}
/>
</Switch>
<div>
)
}
In the above example Parent will get expectedTag, expectedEvent as the match params and there is no conflict with the child components and Child component will get activeTag, activeEvent, activeIndex as the parameters. Same name for params can also be used, I have tried that as well.
Try to do something like this:
<Switch>
<Route path="/auth/login/:token" render={props => <Login {...this.props} {...props}/>}/>
<Route path="/auth/login" component={Login}/>
First, the route with the parameter and after the link without parameter.
Inside my Login component I put this line of code console.log(props.match.params.token); to test and worked for me.
If you happen to use React.FC, there is a hook useRouteMatch.
For instance, parent component routes:
<div className="office-wrapper">
<div className="some-parent-stuff">
...
</div>
<div className="child-routes-wrapper">
<Switch>
<Route exact path={`/office`} component={List} />
<Route exact path={`/office/:id`} component={Alter} />
</Switch>
</div>
</div>
And in your child component:
...
import { useRouteMatch } from "react-router-dom"
...
export const Alter = (props) => {
const match = useRouteMatch()
const officeId = +match.params.id
//... rest function code
}

Categories