useEffect not running at all when navigating to page with React Router - javascript

App.js:
import React, { Fragment } from "react";
import Header from "./components/Header";
import PostList from "./components/PostList";
import Post from "./components/Post";
import TagList from "./components/TagList";
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
const App = () => {
return (
<Fragment>
<Router>
<Header />
<Switch>
<Route exact path="/" component={PostList} />
<Route path="/tags" component={TagList} />
<Route path="/posts/:id" component={Post} />
</Switch>
</Router>
</Fragment>
);
};
export default App;
Post.js:
import React, { useEffect, useState } from "react";
import Tag from "./Tag";
import { useParams } from "react-router-dom";
import axios from "axios";
const Post = () => {
const { id } = useParams();
const [post, setPost] = useState({});
useEffect(() => {
const fetchPost = async () => {
try {
const res = await axios.get(`/api/posts/${id}`);
setPost(res.data);
} catch (err) {
console.error(err);
}
};
fetchPost();
}, []);
return (
<div>
<h2>{post.title}</h2>
<p>{post.text}</p>
<div>
{post.tags.map((tag) => (
<Tag key={tag._id} tag={tag} />
))}
</div>
</div>
);
};
export default Post;
I'm trying to get the skeleton for a simple blog site up and running but I'm having issues with the Post component. When navigating to a specific post with the route '/posts/:id' the useEffect that's supposed to grab the post from my API doesn't seem to run, and inevitably I end up with a 'post.tags is undefined' error. Everything else is working correctly - API responds as expected to requests from Postman and 'useParams' is grabbing the post id from the URL just fine - it's just that the useEffect isn't running at all (console.logs aren't showing up either).
I've had no issues doing things this way in previous projects - in fact the useEffect in the TagList component is virtually identical and the /tags route works as expected, so I'm not sure what I'm missing?

useEffect runs only at first render, and then at any other render IF the dependencies specified have changed. Since you added there an empty array, those never change.
If you want useEffect to run again when the post id has changed, you need to add id as a dependency for useEffect.
useEffect(() => {
....
}, [id, setPost]);
also, your post.tags will still be undefined, because the data comes after the component has finished rendering so you should actually check before that return if you have post data and if you don't have post data, to return null or a loading skeleton.

Related

ProductDetails.js:27 Uncaught TypeError: Cannot read properties of undefined (reading 'params')

The tutorial that I'm going off of is outdated I'm sure. I ran into this error ProductDetails.js:27
Uncaught TypeError: Cannot read properties of undefined (reading 'params')
This is my code for that line ProductDetails.js:27 is the dispatch, alert, error, match.params.id:
import React, { Fragment, useEffect } from 'react'
import { Carousel } from 'react-bootstrap'
import Loader from '../layout/Loader'
import MetaData from '../layout/MetaData'
import { useAlert } from 'react-alert'
import { useDispatch, useSelector } from 'react-redux'
import { getProductDetails, clearErrors } from '../../actions/productActions'
const ProductDetails = ({ match }) => {
const dispatch = useDispatch();
const alert = useAlert();
const { loading, error, product } = useSelector(state => state.productDetails)
useEffect(() => {
dispatch(getProductDetails(match.params.id))
if(error) {
alert.error(error);
dispatch(clearErrors())
}
}, [dispatch, alert, error, match.params.id])
export default ProductDetails
Not sure how much of the ProductDetails.js code but here it is, I left out my return function since it said my post was mostly code. Also posted below is my App.js code:
import { BrowserRouter as Router, Route, Routes } from "react-router-dom"
import Header from './components/layout/Header'
import Footer from './components/layout/Footer'
import Home from './components/Home'
import ProductDetails from './components/product/ProductDetails'
function App() {
return (
<Router>
<div className="App">
<Header />
<div className="container container-fluid">
<Routes>
<Route path="/" element={<Home /> } exact />
<Route path="/product/:id" element={<ProductDetails /> } exact />
</Routes>
</div>
<Footer />
</div>
</Router>
);
}
export default App;
For what it's worth the useParams hook hasn't changed since its introduction in react-router-dom#5 when React introduced hooks.
The issue is that match is simply undefined and/or not explicitly passed as a prop to this ProductDetails component. If you are using const params = useParams() and params as dependency then this very well likely is the cause of render looping as params will be a new object reference each render cycle. You should instead reference the id param directly.
I suggest also splitting up the effects into two separate effects so getProductDetails isn't dispatched because error updated, and vice-versa.
Example:
const ProductDetails = () => {
const dispatch = useDispatch();
const alert = useAlert();
const { id } = useParams();
const { loading, error, product } = useSelector(state => state.productDetails);
useEffect(() => {
if (id) {
dispatch(getProductDetails(id));
}
}, [dispatch, id]);
useEffect(() => {
if (error) {
alert.error(error);
dispatch(clearErrors());
}
}, [dispatch, alert, error]);
...

React-router-dom Render props isn't returning any component

I'm using react-router-dom version 6.0.2 here and the "Render" props isn't working, every time I got to the url mentioned in the Path of my Route tag it keeps throwing me this error - "Matched leaf route at location "/addRecipe" does not have an element. This means it will render an with a null value by default resulting in an "empty" page.". Can someone please help me with this issue
import './App.css';
import Home from './components/Home';
import AddRecipe from './components/AddRecipe';
import items from './data';
import React, { useState } from 'react';
import {BrowserRouter as Router, Routes, Route} from 'react-router-dom';
const App = () => {
const [itemsList, setItemsList] = useState(items)
const addRecipe = (recipeToAdd) => {
setItemsList(itemsList.concat([recipeToAdd]));
}
const removeItem = (itemToRemove) => {
setItemsList(itemsList.filter(a => a!== itemToRemove))
}
return (
<Router>
<Routes>
<Route path="/addRecipe" render={ ({history}) => {
return (<AddRecipe onAddRecipe={(newRecipe) => {
addRecipe(newRecipe);
history.push('/');
} } />);
} } />
</Routes>
</Router>
);
}
export default App;
In react-router-dom version 6, you should use element prop for this.
I suggest your read their document on upgrading from version 5 where they explain the changes.
For your problem, you should write something like this:
<Route
path="/addRecipe"
element={
<AddRecipe
onAddRecipe={(newRecipe) => {
addRecipe(newRecipe);
history.push('/');
}
/>
}
/>
The Route component API changed significantly from version 5 to version 6, instead of component and render props there is a singular element prop that is passed a JSX literal instead of a reference to a React component (via component) or a function (via render).
There is also no longer route props (history, location, and match) and they are accessible only via the React hooks. On top of this RRDv6 also no longer surfaces the history object directly, instead abstracting it behind a navigate function, accessible via the useNavigate hook. If the AddRecipe component is a function component it should just access navigate directly from the hook. If it unable to do so then the solution is to create a wrapper component that can, and then render the AddRecipe component with the corrected onAddRecipe callback.
Example:
const AddRecipeWrapper = ({ addRecipe }) => {
const navigate = useNavigate();
return (
<AddRecipe
onAddRecipe={(newRecipe) => {
addRecipe(newRecipe);
navigate('/');
}}
/>
);
};
...
const App = () => {
const [itemsList, setItemsList] = useState(items);
const addRecipe = (recipeToAdd) => {
setItemsList(itemsList.concat([recipeToAdd]));
};
const removeItem = (itemToRemove) => {
setItemsList(itemsList.filter(a => a !== itemToRemove))
};
return (
<Router>
<Routes>
<Route
path="/addRecipe"
element={<AddRecipeWrapper addRecipe={addRecipe} />}
/>
</Routes>
</Router>
);
};

ProductScreen.js:12 Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'params')

im running into a problem and no matter how much research/googling i do, i cant seem to fix this issue, been dealing with this error for last 2 days, first i tried to find any answer online someone else might have had but i cant seem to figure it out, so now i need to ask someone more experienced as to what im doing wrong, please take a look at my error and code pictures and see if you might catch something i cant... tunnel vision..
frontend is reactjs and backend is node/express
when i open up home screen page, every product loads from the backend, no issue, but once i click onto single product to try and get that specific product page with unique id, initial skeleton loads for a brief moment without having any of the products information, before crashing with following error
"Unhandled Rejection (TypeError): Cannot read properties of undefined (reading 'params')"
now i have tried different solutions and ways to go around that, checked if the issue might with react-router-dom(useParams), backend server/api(get request and find single product) or the frontend side of fetching single product via axios, but since im doing the same thing on HomeScreen page while fetching all products with axios, i cant figure out why it might not work with single product, im using useState and useEffect to fetch single product from the backend api.
here is single product screen, the fetch part of it at least
import React, { useState, useEffect } from 'react'
import { Link } from 'react-router-dom'
import { Row, Col, Image, ListGroup, Card, Button } from 'react-bootstrap'
import Rating from '../components/Rating'
import axios from 'axios'
const ProductScreen = ({ match }) => {
const [product, setProduct] = useState({})
useEffect(() => {
const fetchProduct = async () => {
const { data } = await axios.get(`/api/products/${match.params.id}`)
setProduct(data)
}
fetchProduct()
}, [match])
server file
const express = require('express')
const products = require('./data/products')
const app = express()
app.get('/', (req, res) => {
res.send('API is running...')
})
app.get('/api/products', (req, res) => {
res.json(products)
})
app.get('/api/products/:id', (req, res) => {
const product = products.find((p) => p._id === req.params.id)
res.json(product)
})
app.js file
import React from 'react'
import { BrowserRouter as Router, Route, Routes } from 'react-
router-dom'
import { Container } from 'react-bootstrap'
import Header from './components/Header'
import Footer from './components/Footer'
import HomeScreen from './pages/HomeScreen'
import ProductScreen from './pages/ProductScreen'
const App = () => {
return (
<Router>
<Header />
<main className='py-3'>
<Container>
<Routes>
<Route path='/' element={<HomeScreen />} exact />
<Route path='/product/:id' element={<ProductScreen />} />
</Routes>
</Container>
</main>
<Footer />
</Router>
)
}
export default App
thanks in advance
It looks like you're trying to access route params within your ProductScreen component through props. React Router v6 does not pass props to elements...
<Route path='/product/:id' element={<ProductScreen />} />
From the migration guide...
Using elements instead of components means we don't have to provide a passProps-style API so you can get the props you need to your elements.
To access route params, use the params hook
// ...
import { useParams } from "react-router-dom";
const ProductScreen = () => {
const [product, setProduct] = useState({})
const { id } = useParams()
useEffect(() => {
const fetchProduct = async () => {
const { data } = await axios.get(`/api/products/${encodeURIComponent(id)}`)
setProduct(data)
}
fetchProduct()
}, [ id ])

How to run a function with default "id" on start using useEffect hook in react?

I am making a simple blog post site where user can post an entry.
The objective is:
To have the Homepage display all the blog post in a list and details of any one blog post next to it when the Homepage first loads. When the user clicks on any item from the list the details of which will replace the default blog details.
Basically how Indeed displays the job posts.
Method:
I make 2 Axios calls one to get all the data and the other to get data by id.
The data from getAllData is displayed in a list on HomePage The data is passed as props to HomePageListItems which are wrapped in <Link to= {/${idx}}/>
I use the useParams to get the id and make getDataId call in DataById.
So, HomePage has to child components HomePageListItems and DataById
Issue is:
Function getDataById in does not work when Homepage is first loaded. It only works when route is "localhost/:id" which is "localhost:3000/ae86140b-7ae6-457-826c-5bd324b8cb3"
Because I want one blog already displayed when the first loads: How I do have this getDatabyId function run with a preset id where the id changes when the user clicks on a list item?
The code is:
import React, { useState, useEffect, usePrevious } from "react";
import { Link, useParams } from "react-router-dom";
import Axios from "axios";
import HomePageListItems from "./homePageListItems";
import DataById from "./dataById";
export default function HomePage(props){
<DataById/>
const [getAllData, setGetAllData] = useState()
const getData =async () => {
await Axios.get("http://localhost:5000/get").then((response)=>{
setGetAllData(response.data)
})
.catch((error)=>{console.log(error)})
}
useEffect(()=>{
getData();
},[])
return(
<section>
<Link to = "/postJournal">Post Journal Entry</Link>
{getAllData&&getAllData.map((item)=>{
return(
<HomePageListItems
key={item.id}
idx={item.id}
name={item.name}
title={item.title}
journalEntry={item.journalEntry}
date={item.date}
file={item.file}
/>
)
})
}
{usePrevious}
<DataById/>
</section>
)
}
--
import React, { useState, useEffect, usePrevious } from "react";
import { useParams } from "react-router-dom";
import Axios from "axios";
export default function DataById(props){
console.log("DataProps",props)
const [axiosGetData, setAxiosGetData] = useState([])
const {id} = useParams()
const getDataById =async () => {
await Axios.get(`http://localhost:5000/${id}`).then((response)=>{
setAxiosGetData(response.data[0])
console.log("Data",response.data[0])
})
.catch((error)=>{console.log(error)})
}
useEffect(()=>{
getDataById("35b48be0-0ab8-4409-a5eb-a0c4dbd0a4b3");
},[id])
return(
<>
<p> DataByID</p>
name:{axiosGetData.name}
Date:{axiosGetData.date}
Journal:{axiosGetData.journalEntry}
{usePrevious}
</>
)
}
--
import React, { useState, useEffect, usePrevious} from "react";
import { Link} from "react-router-dom";
export default function HomePageListItems(props){
let {name,title,journalEntry,date,file,idx}=props
return(
<main className= "homepage">
<Link to={`/${idx}`} >
<section className="homepage__listContainer">
<ul className = "homepage__list">
<li className="homepage__listItemWrapper">
<div className ="homepage__listItem">
<div className = "homepage__listItemImgWrapper">
<img className = "homepage__listItemImg" alt="" src={file}/>
</div>
<h3 className= "homepage__listItemTitle">Title:{title}</h3>
<p className = "homepage__listItemAuthor">Name:{name}</p>
<p className = "homepage__listItemDescription">Description:{journalEntry}</p>
<p className = "homepage__listItemDate">Posted Date:{date}</p>
<p>Key:{idx}</p>
</div>
</li>
</ul>
</section>
</Link>
{usePrevious}
</main>
)
}
--
import React from "react";
import "./style/App.css";
import ReactDOM from 'react-dom';
import { BrowserRouter, Switch, Router, Route } from "react-router-dom";
import HomePage from "./components/homePage"
import PostJournalEntry from "./components/postJournalEntry"
import DataByIDfrom "./components/dataById"
function App() {
return (
<div className="app">
<BrowserRouter>
<Switch>
<Route path="/:id" exact component = {HomePage}/>
<Route path="/:id" exact component = {DataByID}/>
<Route path="/postJournal" exact component = {PostJournalEntry}></Route>
<Route path="/" exact component = {HomePage}/>
</Switch>
</BrowserRouter>
</div>
);
}
export default App;
Thanks in advance guys! Any help is appriciated.
It's because getDataById doesn't accept any arguments, it always uses the URL id (useParams) for the axios get call, passing it an id in the useEffect won't do anything. You need to add a parameter to the function and then add some logic so it knows whether to use a passed in value or the URL id value. You could try something like this:
const getDataById = async (startupId = null) => {
await Axios.get(`http://localhost:5000/${startupId ? startupId : id}`).then((response)=>{
setAxiosGetData(response.data[0])
console.log("Data",response.data[0])
})
.catch((error)=>{console.log(error)})
}
This way if you pass getDataById an argument it will use that value for the axios call, otherwise it will try to use the id value from useParams

React hook component renders before API call

I need to create a React app which let's you list pokemons and types.
I fetch data from the PokeAPI. Is it a good practice to fetch it from the App component and then pass it to the child components, or is it better to fetch them from the child?
I am fetching it in the main app, I can see the fetch works because I console.log the data, but my component doesn't get it, and because of that I get a props.map is not a function in .
Here is my App.js:
import React, { useState } from "react";
import logo from "./logo.svg";
import "./App.css";
import axios from "axios";
import { BrowserRouter as Router, Switch, Route, Link } from "react-router-dom";
import PokemonList from "./components/PokemonList";
const App = (props) => {
const [pokemons, setPokemons] = useState([]);
const [types, setTypes] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const getPokemons = () => {
const axios = require("axios").default;
axios.get("https://pokeapi.co/api/v2/pokemon").then(function (response) {
console.log("Fetched pokemons");
console.log(response.data.results);
setIsLoading(false);
setPokemons(response.data.results);
});
};
const getTypes = () => {
setIsLoading(true);
const axios = require("axios").default;
axios.get("https://pokeapi.co/api/v2/type").then(function (response) {
console.log("Fetched types");
console.log(response.data.results);
setIsLoading(false);
setTypes(response.data.results);
});
};
return (
<Router>
<div>
<nav>
<ul>
<li>
<Link to="/pokemons" onClick={getPokemons}>
Pokemons
</Link>
</li>
<li>
<Link to="/types">Types</Link>
</li>
</ul>
</nav>
{/* A <Switch> looks through its children <Route>s and
renders the first one that matches the current URL. */}
<Switch>
<Route path="/pokemons">
<Pokemons pokemons={pokemons} />
</Route>
<Route path="/types">
<Types />
</Route>
</Switch>
</div>
</Router>
);
};
function Pokemons(pokemons) {
return <PokemonList props={pokemons} />;
}
function Types(typeList) {
return <h2>TYPES:</h2>;
}
export default App;
Here is my PokemonList.js:
import React from "react";
import { Card } from "semantic-ui-react";
import PokeCard from "./PokeCard";
const Pokemonlist = (props) => {
let content = (
<Card.Group>
{props.map(function (object, i) {
return <PokeCard pokemon={object} key={i} />;
})}
</Card.Group>
);
return content;
};
export default Pokemonlist;
and last here is my PokeCard.js
import { Card, Image } from "semantic-ui-react";
import React from "react";
const PokeCard = (pokemon) => {
let content = (
<Card>
<Card.Content>
<Image floated="right" size="mini" src={pokemon.img} />
<Card.Header>{pokemon.name}</Card.Header>
<Card.Meta>{pokemon.base_experience}</Card.Meta>
<Card.Description>ID: {pokemon.id}</Card.Description>
</Card.Content>
</Card>
);
return content;
};
export default PokeCard;
So the basic idea is:
On the main page you click Pokemons button, which calls the fetch then renders the PokemonList component which basically just renders multiple PokeCard components from the data I fetched.
1, What am I missing here?
2, In my situation when nothing changes do I need to use useEffect?
3, When should I fetch the data, and where?
EDIT: I want to use hooks with zero classes
here is a summary of my answer
it is best to fetch some initial data in parent and then make further requests in child
component if necessary to save network usage
use the useEffect hook to fetch the results before rendering the elements
What you are missing is that you are not using props in pokemon and you should put the get call inside useEffect hook in App component because the child component is rendering before the props is passed to it and this is the reason you are getting undefined error

Categories