How do I navigate to another page? - reactjs

I take data from the resource thememoviedb. I received a certain number of movies, and I also displayed them on the page. I try to go to each movie to see the details in the file Movie_Details.js. In this file, I get data from the resource as well, but the transition does not occur, and the Movie_Details component is drawn at the bottom. How do I go to the Movie Details page and display the data? Thank you
App.js
import {BrowserRouter as Router, Switch, Route, Link} from "react-router-dom";
import {Popular_Movies} from "./Components/Popular_Movies/Popular_Movies";
import {Movie_Detail} from "./Components/Popular_Movies/Movie_Detail";
function App() {
return (
<div>
<Router>
<Popular_Movies/>
<Route path="/movie/:id" exact>
<Movie_Detail/>
</Route>
</Router>
</div>
);}
export default App;
Popular_Movie.js
import React from "react";
import {image_api} from "../../Services/Service";
import {Link} from "react-router-dom";
export let Popular_Movie = ({name, title, poster_path, vote_average}) => {
return (<div className='different_movie'>
<img src={image_api + poster_path} className='image'/>
<div className='bottom_different_movie'>
<h3> <Link to={`/movie/:id`}>{name} {title} {vote_average}</Link></h3>
</div>
</div>) }
Popular_Movies.js
import React, {useEffect, useState} from "react";
import {Popular_Movie} from "./Popular_Movie";
export let Popular_Movies = () => {
let popularMovieUrl = `https://api.themoviedb.org/3/tv/popular?api_key=e12a044061e5fe9077c7aee8a5165126&language=en-US&page=1`;
let fetchMovies = async () => {
let response = await fetch(popularMovieUrl)
let data = await response.json()
setPopularMovie(data.results)
}
useEffect(() => {
fetchMovies()
},[])
return (<div>
{popularMovie.length > 0 && popularMovie.map(movie => <Popular_Movie key={movie.id} {...movie}/>)}
</div>) }
Movie_Detail
import React, {useEffect, useState} from "react";
export let Movie_Detail = () => {
let movieUrl = 'https://api.themoviedb.org/3/tv/popular?api_key=e12a044061e5fe9077c7aee8a5165126&language=en-US&page=1'
let [differentMovie, setDifferentMovie] = useState([])
let fetchDifferentMovie = async () => {
let response = await fetch(movieUrl)
let data = await response.json()
setDifferentMovie(data.results)
}
useEffect(() => {
fetchDifferentMovie()
},[])
return (<div>
{differentMovie.title}
</div>)}

If you want to show movie list on / route, and movie details on /movie/:id you should have your router structured like this:
<Router>
... // put navigation component here
<Switch>
<Route path="/" exact>
<Popular_Movies/>
</Route>
<Route path="/movie/:id" exact>
<Movie_Detail/>
</Route>
</Switch>
</Router>
Inside Movie_Detail component you need to extract movie id by using the useParams hook
const { id } = useParams();
... // fetch movie details using `id`
It's important for you to understand how Switch operates. It's like you have if-else logic which shoud decide which component to render when certain path is matched. Please, read more on Basic Routing.
P.S. The reason you're seeing both popular movies and movie details underneath is because your Popular_Movies component call was not wrapped with <Route> which means that it should be always present/visible.

Related

Clicking on a Card component should create a new route and display further information

