Setting state on array logs empty array multiple times - javascript

I am trying to fetch data from a backend server and hold it in an array. After I have done this I want to pass the array to another
component. Although, when I try and populate the array and pass it to my component, I get multiple empty arrays passed rather than an array with data.
I first initialise the state of the array using useState()
const [data, setData] = useState([]);
I then have a function that fetches data from the backend and attempts to populate data.
useEffect(() => {
const fetchData = () => {
fetch('/data')
.then((res) => res.json())
.then((data) => {
for (const property in data) {
setDailyCases([...dailyCases].push(`${data[property]}`));
}
});
}
fetchData();
},[])
When I pass this data to another component: <DataComp data={data}, I don't get the data I was expecting.
When I console.log(props.data) this is the output:
Which is strange beacuse If I console.log() while running the data loop all the data is visible:
How can I make sure the data array is updating correctly, and when passed I get one array of all the data?
Here is the DataComp component:
const DataComp = (props) => {
console.log(props.cases)
return (
<h1>Testing</h1>
)
}
export default DataComp
Using #Fardeen Panjwani answer my component is getting the correct data, although I am now getting more outputs to the console that expected?

You're never calling setData with the fetched data as parameter.
useEffect(() => {
const fetchData = () => {
fetch('/data')
.then((res) => res.json())
.then((_data) => { // using "_data" in order to avoid clash with the state-hook
setData(_data) // <= this line is responsible for populating the "data" value.
for (const property in _data) {
setDailyCases([...dailyCases].push(`${_data[property]}`));
}
});
}
fetchData();
},[])
In order to update data's value, you need to call the setData method.

Related

useState and useEffect not playing nice with an API call [duplicate]

This question already has answers here:
When should I use a return statement in ES6 arrow functions
(6 answers)
Closed last month.
const [inputText, setInputText] = useState('');
const [searchTerm, setSearchTerm] = useState('');
const [data, setData] = useState(null);
const handleChange = (event) => {
setInputText(event.target.value);
};
const handleSubmit = (event) => {
event.preventDefault();
setSearchTerm(inputText)
console.log(inputText)
setInputText('')
};
useEffect(() => {
if (searchTerm) {
console.log('starting fetch with -> ', `https://imdb-api.com/en/API/SearchTitle/k_ix11kdvq/${searchTerm}`)
fetch(`https://imdb-api.com/en/API/SearchTitle/k_ix11kdvq/${searchTerm}`)
.then(res => {
res.json()
console.log('json data')
})
.then(json => {
setData(json)
console.log('setting data to this value', json)
})
.then(console.log('current data value after setData runs -> ', data))
}
}, [searchTerm])
Wanted to redo a simple HTML webpage I build in my bootcamp within React.
As you can see I have some super professional console logs in here to track just wth is going on with this data.
inception
App.js:36 starting fetch with -> https://imdb-api.com/en/API/SearchTitle/k_ix11kdvq/inception
App.js:45 current data value after setData runs -> null
App.js:40 json data
App.js:44 setting data to this value undefined
index.js:6 data value within body component currently is undefined
installHook.js:1861 data value within body component currently is undefined
index.js:6 data value within body component currently is undefined
installHook.js:1861 data value within body component currently is undefined
App.js:36 starting fetch with -> https://imdb-api.com/en/API/SearchTitle/k_ix11kdvq/inception
App.js:45 current data value after setData runs -> undefined
App.js:40 json data
App.js:44 setting data to this value undefined
Yeeeeeaaah....
It's not working asynchronously at all. Obviously I'm being stupid, but I just can't figure out where.
I'm trying to utilize useEffect to make an API call, wait for the data, then jsonify it, and setData to that value so that I can display it.
I tried using axios and doing an async/await function, no dice.
Please help 😊
You're not returning your res.json(). Since you're not returning anything, the next .then is empty.
useEffect(() => {
if (searchTerm) {
console.log('starting fetch with -> ', `https://imdb-api.com/en/API/SearchTitle/k_ix11kdvq/${searchTerm}`)
fetch(`https://imdb-api.com/en/API/SearchTitle/k_ix11kdvq/${searchTerm}`)
.then(res => {
console.log('json data')
return res.json()
})
.then(json => {
setData(json)
console.log('setting data to this value', json)
})
.then(console.log('current data value after setData runs -> ', data))
}
}, [searchTerm])

