Can't update React state hook from useEffect - javascript

I want to update my state hook with useEffect. Somehow it gives me an error and brokes my app.
Console.log gives right answer in console but somehow react doesn't want to recognize setCountries as a state hook function. What I am doing wrong here?
import React, { useState, useEffect } from 'react'
import axios from 'axios'
var host = 'https://restcountries.eu/rest/v2/all'
const App = () => {
const { countries, setCountries } = useState([])
useEffect(() => {
axios
.get(host)
.then(response => {
console.log(response.data)
setCountries(response.data)
})
})
return (
<div>
</div>
)
}
export default App
And error I am getting in browser
Unhandled Rejection (TypeError): setCountries is not a function
error in console:
Uncaught (in promise) TypeError: setCountries is not a function
at App.js:14

Two things: useState returns an array with two elements, and an array does not have a countries or setCountries key to destructure for. You could do this:
const { 0: countries, 1: setCountries } = useState([]);
or just
const [countries, setCountries] = useState([]);
Secondly, useEffect(..) without any second argument will cause the effect to be triggered on every rerender, so your component will get stuck in an update loop. Use an empty array as its second argument to only trigger the effect on load.

Related

Variable in React is undefined on first render but defined after re-rendering. (Uncaught TypeError: vairable is undefined)

When I try to show the title of a movie (line 8 of App.js) I get the error "Uncaught TypeError: movies[0] is undefined". If I do a log of the variable "movies" after line 5 the console makes two logs: Frist, logs movies as an empty array and then logs movies as an array with all the content, like I want.
The weird thing is that if I delete the line 8 {movies[0].title} and save the error dissapears as normal, but then if I add it again and save the movie title renders on screen like I'm trying to do.
App.js:
import './App.css';
import useApi from './useApi';
function App() {
const movies = useApi();
return (
<div className="App">
{movies[0].title}
</div>
);
}
export default App;
My useApi returns an array with a list of movies:
import axios from 'axios';
import { useState, useEffect } from 'react';
const useApi = () => {
const [movies, setMovies] = useState([]);
const getData = async () => {
try {
const res = await axios(url);
setMovies(res.data.results);
} catch (error) {
return [];
}
}
useEffect(() => {
getData();
}, [])
return movies;
}
export default useApi;
Knowing that if I print movies it logs an empty array but then it prints it normal like I said, i guess I'm having a problem with asynchrony but I can't figure out what. And the fact that it works when removing and readding line 8 makes me more confused.
Because your getData() function render two times in useEffects hook. You need to check if(movies.length>0) and after that type return!
You may want to use the notion of conditional rendering. Something like this may work:
import './App.css';
import useApi from './useApi';
function App() {
const movies = useApi();
if (movies.length) { // conditionally render when the movie list is not empty
return (
<div className="App">
{movies[0].title}
</div>
);
}
return <div>Loading...</div>
}
export default App;

Why does my useEffect() fetch data unreliably. I cannot map through the data because I am getting some undefined responses - ReactJS

I have a sidebar which sets a category Id on click and shows product information based on this Id.
In order to display each product's details, I make an api post call using axios in a useEffect() hook and setData with useState hook.
However, when I try to console.log the data, I get unreliable data back, that is, some undefined and some data.
import { useState, useEffect, useCallback, useMemo } from "react";
import axios from "axios";
import ProductCard from "../Product-cards/_medium-product-card";
const ProductDisplay = ({ subCategorySkus, categoryId, allSkus }) => {
const [data, setData] = useState();
const [isLoading, setIsLoading] = useState();
const apiRequest = { product_skus: allSkus };
const productData = useProductData(apiRequest);
useEffect(() => {
const fetchData = async () => {
try {
const response = await axios.post("/products", apiRequest);
setData(response.data);
setIsLoading(false);
} catch (err) {
console.log(err.message);
}
};
fetchData();
}, [categoryId]);
console.log(data);
return isLoading ? (
" data is loading ..."
) : (
<div className="product-display">
<h3>Child component Id: {categoryId}</h3>
<ProductCard categoryId={categoryId} />
</div>
);
};
export default ProductDisplay;
You can see here what I get in the console:
.
I have tried adding an if condition, but I am not sure that addresses the issue. I have tried with different dependencies in the useEffect. But I feel there is something I am not understanding.
The api is sending the information correctly, but I get several responses in the console.log and some of them come in as undefined.
This means that I can do a data.map because it crashes the component.
Any help?
UPDATE:
#moonwave99 provided the answer (see below). For the benefit of others who may browse through this question and to clarify what the solution entails, I am including an image below, showing where I changed my code. I think that most importantly, I had not initialised my useState() to an empty array, like this: useState([]).
Any further clarification of the issues here by anyone who knows more, is welcomed. As this is only a quick fix without enough context to understand the logic.

