React custom hook state not 'always there' - reactjs

I thought had a better grasp of hooks but I've clearly got something wrong here. Not all of the character objects will have what I'm trying to get but it wont work with those that do.
I cna't even build in a check for character.comics.available. Same errors appear. I'm presuming I'm getting them before the state is set? But {character.name} always works.
CharacterDetail.js
import React from "react";
import { useParams } from "react-router-dom";
import useCharacter from "../hooks/useCharacter";
const CharacterDetail = () => {
// from the Route path="/character/:id"
const { id } = useParams();
// custom hook. useCharacter.js
const [character] = useCharacter(id);
// this only works sometimes but errors if i refresh the page
// console.log(character.comics.available);
return (
<div>
<h2 className="ui header">Character Details</h2>
<p>Works every time: {character.name}</p>
<div className="ui segment"></div>
<pre></pre>
</div>
);
};
export default CharacterDetail;
Custom hook useCharacter.js
import { useState, useEffect } from "react";
import marvel from "../apis/marvel";
const useCharacter = (id) => {
const [character, setCharacter] = useState({});
useEffect(() => {
loadItem();
return () => {};
}, [id]);
const loadItem = async (term) => {
const response = await marvel.get(`/characters/${id}`);
console.log(response.data.data.results[0]);
setCharacter(response.data.data.results[0]);
};
return [character];
};
export default useCharacter;
error when console is uncommented
Uncaught TypeError: Cannot read property 'available' of undefined
at CharacterDetail (CharacterDetail.js:11)
...
Here is the character object.

thanks to #Nikita for the pointers. Settled on this...
CharacterDetail.js
import React from "react";
import { useParams } from "react-router-dom";
import useCharacter from "../hooks/useCharacter";
const CharacterDetail = () => {
const { id } = useParams();
// custom hook. useCharacter.js
const { isLoading, character } = useCharacter(id);
const isArray = character instanceof Array;
if (!isLoading && isArray === false) {
console.log("isLoading", isArray);
const thumb =
character.thumbnail.path +
"/portrait_uncanny." +
character.thumbnail.extension;
return (
<div>
<h2 className="ui header">{character.name}</h2>
<img src={thumb} />
<div className="ui segment">{character.comics.available}</div>
<div className="ui segment">{character.series.available}</div>
<div className="ui segment">{character.stories.available}</div>
</div>
);
}
return <div>Loading...</div>;
};
export default CharacterDetail;
useCharacter.js
import { useState, useEffect } from "react";
import marvel from "../apis/marvel";
function useCharacter(id) {
const [character, setCharacter] = useState([]);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
setIsLoading(true);
const fetchData = async () => {
setIsLoading(true);
await marvel
.get(`/characters/${id}`)
.then((response) => {
/* DO STUFF WHEN THE CALLS SUCCEEDS */
setIsLoading(false);
setCharacter(response.data.data.results[0]);
})
.catch((e) => {
/* HANDLE THE ERROR (e) */
});
};
fetchData();
}, [id]);
return {
isLoading,
character,
};
}
export default useCharacter;

Related

i am not able to show data from firebase in my react app

I want to show categories of items in div with class name items but this div is not rendering on screen i can only see section tag on inspect please explain what i am doing wrong
import { useEffect } from "react";
import { useState } from "react";
import firebase from "../../Firebase.config";
import './CategoriesPage.css'
const CategoriesPage = () => {
const ref = firebase.firestore().collection("Categories")
const [data, setdata] = useState([])
const [loader, setloader] = useState(true)
function getData() {
const items = []
ref.onSnapshot((querySnapshot) => {
querySnapshot.forEach((doc) => {
items.push(doc.data())
})
})
setdata(items)
setloader(false)
}
useEffect(() => {
getData()
}, [])
return (
<section className="Categoriebox">{
loader === true && [data].map((dev) => {
<div className="items" key={dev.id}>
<h1>{dev.name}</h1>
</div>
})
}
</section>
)
};
export default CategoriesPage;

