Need useEffect hook invoke when URL changes - reactjs

I am trying to have this component load data depending on its current url whether /global or /my-posts. The useEffect() grabs the data from the first loading of the component but when i change to another url i expected useEffect to check the url again and load the correct data but instead i'm stuck with the data from the first load. How do i get useEffect to invoke every time i click between different urls like /global and /my-posts url.
export default function Dashboard() {
const [allRecipes, setAllRecipes] = useState([]);
const [myRecipes, setMyRecipes] = useState([]);
const currentUrl = window.location.pathname;
useEffect(() => {
if (currentUrl === '/dashboard/global') {
console.log('hello');
trackPromise(
RecipeService.getAllRecipes()
.then((data) => {
setAllRecipes(data);
}),
);
} else if (currentUrl === '/dashboard/my-posts') {
console.log('hi');
trackPromise(
RecipeService.getRecipes()
.then((data) => {
setMyRecipes(data);
}),
);
}
}, []);
console.log(window.location.pathname);
return (
<>
<div className="dashboard">
<DashboardHeader />
<div className="created-posts">
{allRecipes.length !== 0
? allRecipes.map((recipe) => <Post recipe={recipe} key={uuidv1()} />)
: null}
{myRecipes.length !== 0
? myRecipes.recipes.map((recipe) => <Post recipe={recipe} key={uuidv1()} />)
: null}
{currentUrl === '/dashboard/create' ? <CreateForm /> : null}
<LoadingIndicator />
</div>
</div>
</>
);
}

to make React.useEffect run on every currentUrl change, you have to add it to useEffect dependencies array.
// first we need to control the state of window.location.pathname by react not the browser
// and make react state be the only source of truth.
const pathname = window.location.pathname
// manage currentUrl in state.
const [currentUrl, setCurrentUrl] = React.useState(pathname)
React.useEffect(() => {
setCurrentUrl(pathname)
}, [pathname])
// now you would add the contolled `currentUrl` state to its useEffect deps.
useEffect(() => {
if (currentUrl === '/dashboard/global') {
// ..........
} else if (currentUrl === '/dashboard/my-posts') {
// ..........
}
}, [currentUrl]);

Related

Retrieve data via link parameter in react

I have a list of user on a page , what i want to achieve is render user detail when i click on the link with his id. for that i have tried to send the id and retrieve the data with a new request like this :
My routes :
<Route path='/coupeurs' element={<Coupeurs />} />
<Route path='/coupeurs/add' element={<AddCoupeur />} />
<Route path='/coupeurs/:id' element={<Coupeur />} />
My list :
<ul>
{coupeurs.map(coupeur => (
<li key={coupeur.id}>
{coupeur.nom} {coupeur.prenom}
<div className='coupeur-actions'>
<Link to={`/coupeurs/${coupeur.id}`}>Voir</Link>
<Link to={`/coupeurs/${coupeur.id}/edit`}>Editer</Link>
</div>
</li>
))}
</ul>
the place where i want display details :
export default function Coupeur(props) {
console.log(props);
let { id } = useParams();
const [coupeur, setCoupeur] = useState(getCoupeurInfoById(id));
console.log(coupeur);
return (
<div>
<h3>Identifiant : {id}</h3>
<h3>Nom : {coupeur.nom}</h3>
<h3>Prenom : {coupeur.prenom}</h3>
<h3>Statut : {coupeur.statut}</h3>
</div>
)
}
My getCoupeurInfoById function :
export async function getCoupeurInfoById(id) {
const docRef = doc(db, "coupeurs", id);
const docSnap = await getDoc(docRef);
if (docSnap.exists()) {
console.log("Document data:", docSnap.data());
return docSnap.data();
} else {
// doc.data() will be undefined in this case
console.log("No such document!");
}
}
i get my page without the info , but when i console log my variable i can see the result !
Can you help me found the right way to do that kind of things ? thank you
Since getCoupeurInfoById is an async function it implicitly returns a Promise object and subsequently (quickly) resolves and mutates the coupeur state variable. When you log it you see the resolved value.
The useState hook expects a synchronous function that returns the initial state value.
Use a useEffect hook to handle the side-effect of fetching/computing the coupeur value.
Example:
export default function Coupeur(props) {
const { id } = useParams();
const [coupeur, setCoupeur] = useState(); // <-- initially undefined
useEffect(() => {
const getCoupeur = async () => {
try {
const coupeur = await getCoupeurInfoById(id);
setCoupeur(coupeur);
} catch(error) {
// handle any rejections/errors/etc
}
};
getCoupeur(); // <-- fetch/compute coupeur value
}, [id]);
if (!coupeur) return null; // or loading indicator/etc
return (
<div>
<h3>Identifiant : {id}</h3>
<h3>Nom : {coupeur.nom}</h3>
<h3>Prenom : {coupeur.prenom}</h3>
<h3>Statut : {coupeur.statut}</h3>
</div>
)
}

