React hook component renders before API call - javascript

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

Related

Losing state data on page refresh 'Cannot read properties of undefined'

I am fetching all teams from an API in my App.js file and storing all the fetched teams in an array, the state is managed by Redux.
Here is my App.js where i use the getTeams() function to send the teams to Redux.
import React, {useEffect} from 'react';
import { Route, Switch } from 'react-router-dom';
import { useDispatch } from 'react-redux';
import { teamActions } from './store/team-slice';
import Header from './components/layout/Header';
import Footer from './components/layout/Footer';
import Teams from './components/pages/Teams';
import Home from './components/pages/Home';
import TeamInfo from './components/pages/TeamInfo';
const App = ()=> {
const dispatch = useDispatch();
const getTeams = () => {
fetch(`https://www.balldontlie.io/api/v1/teams`)
.then(res => res.json())
.then(data => dispatch(teamActions.sortTeams(data.data)));
}
useEffect(() => {
getTeams();
}, []);
return (
<>
<Header/>
<Switch>
<Route exact path={'/'} component={Home}></Route>
<Route exact path={'/teams'} component={Teams}></Route>
<Route exact path={'/team/:teamName'} component={TeamInfo}></Route>
</Switch>
<Footer/>
</>
);
}
export default App;
In Redux I store the teams in an array called totalTeams:
import { createSlice } from "#reduxjs/toolkit";
const teamInitialState = {totalTeams: [], easternTeams:[], westernTeams:[]};
const teamSlice = createSlice({
name: 'team',
initialState: teamInitialState,
reducers: {
sortTeams(state, action){
state.totalTeams = action.payload;
state.westernTeams = action.payload.filter(team => team.conference === 'West');
state.easternTeams = action.payload.filter(team => team.conference === 'East');
}
}
});
export const teamActions = teamSlice.actions;
export default teamSlice;
I then have a "Team Info" page where I take the team name from the url using useParams
to filter out the current team from the redux array of totalTeams.
I then use that data to fill the page with the team info, everything works until I refresh the page, I then lose all data.
Shouldn't all the data still be there since I am using the Redux array to find the team info? The fetch to the API is made on every page reload and the data is saved to the array so why am I losing state?
import React from 'react';
import { useSelector } from 'react-redux';
import { useParams } from 'react-router-dom';
import Container from '../layout/Container';
import classes from './TeamInfo.module.css'
const TeamInfo = () => {
const teams = useSelector(state => state.team.totalTeams);
const params = useParams();
const teamSlug = params.teamName;
const teamNoDashes = teamSlug.replace(/-/g, ' ');
const currentTeam = teams && teams.find(team => team.full_name.toLowerCase() === teamNoDashes);
return (
<Container>
<div className={classes['team-info-container']}>
<div><img src={`/img/team-logos-grid/${teamSlug}.png`} alt={teamSlug} /></div>
<div>
<h2>{currentTeam.full_name}</h2>
<ul>
<li>City: {currentTeam.city}</li>
<li>Division: {currentTeam.division}</li>
<li>Conference: {currentTeam.conference}</li>
</ul>
</div>
</div>
</Container>
)
}
export default TeamInfo;
First you need check exist of data, and then fields in this object:
loading || (data && (!data.me || !data.getPlaces)) ? ...

ReactJs: How to get api data in child component with props?

I am trying to call api data only once thats way I call api in home.js file with componentdidmount in class component and i want to render this data in many child components with functional components.when i call api in every each child component,its work but when i try to call with props coming only empty array by console.log please help.
import React from 'react'
import '../styles/home.css'
import axios from 'axios';
import Teaser from './Teaser'
import Second from './Second'
import Opening from './Opening'
import Menu from './Menu'
export default class Home extends React.Component {
state = {
posts: []
}
componentDidMount() {
axios.get("https://graph.instagram.com/me/media?fields=id,caption,media_url,permalink,username&access_token=IGQ")
.then(res => {
const posts = res.data.data;
this.setState({ posts });
})
}
render() {
return (
<>
<Teaser/>
<Second/>
<Opening/>
<Menu posts={this.state.posts}/>
</>
)
}
}
import React from 'react'
import axios from 'axios';
function Menu(props) {
const {posts} = props.posts;
console.log(props);
return (
<>
{posts.map(
(post) =>
post.caption.includes('#apegustosa_menu') &&
post.children.data.map((x) => (
<div className="menu_item" key={x.id}>
<img className="menu_img" src={x.media_url} alt="image" />
</div>
)),
)}
</>
)
}
export default Menu

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

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

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

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.

Categories