Passing Arguments to a React Element in a React Router - javascript

I have created a Element called Jobscard that takes jobsList as an argument. I am also trying to set this jobsCard as a Router using the react-router.
I have looked into the problem and their seems to be no similar problem on the web, all solutions and guides use react components but this doesn't work for react Elements. Is this possible?
import React from 'react';
import { projectList } from './projectList';
import JobsCard from './JobsCard';
import { jobsList } from './jobsList';
import CardList from './CardList';
import Ham from './hamburger';
import { Route, Switch } from 'react-router-dom';
import { BrowserRouter } from 'react-router-dom';
import jobsList from './jobsList'
const App = () => {
return(
<div className='blue1 center'>
<BrowserRouter>
<Ham />
<Switch>
<Route exact path="/" render{props => <JobsCard jobsList={jobsList}> </JobsCard> }/>
<Route path="/projects" component={<CardList projectList={projectList}/>}/>
</Switch>
</BrowserRouter>
</div>
)
}
export default App;
The JobsCard Element is
import React from 'react';
import Job from './Job.js'
const JobsCard = ({jobsList}) => {
const cardDraw = jobsList.map((proj, i) => {
return <Job key={i} title={jobsList[i].title}
website={jobsList[i].website} description={jobsList[i].description}
logo={jobsList[i].logo} company={jobsList[i].company} years={jobsList[i].years}/>
})
return (
<div className=" cf justify-center ma3">
{cardDraw}
</div>
);
}
export default JobsCard;
jobsList looks like this
import energyxchain from ''
export const jobsList = [
{ title: '',
website: '',
description: '',
logo: ,
company: '',
years: '2018-2019'
},
{
title: '',
company: '',
website: '',
logo: '',
description: '',
years: '2017-2018'
}];
I would like the jobsCard to be a route.

From route rendering props:
render, which takes an inline function, should only be used when you have to pass in-scope variables to the component you want to render.
You should not use the component prop with an inline function to pass in-scope variables because you will get undesired component unmounts/remounts.
So your route should be either the ff:
Using component prop (should not pass variable):
<Route
exact path="/"
component={JobsCard}
/>
Using render prop:
<Route
exact path="/"
render={routeProps => (<JobsCard {...routeProps} jobsList={jobsList} />)}
/>
Normally, you should separate navigation from the internal data management (using redux connect).

Change const JobsCard to function JobsCard
If you are planning to use a constant, it shouldn't have any arguments.
you can do
const paragraph = <p>Hello World!</p>
and render inside a react component using {paragraph}

Related

Why is state being lost in my app when passed via history.push()?

I've created the following very simple React app to simulate the behavior I'm seeing in my production app.
Root component:
import {
BrowserRouter as Router,
Route,
RouteComponentProps,
Switch,
} from "react-router-dom";
import { Home } from "./home";
import { SubRoute } from "./sub-route";
export default function Root(props) {
return (
<Router>
<Switch>
<Route path="/" exact>
<Home {...props} />
</Route>
<Route path="/sub-route" exact>
<SubRoute />
</Route>
</Switch>
</Router>
);
}
Home component:
import { LocationDescriptor } from "history";
import * as React from "react";
import { useHistory } from "react-router-dom";
import { TestLocationState } from "./sub-route";
export const Home = () => {
const history = useHistory();
return (
<>
<h1>Home</h1>
<button
onClick={() => {
const location: LocationDescriptor<TestLocationState> = {
pathname: "/sub-route",
state: {
name: "STATE PAYLOAD",
},
};
history.push(location);
}}
>
Pass state to sub route
</button>
</>
);
};
SubRoute component:
import * as React from "react";
import { useLocation } from "react-router-dom";
export type TestLocationState = {
name: string;
};
export const SubRoute = () => {
const { state } = useLocation<TestLocationState | undefined>();
return (
<>
<h1>Sub Route</h1>
<div>state: {JSON.stringify(state)}</div>
</>
);
};
In the dummy app, when I click the button in the Home component which calls history.push(), passing a location object with a state property, the state is not only successfully passed to the SubRoute component, but if I refresh or hard refresh, the state value is still available.
In my production app (a completely separate app that includes the above in addition to, of course a lot of other stuff), state is successfully passed to the SubRoute component, but it is not retained upon refresh. It is undefined.
I'm very confused as to what could be causing different behavior in my production app versus my test app. Is it a React Router thing? Thoughts?
It turns out that another developer on the team had added this line of code in part of the application that was wiping out the state:
https://github.com/preactjs/preact-router#redirects

Type '{}' is missing the following properties from type 'match<Identifiable>': params, isExact, path, url

