How to pass info with React Router to the new page? - javascript

I've made a quick CodeSandBox example what I am after. I have a "Our Courses" section on the landing page with the button "Read more". Once the "Read more" button gets clicked, depending on the Course it would render that information. Now I got the button to work but now I am stuck and can't figure out how to pass relevant information to the redirected page. Now let's say I want to get the Course "Title" and "Description" get passed onto to the redirected page. How can I do that?
CodeSandBox link here - Link here

Your CardInfo component can look-up the course detail from your courses repository.
To perform the look-up you can determine which card was selected by using the react-router useParams hook; this allows you to determine which course identifier was passed via the selected route i.e.
import React from "react";
import courses from "./courses";
import { useParams } from "react-router-dom";
const CardInfo = () => {
const { id } = useParams();
const course = courses.find(course => course.id === id);
return (
<div>
<h1>{course.title}</h1>
<p>{course.description}</p>
</div>
);
};
export default CardInfo;
A complete working example of this can be seen here (its a fork of your CodeSandBox).

You can pass data between Routes by using the object version of the to prop of Link component, so change your Link component to this:
//Card.jsx
<Link
to={{
pathname: `/card/${course.title}`,
state: {
description: course.description
}
}}>
<button className="btn">Read more</button>
</Link>
Then in your CardInfo.jsx component you can access this data by props.location.state.description
import React from "react";
const CardInfo = (props) => {
console.log(props)
return (
<div>
<h1>
How can I pass course title here depending on which button I click
</h1>
<p>{props.location.state.description}</p>
</div>
);
};
export default CardInfo;
Hope it helps :)

In your CardInfo component you can access the id provided by the route using the useParams from your react-router-dom library.
I'm using your <Route path="/card/:id" component={CardInfo} /> for reference.
Implement it like this:
import React from 'react'
import { useParams } from 'react-router-dom'
const CardInfo = () => {
const { id } = useParams()
return <div>Card ID: {id}</div>
}
export default CardInfo
Now that you've got the id you should be able to use it for whatever you need.