How should I fix eslint warning (React Hook useEffect has a missing dependency) and a loop caused by the warning?

In following codes, eslint will give a warning.
Line 24:6: React Hook useEffect has a missing dependency: 'fetchPosts'. Either include it or remove the dependency array react-hooks/exhaustive-deps
import { useState, useEffect } from 'react';
import { useLocation } from "react-router-dom";
import { Layout } from './Layout';
import { TwitterPost, reloadTwitterEmbedTemplate } from '../TwitterPost';
import '../../styles/pages/TimelinePage.css'
import axios from 'axios';
export const TimelinePage = () => {
const [posts, setPosts] = useState([]);
const [page, setPage] = useState(1);
const location = useLocation();
const fetchPosts = async () => {
const res = await axios.get('/api/posts', { params: { page: page } });
setPosts(posts.concat(res.data));
reloadTwitterEmbedTemplate();
setPage(page + 1);
};
useEffect(() => {
if (location.pathname !== '/') return;
fetchPosts();
}, [location]);
const postTemplates = posts.map((post: any) => {
if (post.media_name === 'twitter') return <TwitterPost mediaUserScreenName={post.media_user_screen_name} mediaPostId={post.media_post_id} />;
return null;
});
return(
<Layout body={
<div id="timeline">
<div>{postTemplates}</div>
<div className="show-more-box">
<button type="button" className="show-more-button" onClick={fetchPosts}>show more</button>
</div>
</div>
} />
);
};
I fixed the warning by adding fetchPosts. Then I followed eslint instructions using useCallback and adding variables used in fetchPosts to deps. This change causes a loop. How should I fix the loop and eslint warning?
import { useState, useEffect, useCallback } from 'react';
import { useLocation } from "react-router-dom";
import { Layout } from './Layout';
import { TwitterPost, reloadTwitterEmbedTemplate } from '../TwitterPost';
import '../../styles/pages/TimelinePage.css'
import axios from 'axios';
export const TimelinePage = () => {
const [posts, setPosts] = useState([]);
const [page, setPage] = useState(1);
const location = useLocation();
const fetchPosts = useCallback(async () => {
const res = await axios.get('/api/posts', { params: { page: page } });
setPosts(posts.concat(res.data));
reloadTwitterEmbedTemplate();
setPage(page + 1);
}, [page, posts]);
useEffect(() => {
if (location.pathname !== '/') return;
fetchPosts();
}, [location, fetchPosts]);
const postTemplates = posts.map((post: any) => {
if (post.media_name === 'twitter') return <TwitterPost mediaUserScreenName={post.media_user_screen_name} mediaPostId={post.media_post_id} />;
return null;
});
return(
<Layout body={
<div id="timeline">
<div>{postTemplates}</div>
<div className="show-more-box">
<button type="button" className="show-more-button" onClick={fetchPosts}>show more</button>
</div>
</div>
} />
);
};
I highly recommend this article to really understand what's going on when you use the useEffect hook. It talks, among other things, about your exact problem and ways to solve it. That said, you should move the function inside the useEffect callback, something like:
export const TimelinePage = () => {
/* ... */
useEffect(() => {
if (location.pathname !== '/') return;
const fetchPosts = async () => {
const res = await axios.get('/api/posts', { params: { page: page } });
setPosts(posts.concat(res.data));
reloadTwitterEmbedTemplate();
setPage(page + 1);
}
fetchPosts();
}, [location]);
/* ... */
};

how to fix "Cannot read properties of undefined (reading 'front_default') "?

