I've built a React Hook as follows:
const Index = (props) => {
const [posts, setPosts] = useState([])
useEffect(() => {
const getPosts = async () => {
const posts = await getPostFromWebService()
for (let i of posts) {
setPosts([ ...posts, i ])
}
}
getPosts()
}, [])
// ... remaining code
}
But even if the web service returns 5 posts, only the last posts is getting updated in the posts state. Hence it only receives one post in it, instead of 5.
What am I doing wrong here?
It sounds like you want something like this. Here we would have the useEffect listen for any changes in postCount so that we can trigger your logic to fetch more posts.
const Index = (props) => {
const [posts, setPosts] = useState([])
const [postCount, setPostCount] = useState(0)
useEffect(() => {
const getPosts = async () => {
const newPosts= await getPostFromWebService()
setPosts([...posts, newPosts])
}
}, [postCount])
return(
<div>
<button onClick={() => setPostCount(postCount + 5)}>Get more posts</button>
</div>
)
}
Related
const Notes = () => {
const history = useNavigate();
const [apiData, setApiData] = useState([]);
useEffect(() => {
axios
.get(`https://6390acc765ff4183111b53e9.mockapi.io/notes`)
.then((getData) => {
setApiData(getData.data);
});
}, []);
const onDelete = (id) => {
axios
.delete(`https://6390acc765ff4183111b53e9.mockapi.io/notes/${id}`)
.then(() => {
history("/notes");
});
};
This way I can delete the note that i fetched earlier, but it still appears on the screen until I refresh manually. It doesn't also go to /notes because i am already on /notes
You can either return the updated data in the delete response to update the local state, or you can trigger a refetch of the data after a successful deletion.
Refetch Example:
const Notes = () => {
const history = useNavigate();
const [apiData, setApiData] = useState([]);
const fetchNotes = useCallback(async () => {
const getData = await axios
.get(`https://6390acc765ff4183111b53e9.mockapi.io/notes`);
setApiData(getData.data);
}, []);
useEffect(() => {
fetchNotes();
}, [fetchNotes]);
const onDelete = async (id) => {
await axios
.delete(`https://6390acc765ff4183111b53e9.mockapi.io/notes/${id}`);
fetchNotes();
history("/notes");
};
...
Returned response Example*:
const Notes = () => {
const history = useNavigate();
const [apiData, setApiData] = useState([]);
useEffect(() => {
axios
.get(`https://6390acc765ff4183111b53e9.mockapi.io/notes`)
.then((getData) => {
setApiData(getData.data);
});
}, []);
const onDelete = async (id) => {
const getData = await axios
.delete(`https://6390acc765ff4183111b53e9.mockapi.io/notes/${id}`);
setApiData(getData.data);
history("/notes");
};
...
*Note: This requires updating the backend code to return the updated data in the response.
In 2nd. useEffect I want to return experiences using resume.id which is coming from 1st useEffect. But I guess they are working in the same time so before returns resume in 1st useEffect, 2nd one is working and it returns undefined. How can I fix this or is there a any way to write better code ?
const { authUser } = useSelector((state) => state.auth);
const [resume, setResume] = useState({});
const [experience, setExperience] = useState([]);
let resumeService = new ResumeService();
let resumeExperienceService = new ResumeExperienceService();
useEffect(() => {
resumeService
.getResumeByCandidateId(authUser.id)
.then((result) => setResume(result.data.data[0]));
}, []);
useEffect(() => {
resumeExperienceService
.getExperiencesByResumeId(resume.id)
.then((result) => setExperience(result.data.data));
}, []);
You should add the state that named 'resume' to the useEffect.
That means second useEffect would run after every setResume call
const { authUser } = useSelector((state) => state.auth);
const [resume, setResume] = useState({});
const [experience, setExperience] = useState([]);
let resumeService = new ResumeService();
let resumeExperienceService = new ResumeExperienceService();
useEffect(() => {
resumeService
.getResumeByCandidateId(authUser.id)
.then((result) => setResume(result.data.data[0]));
}, []);
useEffect(() => {
resumeExperienceService
.getExperiencesByResumeId(resume.id)
.then((result) => setExperience(result.data.data));
}, [resume]);
How can I fetch the version and languages and pass them to getChampions function
const [version, setVersion] = useState(0)
const [languages, setLanguages] = useState([])
const [selectedLanguage, setSelectedLanguage] = useState('')
const [champions, setChampions] = useState([])
useEffect(() => {
getVersion().then((version) => setVersion(version))
.then(getLanguages().then(languages => {
setLanguages(languages)
setSelectedLanguage(languages[0])
}))
.then(getChampions(version, selectedLanguage).then(champions => setChampions(champions)))
}, [])
I'm getting the default values from the initialization of useState where version = 0 and languages = []
setState is asynchronous, so if you setState and then call a function with the state immediately after you are not guaranteed to get the current state value. #Yadab's answer resolves this but calling getChampions with the variables from the response rather than the variables from the state.
My personal preference is to use a separate hook to respond to changes in the state. It also seems like getVersion and getLanguages don't depend on each other and can be run simultaneously rather than one after the other.
const App = () => {
const [version, setVersion] = useState(0);
const [languages, setLanguages] = useState([]);
const [selectedLanguage, setSelectedLanguage] = useState("");
const [champions, setChampions] = useState([]);
useEffect(() => {
getVersion().then(setVersion);
}, []); // run once - include [setVersion] if using eslint
useEffect(() => {
getLanguages().then((languages) => {
setLanguages(languages);
setSelectedLanguage(languages[0]);
});
}, []); // run once - can include deps [setLanguage, setSelectedLanguage] for eslint
useEffect(() => {
// only execute if both version and selectedLanguage have already been set
if (version && selectedLanguage) {
getChampions(version, selectedLanguage).then(setChampions);
}
}, [version, selectedLanguage]); // run whenever version or selected language changes
...
You can use async await in a separate function to fetch the version and language and use the fetched version and language to fetch the champions data. Take a look at below example.
const [version, setVersion] = useState(0)
const [languages, setLanguages] = useState([])
const [selectedLanguage, setSelectedLanguage] = useState('')
const [champions, setChampions] = useState([])
const fetchData = async () => {
const versionData = await getVersion();
setVersion(versionData)
const languageData = await getLanguages();
setLanguages(languageData)
setSelectedLanguage(languageData[0])
const championsData = await getChampions(versionData, languageData[0])
setChampions(championsData)
}
useEffect(() => {
fetchData();
}, [])
I'm exploring hooks with react-redux-firebase but my "setDataProducts" is behaving oddly.
I'm using useEffect() like I could use componentDidMount() but not sure if this is the right way.
export default function ProductList() {
const [dataProducts, setDataProducts] = useState([]);
const firestore = useFirestore();
const fetchProducts = async () => {
const response = firestore.collection("products");
const data = await response.get();
data.docs.forEach((product) => {
setDataProducts([...dataProducts, product.data()]);
console.log(product.data());
});
};
useEffect(() => {
fetchProducts();
}, []);
return (
<div>
{isLoaded &&
dataProducts.map((product) => {
return (
<div>
<h4>{product.title}</h4>
<h3>{product.price}</h3>
</div>
);
})}
</div>
);
}
I cannot render the both products I have in Firestore. Only One is rendering... So I dont understand. Should not it rerender when state is updated ?
Thanks for reply
We can see there was not rerendering
I think it is because you called setDataProducts again before dataProducts updated.
Please replace fetchProducts method with my code following:
const fetchProducts = async () => {
const response = firestore.collection("products");
const data = await response.get();
const newProducts = data.docs.map((product) => product.data());
console.log(newProducts);
setDataProducts([...dataProducts, ...newProducts]);
};
im using this Api to get json data.
const FetchEarthquakeData = url => {
const [data, setData] = useState(null);
useEffect(() => {
fetch(url)
.then(res => res.json())
.then(jsonData => setData(jsonData.features))
}, [url]);
return data;
};
The problem is when I use this function like this:
const jsonData = FetchEarthquakeData(url)
console.log(jsonData);
I get following console.logs:
null
Array(17)
So my function FetchEarthquakeData returns the null variable and! the desired api. However if I want to map() over the jsonData, the null value gets mapped. How can I refactor my code so I get only the Array?
I'm not quite sure what useState() and setData() do. But in order to fetch the json data from the API, you can make the function as followed, then you can perform operations on the fetched data.
const url = "https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/4.5_day.geojson"
const FetchEarthquakeData = url => {
return new Promise(resolve => {
fetch(url)
.then(res => res.json())
.then(jsonData => resolve(jsonData.features))
})
}
FetchEarthquakeData(url).then(features => {
console.log(features)
// Do your map() here
})
as per the documentation of react hooks ,Only Call Hooks from React Functions Don’t call Hooks from regular JavaScript functions.
React Function Components -- which are basically just JavaScript Functions being React Components which return JSX (React's Syntax.)
for your requirement you can do as follows in your react component.
idea is to have state hook initialized with empty array then update it once json data available. the fetch logic can be moved inside useeffect .
const SampleComponent=()=>{
const [data,setData] = useState([])
useeffect (){
fetch(url).then((responsedata)=>setData(responseData) ,err=>console.log (err)
}
return (
<>
{
data.map((value,index)=>
<div key=index>{value}<div>
}
</>
)
}
if you find above fetching pattern repetitive in your app thou can use a custom hook for the same.
const useFetch = (url, options) => {
const [response, setResponse] = React.useState(null);
const [error, setError] = React.useState(null);
React.useEffect(() => {
const fetchData = async () => {
try {
const res = await fetch(url, options);
const json = await res.json();
setResponse(json);
} catch (error) {
setError(error);
}
};
fetchData();
}, []);
return { response, error };
};
in your React component you can use like
const {data,error} = useFetch(url , options)
You have to do it in an async fashion in order to achieve what you need.
I recommend you doing it separately. In case you need the data to load when the component mounts.
Another thing is that your function is a bit confusing.
Let's assume some things:
const Example = () => {
const [data, setData] = useState(null);
useEffect(() => {
const fetchData = async () => {
const result = await fetch(url);
setData(result.features);
};
fetchData();
}, []);
return (
<div>
{console.log(data)}
{data && <p>{JSON.stringify(data, null, 2)}</p>}
</div>
);
};
I am sure that the way you are doing it is storing the data properly on data, but you can not see it on your console.log. It is a bit tricky.
You can read a bit more here => https://medium.com/#wereHamster/beware-react-setstate-is-asynchronous-ce87ef1a9cf3
Adding a bit more of complexity in case you want to handle different states like loading and error.
const Example = () => {
const [data, setData] = useState(null);
const [isLoading, setIsLoading] = useState(false);
const [isError, setIsError] = useState(false);
useEffect(() => {
const fetchData = async () => {
setIsError(false);
setIsLoading(true);
try {
const result = await fetch(url);
setData(result.features);
} catch (error) {
setIsError(true);
}
setIsLoading(false);
};
fetchData();
}, []);
return (
<div>
{console.log(data)}
{data && <p>{JSON.stringify(data, null, 2)}</p>}
</div>
);
};