React Query clean response function

I have a helper function to clean up the response from a useQuery call to an api.
Here is the function:
export const cleanResponse = function (data) {
let x = [];
let y = [];
data.forEach((item) => {
x.push(item.title);
y.push(item.price);
});
return { titles: x, prices: y };
};
In my main components I'm using useQuery to get the data then applying the above function to clean it up and prepare it for plotting:
const {
data: products,
isLoading,
isError,
} = useQuery(['products'], () => {
return axios('https://dummyjson.com/products/').then((res) => res.data);
});
const cleanData = cleanResponse(products.products);
const titles = cleanData?.titles;
const prices = cleanData?.prices;
Then I'm rendering a simple bar chart passing the data as props:
<BarChart titles={titles} prices={prices} />
I'm getting the following error:
Uncaught TypeError: Cannot read properties of undefined (reading 'products')
What am I missing here? Should my cleanResponse function be async because it's accepting data from an api?
The error is due to initial state you can do as below to prevent error.
cleanResponse(products?.products || [])
while the query is still running, you component renders with data:undefined.
one way to solve this is to check for undefined before you do your transformation. Other approaches are doing the data transformation at a different time, e.g. in the query function after fetching:
const {
data,
isLoading,
isError,
} = useQuery(['products'], () => {
return axios('https://dummyjson.com/products/')
.then((res) => res.data)
.then((data) => cleanResponse(data.products))
});
That way, the cleaned data will be stored in the cache already and data returned from useQuery will be the cleaned data.
Other approaches include using the select option. You can read more about that in my blog post about react-query data transformations.

React stale state in useEffect when using empty dependency array

I'm trying to read state after it's modified within useEffect hook. However since useEffect closes over availability being empty array, it's not updated at all causing infinite loop.
I'm intentionally using empty dependency array since I want this to happen only after
the first render, so including availability as dependency is not an option.
I need some way to read up-to-date value from availability but I don't know how to.
const [ availability, setAvailability ] = useState([])
const updateAvailability = (data, manufacturer) => {
setAvailability(availability => {
const copy = {...availability}
copy[manufacturer] = data
return copy
})
}
const parseAvailability = products => {
products.forEach(product => {
if (!availability[product.manufacturer]) { // availability is stale after updates
updateAvailability({}, product.manufacturer) // notify that data is coming soon
service.getAvailability(product.manufacturer).then(data => { // http request to fetch availability
updateAvailability(data, product.manufacturer)
})
}
})
}
useEffect(() => {
config.categories.forEach(category => {
service.getCategory(category.name).then(data => {
parseAvailability(data, availRef)
})
})
},[])

React update state variable with JSON data

