Failing to Access Outlet Context in React - javascript

Okay so I have scoured the internet for an example of how to do this but unfortunately I am not able to do so. Basically I have a componenet structure like this:
App.js
class App extends Componenent {
render() {
return (
<Routes>
<Route path='/signin' exact element={<SignIn />} />
<Route path='/admin' exact element={<Admin />} >
<Route path='home' exact element={<Home/>} />
<Route path='settings' exact element={<Settings/>} />
</Route >
);
}
}
export default App;
admin.jsx
import { useLocation, Outlet } from "react-router-dom";
const Admin = props => {
const location = useLocation();
return (
<div>
<p>Parent</p>
<div>
<Outlet context={'foo'} />
</div>
</div>
}
export default Admin;
settings.jsx
import React from "react";
const Settings = props => {
const context = useOutletContext();
console.log(context);
return (
<React.Fragment>
<p>settings</p>
</React.Fragment>
}
export default Settings;
However, each time I load the page, I get a an error that says exactly:
'useOutletContext' is not defined no-undef
and the app crashes. However, when I look at my componenet tree with the chrome react debug panel, the context is there in the outlet, I just don't know how to access it. Here are some screenshots so that we are on the same page:
Context is in the outlet
The same data is in the Context.Provider as "value" now
Nowhere to be seen in the Route.Provider
Nowhere to be seen in the Settings Componenet
Any and all help here would be appreciated, I am just not entirely sure of how to use useOuletContext(); even if I used followed the steps in the docs. Do I have to import it from somewhere? Does it have to be in the same file for it to work?

Yes, it still needs to be imported in the file using it, i.e. import { useOutletContext } from 'react-router-dom';.
import React from "react";
import { useOutletContext } from 'react-router-dom';
const Settings = props => {
const context = useOutletContext();
console.log(context);
return (
<React.Fragment>
<p>settings</p>
</React.Fragment>
);
}
export default Settings;

Related

Props not passing in dynamic URL - TypeError React.js

I am trying to pass props to a component, Location, using React router as a url parameter, however I am getting a type error because props.match.params.location is undefined. I am passing the location in the url Link of the Locations component and when I click on this link in the browser, it redirects to the correct url with the correct parameter:
http://localhost:3000/locations/tokyo
However this is triggering this error:
Uncaught TypeError: Cannot read properties of undefined (reading 'params')
at Location (Location.js:10:1)
App.js:
class App extends React.Component {
render() {
return (
<div className="App">
<AppNavbar />
<Routes>
<Route path="/" element={<Dashboard />} />
<Route path="/stock-levels" element={<StockLevels />} />
<Route path="/locations" element={<Locations />} />
<Route path="/locations/:location" element={<Location />} />
</Routes>
</div>
)
}
}
export default App;
Locations.js:
import {Link} from "react-router-dom";
function Locations() {
const locations = ["tokyo", "location2", "location3", "location4", "location5", "location6", "location7", "location8"]
const locationList = locations.map(location => {
return <div className="location-container">
<Card className="location-card">
<CardTitle><Link to={`/locations/${location}`}>{location}</Link></CardTitle>
</Card>
</div>
})
return (
<>
<h1>Locations</h1>
{locationList}
</>
)
}
export default Locations
Location.js:
function Location(props) {
//Make location equal to the location passed via route
const location = props.match.params.location
return (
<>
<h1>Location</h1>
<h2>{location}</h2>
</>
)
}
export default Location
As far as I can tell, with how the routes are configured and the link url:
<Link to={`/locations/${location}`}>
This should be passed as props.
Thanks for any help!
In react-router-dom v6 there are no longer any route props, i.e. history, location, and match, instead they are replaced by React hooks. props.match is undefined and throws an error when trying to then access props.match.params.
history object replaced by a navigate function via useNavigation
location object via useLocation
match via a useMatch hook, but params object via useParams was added in v6
The useParams hook is what you want to access the location route param.
import { useParams } from 'react-router-dom';
...
function Location() {
const { location } = useParams();
return (
<>
<h1>Location</h1>
<h2>{location}</h2>
</>
);
}
The question is a bit confising in this format, could you plase share your issue in an online sandbox?
https://codesandbox.io/ - is a goood one.
I think you need to use useParams to get access to the params.
<Routes>
<Route path="/locations/:location" element={<Location />} />
</Routes>
import React from "react"
import { useParams } from "react-router-dom"
export default function Location() {
const { location } = useParams()
return (
<>
<h1>Location</h1>
<h2>{location}</h2>
</>
)
}
Forgive the spacing.

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

Error: Invariant failed: You should not use <withRouter(App) /> outside a <Router>

I'm pretty sure that I am using in the right place, but I am still getting the error. Any idea why?
Error: Invariant failed: You should not use <withRouter(App) /> outside a <Router>
Apologies for the ugly code. I am new to react.
render()
return (
<BrowserRouter>
<div className="App">
<Route path="/" exact render={
(props)=> {
return(
<div>
<input type='text' onChange={this.formChangeHandler}/>
<p><button onClick={ () => this.postData(this.state.message)}>Submit</button></p>
</div>
)}
}/>
<Route path="/post" exact render={
(props)=> {
return(
<div>
<b>SUCCESS!</b>
</div>
)
}
}/>
</div>
</BrowserRouter>
);
}
}
export default withRouter(App);
withRouter
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.
import React from "react";
import PropTypes from "prop-types";
import { withRouter } from "react-router";
// A simple component that shows the pathname of the current location
class ShowTheLocation extends React.Component {
static propTypes = {
match: PropTypes.object.isRequired,
location: PropTypes.object.isRequired,
history: PropTypes.object.isRequired
};
render() {
const { match, location, history } = this.props;
return <div>You are now at {location.pathname}</div>;
}
}
// Create a new component that is "connected" (to borrow redux
// terminology) to the router.
const ShowTheLocationWithRouter = withRouter(ShowTheLocation);
As your error says, you cannot connect a Component to a Router outside of a Router. And your are trying to connecter yhe Component which renders the Router, to the Router
So You need to do something like this
// Main.js
export default class Main extends PureComponent {
render (
<BrowserRouter>
<App />
</BrowserRouter>
)
}
// App.js
class App extends PureComponent {
render (
<div className="app">
...
</div>
)
}
export default withRouter(App)