I'm using react router and typescript to get the id variable from a route to use in a component and typescript is complaining that:
Type '{}' is missing the following properties from type
'match': params, isExact, path, url
Heres my code (App.tsx):
<Route path='/posts/:id'>
<Post />
</Route>
Post.tsx :
import React from 'react';
import { match } from 'react-router';
interface Identifiable {
id: string
params: string,
isExact: string,
path: string,
url: string,
}
const Post = (mathedRoute: match<Identifiable>) => {
return <h1>{mathedRoute.params.id}</h1>
}
export default Post;
Thanks in advance for your help.
In order to extract the match, you need to render the component, rather than pass it as a child.
<Route path='/posts/:id' render={({match}) => <Post match={match} />} />
Typescript might still complain about it, in which case you'd need to import RouteComponentProps from "react-router-dom" and possibly extend it.
However! The good news is, react router hooks exist!
I'd suggest playing around with them, especially the useRouteMatch and useParams hooks.
You could use standard way:
import React from 'react';
import { Route, RouteComponentProps, Switch } from 'react-router-dom';
interface PostParams {
id: string;
}
function Post(props: RouteComponentProps<PostParams>) {
return <>{props.match.params.id}</>;
}
export function App() {
return (
<Switch>
<Route path='/posts/:id' component={Post}/>
</Switch>
);
}
or hook way:
import React from 'react';
import { Route, Switch, useParams } from 'react-router-dom';
interface PostParams {
id: string;
}
function Post() {
const { id } = useParams<PostParams>();
return <>{id}</>;
}
export function App() {
return (
<Switch>
<Route path='/posts/:id'>
<Post/>
</Route>
</Switch>
);
}
In your code you are trying to mix them together.
reed more about react router v5

How to use url params in separate component

I've been learning React over the last 2 days, and I'm having trouble with understanding URL parameters.
Let's say I want a route mysite.com/details/1023. The route is defined like:
<Route path="/details/:id" render={() => <DishDetails />}/>
Now I want the DishDetails object, which is defined in another file, to be able to use the id value 1023. How can I do this? I've found tutorials on route url params but none that explains how to achieve this.
Here's what my DishDetails view looks like right now:
import React, {Component} from "react";
import "./DishDetails.css";
import {Link} from "react-router-dom";
class DishDetails extends Component {
constructor(props) {
super(props);
this.state = {
id: /*url param*/,
};
}
render() {
return this.state.id;
}
}
export default DishDetails;
Where can I get the id in DishDetails? I've tried:
import React, {Component} from "react";
import "./DishDetails.css";
import {Link} from "react-router-dom";
class DishDetails extends Component {
constructor(props) {
super(props);
this.state = {
id: match.id,
};
}
render() {
return this.state.id;
}
}
But match is undefined.
Pass your component to Route via the component props:
<Route path="/details/:id" component={DishDetails} />
If you did this match is available in the props.
If you have to keep the way how you render your routes you can pass the render props manually to your component:
<Route path="/details/:id" render={(props) => <DishDetails {...props} />}/>
You can find the whole documentation for react-router here.
The <Route render> prop receives the router props:
match
location
history
You need to provide that props to the <DishDetails> component and use the match.params.id to retrieve the id from your path="/details/:id"
const DishDetails = props => <div>Details Id: {props.match.params.id}</div>;
<Route path="/details/:id" render={props => <DishDetails {...props} />} />
This is the Route props in your example:
{
match: { path: '/details/:id', url: '/details/1', isExact: true, params: { id: '1' } },
location: { pathname: '/details/1', search: '', hash: '' },
history: { length: 3, action: 'POP', location: { pathname: '/details/1', search: '', hash: '' } }
}
There are 3 ways to render something with a <Route>:
<Route component>
<Route render>
<Route children>
Read more here
Have you tried the withRouter function that comes with react-router?
import { withRouter } from 'react-router'
class App extends Component {
.... your stuff
}
export default withRouter(App);
That should give you access to the "match" prop

react router slug compared to part of array