React flashes message before state is set in useEffect

I am having an issue where the null message (I dont have Partners) is flashing up for a second or so before the state is set and the message changes .. Is there any way to not show it until everything is resolved?
const Dashboard = () => {
const [partners, setPartners] = useState([]);
useEffect(() => {
async function getPartners() {
await axios.get('/api/partners').then(response => {
setPartners(response.data.data);
})
}
getPartners();
}, []);
return (
<>
{partners?.length ? <div><p>I have Partners</p></div> : <div><p>I dont have Partners</p></div>}
</>
)
}
export default Dashboard;
There are several approaches to achieve such a thing the simplest one is to use another state to indicate the HTTP request status. Here how it is can be implemented.
const Dashboard = () => {
const [partners, setPartners] = useState([]);
const [status, setStatus] = useState('idle');
useEffect(() => {
setStatus('pending');
async function getPartners() {
await axios.get('/api/partners').then(response => {
setPartners(response.data.data);
setStatus('resolved');
})
}
getPartners();
}, []);
return (
<>
{status === 'idle' || status === 'pending' ?
<LoadingComponent /> // A custom component to represent loading status
:
<>
{partners?.length ? <div><p>I have Partners</p></div> : <div><p>I dont have Partners</p></div>}
<>
}
</>
)
}
export default Dashboard;

Rerender component after function in provider ends

I'm trying to set components with 3 functionalities. Displaying PokemonList, getting random pokemon and find one by filters. Getting random pokemon works great but since 2 days I'm trying to figure out how to set pokemon list feature correctly
Below full code from this component.
It's render when click PokemonsList button inside separate navigation component and fire handleGetPokemonList function in provider using context.
The problem is that I can't manage rerender components when PokemonList is ready. For now i need to additionally fire forceUpadte() function manually (button onClick = () => forceUpdate())
I tried to use useEffect() in PokemonList component but it didn't work in any way.
I was also sure that after fetching data with fetchData() function I can do .then(changeState of loading) but it didn't work also.
What Am I missing to automatically render data from fetch in provider in PokemonList component? I'm receiving error about no data exist but if I use forceUpdate then everything is ok
Complete repo here: https://github.com/Mankowski92/poke-trainer
handleGetPokemonList function in provider below
const handleGetPokemonList = () => {
setCurrentPokedexOption('pokemonList');
async function fetchData() {
setImgLoaded(false);
let res = await fetch(`${API}?offset=0&limit=6/`);
let response = await res.json();
response.results.forEach((item) => {
const fetchDeeper = async () => {
let res = await fetch(`${item.url}`);
let response = await res.json();
let eachPoke = {
id: response.id,
name: response.name,
artwork: response.sprites.other['officialartwork'].front_default,
stats: response.stats,
};
fetchedPokemons.push(eachPoke);
};
fetchDeeper();
});
setPokemonList(fetchedPokemons);
if (fetchedPokemons) {
return setLoading(false);
}
}
fetchData()
.then((res) => setLoading(res))
.catch((err) => console.log('error', err));
};
PokemonList component below
import React, { useContext, useState, useCallback } from 'react';
import { StyledPokemonListContainer } from './PokemonList.styles';
import { PokemonsContext } from '../../../providers/PokemonsProvider';
const PokemonList = () => {
const ctx = useContext(PokemonsContext);
const [, updateState] = useState();
const forceUpdate = useCallback(() => updateState({}), []);
const { handleSetImgLoaded } = useContext(PokemonsContext);
return (
<>
{ctx.currentPokedexOption === 'pokemonList' ? (
<StyledPokemonListContainer>
{ctx.pokemonList && ctx.pokemonList.length ? (
ctx.pokemonList.map((item, i) => (
<div className="each-pokemon-container" key={i}>
<div className="poke-id">{item.id}</div>
<div className="poke-name">{item.name}</div>
<img className="poke-photo" onLoad={() => handleSetImgLoaded()} src={item ? item.artwork : ''} alt="" />
</div>
))
) : (
<div className="render-info">Hit rerender button</div>
)}
{/* {ctx.pokemonList ? <div>{ctx.pokemonList[0].name}</div> : <div>DUPPSKO</div>} */}
<div className="buttons">
<button onClick={() => console.log('PREVOIUS')}>Previous</button>
<button className="rerender-button" onClick={() => forceUpdate()}>
RERENDER
</button>
<button onClick={() => console.log('NEXT')}>Next</button>
</div>
</StyledPokemonListContainer>
) : null}
</>
);
};
export default PokemonList;