React useEffect rendering more than once

I'm new in Hooks. I coded a project with componentDidMount.
Now I'm learning about hooks and rewriting this project with hooks. I want to fetch the data and print it on the console first.
However, it renders 3 times. It is probably because I used 2 setState in useEffect. However, in one of them I set the data to data array and in the other I keep the loading value for spinner control. How can I use useEffect like componentDidMount just one time to pull data and set my states?
When I write the console into useEffect, "React Hook useEffect has a missing dependency: 'data'." warning and returns an empty list.
Btw I deleted strictmode.
import React, { useState, useEffect } from "react";
import axios from "axios";
function App() {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
const fetchData = async () => {
const { data } = await axios.get("/data/data.json");
setData(data);
setLoading(false);
};
fetchData();
}, []);
console.log(data);
return <div className="App">App</div>;
}
export default App;
The component re-renders normally after each state update.
Hence, in your example, it re-renders when the value of data is updated and again, when loading is updated.
Please check this demo to examine how the renderings occur after each state change:
Initial render
After loading is set to true
After data is fetched and set
After loading is set to false
I think having the data variable twice might be causing a conflict with the linter. You can rename your data coming from your API call to prevent the warning: "React Hook useEffect has a missing dependency: 'data'."
Your component will re-render on each state update, but the useEffect will only run when your dependencies change. Since they aren't going to change, the API call only happens once.
To prove it, you can move the console.log(result) in your useEffect and see it only logs once. However, make sure you call it on your result and not data, because the state won't be updated until the next render after calling setData.
import React, { useState, useEffect } from "react";
import axios from "axios";
function App() {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
const fetchData = async () => {
const { data: result } = await axios.get("/data/data.json");
setData(result);
setLoading(false);
console.log(result); // runs once
};
fetchData();
}, [setData, setLoading]);
console.log(data); // runs 3 times
return <div className="App">App</div>;
}
export default App;

Why is useState() not setting the default value?

I have created following reactComponent. From what I understand useState([]); should set the comments variable to an array. But it does not. I get an exeption that I Try to run .map() on an object. What I have to do is Object.values(comments) to make it work but I don't understand why. My api definetly returns an array of comment objects.
import React, { useState, useEffect } from 'react';
import axios from 'axios';
export default ({ postId }) => {
const [comments, setComments] = useState([]);
const fetchData = async () => {
const res = await axios.get(
`http://localhost:4001/posts/${postId}/comments`
);
setComments(res.data);
};
useEffect(() => {
fetchData();
}, []);
const renderedComments = comments.map(comment => {
return <li key={comment.id}>{comment.content}</li>;
});
return <ul>{renderedComments}</ul>;
};
Your use of state is correct as far as I can tell, the fact that Object.values works on the data handed back to you implies it is definitely either an object or an array, have you run Array.isArray(res.data) as part of your troubleshooting process?
As stated by a commenter above, the most likely scenario is that you are getting an object back from your back end. Other things you can do to confirm its the data handed back to you at fault would be to simply comment out your useEffect and see if it still throws the same exception.

Displaying an array of objects in React native using map method

I have following component where I want to display data from an API, ShopScreen.js. I retrieve data with useEffect hook from API in service folder, and everything is ok, the json data is being loaded into data variable from useState hook, when I console.log it.
I have problem to the display data from this variable with map method. I get the error: Cannot read property 'map' of undefined. Can spot somebody where the problem is?
ShopScreen.js:
import React, { useState, useEffect } from 'react';
import { View, Text, StyleSheet, Button } from 'react-native';
import { fetchShops } from '../services/fetchShops';
const ShopsScreen = props => {
const [data, setShops] = useState({});
useEffect(() => {
fetchShops()
.then(response => response.json())
.then(data => setShops(data));
}, []);
return(
<View>
<Text>The Shops Screen!</Text>
{data.result.map(shop => {return (<Text>{shop.address}</Text>)})}
</View>
);
};
export default ShopsScreen;
My service for fetching data is fetchShops.js
export const fetchShops = () => {
const URL = `https://vilvumbiyl.execute-api.eu-west-1.amazonaws.com/Dev/store/MEBD/list`;
return fetch(URL)
}
useEffect without any params is equal to componentDidMount and for this reason, is called after the render.
So, the first time your jsx code is called, data.result.map is undefined and only after the re-render, do to the response of fetchShops(), it has a value.
You simply need to check the value like this:
data.result && data.result.map()
You can try:
const [data, setShops] = useState({result : {}});
or test data.result before use map on it.
ShopsScreen returns your view(JSX) before you get answer from your rest API. The result is null. You get the exception.

Categories