I started to experiment with react router, and dynamic matches.
I wanted to create a function which matches the slug of the URL to a slug in a JSON file.
The error I get:
TypeError: Unable to get property 'slug' of undefined or null reference
I think that the 'Slug' of the url is undefined, but I am not sure on how to fix it.
screenshot of error
my code for routes.js:
import React from 'react';
import Header from './components/header/header.js';
import Home from './components/home/home.js';
import About from './components/about/about.js';
import NotFound from './components/notFound/notFound.js'
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import PostPage from './components/postpage/postpage.js'
import posts from './files/data.json';
class Routes extends React.Component {
render(){
return (
<Router>
<div>
<Header />
<Switch>
<Route exact path="/" component={Home} />
<Route path="/about" component={About}/>
<Route path="/home" component={Home}/>
<Route path="/:slug" component={props => {
const postt = posts.posts.filter (post => props.params.slug === post.slug)
console.log(postt.length)
return <PostPage post={postt} />
} } />
}}/>
<Route component={NotFound} />
</Switch>
</div>
</Router>
);
}
}
export default Routes;
PostsPage.js:
import React from 'react';
import Post from '../post/post.js'
const PostPage = (props) => (
<div>
<Post {...props.post}/>
</div>
);
export default PostPage;
and posts.js:
import React from 'react';
import { Link } from 'react-router-dom';
import './post.css';
class Post extends React.Component {
render(){
return(
<div>
<div >
<h2 className='subTitle'><Link to={`/post/${this.props.slug}`} className='link'>{this.props.title}</Link></h2>
<p className='content'>{this.props.excerpt}</p>
</div>
</div>
);
}
}
export default Post;
If you made it this far thank you for helping
slug variable is given inside match props which you are missing.
<Route path="/:slug" render={props => {
const postt = posts.posts.filter (post => props.match.params.slug === post.slug)
console.log(postt.length)
return <PostPage post={postt} />
} } />
}}/>
Also, do not inline component use a render function instead. From the docs:
When you use component (instead of render or children, below) the
router uses React.createElement to create a new React element from the
given component. That means if you provide an inline function to the
component prop, you would create a new component every render. This
results in the existing component unmounting and the new component
mounting instead of just updating the existing component. When using
an inline function for inline rendering, use the render or the
children prop (below).
https://reacttraining.com/react-router/web/api/Route/render-func
One of the ways you can get this fixed is by using .find() instead of .filter() like this :
const postt = posts.find (post => props.match.params.slug === post.slug)
And then inside your <Router /> make sure to send the rest of {...props} as well :
<Route path="/:slug" component={props => {
const postt = posts.find (post => props.match.params.slug === post.slug)
console.log(postt.length)
return <PostPage post={postt} {...props} />
} } />

Passing custom props to router component in react-router v4

I'm using React Router to create a multi page app. My main component is <App/> and it renders all of the routing to to child components. I'm trying to pass props via the route, and based on some research I did, the most common way for child components to tap into props passed down is via the this.props.route object that they inherit. However, this object is undefined for me. On my render() function in the child component, I console.log(this.props) and am return an object that looks like this
{match: Object, location: Object, history: Object, staticContext: undefined}
Doesn't look like the props I expected at all. Here is my code in detail.
Parent Component (I'm trying to pass the word "hi" down as a prop called "test" in all of my child components):
import { BrowserRouter as Router, HashRouter, Route, Switch } from 'react-router-dom';
import Link from 'react-router';
import React from 'react';
import Home from './Home.jsx';
import Nav from './Nav.jsx';
import Progress from './Progress.jsx';
import Test from './Test.jsx';
export default class App extends React.Component {
constructor() {
super();
this._fetchPuzzle = this._fetchPuzzle.bind(this);
}
render() {
return (
<Router>
<div>
<Nav />
<Switch>
<Route path="/" exact test="hi" component={Home} />
<Route path="/progress" test="hi" component={Progress} />
<Route path="/test" test="hi" component={Test} />
<Route render={() => <p>Page not found!</p>} />
</Switch>
</div>
</Router>
);
}
}
Child:
import React from 'react';
const CodeMirror = require('react-codemirror');
import { Link } from 'react-router-dom';
require('codemirror/mode/javascript/javascript')
require('codemirror/mode/xml/xml');
require('codemirror/mode/markdown/markdown');
export default class Home extends React.Component {
constructor(props) {
super(props);
console.log(props)
}
render() {
const options = {
lineNumbers: true,
theme: 'abcdef'
// mode: this.state.mode
};
console.log(this.props)
return (
<div>
<h1>First page bro</h1>
<CodeMirror value='code lol' onChange={()=>'do something'} options={options} />
</div>);
}
}
I'm pretty new to React so my apologies if I'm missing something obvious.
Thanks!
You can pass props to the component by making use of the render prop to the Route and thus inlining your component definition. According to the DOCS:
This allows for convenient inline rendering and wrapping without the
undesired remounting explained above.Instead of having a new React
element created for you using the component prop, you can pass in a
function to be called when the location matches. The render prop
receives all the same route props as the component render prop
So you can pass the prop to component like
<Route path="/" exact render={(props) => (<Home test="hi" {...props}/>)} />
and then you can access it like
this.props.test
in your Home component
P.S. Also make sure that you are passing {...props} so that the
default router props like location, history, match etc are also getting passed on to the Home component
otherwise the only prop that is getting passed down to it is test.

Categories