Can you interpolate path using react-router-dom v6 - ReactJS - javascript

I am aiming to create a custom path using react router dom v6 whereby my child component passes a string to my parent component (via a function) and then my parent component puts the strings value as a /param in the routes path parameter.
I have had success with all of this up to the last ste. I cannot figure out how to interpolate my dynamic value into the paths value (which is a string) the way you would interpolate a value into a regular string for example.
In a regular string I would just do Hello my URL is ${myurl} But this is does not work when trying to do it in the path value. Is there another way particular to react router dom v6 to accomplish my same goal (putting a dynamic string from my state into the parameters of path)?
Although this is just a exercise to see if its possible to do something like this (string interpolation in react router dom v6 route params), what I am trying to accomplish with this bit of code in the bigger picture is have my child component (which has buttons that can be clicked on, feed my parent component the name of the button that was clicked and then have the parent component (App.js) put the name of that button in the URL. That is why I have a state called myurl. I want the URL name to change based on the button clicked in the child component.
import logo from "./logo.svg";
import "./App.css";
import Genrenavbar from "./NavBars/Genrenavbar";
import { useState } from "react";
import { BrowserRouter as Router, Route, Routes } from "react-router-dom";
function App() {
const [myurl, Setmyurl] = useState("");
console.log("My URL IS", myurl);
return (
<Router>
<Routes>
<Route path=`/${myurl}` element={<Genrenavbar Setmyurl={Setmyurl} />} />
</Routes>
</Router>
);
}
export default App;
Also below is my Genrenavbar component for reference
import React from "react";
import { useState, useEffect } from "react";
import useFetchgenrenavbar from "../utils/useFetchgenrenavbar";
import Moviedisplay from "./Moviedisplay";
import { useParams } from "react-router-dom";
export default function Genrenavbar({ Setmyurl }) {
const [mygenres, setMygenres] = useState({ genres: [] });
const [myselectedgenre, setMyselectedgenre] = useState({});
const mygottengenres = useFetchgenrenavbar();
useEffect(() => {
setMygenres(mygottengenres);
}, [mygottengenres]);
const help = mygenres.genres.map((elem) => console.log(elem.name));
const trending = "Trending";
const TopRated = "Top Rated";
const myuseeffectfunction = useEffect(
() => console.log("My selected genre is ", myselectedgenre.name),
[myselectedgenre]
);
return (
<div>
<h1>Hello</h1>
{mygenres.genres.map((elem) => (
<button
onClick={() => {
setMyselectedgenre(elem);
Setmyurl(elem.name);
}}
>
{elem.name}
</button>
))}
<Moviedisplay myselectedgenre={myselectedgenre} />
</div>
);
}

I don't think the myurl state in the parent component is necessary. Instead of trying to pass down a state updater function to the child component it can issue navigation actions to a route with a dynamic route param.
Example:
App
function App() {
return (
<Router>
<Routes>
... other routes ...
<Route path="/:genre" element={<Genrenavbar />} />
... other routes ...
</Routes>
</Router>
);
}
Genrenavbar
import { useNavigate } from 'react-router-dom';
export default function Genrenavbar() {
const navigate = useNavigate(); // <-- access navigate function
const [mygenres, setMygenres] = useState({ genres: [] });
const [myselectedgenre, setMyselectedgenre] = useState({});
const mygottengenres = useFetchgenrenavbar();
useEffect(() => {
setMygenres(mygottengenres);
}, [mygottengenres]);
const help = mygenres.genres.map((elem) => console.log(elem.name));
const trending = "Trending";
const TopRated = "Top Rated";
const myuseeffectfunction = useEffect(
() => console.log("My selected genre is ", myselectedgenre.name),
[myselectedgenre]
);
return (
<div>
<h1>Hello</h1>
{mygenres.genres.map((elem) => (
<button
onClick={() => {
setMyselectedgenre(elem);
navigate(`/${elem.name}`); // <-- navigate to genre
}}
>
{elem.name}
</button>
))}
<Moviedisplay myselectedgenre={myselectedgenre} />
</div>
);
}

Related

Why is useParams returning undefined even with the correct syntax?

I know this question has been asked a lot, but I read every question and answer and my problem is not gone.
I'm trying to access http://localhost:3000/1 and the useParams should receive 1 to fetch data from an API, but I'm receiving undefined.
In Component.tsx I'm using console.log to receive the useParams but I'm getting undefined. All related files are posted as I'm using BrowserRouter correctly and other related React Router imports.
What is wrong in my code? Why am I not getting the correct params?
Component.tsx:
import { createContext, useEffect, useState } from "react";
import { useNavigate, useParams } from "react-router-dom";
import { CharacterSchema, PageSchema } from "../interfaces/characterInterfaces";
import { IGetAllCharacters } from "../interfaces/contextInterfaces";
import { IChildren } from "../interfaces/reactInterfaces";
import api from "../services/api";
export const GetAllCharactersContext = createContext<IGetAllCharacters>({} as IGetAllCharacters);
export const GetAllCharactersInfo = ({ children }: IChildren) => {
const [charactersList, setCharactersList] = useState<CharacterSchema[]>([]);
let navigate = useNavigate();
const { page } = useParams();
console.log(page);
useEffect(() => {
api
.get(`/characters?page=${page}`)
.then((res) => {
setCharactersList(res.data.data);
window.localStorage.setItem("lastPage", String(page));
return res;
})
.catch((err) => console.error(err));
}, [page]);
const nextPage = () => {
navigate(`/2`);
};
const prevPage = () => {
navigate(`/1`);
};
return (
<GetAllCharactersContext.Provider value={{ charactersList, nextPage, prevPage }}>
{children}
</GetAllCharactersContext.Provider>
);
};
AllRoutes.tsx:
const AllRoutes = () => {
return (
<Routes>
<Route path="/" element={<Navigate to="/1" />} />
<Route path="/:page" element={<Home />} />
<Route path="*" element={<Home />} />
</Routes>
);
};
export default AllRoutes;
index.tsx:
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import { BrowserRouter } from "react-router-dom";
import Providers from "./contexts/Providers";
const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement);
root.render(
<React.StrictMode>
<BrowserRouter>
<Providers>
<App />
</Providers>
</BrowserRouter>
</React.StrictMode>
);
Having this <Route path="/:page" element={<Home />} /> will let you consume page with useParams only in Home, the component rendered by Route for this specific url.
A way to accomplish what you want is to move the fetching part inside Home. To do so export as part of the context a function that fetches and updates the state:
// ⚠️ import what's needed
export const GetAllCharactersContext = createContext<IGetAllCharacters>({} as IGetAllCharacters);
export const GetAllCharactersInfo = ({ children }: IChildren) => {
const [charactersList, setCharactersList] = useState<CharacterSchema[]>([]);
const navigate = useNavigate();
const fetchCharactersList = useCallback((page) => {
api
.get(`/characters?page=${page}`)
.then((res) => {
setCharactersList(res.data.data);
window.localStorage.setItem("lastPage", String(page));
})
.catch((err) => console.error(err));
}, []);
const nextPage = () => {
navigate(`/2`);
};
const prevPage = () => {
navigate(`/1`);
};
return (
<GetAllCharactersContext.Provider value={{ charactersList, fetchCharactersList, nextPage, prevPage }}>
{children}
</GetAllCharactersContext.Provider>
);
};
And move that useEffect you had in the provider inside Home. Something like this:
// ⚠️ import what's needed
export default function Home() {
const { fetchCharactersList, charactersList } = useContext(GetAllCharactersInfo);
const { page } = useParams();
useEffect(() => {
if (!page) return;
fetchCharactersList(page);
}, [page]);
// render data
return <div></div>;
}
Just tested this myself. useParams needs to be used inside the <Route> where the param is specified. It doesn't just pull them from the url. If you want to keep the structure the same I would suggest exposing a page setter from your context and setting it inside one of the components that can access the hook.
Alternatively you could use useSearchParams which actually takes data from the url search params itself, but you wouldn't be able to have it on the url then.

