Trying to pass state through link - React - reactjs

I can't figure out why I cannot pass state from component ArrayList to ArrayList2. Can anyone chime in and offer a solution to the problem. I think I've exhausted every SO thread, YouTube video and Google search on the subject and I keep getting the following error message,
"Uncaught TypeError: Cannot destructure property 'message' of 'location.state' as it is null.".
My code is as follows:
Component 1
import "./App.css";
import ArrayList from "./ArrayList";
import { Route, BrowserRouter as Router, Routes } from "react-router-dom";
import ArrayList2 from "./dataFolder/ArrayList2";
function App() {
return (
<Router>
<Routes>
<Route exact path="*" element={<ArrayList />} />
<Route exact path="/2" element={<ArrayList2 />} />
</Routes>
</Router>
);
}
export default App;
Component 2
import { Link } from "react-router-dom";
import "./App.css";
import Years from "./dataFolder/Years";
import NavBar from "./NavBar";
function ArrayList() {
const years = Years;
return (
<div className="App">
<NavBar />
<nav>
<ul className="no-bullets">
{years.map((item) => (
<Link
className="no-link-style"
to={{
pathname: "/2",
state: { message: "This is a passed Item" },
}}
>
<li>{item}</li>
</Link>
))}
</ul>
</nav>
</div>
);
}
export default ArrayList;
Component 3
import React, { useState } from "react";
import { useLocation } from "react-router-dom";
import ArrayList from "../ArrayList";
import NavBar from "../NavBar";
function ArrayList2(props) {
const types = ["General", "Primary"];
const location = useLocation();
const { message } = props.location.state;
console.log(message)
return (
<div className="App">
<NavBar />
<ul className="no-bullets">
{types.map((item) => (
<li>{item}</li>
))}
</ul>
</div>
);
}
export default ArrayList2;
Any help on this would be greatly appreciated.

As I can see from the first code block, you are not passing any prop to ArrayList component, you are passing it to Link prop. Without passing a prop to the ArrayList component you are trying to access it. This leads to destructuring of an undefined value. You can use the passed route parameters by using the useParams hook that is defined in the react-router package.

Related

Error: useLocation() may be used only in the context of a <Router> component

I have installed react-router-domV6-beta.
By following the example from a website I am able to use the new option useRoutes I have setup page routes and returning them in the App.js file.
After saving I am getting the following error:
Error: Error: useLocation() may be used only in the context of a
component.
App.js
import "./App.css";
import react, { useState, useEffect } from "react";
import { BrowserRouter as Router, Route, Routes } from "react-router-dom";
import _ from "lodash";
import Cast from "./Cast";
import list from "./list";
import Footer from "./Footer";
import DetailLayout from "./DetailLayout";
function App() {
const [charnames, setCharnam] = useState("");
function charname(names) {
setCharnam(names);
}
function castDetail(everycast) {
return (
<Cast
key={everycast.id}
url={everycast.url}
name={everycast.name}
castp={charname}
/>
);
}
const cc = charnames;
const p = "/" + cc;
//const p="/haha h";
const res = p.replace(/ /g, "");
return (
<div className="App">
<div className="overview">{list.map(castDetail)}</div>
{/* <DetailLayout /> */}
{console.log(charnames)}
<Router>
<Cast />
<Routes>
<Route exact path={res} element={<DetailLayout />} />
</Routes>
</Router>
<Footer />
</div>
);
}
export default App;
Cast.js
import react from "react";
import { NavLink } from "react-router-dom";
export default function Cast(props) {
function setpath() {
props.castp(props.name);
}
const path = "/" + props.name;
return (
<div className="cast">
{/* <Link to="/about">About</Link> */}
{/* <NavLink to="/about">About</NavLink> */}
<NavLink to={path}>
<img onClick={setpath} src={props.url} />{" "}
</NavLink>
<p>{props.name}</p>
</div>
);
}
this error simply says wrap your useLocation() inside
<Router> </Router>
all react-router-dom hooks and functions should be used inside
<BrowserRouter> </BrowserRouter>
otherwise they simply don't work

