Okay, so my problem is that I'm trying to modify an array after fetching JSON and storing as an array with hooks. How would I go about doing that?
I tried using another .then and calling the function I want to call, but when logging the array to the console in the function it just gives me an "cannot read property of description undefined error" Any help? Thanks!
const [repos, setRepos] = useState([]);
const [isLoaded, setIsLoaded] = useState(false);
const [error, setError] = useState(null);
function updateDates() {
var i;
console.log(repos[1].description);
}
// fetch github repo sorted by updated date and store in repos
useEffect(() => {
fetch("https://api.github.com/users/benngagne/repos?sort=updated")
.then(res => res.json())
.then(
(result) => {
setRepos(result);
setIsLoaded(true);
},
(error) => {
setIsLoaded(true);
setError(error);
}
)
// .then(() => {
// updateDates();
// })
}, [])
Updating state is asynchronous, and you are trying to access updated state data immediately after state updating function(setRepos()).
useEffect hook is what you should be looking for. Set dependency array of useEffect to your state, so it will only work when your state changes. There, you can do your manipulations of your new state, do functions etc.
useEffect(() => {
if(repos){ //checking if state is not null; to not catch initial state.
//Do your functions, calculations etc..
}
},[repos]);
how are you?
Your code has an error in your fetch, I'm adding those corrections and also a full class showing how to rander the incomming data. btw I just showing the full_name property from the response, but you can also grab all, like:
const {full_name,description} = repo;
Here's the full class:
import React, {useState, useEffect} from 'react'
function App() {
const [repos, setRepos] = useState([]);
const [isLoaded, setIsLoaded] = useState(false);
const [error, setError] = useState(null);
// fetch github repo sorted by updated date and store in repos
useEffect(() => {
fetch("https://api.github.com/users/benngagne/repos?sort=updated")
.then(res => res.json())
.then(result => {
setRepos(result);
setIsLoaded(true);
})
.catch((error) => {
setIsLoaded(true);
setError(error);
})
}, [])
return (
<div>
{
repos.map((repo, idx) => {
const {full_name} = repo;
return <div>{full_name}</div>
})
}
</div>
)
}
export default App
Related
I would like to know how to store the data in the cache when the user come to my website first time and for the subsequent visit I want to fetch the data from cache and only want to fetch new data/updated data from the server.
Right now, it fetches data every time user comes to my website, which causes a lot of reads, so I want to reduce those reads by storing data in the cache.
My code:
import { useEffect, useState } from "react"
// firebase import
import { collection, limit, onSnapshot, orderBy, query, where } from "firebase/firestore"
import { db } from "../firebase/config"
export const useCollection = (c) => {
const [documents, setDocuments] = useState([])
const [isLoading, setIsLoading] = useState(true)
const [error, setError] = useState(null)
useEffect(() => {
let ref = collection(db, c)
const unsubscribe = onSnapshot(ref, (snapshot) => {
const results = []
snapshot.docs.forEach(
(doc) => {
results.push({ ...doc.data(), id: doc.id })
},
(error) => {
console.log(error)
setError("could not fetch the data")
}
)
// update state
setDocuments(results)
setIsLoading(false)
setError(null)
})
// unsubscribe to the previous listener before running the side effect again
return () => unsubscribe()
}, [openTab])
return { documents, error, isLoading }
}
import React, { useEffect, useState } from "react";
import { endpoint, apiKey } from "../api";
import Container from "../components/layouts/Container";
export default function Movie({ route }) {
const { movieId } = route.params;
const [movieDetails, setMovieDetails] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const urls = [
`${endpoint}/movie/${movieId}?api_key=${apiKey}`,
`${endpoint}/movie/${movieId}/credits?api_key=${apiKey}`,
`${endpoint}/movie/${movieId}/images?api_key=${apiKey}`,
`${endpoint}/movie/${movieId}/reviews?api_key=${apiKey}`,
`${endpoint}/movie/${movieId}/similar?api_key=${apiKey}`,
];
useEffect(() => {
const fetchData = () => {
setIsLoading(true);
Promise.all(
urls.map((url) => {
return fetch(url);
})
)
.then((response) => {
return Promise.all(response.map((res) => res.json()));
})
.then((data) => {
setMovieDetails(data);
setIsLoading(false);
})
.catch((err) => {
console.log(err);
});
};
fetchData();
}, []);
console.log(movieDetails[0]);
Hello,
I've encountered a problem tha that when i try to fetch the request above when i console.log() it it first returns undefined and then return the desired response.
The response is expected as initially the state is undefined.
During the request also, till the response is unresolved, the process is suspended and the state stays undefined.
A simple solve will be to move the console.log(movieDetails[0]) into the last .then() body or you could write your own Promise resolution functions.
I can load my data but only after I refresh the page. Until then, it shows the data from the previous item I clicked on. It's behaving like a cache would.
Here is my mediaSlice
import { createSlice, createAsyncThunk } from "#reduxjs/toolkit";
import axios from "axios";
const KEY = process.env.REACT_APP_API_KEY
const BASE_URL = process.env.REACT_APP_BASE_URL
const HBO_SINGLE_MEDIA_API = `${BASE_URL}/titlestest`
const initialState = {
media:{},
status: 'idle', //'idle', 'loading', 'succeeded', 'failed'
error:null
}
export const fetchSingleMediaTitle = createAsyncThunk(
'media/fetchSingleMediaTitle',
async (id) => {
const response = await axios.get(
HBO_SINGLE_MEDIA_API,
{
headers: {
'Content-Type': 'application/json',
'X-API-KEY': KEY,
},
params: {
titleId: id,
}
}
)
return response.data.Item;
}
)
const mediaSlice = createSlice({
name: 'media',
initialState,
reducers:{},
extraReducers: {
[fetchSingleMediaTitle.pending]: () => {
console.log("Pending");
},
[fetchSingleMediaTitle.fulfilled]: (state, { payload }) => {
state.status = 'succeeded'
state.media = payload
},
[fetchSingleMediaTitle.rejected]: () => {
console.log("Rejected");
},
}
})
// SELECTORS
export const selectSingleMedia = (state) => state.media.media;
export const getMediaStatus = (state) => state.media.status;
export default mediaSlice.reducer
And then the Media Component has what you would expect
const [media, setMedia] = useState([]);
const {id} = useParams();
const dispatch = useDispatch();
const singlemedia = useSelector((state) => selectSingleMedia(state, id))
const mediaStatus = useSelector(getMediaStatus)
useEffect(() => {
if (mediaStatus === 'idle') {
dispatch(fetchSingleMediaTitle(id)) //yes, it's imported
}
setMedia(singlemedia);
//it returns the array with the data but not the current one
console.log("singlemedia: ", singlemedia);
return () => { };
// Someone suggested removing the dependencies below but then it doesn't load anything.
}, [id, singlemedia, mediaStatus, dispatch, media_data])
I am at a loss here because as I understand it, the useEffect is supposed to fire onload and give me the current data. The ID is correct in the params but the state is not mutating.
Thanks in advance
EDIT
For reference, here is the console log. Those console logs are in the useEffect. The API is slightly slower than the useEffect and the render is happening before the data is ready (race condition) or at least that's what I think it's happening here that's why it loads empty and then it loads again. But the confusing part is that ALL of that happens on a page refresh only. On a normal load the state is not empty is loaded and in time for the UI to render it (no race condition), only, it's loaded with the old data from a previous state shape
Here is the redux dev tools
Your selector takes one argument, but you're passing it two. Change it to:
const singlemedia = useSelector(selectSingleMedia);
Second, your singlemedia is the state. There's no need to setMedia(singlemedia);.
Your useEffect should look like:
useEffect(() => {
if (mediaStatus === 'idle') {
dispatch(fetchSingleMediaTitle(id));
}
}, [dispatch, id, mediaStatus]);
Also, you should look into RTK Query, which would replace createAsyncThunk: https://redux-toolkit.js.org/tutorials/rtk-query
Edit per our discussion:
useEffect(() => {
dispatch(fetchSingleMediaTitle(id));
}, [dispatch, id]);
The problem is very simple, just remove the media internal state variable:
const [media, setMedia] = useState([]);
As what you are doing now is that you send the async request here:
if (mediaStatus === 'idle') {
dispatch(fetchSingleMediaTitle(id)) //yes, it's imported
}
And before the backend responds you read the store and set it in the internal state:
setMedia(singlemedia); // This is old data now, as we are waiting for the backend
If you wish to display the store state just use singlemedia in the render method.
If you wish to have some temporary state that mimics a backend response again use singlemedia and implement the temporary state in the redux store.
PS. the useEffect should depend only on id and useDispatch
I have an API that sometimes doesn't work. I would like for the App to refetch automaticaly if this happens until it gets the necessary data. How can I do that? I'm thinking that maybe this could be done by using a dependency on the useEffect hook, but I'm not clear on how to do it.
Lets say we have this App component
export default function App() {
const [data, setData] = useState([])
useEffect(() => {
getData({ setData })
}, [])
return [
<h3>
{data[0].title}
</h3>
]
}
And this API component
const url = 'https://some-random-url.com/whatever-api'
export default function getData({ setData }) {
axios.get(url)
.then((response) => {
let dataArray = response.data.results
setData(dataArray)
})
.catch((error) => {
console.log(error)
})
}
If you were to do it with useEffect, you could pass an error counter state to getData function and increase it on error or empty data.
Then add to your useEffect dependency array to refetch.
But this certainly implies that you have to think further what you are wanting to do after a certain amount of retries, to avoid an infinite loop.
export default function App() {
const [data, setData] = useState([])
const [errCount, setErrCount] = useState(0)
useEffect(() => {
getData({ setData, errCount, setErrCount })
}, [errCount])
return [
<h3>
{data[0].title}
</h3>
]
}
And this API component
const url = 'https://some-random-url.com/whatever-api'
export default function getData({ setData, errCount, setErrCount }) {
axios.get(url)
.then((response) => {
let dataArray = response.data.results
setData(dataArray)
!dataArray.length && setErrCount(errCount+1);
})
.catch((error) => {
setErrCount(errCount+1);
console.log(error)
})
}
I am new with react hooks, i'm trying to get info from an API but when i do the request i get 2 responses first an empty array and then the data of the API, why am i getting that empty array! , this is my first question, i'm sorry.
Thanks for helping me !
import {useState, useEffect} from 'react';
const getSlides = (API) => {
const[data,setData] = useState([]);
const getData = () =>
fetch(`${API}`)
.then((res) => res.json())
useEffect(() => {
getData().then((data) => setData(data))
},[])
return data
}
export default getSlides;
The useEffect() hook runs after the first render. Since you've initialized the data state with an empty array, the first render returns an empty array.
If you're component depends on data to render, you can always conditionally return null until your data is loaded.
Also, I recommend using an async function for api requests, it allows you to use the await keyword which makes your code easier to read. The only caveat, is that you cannot pass an async function to useEffect, instead define an async function inside your hook, and then call it.
import React, { useState, useEffect } from "react";
const API = "https://example.com/data";
const GetSlides = (props) => {
const [data, setData] = useState();
useEffect(() => {
async function getData() {
const request = fetch(API);
const response = await request;
const parsed = await response.json();
setData(parsed);
}
getData();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
if (data === undefined) {
return null;
}
return <>data</>;
};
export default GetSlides;
Of course, you can still use Promise chaining if you desire.
useEffect(() => {
async function getData() {
await fetch(API)
.then((res) => res.json())
.then((data) => setData(data));
}
getData();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
<GetSlides api="https://yay.com" />
react components need to be title case
import React, { useState, useEffect } from 'react'
const GetSlides = ({ api }) => {
const [data, setData] = useState(null)
const getData = async () =>
await fetch(`${api}`)
.then((res) => res.json())
.then((data) => setData(data))
useEffect(() => {
getData()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
console.log(data)
return <div>slides</div>
}
export default GetSlides
The effect callback function is called after the render of your component. (Just like componentDidMount) So during the first render phase, the data state has not been set yet.
You initialize your data with and empty array here:
const[data,setData] = useState([] <- empty array);
useEffect runs after your component is mounted, and then calls the API, that it might take a few seconds or minutes to retrieve the data, but you return the data right away before knowing if the API finished its call.
If you want to return the data after it has been retrieved from the API, you should declare and async method
const getSlides = async (API) => {
try {
const res = await fetch(API);
const data = await res.json();
return data;
} catch (e) {
throw new Error(e);
}
}
Note that it is not necessary hooks for this function