App.js:
function App() {
const [items, setItems] = useState([]);
useEffect(() => {
const searchDB = () => {
fetch("http://127.0.0.1:8443/subColumns/5/?key=fc257229-8f91-4920-b71f-885403114b35", {
mode: 'cors',
credentials: 'include'
})
.then(res => res.json())
.then((json) => {
setItems(json);
})
console.log({items});
}
searchDB();
}, [])
I need to keep the json response in a state varibale because in the future, the API request will nt be hard coded and I expect the user will make multiple API requests without refreshing, and the results will have to be mapped to different components. At the moment, trying to print {items} to the console returns an empty array.
Since setItems is the asynchronous method, you can't get the updated value immediately after setItems. You should use another useEffect with dependency to see the value.
useEffect(() => {
console.log(items);
}, [items]);

Setting a useEffect hook's dependency within`useEffect` without triggering useEffect

Edit: It just occurred to me that there's likely no need to reset the variable within the useEffect hook. In fact, stateTheCausesUseEffectToBeInvoked's actual value is likely inconsequential. It is, for all intents and purposes, simply a way of triggering useEffect.
Let's say I have a functional React component whose state I initialize using the useEffect hook. I make a call to a service. I retrieve some data. I commit that data to state. Cool. Now, let's say I, at a later time, interact with the same service, except that this time, rather than simply retrieving a list of results, I CREATE or DELETE a single result item, thus modifying the entire result set. I now wish to retrieve an updated copy of the list of data I retrieved earlier. At this point, I'd like to again trigger the useEffect hook I used to initialize my component's state, because I want to re-render the list, this time accounting for the newly-created result item.
​
const myComponent = () => {
const [items, setItems] = ([])
useEffect(() => {
const getSomeData = async () => {
try {
const response = await callToSomeService()
setItems(response.data)
setStateThatCausesUseEffectToBeInvoked(false)
} catch (error) {
// Handle error
console.log(error)
}
}
}, [stateThatCausesUseEffectToBeInvoked])
const createNewItem = async () => {
try {
const response = await callToSomeService()
setStateThatCausesUseEffectToBeInvoked(true)
} catch (error) {
// Handle error
console.log(error)
}
}
}
​
I hope the above makes sense.
​
The thing is that I want to reset stateThatCausesUseEffectToBeInvoked to false WITHOUT forcing a re-render. (Currently, I end up calling the service twice--once for win stateThatCausesUseEffectToBeInvoked is set to true then again when it is reset to false within the context of the useEffect hook. This variable exists solely for the purpose of triggering useEffect and sparing me the need to elsewhere make the selfsame service request that I make within useEffect.
​
Does anyone know how this might be accomplished?
There are a few things you could do to achieve a behavior similar to what you described:
Change stateThatCausesUseEffectToBeInvoked to a number
If you change stateThatCausesUseEffectToBeInvoked to a number, you don't need to reset it after use and can just keep incrementing it to trigger the effect.
useEffect(() => {
// ...
}, [stateThatCausesUseEffectToBeInvoked]);
setStateThatCausesUseEffectToBeInvoked(n => n+1); // Trigger useEffect
Add a condition to the useEffect
Instead of actually changing any logic outside, you could just adjust your useEffect-body to only run if stateThatCausesUseEffectToBeInvoked is true.
This will still trigger the useEffect but jump right out and not cause any unnecessary requests or rerenders.
useEffect(() => {
if (stateThatCausesUseEffectToBeInvoked === true) {
// ...
}
}, [stateThatCausesUseEffectToBeInvoked]);
Assuming that 1) by const [items, setItems] = ([]) you mean const [items, setItems] = useState([]), and 2) that you simply want to reflect the latest data after a call to the API:
When the state of the component is updated, it re-renders on it's own. No need for stateThatCausesUseEffectToBeInvoked:
const myComponent = () => {
const [ items, setItems ] = useState( [] )
const getSomeData = async () => {
try {
const response = await callToSomeService1()
// When response (data) is received, state is updated (setItems)
// When state is updated, the component re-renders on its own
setItems( response.data )
} catch ( error ) {
console.log( error )
}
}
useEffect( () => {
// Call the GET function once ititially, to populate the state (items)
getSomeData()
// use [] to run this only on component mount (initially)
}, [] )
const createNewItem = async () => {
try {
const response = await callToSomeService2()
// Call the POST function to create the item
// When response is received (e.g. is OK), call the GET function
// to ask for all items again.
getSomeData()
} catch ( error ) {
console.log( error )
}
} }
However, instead of getting all items after every action, you could change your array locally, so if the create (POST) response.data is the newly created item, you can add it to items (create a new array that includes it).

Categories