Issues with Reach router Nested routes

i am having trouble with reach router nested routes, I am trying to navigate to / and render page2, but I am stuck on "/" homepage the same page when the route changes to //
<Appjs>
import React from "react";
import "./App.css";
import Homepage from "./Components/Homepage";
import Details from "./Components/Details";
function App() {
return (
<div>
<Router>
<Homepage path="/">
<Details path="details" />
</Homepage>
</Router>
</div>
);
}
export default App;
import React, { Component, useEffect, useState } from "react";
import styled, { isStyledComponent } from "styled-components";
import NavLink from "./NavLink";
import { Link } from "#reach/router";
const Homepage = () => {
const [users, setUsers] = useState([]);
return (
<React.Fragment>
<div className={"container"}>
<Styleddiv>
<h2>Select an Account</h2>
<div style={{ padding: 0 }}>
{Object.values(users).map((item) => (
<Link to={`details/${item.name}`}>
<img src={item.profilepicture} alt="Girl in a jacket"></img>
<span>{item.name}</span>
</Link>
))}
</div>
</Styleddiv>
</div>
</React.Fragment>
);
};
export default Homepage;
Am I missing something while structuring the routes inside the router, Kindly help me
So in Homepage If i click on any Link the route changes to /details but the details page fails to render
https://codesandbox.io/s/billowing-hill-j5gmy?file=/src/Homepage.js
Did you miss your Router import on your App.js file?
import React from "react";
import { Router, Link } from "#reach/router";
import Homepage from "./Homepage";
import Details from "./Details";
export default function App() {
return (
<div>
<nav>
<Link to="/">Home</Link>
<Link to="dashboard">Detail</Link>
</nav>
<Router>
<Homepage path="/" />
<Details path="dashboard" />
</Router>
</div>
);
}
Edit: Code Sand Box
This is how I use nested routes
Code sandbox
In order to nest routes you need to place them in the child component of the route.
The render prop is used instead of component because it stops the inline functional component from being remounted every render, see explanation.
The match object contains the information about how a route matched the URL, so we can have a nested route using the url property which gives us the matched portion of the URL, see explanation.

React Router: need

I have this code I have been trying to get to work. Cannot figure out whats wrong with it! There are no errors and I have tried several ways of importing/exporting, and changing the functions to const.
import React, { Component } from "react";
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import { Home } from "./Home";
import { Contact } from "./Contact";
class App extends Component {
render() {
return (
<React.Fragment>
<Router>
<Switch>
<Route path="/" component={Home} />
<Route path="/contact">
<Contact />
</Route>
</Switch>
</Router>
</React.Fragment>
);
}
}
export default App;
Then I have two separate functions
import React from "react";
export function Home() {
return (
<div>
<h2>Whats up!</h2>
<p>This is sample text. </p>
</div>
);
}
import React from "react";
export function Contact() {
return (
<div>
<h2>Contact page!</h2>
<p>This is different text. </p>
</div>
);
}
export default Contact;
Based on your last comment - "The above code actually displays whatever is in the first Route no matter the url", the issue is that you're not providing the Route components with an exact prop of true.
Switch will render the first matching Route and skip all the other ones. Since / "matches" all routes, you're only seeing the Home component.

Navigate programmatically in React router 4 with mix of function/class components & TypeScript