I'm trying to display a piece of data from an API, I can reach it, but its giving me an unknown error I cant find an answer for it.
The error message :
Uncaught TypeError: Cannot read properties of undefined (reading 'front_default')
now, here's the funny part, when i first starting the page, its working, but if i refresh it, its giving me the error message on console and a white screen page.
this is the part of my code that don't seems to work.
<img src={urlData.sprites.front_default}></img>
but, similar data can work, such as this line:
<p> {urlData.id}</p>
I'll post the all code under here, but don't waste your time on reading it all, try focus on the error message and error origin in order of helping me identifying the problem.
dad component:
import './App.css';
import React, { useEffect, useState } from 'react';
import axios from 'axios';
import { PokemonContainer } from './components/Pokemon';
function App() {
const [pokemonData, setPokemonData] = useState([]);
const [loaded, setLoaded] = useState(false);
const get_pokemon_data = async () => {
setPokemonData((await axios.get('https://pokeapi.co/api/v2/pokemon?limit=9')).data.results);
};
useEffect(() => {
get_pokemon_data();
}, []);
useEffect(() => {
setLoaded(true);
}, [pokemonData]);
if (loaded) {
return (
<div>
{pokemonData.map((pokemon: any) => (
<PokemonContainer key={pokemon.name} url={pokemon.url} />
))}
</div>
);
} else {
return (
<div>
<p>wait a minute</p>
</div>
)
}
};
export default App;
child component :
import React, { useState, useEffect } from 'react';
import axios from 'axios';
export const PokemonContainer = (props: any) => {
const [urlData, setURLData] = useState<any>([]);
const [loaded, setLoaded] = useState<boolean>(false);
useEffect(() => {
const get_url_data = async () => {
setURLData((await axios.get(props.url)).data);
};
get_url_data();
}, []);
useEffect(() => {
setLoaded(true);
}, [urlData]);
if (loaded) {
return (
<div>
<div key={urlData.name}>
<p> {urlData.name}</p>
<p> {urlData.id}</p>
<img src={urlData.sprites.front_default}></img>
</div>
</div>
)
} else {
return (
<div>
<p>Loading...</p>
</div>
)
}
};
I think the problem is with your second useEffect
useEffect(() => {
setLoaded(true);
}, [urlData]);
When component mounts this will set loaded to true but response is not fetched yet and urlData is still an empty array (which should be an empty object, I believe) therefore you get the error.
One way to fix this would be to check if urlData has been fetched inside the useEffect? if yes, then set loaded to true.
But I think this useEffecte is not needed at all. Instead of if(loaded) you could check for one urlData's properties, like: if(urlData.id)
child component:
import React, { useState, useEffect } from 'react';
import axios from 'axios';
export const PokemonContainer = (props: any) => {
const [urlData, setURLData] = useState<any>([]);
useEffect(() => {
const get_url_data = async () => {
setURLData((await axios.get(props.url)).data);
};
get_url_data();
}, []);
if (urlData.id) {
return (
<div>
<div key={urlData.name}>
<p> {urlData.name}</p>
<p> {urlData.id}</p>
<img src={urlData.sprites.front_default}></img>
</div>
</div>
)
} else {
return (
<div>
<p>Loading...</p>
</div>
)
}
};
Or you can do it like:
import React, { useState, useEffect } from 'react';
import axios from 'axios';
export const PokemonContainer = (props: any) => {
const [urlData, setURLData] = useState<any>([]);
useEffect(() => {
const get_url_data = async () => {
setURLData((await axios.get(props.url)).data);
};
get_url_data();
}, []);
if (!urlData.id) {
return (
<div>
<p>Loading...</p>
</div>
);
}
return (
<div>
<div key={urlData.name}>
<p> {urlData.name}</p>
<p> {urlData.id}</p>
<img src={urlData.sprites.front_default}></img>
</div>
</div>
);
};

How to update a function in react when firing off onChange event

