Dynamically create pages based on slug from API in ReactJS - javascript

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

Related

React.js Render new page from API data using Slug. (click on an article from a list of articles and direct to the page where that article is)

The problem is - (trying to solve to problem of clicking on one article from a list of articles to then direct the user to that particular article to read) need to render a new page from an API call using the slug. The AllPosts page lists different articles. It requires a clickable link on the image in the list of articles to direct to the OnePost page that has the article on it, using data from the API. I have started on the BlogCard component where I use a Link component that wraps around the Title and Image from the article list. The Link component has the destination as {/posts/${slug}}>. the next part is trying to manage the routing on the App.js page, or if there is a more effective solution to this for the desired result(or If someone chooses a better way to do this). and finally, on the OnePost page (that the article is written on) I have started to make the API call with the intention of making it the same as the AllPost component(using AXIOS with graphQL schema), so far with little success. the OnePost page will display the article title and cover image. I have included a sandbox link to follow. And have left a commented-out code so it's not broken. Where it's at It will display the list of articles on the AllPosts page taken from the API data. Thanks so much in advance, I hope this all makes sense, any help or direction would be much appreciated.............. Thanks again - Python-Pirate 🐍🏴‍☠️😃...................
sandbox link -----> https://codesandbox.io/s/2-1-23-react-slug-link-3ncqli?file=/src/components/BlogCard.js
import React from "react";
import { Link } from "react-router-dom";
import "./styles.css";
const BlogCard = ({ coverPhoto, title }) => {
return (
<div className="card">
<Link to={`/posts/${slug}`}>
<h2>{title}</h2>
<img
src={coverPhoto ? coverPhoto.url : null}
alt=""
height={200}
width={200}
></img>
</Link>
</div>
);
};
export default BlogCard;
import React from "react";
import { useQuery } from "react-query";
import axios from "axios";
const endpoint =
"";
const QUERY = `
{
posts {
id
title
slug
coverPhoto {
id
url
}
}
}
`;
const SLUGLIST = `
{
posts {
slug
}
}
`;
const OnePost = ({ post }) => {
return (
<div>
<h1>One Post Page</h1>
<img
src={post.coverPhoto ? post.coverPhoto.url : null}
alt=""
height={200}
width={600}
></img>
<h2>{post.title}</h2>
</div>
);
};
export default OnePost;
import React from "react";
import { useQuery } from "react-query";
import axios from "axios";
import BlogCard from "../../components/BlogCard";
const endpoint =
"";
const QUERY = `
{
posts {
id
title
slug
coverPhoto {
createdBy {
id
}
url
}
}
}
`;
const AllPosts = () => {
const { data, isLoading, error } = useQuery("blog_posts", async () => {
const response = await axios({
url: endpoint,
method: "POST",
data: {
query: QUERY
}
});
return response.data.data;
});
if (isLoading) return "Loading...";
if (error) return <pre>{error.message}</pre>;
return (
<div>
<h1>Top Web Development Resources 2023</h1>
<ul>
{data.posts.map((post) => (
<BlogCard
title={post.title}
key={post.id}
coverPhoto={post.coverPhoto}
/>
))}
</ul>
</div>
);
};
export default AllPosts;
import React from "react";
import { QueryClient, QueryClientProvider } from "react-query";
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
import AllPosts from "./Pages/AllPosts/AllPosts";
import OnePost from "./Pages/OnePost/OnePost";
const queryClient = new QueryClient();
const App = () => (
<>
<Router>
<Switch>
<QueryClientProvider client={queryClient}>
<Route path="/" component={AllPosts} />
<Route path="/onepost" component={OnePost} />
<AllPosts />
</QueryClientProvider>
</Switch>
</Router>
</>
);
export default App;
The 2 images are examples of a list of posts and a single post, using the same data.
Since the data between the two pages/components is identical then I'd suggest fetching is once in a parent component and making it available to descendants via a React context.
Create a layout route that fetches the posts and provides it via an Outlet component's context. This is effectively just moving the fetching logic from AllPosts to a new layout route component.
import { QueryClient, QueryClientProvider, useQuery } from "react-query";
import axios from "axios";
import {
BrowserRouter as Router,
Routes,
Route,
Navigate,
Outlet
} from "react-router-dom";
import AllPosts from "./Pages/AllPosts/AllPosts";
import OnePost from "./Pages/OnePost/OnePost";
const queryClient = new QueryClient();
const endpoint =
"https://api-ap-southeast-2.hygraph.com/v2/clcdbkbxr45xk01tcdpxgg3sh/master";
const QUERY = `
{
posts {
id
title
slug
coverPhoto {
createdBy {
id
}
url
}
}
}
`;
const PostsLayout = () => {
const { data, isLoading, error } = useQuery("blog_posts", async () => {
const response = await axios({
url: endpoint,
method: "POST",
data: {
query: QUERY
}
});
return response.data.data;
});
if (isLoading) return "Loading...";
if (error) return <pre>{error.message}</pre>;
return <Outlet context={{ posts: data.posts }} />;
};
Render the layout route to wrap the two existing routes.
const App = () => (
<QueryClientProvider client={queryClient}>
<Router>
<Routes>
<Route path="/posts" element={<PostsLayout />}>
<Route index element={<AllPosts />} />
<Route path=":id" element={<OnePost />} />
</Route>
<Route path="*" element={<Navigate to="/posts" replace />} />
</Routes>
</Router>
</QueryClientProvider>
);
Update AllPposts to use the useOutletContext hook to access the provided posts context value.
import BlogCard from "../../components/BlogCard";
import { useOutletContext } from "react-router-dom";
const AllPosts = () => {
const { posts } = useOutletContext();
return (
<div>
<h1>Top Web Development Resources 2023</h1>
<ul>
{posts.map((post) => (
<BlogCard {...post} key={post.id} />
))}
</ul>
</div>
);
};
Update BlogCard to correctly render that link to the details route.
import { Link } from "react-router-dom";
const BlogCard = ({ coverPhoto, id, title }) => {
return (
<div className="card">
<Link to={`/posts/${id}`}>
<h2>{title}</h2>
<img
src={coverPhoto ? coverPhoto.url : null}
alt=""
height={200}
width={200}
/>
</Link>
</div>
);
};
Update OnePost to access the id route path parameter and the posts array and find the matching post by id.
import { useOutletContext, useParams } from "react-router-dom";
const OnePost = () => {
const { id } = useParams();
const { posts } = useOutletContext();
const post = posts.find((post) => post.id === id);
if (!post) {
return <div>No post.</div>;
}
return (
<div>
<h1>One Post Page</h1>
<img
src={post.coverPhoto ? post.coverPhoto.url : null}
alt="cover art"
height={200}
width={600}
/>
<h2>{post.title}</h2>
</div>
);
};

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

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>
);
}

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('');