I'm using React Router 4 in a TypeScript app where I have a React.Component that's used within a React.FunctionalComponent. I need to be able to navigate programmatically to a particular route from within the React.Component, but I can't seem to figure out how to pass the router down to the child component so that I can call this.props.history.push(). What complicates matters is that I'm using TypeScript, too.
Here's a code sandbox with a working demo of my component layout: https://codesandbox.io/s/react-programmatic-routing-xebpg
And now, the components:
app.tsx:
import * as React from 'react';
import { HashRouter } from 'react-router-dom';
import Header from './header';
import Footer from './footer';
import AppRouter from './app-router';
export default class App extends React.PureComponent {
public render() {
return (
<HashRouter>
<Header />
<AppRouter />
<Footer />
</HashRouter>
);
}
}
header.tsx:
import * as React from 'react';
import Navbar from 'react-bootstrap/Navbar';
import Nav from 'react-bootstrap/Nav';
import { NavLink } from 'react-router-dom';
export default class Header extends React.PureComponent<any> {
public render() {
return (
<Navbar>
<Nav.Link as={NavLink} exact to="/home">
Home
</Nav.Link>{' '}
<Nav.Link as={NavLink} to="/customers">
Customers
</Nav.Link>
</Navbar>
);
}
}
app-router.tsx:
import * as React from 'react';
import { Switch, Route } from 'react-router-dom';
import Home from './pages/home';
import Customers from './pages/customers';
const AppRouter: React.FC = () => {
return (
<div>
<Switch>
<Route exact path="/" component={Home} />
<Route path="/home" component={Home} />
<Route path="/customers" component={Customers} />
</Switch>
</div>
);
};
export default AppRouter;
pages/customers.tsx:
import * as React from 'react';
import MyFakeGrid from './customers-grid';
const Customers: React.FC = () => {
return (
<div>
<p>This is the customers page</p>
<MyFakeGrid />
</div>
);
};
export default Customers;
pages/customers-grid.tsx:
import * as React from 'react';
import { NavLink } from 'react-router-dom';
export default class MyFakeGrid extends React.Component {
public render() {
return (
<div style={{ borderColor: 'lightgray', borderStyle: 'solid' }}>
<p>
I need to be able to route programmatically from this
component
</p>
<p>
but I can't just use a NavLink like 'Home' (below), I have
to be able to navigate from within a method
</p>
<NavLink to="/home">Home</NavLink>
</div>
);
}
}
pages/home.tsx:
import * as React from 'react';
const Home: React.FC = () => {
return (
<div>
<p>This is the home page</p>
</div>
);
};
export default Home;
I've recently started learning React and I don't want to re-write my class-based components as functional components, which have become quite detailed/useful, especially not given React's gradual adoption strategy.
Base on React-router training, You can get access to the history object's properties and the closest 's match via the withRouter higher-order component. withRouter will pass updated match, location, and history props to the wrapped component whenever it renders.
For example, you can re-write Customer component as blow:
import * as React from 'react';
import MyFakeGrid from './customers-grid';
import { withRouter } from "react-router";
const Customers: React.FC = () => {
return (
<div>
<p>This is the customers page</p>
<MyFakeGrid />
</div>
);
};
export default withRouter(Customers);
now you access to the history and other parameter as i said, and you can easily navigate between routes.

How to click a button and navigate to a new route with URL params using React Router 4

