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.
Related
This question already has an answer here:
How to run a function when user clicks the back button, in React.js?
(1 answer)
Closed 5 months ago.
I'm new to React, so I'm sure I'm not understanding the use cases for useLocation - like what it is good for and what it is not intended for.
I'd like to have a method that a specific component can be aware of any location change included those from pushState. Note: I'm converting an Anuglar JS 1.0 code base that just used all query info in the hash. I'd like to use pushState browser feature in this rewrite.
Sample code below (I just have it as the single component in a new React app component:
import React, { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
const RandLocation: React.FC = () => {
const location = useLocation();
useEffect(() => {
console.log('location: ', location);
}, [location]);
return (
<div>
<button
onClick={() => {const r = Math.random(); window.history.pushState({'rnd': r }, '', '/?rnd=' + r)}}>
Click Me</button>
<br/>
</div>
)
}
export default RandLocation;
I only see the useEffect run on load, and if I move forward or back using the browser buttons. But not when I click the "Click Me" button. What am I missing? Id like to keep this "awareness of location" as simple as possible within the React frontend code. Like is there a technique that works in apps regardless of if you have React Router routes defined?
I am using React version 17.0.2 and react-router-dom version 6.2.2
I think because the window.history.pushState call is outside of React's state management it react-router won't be aware of it. There used to be a way to listen for these events, but I'm not sure something equivalent exist in React Router 6.
You could use the useNavigate hook. Maybe something like:
import React, { useEffect } from "react";
import { useLocation, useNavigate } from "react-router-dom";
const RandLocation = () => {
const location = useLocation();
const navigate = useNavigate();
useEffect(() => {
console.log("location: ", location);
}, [location]);
return (
<div>
<button
onClick={() => {
const r = Math.random();
//window.history.pushState({ rnd: r }, "", "/?rnd=" + r);
navigate("/?rnd=" + r, { state: { rnd: r } });
}}
>
Click Me
</button>
<br />
</div>
);
};
export default RandLocation;
One issue with this approach, is you'd have to set up a default route to catch anything that no route is defined for like this:
<BrowserRouter>
<Routes>
<Route path="/" element={<App />} />
<Route path="*" element={<WhereYouWantDefaultRoutesToGoTo />} />
</Routes>
</BrowserRouter>
You might also want to take a look at: https://stackoverflow.com/a/70095819/122201
The version of react-router-dom is v6 and I'm having trouble with passing values to another component using Navigate.
I want to pass selected rows to another page called Report. But, I'm not sure I'm using the right syntax for navigate method and I don't know how to get that state in the Report component.
Material-ui Table: I'm trying to use redirectToReport(rowData) in onClick parameter.
function TableRows(props){
return (
<MaterialTable
title="Leads"
columns={[
...
]}
data = {props.leads}
options={{
selection: true,
filtering: true,
sorting: true
}}
actions = {[{
position: "toolbarOnSelect",
tooltip: 'Generate a report based on selected leads.',
icon: 'addchart',
onClick: (event, rowData) => {
console.log("Row Data: " , rowData)
props.redirect(rowData)
}
}]}
/>
)}
LeadTable component
export default function LeadTable(props) {
let navigate = useNavigate();
const [leads, setLeads] = useState([]);
const [loading, setLoading] = useState(true);
async function fetchUrl(url) {
const response = await fetch(url);
const json = await response.json();
setLeads(json[0]);
setLoading(false);
}
useEffect(() => {
fetchUrl("http://localhost:5000/api/leads");
}, []);
function redirectToReport(rowData) {
navigate('/app/report', { state: rowData }); // ??? I'm not sure if this is the right way
}
return(
<div>
<TableRows leads={leads} redirect={redirectToReport}></TableRows>
</div>
)}
Report component
export default function ReportPage(state) {
return (
<div>
{ console.log(state) // This doesn't show anything. How to use the state that were passed from Table component here?}
<div className = "Top3">
<h3>Top 3 Leads</h3>
<ReportTop3 leads={[]} />
</div>
</div>
);}
version 6 react-router-dom
I know the question got answered but I feel this might be helpful example for those who want to use functional components and they are in search of passing data between components using react-router-dom v6.
Let's suppose we have two functional components, first component A, second component B. The component A wants to share data to component B.
usage of hooks: (useLocation,useNavigate)
import {Link, useNavigate} from 'react-router-dom';
function ComponentA(props) {
const navigate = useNavigate();
const toComponentB=()=>{
navigate('/componentB',{state:{id:1,name:'sabaoon'}});
}
return (
<>
<div> <a onClick={()=>{toComponentB()}}>Component B<a/></div>
</>
);
}
export default ComponentA;
Now we will get the data in Component B.
import {useLocation} from 'react-router-dom';
function ComponentB() {
const location = useLocation();
return (
<>
<div>{location.state.name}</div>
</>
)
}
export default ComponentB;
Note: you can use HOC if you are using class components as hooks won't work in class components.
Your navigate('/app/report', { state: rowData }); looks correct to me.
react-router-v6
If you need state, use navigate('success', { state }).
navigate
interface NavigateFunction {
(
to: To,
options?: { replace?: boolean; state?: any }
): void;
(delta: number): void;
}
Your ReportPage needs to be rendered under the same Router that the component doing the push is under.
Route props are no longer passed to rendered components, as they are now passed as JSX literals. To access route state it must be done so via the useLocation hook.
function ReportPage(props) {
const { state } = useLocation();
console.log(state);
return (
<div>
<div className="Top3">
<h3>Top 3 Leads</h3>
<ReportTop3 leads={[]} />
</div>
</div>
);
}
If the component isn't able to use React hooks then you still access the route state via a custom withRouter Higher Order Component. Here's an example simple withRouter HOC to pass the location as a prop.
import { useLocation, /* other hooks */ } from 'react-router-dom';
const withRouter = WrappedComponent => props => {
const location = useLocation();
// other hooks
return (
<WrappedComponent
{...props}
{...{ location, /* other hooks */ }}
/>
);
};
Then access via props as was done in pre-RRDv6.
class ReportPage extends Component {
...
render() {
console.log(this.props.location.state);
return (
<div>
<div className="Top3">
<h3>Top 3 Leads</h3>
<ReportTop3 leads={[]} />
</div>
</div>
);
}
}
2 things (just a suggestion):
Rather than a ternary use &&
{location && <div>{location.state.name}</div>}
Why are you checking location and rendering location.state.name? I would use the check on the data you are fetching or make sure the data returns null or your value.
On Sabaoon Bedar's Answer, you can check if there is any data or not before showing it :
Instead of this <div>{location.state.name}</div>
Do this { location != null ? <div>{location.state.name}</div> : ""}
if you want to send data with usenavigate in functional component you can use like that
navigate(`/take-quiz/${id}`, { state: { quiz } });
and you can get it with uselocation hook like this
const location = useLocation();
location.state.quiz there is your data
But you cannot get this data in props it;s tricky part ;)!!
on SABAOON BEDAR answer,
from component A: navigate('/', {state:"whatever"}
in component B: console.log(location.state) //output = whatever
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
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.
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