There are multiple approaches to pass this data:
You can pass data through the link state like this:
<Link
to={{
pathname: `/card/${course.title}`,
state: { description: course.description }
}}
>...</Link>
And then read it in the CardInfo component like this:
import { useLocation } from "react-router-dom";
const CardInfo = () => {
const location = useLocation();
console.log(location.state) // { description: 'Lorem ipsum...' }
However, the best way to do this is to pass the course id in the URL and read the rest of the information from the courses.js file:
This is already correct, you accept the course id as URL paramter:
<Route path="/card/:id" component={CardInfo} />
Pass the course id in the link:
<Link to={`/card/${course.id}`}>
Read the id parameter from the URL and get the rest of the course information from the courses file:
import { useParams } from "react-router-dom";
import courses from './courses'
const CardInfo = () => {
const params = useParams();
console.log(courses[params.id]);

Related

What is the best way to query previous slug name in NextJS

How to get postID?
Hi I am making a simple post detail page with NextJS.
I have my url: [postID]/[title].tsx
And for the post detail page, I want to query for postID: green highlighted area, so that I can fetch the post's data with it.
I use useRouter to get title name, so I am guessing I can do same thing to postID too.
How can I query for any slug key word like my case?
My code
import { useRouter } from 'next/router'
import React from 'react'
export default function Title() {
const router = useRouter()
const title = router.query.title
// const postID = router.query... I want to get postID from url somehow.
return (
<div>{title}</div>
)
}
So you are thinking of passing n level of slugs if I am not wrong.
Like this => http://localhost:3000/postDetail/postId/title/city/tags
Here in the URL above postDetail is your page and postId/title/city/tags are query params(slug)
Solution
Create a folder inside the pages folder (pages/productDetail)
And then create a file named [...slug].js inside pages/productDetail
[...slug].js
import { useRouter } from 'next/router';
export default function Slug() {
const router = useRouter();
console.log(router.query.slug); // you will get an array of all query params here.. check your console...
return (
<>
Pass data like http://localhost:3000/postDetail/postId/title/city/tags
</>
);
}
Stackblitz Demo

Single Page Application in React with routing - same structure, different content

Problem
I'm trying to make an SPA with routing (ideally with React hooks) in React, but all the examples, descriptions i find are about displaying different components based on the URL. What i want is something like Youtube or Google docs, where the page structure/components are (mostly) the same and only the content changes.
Context
(Edit: adding a bit more context.)
This is going to be a document editor/presenter.
Page structure: after login, there is always a toolbar(blue color) on the top, for menus, notifications, etc. The rest of the screen will be mostly like the two examples below:
Example1:
Example2:
The search pane(orange) could be switched on/off by a button on the toolbar or by a user session variable. The document will be presented in the document section(grey) based on either a user session variable, doc ID provided in URL or selecting a document in the search pane.
Planned URLs
(Added in edit.)
Landing page: /login , login page.
Landing page: / , here the toolbar and a preconfigured, user session based default doc would be presented.
Document page: /doc?id=oys2OPkfOwQ , same as landing page but document section would contain the document with ID provided as query param.
Anything else: /something , toolbar and something under it.
Idea
(Added in edit.)
The layout is defined by CSS grid and page structure changes based on a variable. So this is going to be a prop for the App component coming from default value and user session configured variable and could change later.
This is the functionality i imagine for the App component (pseudo code-like thing):
<Router>
<Route path='/login'>
<Login/>
// Components: Toolbar and something under it
</Route>
<Route path='/'>
<DocApp/>
// Components: Toolbar, Document or Toolbar, Search and Document
// Default document loaded for default, not logged in user
// Default document loaded from stored user session
</Route>
<Route path='/doc'>
<DocApp/>
// Components: Toolbar, Document or Toolbar, Search and Document
// Same as for '/' except document with ID set as query param is displayed
// This could be called/triggered from search and document component as well
</Route>
<Route path='/somethingelse'>
<SomethingElse/>
</Route>
</Router>
Question
(Edit: rephrased, original question was how to implement a solution where different documents loaded based on URL query parameter.)
What i'm mostly interested in if there is a simpler way to draw the landing layout '/' and specific doc presenter /doc?id=oys2OPkfOwQ layout? In both cases the same components get displayed, only the provided parameter(doc to present) is different.
Solution
(Added in edit.)
By reading the answers and feedback and re-thinking my problem i realized that i have a multiple URLs same content problem.
Using React Router to render components based on UrlParams.
First of all, edit your routes to render DocumentLoader component under the route /doc
// file: app.js
import React from "react";
import { BrowserRouter, Route } from "react-router-dom";
import DocumentLoader from "./DocumentLoader";
const App = (props) => {
return <BrowserRouter>
<Routes>
<Route path="/doc" element={<DocumentLoader />}>
</Routes>
</BrowserRouter>
}
Create custom hooks for loading documents
You need two custom hooks, one for loading new document by changing the docId query parameter, and another hook to listen to docId changes to reload new document from your backend.
NOTE: Edit loadDocumentData to load from your backend
// file: hooks.js
import { useState, useEffect, useCallback } from 'react';
import { useSearchParams } from 'react-router-dom';
/**
* TODO:// Refactor this function to call your backend to get
* Document data by docId
*/
const loadDocumentData = (docId) =>
new Promise((resolve, reject) => {
// this setTimeout for demonstration porpuse only
setTimeout(() => {
resolve({ id: docId, name: `Document name for ${docId}` });
}, 3000);
});
export const useDocument = () => {
const [loading, setLoading] = useState(true);
const { docId, loadDocument } = useDocumentParam();
const [document, setDocument] = useState(null);
useEffect(() => {
setLoading(true);
// Load your document data based on docID
loadDocumentData(docId)
.then((doc) => {
setDocument(doc);
setLoading(false);
})
.catch((e) => {
console.error('Failed to load doc', docId);
});
}, [docId, setLoading]);
return { document, loading, loadDocument };
};
export const useDocumentParam = () => {
const [searchParams, setSearchParams] = useSearchParams();
const docId = searchParams.get('d');
const loadDocument = useCallback(
(newDocId) => {
setSearchParams({ d: newDocId });
},
[setSearchParams]
);
return { docId, loadDocument };
};
Create DocumentLoader component
To listen on query param changes, load document from server-side, display loading indicator and render the "DocPresenter" component.
// file: DocumentLoader.js
import * as React from 'react';
import DocPresenter from './DocPresenter';
import { useDocument } from './hooks';
const DocumentLoader = (props) => {
const { loading, document, loadDocument } = useDocument();
if (loading) {
return <div>Display loading indicator while loading the document</div>;
}
return (
<div className="document-container">
<div className="toolbar">NavBar</div>
<div className="searchbox">search component</div>
<div className="editor">
<DocPresenter document={document} setParentstate={loadDocument} />
</div>
</div>
);
};
export default DocumentLoader;
Checkout Live Example on StackBlitz.
Helper Links:
React Router Docs
React Custom Hooks Docs
Here's how I would do it. Notice that the URL will remain the same.
const DynamicComponent = () => {
const components = {
Component1: <Component1 />,
Component2: <Component2 />,
Component3: <Component3 />,
};
const [component, setComponent] = useState(components["Component1"]);
return (
<div>
<div id="nav">
<span onClick={(e) => setComponent(components["Component1"])}>
Set to component 1
</span>
<span onClick={(e) => setComponent(components["Component2"])}>
Set to component 2
</span>
<span onClick={(e) => setComponent(components["Component3"])}>
Set to component 3
</span>
</div>
<div>{component}</div>
</div>
);
};
export default DynamicComponent;

React api router can't show data in detail

Im learning React with routing and api calls at the moment. I've got an api were I need to find all the urls and post them on a page that was successful. But now I need to make a detail page where I can show more information about the url. Page 1 is like : gather all the urls and show them in a list. And page 2 is if you have clicked on a url in the list on page 1 you will be navigated to page 2 where a detail dialog is showed.
I have the page 1 completely and I can navigate to page 2 but I can't show any data from the api in page 2 information/detailpage.I don't know how to work with the key and get the info. I Tried several things but none of them works. Im using link.Link as key and I need description property , url , api name.
This is my page 1:
import React from "react";
import {Link, NavLink} from 'react-router-dom';
import { Detail } from './Detail';
export default class FetchLinks extends React.Component {
state = {
loading: true,
links: [],
};
async componentDidMount() {
const url = "https://api.publicapis.org/entries?category=development";
const response = await fetch(url);
const data = await response.json();
this.setState({links: data.entries, loading: false});
}
render() {
if (this.state.loading) {
return <div>loading...</div>
}
if (!this.state.links.length) {
return <div>geen links gevonden</div>
}
return (
<div>
{this.state.links.map(link => (
<div key={link.Link}>
<ul><li>
<p>
<NavLink to={`/Detail/${link.Link}`}>
{link.Link}
</NavLink>
</p>
</li></ul>
</div>))}
</div>
);
}
}
this is page 2:
import React from 'react';
import Content from './Content';
const Detail = () =>{
return(
<div>
<h1>Detail Page</h1>
<p>{this.state.links.Link}</p>
</div>
)
}
export default Detail();
This is how it should be
So if I understood correctly, you have all your data in page 1 and need to pass the data to page 2. For this you have to write your NavLink like this:
<NavLink to={{
pathname: `/Detail/${link.Link}`,
state: link.Link
}}>
{link.Link}
</NavLink>
Now you can use UseLocation hook of react-router to access this state that you just passed. So in page 2:
import React from 'react';
import { useLocation } from "react-router-dom";
const Detail = () =>{
const location = useLocation();
return (
<div>
<h1>Detail Page</h1>
<p>{location.state.link}</p>
</div>
);
}
export default Detail();
You can read more about react-router hooks here.

Reactjs: On clicking title display blog post contents

I'm trying to make a blogsite using MERN stack. So here I have my Blogs component which fetches all blogposts from db and display as shown below
here's the react code for the above pic
import { useEffect, useState } from "react";
import axios from 'axios';
import BlogDetails from "./BlogDetails";
const Blogs = () => {
const [blogPost, setPosts] = useState([])
useEffect(()=>{
const fetchBlog = async ()=>{
const blogsData = await axios('http://localhost:4000/blogs')
const blogsFetched = blogsData.data
console.log(blogsFetched);
setPosts(blogsFetched)
}
fetchBlog()
},[])
return (
<div className="blogs content">
<h2>All Blogs</h2>
{ blogPost.map((post)=>(
<div key={post._id}>
<a className="single" href={post._id}>
<h3 className="title" >{post.title}</h3>
<p className="snippet">{post.snippet}</p>
</a>
</div>
))
}
</div>
);
}
export default Blogs;
I want to show a particular blog post in detail when I click on its title from the above page.
On clicking the title, those details will be passed as props to another component named BlogDetails and will be rendered. The part where I'm stuck is routing to BlogDetails component with blogpost id. Is there any way in which on clicking the title, I can use Route to render BlogDetails component?
Please help
Assuming you're using react-router, you could do something like:
const history = useHistory();
.
.
.
<div onClick={() => history.push(`/blog/${post.id}`)}>
<h3 className="title">{post.title}</h3>
</div>
And then your router will render your component which you specified to be rendered for the /blog route. In that route, you can fetch the url using useLocation and then parse the id by splitting the url.
const { pathName } = useLocation(); // also from react-router
Alternatively (and more elegantly), you can push pieces of state when you do history.push, like:
...onClick={() => history.push(`/blog${post.id}`, { blogId: ${post.id}})}
Then again in your subsequently rendered component you use useLocation to get the state passed:
const { state } = useLocation();
yes you can use route for BlogDetail component
add BlogDetail link in BrowserRouter
<Route path={'url'} render={(props) => <BlogDetail/>}/>
In Blogs component use Link of react router dom and then use this link on title in render html like this
import { Link } from "react-router-dom";
{ blogPost.map((post)=>(
<div key={post._id}>
<a className="single" href={post._id}>
<Link to={{
pathname:'url', // this will be your url of BlogDetail
state: post //if you want post object detail in next component
}}>
<h3 className="title" >{post.title}</h3>
</Link>
<p className="snippet">{post.snippet}</p>
</a>
</div>
))
}

Query values lost on page refresh in Next js? [Example given]

I am making a simple Next Js application which has only two pages..
index.tsx:
import React from "react";
import Link from "next/link";
export default function Index() {
return (
<div>
<Link
href={{
pathname: "/about",
query: { candidateId: 8432 }
}}
as="about"
>
Go to the about page
</Link>
</div>
);
}
As per the above code, on click Go to the about page it goes to about page and using query I also receive the passed query values in about page.
about.tsx
import React from "react";
import Router, { withRouter } from "next/router";
function About({ router: { query } }: any) {
return (
<div>
Candidate Id: <b> {query.candidateId} </b>
</div>
);
}
export default withRouter(About);
This displays the value but on page refresh while we are in /about page, the candidateId received gets disappeared.
Requirement: Kindly help me to retain the query value passed down from one page to another page even on page refresh.
Note: As per my requirement I should not display the canidateId on url while navigating and hence I am using as approach.. I know I can achieve it if I remove as but I cannot remove that here in index page while navigating.. Reason is this will lead to displaying candidateId in the url which is not intended..
Tried this solution: https://stackoverflow.com/a/62974489/7785337 but this gives empty query object on refresh of page.
Stuck for very long time with this please kindly help me.
If you do not want to use the query parameter you may need to create a "store" that saves your variable that persist throughout your pages.
Sample code as follows.
//candidatestore.js
export const CandidateStoreContext = createContext()
export const useCandidateStore = () => {
const context = useContext(CandidateStoreContext)
if (!context) {
throw new Error(`useStore must be used within a CandidateStoreContext`)
}
return context
}
export const CandidateStoreProvider = ({ children }) => {
const [candidateId, setCandidateId] = useState(null);
return (
<CandidateStoreContext.Provider value={{ candidateId, setCandidateId }}>
{children}
</CandidateStoreContext.Provider >
)
}
Then you need to wrap the Provider around your app like
<CandidateStoreProvider><App /></CandidateStoreProvider>
This way you can use anywhere as follows both in your index page and your about page.
const { candidateId, setCandidateId } = useCandidateStore()
UseContext
In your codes, it should probably look something like that.
import React from "react";
import Link from "next/link";
import { useCandidateStore } from './candidatestore'
export default function Index() {
const { candidateId, setCandidateId } = useCandidateStore()
useEffect(() => {
setCandidateId(thecandidateId)
})
return (
<div>
<Link
href={{
pathname: "/about",
}}
as="about"
>
Go to the about page
</Link>
</div>
);
}
function About({ router: { query } }: any) {
const { candidateId, setCandidateId } = useCandidateStore()
return (
<div>
Candidate Id: <b> {candidateId} </b>
</div>
);
}
Update to Next.JS 10. It comes with Automatic Resolving of href which fixes your problem.
Try to delete the as="about" and then navigate again to the "about" page, the issue should be gone.
Codesandbox
My best bet would be to store the candidateId in an encrypted session on the client side. You could read/verify cookies in getServerSideProps() and pass their contents to the page component. If this sounds feasible, I'd recommend checking out the next-iron-session.
Another approach would be to check if candidateId exists in the query object in getServerSideProps(). If it does then pass it straight to the page component. If not, either get it elsewhere, redirect, or pass some default value. Append the following starter code to your about.tsx:
/* ... */
export function getServerSideProps({ query }: any) {
// if query object was received, return it as a router prop:
if (query.candidateId) {
return { props: { router: { query } } };
}
// obtain candidateId elsewhere, redirect or fallback to some default value:
/* ... */
return { props: { router: { query: { candidateId: 8432 } } } };
}
index.tsx file
Keep the code same as it is.
import React from "react";
import Link from "next/link";
export default function Index() {
return (
<div>
<Link
href={{
pathname: "/about",
query: { candidateId: 8432 }
}}
as="about"
>
Go to the about page
</Link>
</div>
);
}
AboutUs.tsx
Code starts from here
Adding router as a dependency in the useEffect the issue should get solved.
import Router, { useRouter } from "next/router";
import React, { useState, useEffect } from 'react';
function About({ router: { query } }: any) {
const route = userRouter();
const [candidateId, setCandidateid] = useState();
useEffect(() => {
const {candidateId} = router.query
if(candidateId) {
setCandidateid(candidateid)
}},[router]) //Here goes the dependency
return (
<div>
Candidate Id: <b> {candidateId} </b>
</div>
);
}
export default (About);

Categories