I'm building a simple app in Meteor with React, and I'm using React Router 4.
There is a button which creates a new map, and when the server returns the new map's id, the browser should navigate to the page that shows the newly created map.
I have got it nearly working but when I click the button, the URL changes in the address bar but the map component doesn't render. Refreshing the page doesn't help. But it works fine if I click the new map's link in the list of maps.
This code is ugly and I'm sure it's not the best way to do any of it, but it is literally the only way I've been able to make it work at all. I've read all the forum posts and docs I can find but nothing is clear to me.
I'd be very grateful for any help.
Main.js
```JSX
/* global document */
import React from 'react';
import { Meteor } from 'meteor/meteor';
import { render } from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
// Start the app with imports
import '/imports/startup/client';
import '../imports/startup/accounts-config.js';
import App from '../imports/ui/layouts/App.jsx';
Meteor.startup(() => {
render((<BrowserRouter><App /></BrowserRouter>), document.getElementById('app'));
});
App.js:
import React, { Component } from 'react';
import { withRouter, Switch, Route, Link, Redirect } from 'react-router-dom';
import { Meteor } from 'meteor/meteor';
import { withTracker } from 'meteor/react-meteor-data';
// Mongodb collection
import { Maps } from '../../api/maps/maps.js';
// User Accounts
import AccountsUIWrapper from '../AccountsUIWrapper.jsx';
import Home from '../pages/home';
import Map from '../pages/map';
import About from '../pages/about';
class App extends Component {
renderTestButton() {
return (
<button onClick={this.handleClick.bind(this)}>New Map</button>
);
}
handleClick() {
let that = this;
Meteor.call('newMap', {'name': 'new map'}, function(error, result) {
that.props.history.push(`/map/${result}`);
});
}
render() {
let newMap = this.renderNewMap();
let testButton = this.renderTestButton();
return (
<div className="primary-layout">
<header>
<AccountsUIWrapper />
{testButton}
<nav>
<ul>
<li><Link to='/'>Home</Link></li>
<li><Link to='/about'>About</Link></li>
</ul>
</nav>
</header>
<main>
<Switch>
<Route exact path='/' component={Home}/>
<Route exact path="/map/:_id" render={({ match }) => (
<Map
params={match.params}
/>
)} />
<Route path='/about' component={About}/>
</Switch>
</main>
</div>
);
}
}
export default withRouter(withTracker(() => {
const mapsHandle = Meteor.subscribe('maps');
return {
'maps': Maps.find({}).fetch(),
'loading': !mapsHandle.ready(),
'currentUser': Meteor.user(),
};
})(App));
EDIT: I had a typo in my path, I wrote that.props.history.push(/maps/${result}); when it should be that.props.history.push(/map/${result}); to match the defined route.
I've correct the code, it now works but I still feel this can't be the best solution...
After finding a typo in my original code ('/maps/' where the path should have been '/map/') I found another 'gotcha' which is this:
If the route expects a URL parameter, and it is not supplied, then the route doesn't seem to render at all. My route is defined as:
```JSX
<Route path="/map/:_id" render={({ match }) => (
<Map
params={match.params}
/>
)} />
If you try to navigate to 'http://localhost:3000/map/' then the component doesn't render. If you put any value on the end e.g. 'http://localhost:3000/map/dummyvalue' it renders.
I've now got a tidier version working:
```JSX
import React, { Component } from 'react';
import { withRouter, Switch, Route, Link } from 'react-router-dom';
import { Meteor } from 'meteor/meteor';
import { withTracker } from 'meteor/react-meteor-data';
// Mongodb collection
import { Maps } from '../../api/maps/maps.js';
// User Accounts
import AccountsUIWrapper from '../AccountsUIWrapper.jsx';
import Home from '../pages/home';
import Map from '../pages/map';
import About from '../pages/about';
function NewMap(props) {
function handleClick(e) {
e.preventDefault();
let history = props.history;
Meteor.call('newMap', {'name': 'new map'}, (error, result) => {
history.push(`/map/${result}`);
});
}
let disabled = 'disabled';
if (Meteor.userId()) { disabled = '';}
return (
<button disabled={disabled} onClick={handleClick}>New Map</button>
);
}
class App extends Component {
renderNewMap() {
const history = this.props.history;
return (
<NewMap history={history}
/>
);
}
render() {
let newMap = this.renderNewMap();
return (
<div className="primary-layout">
<header>
<AccountsUIWrapper />
{newMap}
<nav>
<ul>
<li><Link to='/'>Home</Link></li>
<li><Link to='/about'>About</Link></li>
</ul>
</nav>
</header>
<main>
<Switch>
<Route exact path='/' component={Home}/>
<Route path="/map/:_id" render={({ match }) => (
<Map
params={match.params}
/>
)} />
<Route path='/about' component={About}/>
</Switch>
</main>
</div>
);
}
}
export default withRouter(withTracker(() => {
const mapsHandle = Meteor.subscribe('maps');
return {
'maps': Maps.find({}).fetch(),
'loading': !mapsHandle.ready(),
'currentUser': Meteor.user(),
};
})(App));

Resources