How can I make sure that when I click on an icon another page appears with React.js

I need some help. I have an icon and I want that when I press the icon that I can go to an other page, but I get this error Error: Invariant failed: You should not use <Link> outside a <Router>
Error
This is mine code :
import React from 'react';
import { IconContext } from "react-icons";
import { BsFileEarmarkArrowDown } from "react-icons/bs";
// import { Link } from "react-router-dom";
import "./icon.css"
const Question = () => {
return (
<IconContext.Provider value={{ color: "Green", className: "Icon", size:"3em" }}>
<BsFileEarmarkArrowDown />
</IconContext.Provider>
);
};
// export default function Firms() {
// return (
// <Link to="/Upload.js">
// <Question />
// </Link>
// );
// }
export default Question
You can only use <Link> inside of a <Router> tree component.
<Router>
<Switch>
<Route exact path="/upload">
<Upload /> // Conditionally rendered views that may contain links pointing to different routes
</Route>
</Switch>
</Router>
Your <Question/> component needs to stay inside of the <Router> component (as everything related to routing context).
To assign a Link to your question, you can do like so:
<Question component={Link} to="/upload"/>
And eventually, put it somewhere inside your <Router> tree. Doesn't matter at which level as long as it's enclosed within the routing context:
<Router>
<Switch>
....
</Switch>
<Question component={Link} to="/upload"/>
</Router>
I strongly encourage you to check the official documentation for further insights.
1 - <Link to="/Upload.js"> - This is wrong, <Link> is not used for linking to files, it should be <Link to="/upload">.
2 - Setup some routes
<Router>
<Switch>
<Route exact path="/upload">
<Upload /> /* This component from ur upload.js file */
</Route>
<Route path="/firms">
<Firms />
</Route>
</Switch>
</Router>
Document: https://reactrouter.com/web/example/basic
Link Can't be used without Router if you want to redirect the page to uploads
if you want another React Component to load as you have written Upload.js in link path
do this
import Uploads from 'upload.js file path';
import React from 'react';
import { IconContext } from "react-icons";
import { BsFileEarmarkArrowDown } from "react-icons/bs";
import "./icon.css"
const Question = () => {
return (
<IconContext.Provider value={{ color: "Green", className: "Icon", size:"3em" }}>
<BsFileEarmarkArrowDown />
</IconContext.Provider>
);
};
export default function Firms() {
return (
<Uploads/>
<Question />
);
}
export default Question
you can use Use History hooks if you want your url to go to uploads so you can do this
import React from 'react';
import { IconContext } from "react-icons";
import { BsFileEarmarkArrowDown } from "react-icons/bs";
import { useHistory } from "react-router-dom";
import "./icon.css"
const Question = () => {
let history = useHistory();
return (
<IconContext.Provider value={{ color: "Green", className: "Icon", size:"3em" }}>
<BsFileEarmarkArrowDown />
</IconContext.Provider>
);
};
export default function Firms() {
return (
history.push("/upload");
<Question />
);
}
export default Question
Hope it helps you Thank You

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} />
} } />

Categories