Dynamically create pages based on slug from API in ReactJS

I have been learning about React Router have run into some issues while I was trying to implement a dynamic page creation based on the slugs that I receive from my fetch api call.
basically, I'm trying to redirect user to a new page after they click on a link - This new page will be a new component and I will make a new api call on this component with the slug as my search parameter.
However I'm struggling to dynamically change pages based on slugs.
Here is the component (BoxScore.js) in which I make the initial fetch and display data -
import React from "react";
import { useEffect, useState } from "react";
import { Link } from "react-router-dom";
const BoxScore = () => {
const [users, setUsers] = useState([]);
const [pageNumber, setPageNumber] = useState(1);
useEffect(() => {
fetch(
`myfetchapihere.com`
)
.then((response) => {
return response.json();
})
.then((data) => {
setUsers(data);
console.log(data);
setPageNumber(pageNumber);
});
}, [pageNumber]);
return (
<> {users.map((user) => (
<div
className="column"
key={user.id}
id={user.id}>
<div className="inner">
<div className="flex">
<h2 className="uk-text-small">
<Link to={user.slug} className="h2 link" id={user.slug}>
{user.name}
</Link>
</div>
</div></div>
)}
</>);
In my App.js I have react router set up -
<Routes>
<Route path="boxscore/:slug" element={<BoxScore />}>
<Route path=":slug" element={<OneScore />} />
</Route>
</Routes>
My OneScore component which isn't being rendered on the click of the link I set up in the Boxscore component -
import React from 'react'
import BoxScore from './Boxscore'
import { useParams } from "react-router";
import {useEffect} from 'react'
function OneScore() {
const { slug } = useParams();
useEffect(() => {
// Fetch post using the postSlug
console.log({slug});
}, [slug]);
return (
<div>
Hiii
</div>
)
}
export default OneScore
EDIT - I have managed to make the linking work thanks to #DrewReese comments however, the only issue remains now is that after the url is changed to (ex- www.a.com/boxscore/) the 'OneScore' component is not rendered instead the same BoxScore remains just the url is changed.
Instead of a raw anchor (<a />) tag use the Link or NavLink component. These link components work with the routing context being provided by the router. Using the anchor tag will reload the app, which very likely isn't what you want to occur.
import { Link } from 'react-router-dom';
...
{users.map((user) => (
<div
className="column"
key={user.id}
id={user.id}
>
<div className="inner">
<div className="flex">
<Link to={user.slug} className="h2link" id={user.slug}>
<h2 className="uk-text-small">{user.name}</h2>
</Link>
</div>
</div>
</div>
)};
The routed component should use the useParams hook to access the route's slug path param and an useEffect hook to rerun any logic that depends on this slug value.
import { useEffect } from 'react';
import { useParams } from 'react-router-dom';
...
const { slug } = useParams();
useEffect(() => {
// "make a new api call on this component with the slug as my search parameter"
}, [slug]);
Look into useParams to get the slug from the url.
https://v5.reactrouter.com/web/api/Hooks/useparams

Why does optional chaining allows rendering when fetching data through useEffect in an app that uses context?

I'm new to the webdev world and want to learn ReactJS. I followed a tutorial I found on YouTube made by Traversy where he makes a task tracker and now I want to make some changes to it to learn and practice some more.
I want to use context for the appointments (originally named tasks in the tutorial), add a calendar with react-calendar and use react-router-dom.
I got stuck for a while trying to make the list render, because it only rendered "empty". Later on found this post with a similar issue to mine: Only run a useEffect fetch after first useEffect fetch has fired and setUser in context
I changed bits of my code based on that post and now it does render the appointment list, but I don't know why it didn't work before and I'm unsure on why it does work now. I don't even know if I'm using context correctly or just prop-drilling. Help would be greatly appreciated. Thank you.
Also, sorry if my code is a mess, I'm new at this.
App.js
import { createContext, useState, useEffect } from "react";
import Dashboard from "./views/Dashboard";
import './App.css';
import { BrowserRouter as Router, Route, Routes} from "react-router-dom";
import AddAppointmentForm from "./views/AddAppointmentForm";
export const AppContext = createContext();
export const AppUpdateContext = createContext();
function App() {
const [appointments, setAppointments] = useState([])
const updateAppointments = (apptList) => {
setAppointments(apptList)
}
return (
<AppContext.Provider value={ appointments }>
<AppUpdateContext.Provider value={ updateAppointments }>
<Router>
<Routes>
<Route path="/" element={<Dashboard appointments={appointments} />} />
{/* <Route path="/add" element={<AddAppointmentForm />} /> TBA */}
</Routes>
</Router>
</AppUpdateContext.Provider>
</AppContext.Provider>
);
}
export default App;
Dashboard.js
import { useEffect, useContext} from "react";
import { AppContext } from "../App";
import { AppUpdateContext } from "../App";
import AppointmentList from "../components/AppointmentList";
import Header from "../components/Header";
// function Dashboard() { // this is how it used to be
function Dashboard(props) {
const appointments = useContext(AppContext)
const setAppointments = useContext(AppUpdateContext)
const fetchAppointmentList = async () => {
const res = await fetch("http://localhost:5000/appointments");
const data = await res.json();
return data;
}
useEffect(() => {
const getAppointments = async () => {
const appointmentsFromServer = await fetchAppointmentList();
setAppointments(appointmentsFromServer);
}
getAppointments();
console.log("ñññññ",appointments)
}, []);
console.log("aagh",appointments)
return (
<div style={dashboardStyle}>
<Header />
{/* {appointments.lenght>0 ? (<AppointmentList />) : <p>empty</p>} this is how it used to be */}
<AppointmentList appointments={props?.appointments}/>
</div>
);
}
const dashboardStyle = {
maxWidth: "31.25rem",
overflow: "auto",
minHeight: "18.75rem",
border: "1px solid steelblue",
margin: "1.875rem auto",
padding: ".5rem",
boxSizing: "border-box",
}
export default Dashboard;
AppointmentList.js
import Appointment from "./Appointment";
import { AppContext } from "../App";
import { useContext } from "react";
function AppointmentList({ appointments }) {
// function AppointmentList() { // this is how it used to be
// const { appointments, setAppointments } = useContext(AppContext)
console.log("appList",appointments) // this is how it used to be
return (
<>
{
appointments.map(appt => (
<Appointment key={appt.id} appointment={appt} />
))
}
</>
);
}
export default AppointmentList;
Why does optional chaining allows rendering when fetching data through
useEffect in an app that uses context?
<AppointmentList appointments={props?.appointments}/>
It allows rendering by preventing accidental accesses into potentially null or undefined objects. The only way props could be undefined though is if you just simply don't declare it, i.e. const Dashboard = () => {.... vs const Dashboard = (props) => {.....
You are drilling the appointments state through props. AppointmentList can use the AppContext context to access the appointments state, while Dashboard can use the AppUpdateContext context to update the appointments state.
App
function App() {
const [appointments, setAppointments] = useState([]);
const updateAppointments = (apptList) => {
setAppointments(apptList);
};
return (
<AppContext.Provider value={{ appointments }}> // <-- need object here
<AppUpdateContext.Provider value={{ updateAppointments }}> // <-- and here
<Router>
<Routes>
<Route path="/" element={<Dashboard />} /> // <-- don't pass props
</Routes>
</Router>
</AppUpdateContext.Provider>
</AppContext.Provider>
);
}
Dashboard
function Dashboard() { // <-- no props
const { updateAppointments } = useContext(AppUpdateContext); // <-- access from context
const fetchAppointmentList = async () => {
const res = await fetch("http://localhost:5000/appointments");
const data = await res.json();
return data;
};
useEffect(() => {
const getAppointments = async () => {
const appointmentsFromServer = await fetchAppointmentList();
updateAppointments(appointmentsFromServer);
}
getAppointments();
}, []);
return (
<div style={dashboardStyle}>
<Header />
<AppointmentList /> // <-- don't pass props
</div>
);
}
AppointmentList
function AppointmentList() { // <-- no props
const { appointments } = useContext(AppContext); // <-- access from context
return appointments.map(appt => (
<Appointment key={appt.id} appointment={appt} />
));
}

How to redirect to a url along with a component in react such that props passed to the component are not lost

When onClick event is triggered, I want to redirect to a new component (props passed to it) with a new url.
My App.js
import React from "react";
import Main from "./Components/Main/Main";
import "bootstrap/dist/css/bootstrap.min.css";
import styles from "./App.module.css";
import { BrowserRouter as Router, Route} from "react-router-dom";
import SearchBar from "./Components/SearchBar/SearchBar";
import AnimeInfo from "./Components/AnimeInfo/AnimeInfo";
import Cards from "./Components/Cards/Cards"
const App = () => {
return (
<Router>
<div className={styles.container}>
<SearchBar />
<Route path="/" exact component={Main} />
<Route path="/anime/info" component={AnimeInfo} />
<Route path="/anime/cards" component={Cards} />
</div>
</Router>
);
};
export default App;
In the following component, I am passing props to a component but I want to redirect to the url too, but doing so, the props passed that component are lost and I just get redirected
import React, { useEffect, useState } from "react";
import { apiDataTop, apiDataUpcoming, apiDataDay } from "../../api";
import styles from "./TopAnime.module.css";
import AnimeInfo from "../AnimeInfo/AnimeInfo";
import {
BrowserRouter as Router,
Switch,
Route,
Link,
Redirect,
} from "react-router-dom";
const TopAnime = () => {
const [animeData, setAnimeData] = useState([]);
const [animeDataHype, setAnimeDataHype] = useState([]);
const [animeDataDay, setAnimeDataDay] = useState([]);
const [image_url, setImageUrl] = useState("");
useEffect(() => {
callApi();
}, []);
const callApi = async () => {
const results = await apiDataTop();
const hypeResults = await apiDataUpcoming();
const dayResults = await apiDataDay();
setAnimeData(results);
setAnimeDataHype(hypeResults);
setAnimeDataDay(dayResults);
};
console.log(animeDataDay);
return (
<div>
<h1>Recent Release</h1>
<div className={styles.container}>
<br />
{animeDataDay === []
? null
: animeDataDay.map((anime) => {
return (
<a
href
onClick={(event) => {
event.preventDefault();
let animeName = anime.title;
animeName = animeName.replace(/\s+/g, "");
setImageUrl(anime.image_url);
console.log("image url original", anime.image_url);
console.log("image url", image_url);
}}
className={styles.move}
>
<img src={anime.image_url} alt="anime" />
<div className={styles.size}>
<h5>
<b>{anime.title}</b>
</h5>
</div>
</a>
);
})}
{image_url ? (
<Router>
// below commented approch first display the component on the same page and then redirects to the url
// but the props passed are lost !
// <Link to="/anime/info">
// <AnimeInfo image_url={image_url} />
// {window.location.href = `/anime/info`}
// </Link>
<Route
path="/anime/info"
render={() => <AnimeInfo image_url={image_url} />}
/>
</Router>
) : null}
</div>
export default TopAnime;
Following is the component, to whom I want to pass props and use the data passed to display (on a whole new page)!
import React, { useEffect, useState } from "react";
import styles from "./AnimeInfo.module.css";
console.log("The data image props issss", props.image_url);
return (
<div className={styles.container}>
<h1> I am info component</h1>
<img src={props.image_url} alt="anime" />
</div>
);
};
export default AnimeInfo;
Why not use the state property in history.push()?
See it in action here
use the history package.
then create a file at 'src/history.js'
import { createBrowserHistory } from 'history';
export default createBrowserHistory();
then in your component
import history from './history'
history.push({
pathname: '/path',
data_name: dataObject,
});
Then you can access the props in your other component:
this.props.location.data_name
Use render method in router
const renderComponent = (props, Component) => {
// write logic if needed
return <Component {...props} />
}
<Route path="/earner" render={(props) => renderComponent(props, Main)}/>

React js TypeError: Cannot read property 'id' of undefined

I am developing a site where posts of users with their email will appear in the screen, when a button is clicked it will go to the details of that specific post. I can change the route but it is not giving me the details. Rather it is saying that the variable decleared in PostDetails.js named 'id' is undefined.
App.js code
`
//app.js code
import React from 'react';
import './App.css';
import {
BrowserRouter as Router,
Switch,
Route,
Link
} from "react-router-dom";
import AllPosts from './Components/AllPosts/AllPosts';
import NoMatch from './Components/NoMatch/NoMatch';
import PostDetails from './Components/PostDetails/PostDetails';
function App() {
return (
<Router>
<Switch>
<Route exact path='/'>
<AllPosts></AllPosts>
</Route>
<Route to='/PostDetails/:id'>
<PostDetails></PostDetails>
</Route>
<Route path='*'>
<NoMatch></NoMatch>
</Route>
</Switch>
</Router>
)
}
export default App;
//ShowPost.js code
`
ShowPost.js code
`
//ShowPost.js code
import React from 'react';
import { Link } from 'react-router-dom';
const ShowPost = (props) => {
const {title, id} = props.post;
return (
<div>
<h1>{title}</h1>
<p>{id}</p>
<button><Link to={`/PostDetails/${id}`}>Click</Link></button>
</div>
);
};
export default ShowPost;
//PostDetails.js code
import React, { useState, useEffect } from 'react';
import { useParams } from 'react-router-dom';
const PostDetails = () => {
let {id} = useParams();
const {singlePost,setSinglePost} = useState();
useEffect(()=>{
fetch(`https://jsonplaceholder.typicode.com/posts/${id}`)
.then(res => res.json())
.then(data => setSinglePost(data))
},[])
return (
<div>
<h1>This is details {singlePost.id} </h1>
</div>
);
};
export default PostDetails;
Try passing a default empty value for singlePost and ensure you can console log the return data from fetch
const {singlePost,setSinglePost} = useState('');
You need to set an initial state to what you want your post to show by default, for example
const {singlePost,setSinglePost} = useState({title: "Loading", id: "Loading"});
If you do not want to show the post while the post is being fetched, you can do conditional rendering of the component. Then you can use your current way of setting state: const {singlePost,setSinglePost} = useState(); , and render the post conditionally:
return (
<div>
{singlePost ? <h1>This is details {singlePost.id} </h1> : null}
</div>
);
This will hide the post until it is fetched.
To your initial question: singlePost will be undefined and trying to reach the property id will yield an error. This is because fetch is async and will not set the state immediately.
Both of the answers above do not mention the most crucial thing - useState hook does not return an object, you can't use an object destructuring since useState returns an array. You will have to use array destructuring instead.
wrong:
const { singlePost, setSinglePost } = useState('');
correct:
const [singlePost, setSinglePost] = useState('');

Categories

Resources