I'm new to react-router v6
I have 4 components, App, CardList, Card and CardInfo. There is data (an array of objects, each object represents a movie) coming from an API that gets saved in App.js with useState hook.
Within CardList, I use map to iterate over the array to generate a bunch of Card components and passing in data via props.
What I want now is to be able to click on any Card component and for it to navigate to a different route, e.g. localhost:3000/1 (for Card with the id of 1), localhost:3000/2 (for Card with the id of 2) etc. and within each route that corresponds to the Card id, there would be a box/modal (CardInfo.js) component with further information about the movie.
I'm trying to accomplish this with react-router-dom (version 6).
It looks like within the CardList.js or Card.js component you would need to create links (<Link>) and routes (<Route>) (both which are equal to the number of movies in the data) on the fly with the .map function and wrapping the Card component in <Link> and <Route> tags. Something like
{items.map(movie => (
<Route path="/:id" element={<Card items={movies} />} exact>
<Link to={`/${movie.id}`}>
<Card
key={movie.id}
id={movie.id}
name={movie.name}
description={movie.description}
img={movie.image_url}
/>
</Link>
</Route>
))}
Obviously that doesn't work.
App.js:
import './App.css';
import CardList from './features/Card/CardList';
import CardInfo from './features/Card/CardInfo';
import { useState, useEffect } from "react";
import { BrowserRouter, Route, Routes } from 'react-router-dom';
function App() {
const [movies, setMovies] = useState([]);
useEffect(() => {
const getData = fetch('https://api.com/movies')
.then(data => data.json())
.then(items => { setMovies(items) })
}, [])
return (
<BrowserRouter>
<Routes>
<Route path="/:id" element={<CardInfo items={movies} />} exact></Route>
</Routes>
<CardList items={movies} />
</BrowserRouter>
);
}
export default App;
CardList.js:
import React from 'react'
import Card from './Card'
import "./CardList.css";
const CardList = ({ items }) => {
return (
<div className="cardList">
{items.map(movie => (
<Card
key={movie.id}
id={movie.id}
name={movie.name}
description={movie.description}
img={movie.image_url}
/>
))}
</div>
)
}
export default CardList
Card.js:
import React from 'react'
import "./Card.css";
import { Link } from 'react-router-dom';
function Card(props) {
return (
<Link to={`/${props.id}`}>
<div className="card">
<div>
<img src={props.img} />
<h2>{props.name}</h2>
</div>
</div>
</Link>
)
}
export default Card
CardInfo.js:
import React from 'react'
function CardInfo(props) {
return (
<div>
<p>{props.description}</p>
</div>
)
}
export default CardInfo
your code structure is correct. You just need to useLink, you don't need en external Route component for every card you map.
Here is a link for further information. Hope you find it helpful.
https://stackoverflow.com/a/57059249/17715977

Delay in loading the array crushs the app