Form submitting from another router in react

I'm a newbie in React and I'm trying to make a basic meal app. In App component, I'm searching for meal and fetching data from my api and clicking on any meal for its detail page. However, on detail page when searching a new meal it doesn't work. How can I solve this problem?
Thanks for your answer in advance.
It's my App.js
import React,{ useState } from 'react';
import { BrowserRouter, Route, } from 'react-router-dom';
import Header from './components/Header'
import Search from './components/Search'
import MealList from './components/MealList'
import MealDetail from './components/MealDetail'
import axios from 'axios';
import styles from './App.module.css';
function App(props) {
const [meals, setMeals] = useState([]);
const [isLoaded, setIsLoaded] = useState(true);
console.log(props)
const getQuery = (query) => {
//history.replace('/');
setIsLoaded(false);
axios.get(`https://www.themealdb.com/api/json/v1/1/search.php?s=${query}`)
.then(response => {
setIsLoaded(true);
setMeals(response.data.meals)
})
}
return (
<BrowserRouter>
<div className={styles.body}>
<Header />
<div className={styles.container}>
<Search getQuery={getQuery} />
<Route path='/' exact render={() => <MealList meals={meals} />} />
<Route path='/meals/:id' component={MealDetail} />
</div>
</div>
</BrowserRouter>
);
}
export default App;
I solved this problem with "withRouter" in App component. It gave me some information with props and I redirected to the homepage when form submitting.

Categories

Resources