Memorize fetched results in component

I'm working on a component that adds images to items. You can either upload your own image or pick an image, loaded from an API based on the name of the item.
Here is the root component:
const AddMedia = (props) => {
const [currentTab, setCurrentTab] = useState(0);
const [itemName, setItemName] = useState(props.itemName);
return (
<div>
<Tabs
value={currentTab}
onChange={() => setCurrentTab(currentTab === 0 ? 1 : 0)}
/>
<div hidden={currentTab !== 0}>
<FileUpload />
</div>
<div hidden={currentTab !== 1}>
{currentTab === 1 && <ImagePicker searchTerm={itemName} />}
</div>
</div>
);
};
And here is the <ImagePicker />:
import React, { useState, useEffect } from "react";
function ImagePicker({ searchTerm, ...props }) {
const [photos, setPhotos] = useState([]);
const searchForImages = async (keyword) => {
const images = await api.GetImagesByKeyword(keyword);
return images;
};
useEffect(() => {
const result = searchForImages(searchTerm);
setPhotos(result);
}, []);
return (
<>
{photos.map(({ urls: { small } }, j) => (
<img alt={j} src={small} className={classes.img} />
))}
</>
);
}
const areSearchTermsEqual = (prev, next) => {
return prev.searchTerm === next.searchTerm;
};
const MemorizedImagePicker = React.memo(ImagePicker, areSearchTermsEqual);
export default MemorizedImagePicker;
What I'm struggling with is getting the component to not fetch the results again if the searchTerm hasn't changed. For example, when the component loads it's on tab 0 (upload image), you switch to tab 1 (pick an image) and it fetches the results for searchTerm, then you switch to 0 and again to 1 and it fetches them again, although the searchTerm hasn't changed. As you can see, I tried using React.memo but to no avail. Also, I added the currentTab === 1 to stop it from fetching the photos when the root component renders and fetch them only if the active tab is 1.
You should add the searchTerm as dependency of the useEffect so that it will not fetch again if searchTerm hasn't change:
useEffect(() => {
const result = searchForImages(searchTerm);
setPhotos(result);
}, [searchTerm]);
Additional information, if you are using eslint to lint your code, you can use the react-hooks/exhaustive-deps rule to avoid this kind of mistake.

how to fix useEffect has a missing dependency "Dispatch"

Hello I have a problem with my eslint and I'm not sure how to respond with my dispatch's:
const { messages } = ChatReducer;
const [isTyping, setIsTyping] = useState(false);
console.log(isTyping)
useEffect(() => {
if (messages[messages.length - 1].type === 'bot') {
setIsTyping(true);
const timeoutId = setTimeout(() => {
setIsTyping(false);
dispatch(wait_end());
}, 3000);
}
}, [messages]);
useEffect(() =>
dispatch(answer_Message(['hi 😃','hi two'])
), []);
return (
<>
{messages.map((message, index) => (
<>
{message.text ? (
<Styled.MessageFlexColumn ref={messagesEndRef} key={index}>
{message.type === 'user' ? (
<UserText key={index}>{message.text}</UserText>
) : (
<BotText
key={index}
isTyping={isTyping && index === messages.length - 1}
>
{message.text}
</BotText>
)}
<Styled.Status />
</Styled.MessageFlexColumn>
) : (
''
)}
<div ref={messagesEndRef} />
</>
))}
</>
)
action:
export const checkMessage = text => {
return dispatch => {
dispatch(sendMessage(text));
dispatch(wait_anwser());
dispatch(botMessage(verify(text)));
};
};
export const answer_Message = text => {
return dispatch => {
text.map((message, index) => {
dispatch(botMessage(message));
})
}
}
Well I also have a problem with my useEffect and my state
I'm basically using it to send an array of initial messages to display in my chat:
useEffect(() =>
dispatch(answer_Message(['hi 😃','hi two'])
), []);
The problem is that when sending 2 texts
and my isTyping state is true only once
even being 2 texts and not enter on my if:
if (messages[messages.length - 1].type === 'bot'
alert eslint:
Line 70:6: React Hook useEffect has a missing dependency: 'dispatch'. Either include it or remove the dependency array
Line 74:6: React Hook useEffect has a missing dependency: 'dispatch'. Either include it or remove the dependency array
I don't think you need to dispatch that as your function dispatches as well, or you could try adding the dispatch as a dependency inside the [dispatch]

Resources