Yo-yo everyone,
along my path of practicing the art of React, I noticed a bug that I couldn't seem to find a good source to help me understand what causes the problem.
My array in a child component takes too long to load, resulting in an error.
The data is fetched from "jsonplaceholder," users list.
Data is set as a state.
Sent to "UserProfilePage".
Sent to "UserProfileComponent".
Trying to reach the URL "/user/1" will not succeed since the object is undefined.
*) Commenting the "UserProfileComponent," and then uncomment without refreshing will successfully load the page.
*) Coping (not fetching) the data to the App.js, assigning it to the state, will not crush the system.
APP.js
import { Component } from "react";
import { Redirect, Route, Switch } from "react-router-dom";
import "./App.css";
import Navigation from "./components/header/Navigation";
import PostsLog from "./components/Posts/PostsLog";
import UserProfileCollection from "./pages/UserProfileCollection";
import UserProfilePage from "./pages/UserProfilePage";
const POST_ENDPOINT = "https://jsonplaceholder.typicode.com/posts";
const USER_ENDPOINT = "https://jsonplaceholder.typicode.com/users";
class App extends Component {
constructor() {
super();
this.state = {
exUsersArray: [],
exPostsArray: [],
};
}
async componentDidMount() {
try {
const responseUser = await fetch(USER_ENDPOINT);
const responsePost = await fetch(POST_ENDPOINT);
const dataResponseUser = await responseUser.json();
const dataResponsePost = await responsePost.json();
this.setState({ exUsersArray: dataResponseUser });
this.setState({ exPostsArray: dataResponsePost });
} catch (error) {
console.log(error);
}
}
render() {
const { exUsersArray, exPostsArray } = this.state;
console.log(exUsersArray);
return (
<div className="app">
<Navigation />
<main>
<Switch>
{/* REROUTES */}
<Route path="/" exact>
<Redirect to="/feed" />
</Route>
<Route path="/users" exact>
<Redirect to="/user" />
</Route>
{/* REAL ROUTES */}
<Route path="/feed">
<PostsLog usersInfo={exUsersArray} usersPosts={exPostsArray} />
</Route>
<Route path="/user" exact>
<UserProfileCollection usersInfo={exUsersArray} />
</Route>
{/* DYNAMIC ROUTES */}
<Route path="/user/:userId">
<UserProfilePage usersInfo={exUsersArray} />
</Route>
</Switch>
</main>
</div>
);
}
}
export default App;
UserProfilePage.js
import { useParams } from "react-router-dom"
import UserProfileComponent from "../components/UserProfileComponent";
const UserProfilePage = ({usersInfo}) => {
const params = useParams();
const foundUser = usersInfo.find((user) => Number(user.id) === Number(params.userId))
console.log("found user ", foundUser);
// console.log(usersInfo);
console.log(params, " is params");
return(
<div>
<UserProfileComponent userProfile={foundUser}/>
<p>Yo YO</p>
</div>
)
}
export default UserProfilePage;
UserProfileComponent
const UserProfileComponent = ({userProfile}) => {
console.log(userProfile)
return (
<div className="text-group">
<div className="wrap-post">
<p>
<strong>Info</strong>
</p>
<img
src={`https://robohash.org/${userProfile.Id}.png`}
id="small-profile"
alt="user profile in circle"
/>
<p><u><strong>ID</strong></u> : {userProfile.id}</p>
<p>Name: {userProfile.name}</p>
<p>#{userProfile.username}</p>
<p>Email: {userProfile.email}</p>
<p>
{userProfile.address.street} {userProfile.address.suite}<br/>
{userProfile.address.zipcode} {userProfile.address.city}
</p>
<p>Global position</p>
<p>{userProfile.address.geo.lat}, {userProfile.address.geo.lang}</p>
<p>{userProfile.phone}</p>
<p>{userProfile.website}</p>
<p>Company</p>
<p>{userProfile.company.name}</p>
<p>{userProfile.company.catchPhrase}</p>
<p>{userProfile.company.bs}</p>
</div>
</div>
);
};
export default UserProfileComponent;
Complete repository here.
I will be happy to any tips to help me understand what happened here.
Appreciation will be given to any tip that will help me be a better programmer.
Best wishes y'all.
it seems like usersInfo hasn't loaded a quick way to fix it is to just add this to the users component.
UserProfilePage.js
import { useParams } from "react-router-dom"
import UserProfileComponent from "../components/UserProfileComponent";
const UserProfilePage = ({usersInfo}) => {
const params = useParams();
if(!usersInfo) {
return <p>Loading...</p>
}
const foundUser = usersInfo.find((user) => Number(user.id) === Number(params.userId))
console.log("found user ", foundUser);
// console.log(usersInfo);
console.log(params, " is params");
return(
<div>
<UserProfileComponent userProfile={foundUser}/>
<p>Yo YO</p>
</div>
)
}
export default UserProfilePage;
UserProfileComponent.js
const UserProfileComponent = ({userProfile}) => {
if(!userProfile) {
return <p>Loading...</p>
}
console.log(userProfile)
return (
<div className="text-group">
<div className="wrap-post">
<p>
I see that you're rendering your compoonent without doing any null check in UserProfileComponent. Actually to be a better programmer or doing better work, you have to control every null case in order not to crash your app.
<p><u><strong>ID</strong></u> : {userProfile.id}</p>
<p>Name: {userProfile.name}</p>
<p>#{userProfile.username}</p>
<p>Email: {userProfile.email}</p>
<p>
{userProfile.address.street} {userProfile.address.suite}<br/>
{userProfile.address.zipcode} {userProfile.address.city}
</p>
<p>Global position</p>
<p>{userProfile.address.geo.lat}, {userProfile.address.geo.lang}</p>
<p>{userProfile.phone}</p>
<p>{userProfile.website}</p>
<p>Company</p>
<p>{userProfile.company.name}</p>
<p>{userProfile.company.catchPhrase}</p>
<p>{userProfile.company.bs}</p>
You'll see that there's no null check. It would be better if you have some null check on your userProfile
Also, my suggestion is, you can create a loading in your state.
Before sending your request, you can set the loading to true.
And when your loading is true, you can show some spinner or sth like that. When your request finishes, you can set the loading variable to false and you can show your data.
The main point is, always use a loading variable to check the loading state instead of checking the null | undefined state of your data.

How to map a filter of a string value in an array of strings

I have an app built with React and Express Node, and in it I have 3 separate components. The first component is a gallery, where user selects an image to create a post with an background image. When button is clicked, user is taken to a form. The user will edit the inputs with some text and save the form which has a axios.post request to send the data to mongo db through express route. After saving user clicks view post that takes them to another component with axios.get request displaying image and input data to the user.
I have routes that have a unique http path to show the component that is active. My question is how can I map the routes to dynamically load the name of the image that comes from mongodb collection, instead of manually writing in the paths image name ie: path={"/getinputwaterfall/:id"}, path={"/getinputcross/:id"}, path={"/getinputfreedom/:id"} . I would like to have instead somthing like: path={"/getinput{urlName}/:id"}.
In the mongoDB collection I have a URL and name string array. The URL string is an http path from firebase and the name string are images names.
Is this possible to do?
Bellow is the code and my attempts to do this.
import React, {useState, useEffect} from "react";
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import axios from "axios";
import "bootstrap/dist/css/bootstrap.min.css";
import "./index.css";
//gallery imports
import Cross from "./Components/Gallery/posts/Cross";
import Waterfall from "./Components/Gallery/posts/Waterfall";
import Freedom from "./Components/Gallery/posts/Freedom";
import CrossPost from "./Components/Gallery/get/CrossPost";
import WaterfallPost from "./Components/Gallery/get/WaterfallPost";
import FreedomPost from "./Components/Gallery/get/FreedomPost";
function App() {
const [name, setName] = useState([]);
const loadImage = async () => {
try {
let res = await axios.get("http://localhost:5000/geturls");
console.log(res.data)
setName(res.data.map(n=>n.name)); //array of names
} catch (error) {
console.log(error);
}
};
useEffect(() => {
loadImage();
}
,[]);
return (
<div className="App">
<Router>
<Switch>
{/* routes for gallery */}
<Route path={"/waterfall"} component={Waterfall} />
<Route path={"/cross"} component={Cross} />
<Route path={"/freedom"} component={Freedom} />
<Route path={"/getinputwaterfall/:id"} component={WaterfallPost} />
<Route path={"/getinputcross/:id"} component={CrossPost} />
<Route path={"/getinputfreedom/:id"} component={FreedomPost} />
{/* what I tryed to map */}
{name.filter(name => name === `${name}` ).map((urlName) => (
<Route exact path={`/getinputt/${urlName}`} component={CrossPost} />
))}
</Switch>
</Router>
</div>
);
}
export default App;
update: I applied the first option of the answer to my code for those who wish to see the complete solution: Note: I had to remove the '/' in /getinput/${name}/:id to make my code work! Thanks Drew!
const imagePostRoutes = [
{ name: "cross", component: CrossPost },
{ name: "freedom", component: FreedomPost },
{ name: "waterfall", component: WaterfallPost },
];
return (
<div className="App">
<Router>
<Switch>
{imagePostRoutes.map(({ component, name }) => (
<Route
key={name} path={`/getinput${name}/:id`} component={component}
/>
))}
</Switch>
</Router>
</div>
);
}
I was first suggesting to create a "routes" config array that can be mapped.
const imagePostRoutes = [
{ name: "cross", component: CrossPost },
{ name: "freedom", component: FreedomPost },
{ name: "waterfall", component: WaterfallPost },
];
...
{imagePostRoutes.map(({ component, name }) => (
<Route key={name} path={`/getInput/${name}/:id`} component={component} />
))}
The second suggestion is to use a single generic dynamic route where a match parameter could specify the post type and a general post component to render the specific image post component. This is a very stripped down minimal version.
Define the route.
<Route path="/getInput/:imagePostType/:id" component={ImagePost} />
Create a Map of match param to component to render.
const postComponents = {
cross: CrossPost,
freedom: FreedomPost,
waterfall: WaterfallPost,
};
Create a component to read the match params and load the correct post component from the Map.
const ImagePost = () => {
const { id, imagePostType } = useParams();
const Component = postComponents[imagePostType];
if (!Component) {
return "Unsupported Image Post Type";
}
return <Component id={id} />;
}

useParams hook returns undefined in react functional component

The app displays all photos <Photo> in a grid <PhotoGrid>, then once clicked, a function in <Photo> changes URL with history.push, and Router renders <Single> based on URL using useParams hook.
PhotoGrid -> Photo (changes URL onClick) -> Single based on URL (useParams).
I must have messed something up, becouse useParams returns undefined.
Thanks for all ideas in advanced.
App.js
class App extends Component {
render() {
return (
<>
<Switch>
<Route exact path="/" component={PhotoGrid}/>
<Route path="/view/:postId" component={Single}/>
</Switch>
</>
)
}
}
export default App;
Photogrid.js
export default function PhotoGrid() {
const posts = useSelector(selectPosts);
return (
<div>
hi
{/* {console.log(posts)} */}
{posts.map((post, i) => <Photo key={i} i={i} post={post} />)}
</div>
)
}
in Photo I change URL with history.push
const selectPost = () => {
(...)
history.push(`/view/${post.code}`);
};
Single.js
import { useParams } from "react-router-dom";
export default function Single() {
let { id } = useParams();
console.log("id:", id) //returns undefined
return (
<div className="single-photo">
the id is: {id} //renders nothing
</div>
)
}
When using useParams, you have to match the destructure let { postId } = useParams(); to your path "/view/:postId".
Working Single.js
import { useParams } from "react-router-dom";
export default function Single() {
const { postId } = useParams();
console.log("this.context:", postId )
return (
<div className="single-photo">
{/* render something based on postId */}
</div>
)
}
You should use the same destructure as mentioned in your Route path. In this case, you should have written :
let { postID } = useParams();
I will mention two more mistakes which someone could make and face the same problem:
You might use Router component in place of Route component.
You might forget to mention the parameter in the path attribute of the Route component, while you would have mentioned it in the Link to component.
Ensure the component where you call useParams() is really a child from <Route>
Beware of ReactDOM.createPortal
const App = () => {
return (
<>
<Switch>
<Route exact path="/" component={PhotoGrid}/>
<Route path="/view/:postId" component={Single}/>
</Switch>
<ComponentCreateWithPortal /> // Impossible to call it there
</>
)
}
You have to check API that you are using. Sometimes it's called not just id. That's why useParams() do not see it

react-router#5 route `onEnter` method not calling, scrolltoview not working

according to my requirement, when a user click on
<Link to="/products/shoe#product9">Go to projects and focus id 9</Link> I would like to show him the product. (hello page) for that I do this:
import React from "react";
import { Link, Route, Switch, Redirect } from "react-router-dom";
import "./products.scss";
const Shoes = React.lazy(() => import("./shoes/shoes.component"));
const Cloths = React.lazy(() => import("./cloths/cloths.component"));
function hashScroll() {
alert("called");
const { hash } = window.location;
if (hash !== "") {
setTimeout(() => {
const id = hash.replace("#", "");
const element = document.getElementById(id);
if (element) element.scrollIntoView();
}, 0);
}
}
export default class Products extends React.Component {
render() {
return (
<div>
<header>
<Link to="/products/shoe">Shoes</Link>
<Link to="/products/cloths">Cloths</Link>
</header>
<h1>Products page</h1>
<main>
<Switch>
<Redirect exact from="/products" to="/products/shoe" />
<Route path="/products/shoe" onEnter={hashScroll}>
<Shoes />
</Route>
<Route path="/products/cloths">
<Cloths />
</Route>
</Switch>
</main>
</div>
);
}
}
I attached an onEnter function to call and scroll, so when there is a #hash let it scroll. It's not working at all. Please navigate to Hello page, from you click the link to go to products page.
Live Demo
onEnter is no longer working in react-router
What you can do is pass a prop to the component
<Shoes onEnter={hashScroll} />
inside the Shoes component execute it on componentDidMount.
componentDidMount = () => {
if (this.props.onEnter) {
this.props.onEnter();
}
};
demo

Resources