I have a function that filters the customers based on their levels (intermediate, beginner ), I'm passing this function through a component that has React select to filter my Data(async)
The filter is working only when I filter the first time but when I choose another value to filter it gave me a blank page?
I tried useEffect to keep it updated but it not working
Do you have any suggestions?
//APP.js
import React,{useState, useEffect} from "react";
import YogaCourses from "./components/YogaCourses/YogaCourses";
import Loading from "./components/IsLoading/Loading";
import LevelsFilter from './components/LevelsFilter/LevelsFilter';
//API to fetch the data
const url = 'https://gist.githubusercontent.com/Tayarthouail/8fb14fe117fdd718ceabd6ee05ed4525/raw/8c86c4bb89fc51667ba0578b2dcba14a0b21f08c/Yoga-courses-api.json';
function App() {
//states
const [yogaCourses, setYogaCourses] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const [levels, setLevels] = useState([]);
//Filter by Levels
const filterLevels = (level) => {
const getLevels = yogaCourses.filter((singleLevel)=> singleLevel.level === level.value);
setYogaCourses(getLevels);
}
//Function to fetch the data from the API
const GetCourses = async () => {
const response = await axios.get(url)
const {data} = response;
return data;
}
//UseEffect to run the function on every render
useEffect(()=> {
const GetCoursesYoga = async () => {
const result = await GetCourses();
setYogaCourses(result);
console.log(result);
setLevels(Array.from(new Set(result.map((result)=> result.level))));
}
GetCoursesYoga();
}, []);
//check if the we got response
useEffect(()=> {
if(yogaCourses.length > 0) {
setIsLoading(false);
}
}, [yogaCourses])
if(isLoading) {
return (
<Loading/>
)
}
else {
return (
<main>
<div className="title">
<h2>YOUR PRACTICE REIMAGINED</h2>
</div>
<LevelsFilter levels={levels} filterLevels={filterLevels}/>
<YogaCourses yogaCourses= {yogaCourses}/>
</main>
);
}
}
export default App;
//LevelsFilter component
import React from 'react';
import Select from 'react-select';
import './LevelsFilter.css';
const LevelsFilter = ({levels, filterLevels}) => {
const option = levels.map((level)=> ({value : level, label: level}));
return (
<div>
<Select
options ={option}
className="select-option"
placeholder={"Type..."}
onChange={filterLevels}
/>
</div>
)
}
export default LevelsFilter;
Issue
You are replacing your state with the filtered data and subsequent filtering filters from there, so you only ever reduce your data.
Solution
I suggest storing an active filter state (i.e. level) and do the filtering inline when rendering so you skip the issue of stale/bad state.
function App() {
//states
const [yogaCourses, setYogaCourses] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const [levels, setLevels] = useState([]);
const [level, setLevel] = useState('');
const levelChangeHandler = ({ value }) => {
setLevel(value);
}
//Filter by Levels
const filterLevels = (level) => {
return yogaCourses.filter(
(singleLevel) => level ? singleLevel.level === level : true
);
}
...
if(isLoading) {
return (
<Loading/>
)
}
else {
return (
<main>
<div className="title">
<h2>YOUR PRACTICE REIMAGINED</h2>
</div>
<LevelsFilter levels={levels} onChange={levelChangeHandler}/>
<YogaCourses yogaCourses={filterLevels(level)}/>
</main>
);
}
}
LevelsFilter
import React from 'react';
import Select from 'react-select';
import './LevelsFilter.css';
const LevelsFilter = ({ levels, onChange }) => {
const option = levels.map((level)=> ({value : level, label: level}));
return (
<div>
<Select
options ={option}
className="select-option"
placeholder={"Type..."}
onChange={onChange}
/>
</div>
)
}
You need a copy state.
Your code is replacing the data source with filtered data. When you first time selects the option then your state replaces it with that one and you no longer have previous state data. On the second time, you don't have data that why it's blank on-screen.
Just copy and replace the below app.js code:
import React,{useState, useEffect} from "react";
import YogaCourses from "./components/YogaCourses/YogaCourses";
import Loading from "./components/IsLoading/Loading";
import LevelsFilter from './components/LevelsFilter/LevelsFilter';
//API to fetch the data
const url = 'https://gist.githubusercontent.com/Tayarthouail/8fb14fe117fdd718ceabd6ee05ed4525/raw/8c86c4bb89fc51667ba0578b2dcba14a0b21f08c/Yoga-courses-api.json';
function App() {
//states
const [yogaCourses, setYogaCourses] = useState([]);
const [filteredYogaCourses, setFillteredYogaCourses] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const [levels, setLevels] = useState([]);
//Filter by Levels
const filterLevels = (level) => {
const getLevels = yogaCourses.filter((singleLevel)=> singleLevel.level === level.value);
setFillteredYogaCourses(getLevels);
}
//Function to fetch the data from the API
const GetCourses = async () => {
const response = await axios.get(url)
const {data} = response;
return data;
}
//UseEffect to run the function on every render
useEffect(()=> {
const GetCoursesYoga = async () => {
const result = await GetCourses();
setYogaCourses(result);
setLevels(Array.from(new Set(result.map((result)=> result.level))));
}
GetCoursesYoga();
}, []);
//check if the we got response
useEffect(()=> {
if(yogaCourses.length > 0) {
setIsLoading(false);
}
}, [yogaCourses])
if(isLoading) {
return (
<Loading/>
)
}
else {
return (
<main>
<div className="title">
<h2>YOUR PRACTICE REIMAGINED</h2>
</div>
<LevelsFilter levels={levels} filterLevels={filterLevels}/>
<YogaCourses yogaCourses= {filteredYogaCourses}/>
</main>
);
}
}
export default App;
I hope it will work, if not then please debug it because I haven't tested it but the idea will be same. :)

