Why does my react function function iterate twice? - javascript

I am currently trying out a project with the PokeAPI. And have used his guide for help. I can't get rid of the problem that the function iterates twice when called in the useEffect.
When I run the following code with the getAllPokemons in the useEffect
const PokeListPage = () => {
const [layoutToggle, setLayoutToggle] = useState(false);
const [allPokemons, setAllPokemons] = useState([]);
const [loadPoke, setLoadPoke] = useState(
"https://pokeapi.co/api/v2/pokemon?limit=20"
);
useEffect(() => {
getAllPokemons();
console.log(allPokemons);
}, []);
const getAllPokemons = async () => {
const res = await fetch(loadPoke);
const data = await res.json();
setLoadPoke(data.next);
function createPokemonObject(result) {
result.forEach(async (pokemon) => {
const res = await fetch(
`https://pokeapi.co/api/v2/pokemon/${pokemon.name}`
);
const data = await res.json();
setAllPokemons((currentList) => [...currentList, data]);
});
}
createPokemonObject(data.results);
console.log(allPokemons);
};
I get doublets of the first 20 objects in allPokemons. See output:
enter image description here
But when I remove the function and uses a button to trigger the function it behaves as expected. Which means that the function populates the allPokemon array with one object per pokemon. See output.
enter image description here
I have tried everything from copying entire files from other repositories and with an accuracy I didn't knew I had followed different tutorials but the problem remains. Does anyone know why?

It's bcz, you are rendering your app into a React. Strict mode component that runs specific functions and methods twice as a way to help you detect unintentional side effects. Since the side-effect is a state update, this triggers a rerender.
Use a useEffect to run the effect once when the component mounts.

Related

React app not re-rendering after infinite scroll implementation

Hi guys hoping someone can help me with an issue. I built a function that fetches user posts from backend and returns them as a response:
const [posts, setPosts] = useState([]);
const newsFeed = async () => {
await axios
.post(`${process.env.REACT_APP_API}/news-feed`)
.then((res) => {
setPosts(res.data);
})
.catch((err) => {
console.log(err);
});
};
This function gets called by useEffect and also whenever a post is submitted, liked, commented on etc. so that users' posts are always re-rendered every time they are modified or added.
It was working fine until I decided to implement Infinite Scroll to my application. I installed the npm package react-infinite-scroll-component and modified my function to look like this:
const [posts, setPosts] = useState([]);
const [page, setPage] = useState(1);
const newsFeed = async () => {
await axios
.post(
`${process.env.REACT_APP_API}/news-feed/${page}`)
.then((res) => {
let newPosts = posts;
newPosts = newPosts.concat(res.data);
setPosts(newPosts);
setPage(page + 1);
})
.catch((err) => {
console.log(err);
});
};
The infinite scroll is working just fine but now the posts are not being re-rendered every time this function gets called and instead I need to refresh the page to see changes. I tried resetting the state of page back to 1 again on my postSubmit/likeHandler functions but this didn't have any effect. I'm not seeing any errors in the console so am unsure what is going on.
Replace let newPosts = posts; with let newPosts = [...posts]; Passing the same array reference to setState will not cause an update, since the value hasn't changed. By using [...posts], you are creating a new array, causing the component to update.

Axios is not getting response on first load

I am new in React JS and trying to get the data inside useEffect and I have a separate function for my api, but when I check the data.next in console.log there is no data in the first load but after adding few changes it works fine but still has an error when I refresh the page. Also, I noticed when I tried to console.log inside of function where the Axios or api declared, there's already a data in the first load of an application. Did anyone encounter this issue? Or my approach is wrong?
Here are my codes
/src/App.js
useEffect(async () => {
const res = await service.apiPokemon.fetchAll();
console.log(res.data.next)
}, []);
/src/api/pokemon.js
import axios from 'axios';
const URL = 'https://pokeapi.co/api/v2/pokemon';
export const fetchAll = async () => {
try {
const res = await axios.get(URL);
console.log(res.data.next);
return res;
} catch (error) {
console.log(error);
};
};
This is a very common problem. Your content is loaded before fetching the data. To solve this, you can add a condition to not render your content until you get the data:
let [pokemonsList, setPokemonsList] = useState([])
// Your useEffect hook to fetch data on mount
return (
{ pokemonsList.lenght > 0 && // If you are sure you'll receive pokemons
<div> {pokemonList.map((pokemon) => (
<p> {pokemon.name} </p>
)} </div>
}
)
Now, you'll only see the names of the pokemons when you have the list.
You can also add a loading message in case the response takes time with Redux and selectors.

How to render something that is async in React?

I'm making a react app that works with a API that provides data to my App. In my data base I have data about pins on a map. I want to show the info of those pins on my react app, I want them to render. I get that information with axios and this url: http://warm-hamlet-63390.herokuapp.com/pin/list
I want to retrieve the info from that url, with axios.get(url), stringify the JSON data and then parse it to an array of pins.
The Problem:
My page will be rendered before I get the data back from the server, because axios is async, so I will not be able to show anything. UseEffect and useState won't work because I need something in the first place (I think).
What i've tried:
I tried to use useEffect and useState, but as I said, I think I need something in the first place to change it after. I also tried to use await, but await won't stop the whole React App until it has a response, although it would be nice if there is something that stops the app and waits until I have the array with the info so I can show it on the App then. I tried everything with async. I'm fairly new to React so there might be something basic i'm mssing (?). I've been on this for days, I can't get this to work by any means.. Any help, youtube videos, documentation, examples, is help. Anything. How the hell do I render something that needs to wait for the server respond?
My code:
//function that stores the data in the result array,
//but result array will only be available after the
//server response, and after the page is rendered
async function pin(){
const result = []
var url = "http://warm-hamlet-63390.herokuapp.com/pin/list"
const res = await axios.get(url)
console.log(res.data.data);
if(res.data){
const txt = JSON.stringify(res.data.data)
const result = JSON.parse(txt)
console.log(result);
}
return result;
}
class App extends React.Component{
render(){
return(
<div>
<Pin/>
<Mapa/>
</div>
)
}
}
export default App
I don't fully understand what you are trying to output but how you would usually handle this is with both the useState hook and the useEffect hook see example below.
//function that stores the data in the result array,
//but result array will only be available after the
//server response, and after the page is rendered
const pin = () => {
const [result, setResults] = useState([]);
var url = "http://warm-hamlet-63390.herokuapp.com/pin/list"
useEffect(() => {
//Attempt to retreive data
try {
const res = transformData();
if (res) {
// Add any data transformation
setResults(transformData(res))
}
else {
throw (error)
}
}
catch (error) {
//Handle error
}
}, [])
// Handle data transformation
const transformData = async () => {
const res = await axios.get(url)
const txt = JSON.stringify(res.data.data)
const result = JSON.parse(txt)
return result
}
if (!result) {
// Return something until the data is loaded (usually a loader)
return null
}
// Return whatever you would like to return after response succeeded
return <></>;
}
This is all assuming that Pin is a component like you have shown in your code, alternatively, the call can be moved up to the parent component and you can add an inline check like below to render the pin and pass some data to it.
{result && <Pin property={someData} />}
Just a bit of background the useEffect hook has an empty dependency array shown at the end "[]" this means it will only run once, then once the data has updated the state this will cause a rerender and the change should be visible in your component
Rest assured, useEffect() will work. You need to use a condition to conditionally render the content when it comes back from the server.
In the example below if results has a length < 1 the message Loading ... will be rendered in the containing <div>, once you're results are received the state will be updated (triggering a re-render) and the condition in the template will be evaluated again. This time though results will have a length > 1 so results will be rendered instead of Loading ...
I’m operating under the assumption that you’re function pin() is returning the results array.
const app = (props) => {
const [results, setResult] = useState([]);
React.useEffect(() => {
const getPin = async () => {
if (!results) {
const results = await pin();
setResult([…results])
}
}
getPin();
},[results]);
return (
<div>
{result.length ? result : 'Loading ... '}
</div>
)
}

Reset React state before a render

I'm building a web application that consumes TMDB Api. I have the following code that fetch all information about a TV Show
export const useShowInfoFetch = ({showId}) => {
const [data, setData] = useState({})
const [loading, setLoading] = useState(false)
const [_error, _setError] = useState(false)
const fetchShowInfo = useCallback(() => {
setLoading(true)
try {
axios.get(getShowInfo(showId))
.then(response => {
setData(response.data)
})
} catch (error) {
_setError(true)
} finally {
setLoading(false)
}
}, [showId])
useEffect(() => {
fetchShowInfo()
}, [fetchShowInfo])
return [data, loading, _error]
}
All the information fetched is displayed in page, that also has Links with react-router-dom. Those links goes to another tv show page.
The problem is that when I'm in a page with a tv show that has X amount of seasons and I click a tv show with less seasons, the seasons from the page I was are persisting for a little bit of time. So, when I fetch the information for each season I got a 404 in the page that has less seasons.
Here is a screenshot of the error
The orange circle is what it's displayed since I click the tv show with less seasons.
As you can see, the seasons from the previous page are persisting for a little time, and because The Alienist has only 2 seasons (not 9) I get the 404. You can also note that latter, the correct amount of seasons are displayed.
I've tried to add a cleanup method in the useEffect hook. Something like this:
useEffect(() => {
fetchShowInfo()
return function cleanup() {
setData({})
}
}, [fetchShowInfo])
But this did not work.
I know that I can handle that with a catch after the then Axios promise, but I want to figure out why this is happening and fix the issue with a good solution instead of avoiding it.
Any help is welcomed and I can share the repository with all the code if needed.
EDIT:
To display the similar movies I use another custom hook
export const useSimilarFetch = (elementType, elementId) => {
const [similarElements, setSimilarElements] = useState({elements: []})
const [similarLoading, setSimilarLoading] = useState(false)
const [_error, _setError] = useState(false)
const fetchSimilarElements = useCallback(async (endpoint) => {
console.log(">>> fetching similar elements <<<")
setSimilarLoading(true)
try {
await axios.get(endpoint)
.then(response => {
setSimilarElements(() => ({
elements: [...response.data.results],
currentPage: response.data.page,
totalPages: response.data.total_pages
}))
})
} catch (error) {
_setError(true)
} finally {
setSimilarLoading(false)
}
}, [])
useEffect(() => {
fetchSimilarElements(getSimilar(elementType, elementId));
}, [fetchSimilarElements, elementType, elementId])
return [{similarElements, similarLoading, _error}, fetchSimilarElements]
}
Then, in my ShowInfoComponent I call all the needed hooks like this:
const {showId} = useParams()
const [data, loading, _error] = useShowInfoFetch({showId})
const [{similarElements, similarLoading}] = useSimilarFetch("tv", showId)
Thanks.
By the time showId changes, data has to wait one additional render cycle, so showId is already used even though data has not yet been fetched. The UI relies on both showId and data, yet data depends on showId. One way to solve this could be having your UI to rely on data alone. What about the id? Add it to data for example. We merely want to avoid the desynchronization.
Something like this:
export const useShowInfoFetch = ({showId}) => {
const [data, setData] = useState({})
const [loading, setLoading] = useState(false)
const [_error, _setError] = useState(false)
const fetchShowInfo = useCallback(() => {
setLoading(true)
try {
axios.get(getShowInfo(showId))
.then(response => {
setData({ id: showId, info: response.data})
})
} catch (error) {
_setError(true)
} finally {
setLoading(false)
}
}, [showId])
useEffect(() => {
fetchShowInfo()
}, [fetchShowInfo])
return [data, loading, _error]
}
Then use data.id to build your links.
If response.data already contains the id, then even better, use that.
That's just an example, of course but hopefully you get the idea.
I might be wrong but I believe you are not watching the correct value on the useEffect. You should be watching showId and not the function fetchShowInfo. That is:
useEffect(() => {
fetchShowInfo()
}, [showId]) --> HERE
And as you are memoized the callback, if you are watching the wrong variable then you will get back the 'last answered'.

How to re-render react component depends on a file?

I have a file that stores an array of objects. I have a component that fetches data from this file then render the list. The file could be updated somewhere else, I need the component to be updated if the file is modified. I have following code example
const header = () => {
const [list, setList] = useState([]);
// fetch
useEffect(() => {
const loadList = async () => {
const tempList = await getList("/getlist"); // get call to fetch data from file
setList(tempList);
};
loadList ();
}, [list]);
// function to render content
const renderList = () => {
return list.map(obj => (
<div key={obj.name}>
{obj.name}
</div>
));
};
return (
<div>{renderList()}</div>
)
}
// get call
router.get('/getlist',
asyncWrapper(async (req, res) => {
const result = await getList();
res.status(200).json(result).end();
})
);
const getList= async () => {
const list = JSON.parse(await fs.readFile(listPath));
return list;
}
Code has been simplified. If I remove the list from useEffect, then it will only render once and will never update unless I refresh the page. If I include list there, loadList() will get called constantly, and component will get re-rendered again and again. This is not the behavior I want. I am just wondering without making header component async component, how do I only re-render this component when the file is changed?
Thank you very much.
There are two approaches you can take to this:
Polling
Request the URL on an interval, and clear it when the component is unmounted.
Replace loadList () with:
const interval = setInterval(loadList, 60000); // Adjust interval as desired
return () => clearInterval(interval)
Make sure the cache control headers set in the response to /getlist don't stop the browser from noticing updates.
Server push
Rip out your current code to get the data and replace it with something using websockets, possibly via Socket.IO. (There are plenty of tutorials for using Socket.io with React that can be found with Google, but its rather too involved to be part of a SO answer).

Categories

Resources