Correct way to use useEffect() to update when data changes

The useEffect below renders, fetches data, and displays it once (using an empty array for 2nd parameter in useEffect).
I need it to rerun useEffect everytime the user changes data to the database (when user uses axios.post).
What i've tried
using [tickets], but that just causes the useEffect to run infinitly
also using [tickets.length] and [tickets, setTickets]
trying to use props as parameter but didnt find anything useful
import React, { useState, createContext, useEffect } from "react";
import axios from "axios";
export const TicketContext = createContext();
export const TicketProvider = (props) => {
console.log(props);
const [tickets, setTickets] = useState([]);
useEffect(() => {
getTickets();
console.log("1", { tickets });
}, []);
const getTickets = async () => {
const response = await axios.get("http://localhost:4000/tickets/");
setTickets(response.data);
};
return <TicketContext.Provider value={[tickets, setTickets]}>{props.children}
</TicketContext.Provider>;
};
import React from "react";
import { useState, useEffect, useContext } from "react";
import Ticket from "../Ticket";
import { TicketContext } from "../contexts/TicketContext";
import AddBacklog from "../addData/AddBacklog";
const TicketDisplay = (props) => {
const [tickets, setTickets] = useContext(TicketContext);
return (
<div className="display">
<p>Antony Blyakher</p>
<p>Number of Tickets: {tickets.length}</p>
<div className="backlog">
<h1>Backlog</h1>
{tickets.map((currentTicket, i) => (
<div className="ticketBlock">
<Ticket ticket={currentTicket} key={i} />
</div>
))}
</div>
</div>
);
const AddBacklog = (props) => {
const [tickets, setTickets] = useState("");
...
axios.post("http://localhost:4000/tickets/add", newTicket).then((res) => console.log(res.data));
setTickets((currentTickets) => [...currentTickets, { name: name, status: "backlog", id: uuid() }]);
};
You'll need to watch for tickets and return if it has data to not cause infinite loop:
useEffect(() => {
if (tickets.length) return // so, we call just once
getTickets();
console.log("1", { tickets });
}, [tickets]);
const fetchData = () => {
axios.get("http://localhost:7000/api/getData/").then((response) => {
console.log(response.data);
if (response.data.success) {
SetIsLoading(false);
}
setDataSource(response.data.data);
});
};
useEffect(() => {
fetchData();
if (fetchData.length) fetchData();
}, [fetchData]);
by this you can fetch the data in real-time as any change in data occurs.

Resources