How do I map data from state to component prop? - javascript

To start with I'm a beginner. Any help would be appreciated.
So I'm getting my data from mongoDB atlas using node+express API. I'm successfull at getting the array to show up in console log using following code.
const [product, setProduct] = useState();
const url = "http://localhost:5000/api/items";
useEffect(() => {
axios.get(url).then((res) => {
setProduct(res.data);
// setProduct(
// JSON.stringify({
// title: setProduct.title,
// price: setProduct.price,
// image: setProduct.image,
// details: setProduct.details,
// })
// );
})
}, [url])
console.log(product)
The console log displays the array properly as collection named 'items' with content of arrays. As you can see I tried to stringify the response as the response returns JSON but again I didn't know how to map Following is the code where I tried to map the contents like id, name etc as props to component.
<div>
{product.map((product) => {
<Product name={product.title} />
})}
</div>
When I do this I get error that the map is not a function. I don't know what I'm doing wrong here. I know I'm supposed to use redux or reducer/context here but I want to get this to work before updating it with those.
[![Response from res.data][1]][1]
[1]: https://i.stack.imgur.com/auxvl.png

you didnt get yours products.
As we can see from screenshot
res.data equal to object with one property items:
res.data= {items: []}
and we need to take/access to these items
use this: setProducts(res?.data?.items || [])
const [products, setProducts] = useState();
useEffect(() => {
axios.get(url).then((res) => {
setProducts(res?.data?.items || []);
})
}, [url])
<div>
{products?.map((product) => {
<Product name={product.title} />
})}
</div>

On the first render the value for types will be undefined ( on sync code execution ), try using it as
<div>
{product?.map((product) => {
<Product name={product.name} />
})}
</div>
? will make sure to run map once value for types is there ( also make sure it is mappable ( is an array ))

Related

Loop through const in React JSX return [duplicate]

This is based on exercise 2.14 of this course https://fullstackopen.com/en/part2/getting_data_from_server.
The user can select a country, then the weather information for that country's capital will be dislpayed. My code gives me error Cannot read property 'temperature' of undefined
const Weather = ({ city }) => {
const [weatherDetails, setWeatherDetails] = useState([])
useEffect(() => {
axios.get('http://api.weatherstack.com/current', {
params: {
access_key: process.env.REACT_APP_WEATHER_KEY,
query: city
}
}).then(
(response) => {
setWeatherDetails(response.data)
}
)
}, [city])
console.log('weather', weatherDetails);
return (
<div>
<h3>Weather in {city} </h3>
{weatherDetails.current.temperature}
</div>
)}
Basically, the line
{weatherDetails.current.temperature}
makes my code crash. When I do remove that line, I am able to see the response thanks to the console.log, but there are two consecutive logs
weather []
weather {request: {…}, location: {…}, current: {…}}
I figured that my code happens in between these two, and it tries to access the data before it has even arrived, but I don't know what to do to fix this.
Also, I don't know what the argument [city] of useEffect() does, so it'd be great if someone can explain to me what it does.
Edit: Solved!
Set weatherDetail's initial state to null and did some conditional rendering
if (weatherDetails) {
return (
<div>
<h3>Weather in {capital}</h3>
{weatherDetails.current.temperature} Celsius
</div>
)
} else {
return (
<div>
Loading Weather...
</div>
)
}
weatherDetails is an empty array, initially, so there is no current property to read from.
Use some conditional rendering. Use initial null state and then check that it is truthy to access the rest of the object when it is updated.
const Weather = ({ city }) => {
const [weatherDetails, setWeatherDetails] = useState(null) // <-- use null initial state
useEffect(() => {
axios.get('http://api.weatherstack.com/current', {
params: {
access_key: process.env.REACT_APP_WEATHER_KEY,
query: city
}
}).then(
(response) => {
setWeatherDetails(response.data)
}
)
}, [city])
console.log('weather', weatherDetails);
return (
<div>
<h3>Weather in {capital} </h3>
{weatherDetails && weatherDetails.current.temperature} // check that weatherDetails exists before accessing properties.
</div>
)}
What does the argument [city] of useEffect do?
This is the hook's dependency array. Hooks run on each render cycle, and if any values in the dependency array have updated it triggers the hook's callback, in this case, the effect to get weather data when the city prop updates.
useEffect
By default, effects run after every completed render, but you can
choose to fire them only when certain values have changed.

React | Adding and deleting object in React Hooks (useState)

How to push element inside useState array AND deleting said object in a dynamic matter using React hooks (useState)?
I'm most likely not googling this issue correctly, but after a lot of research I haven't figured out the issue here, so bare with me on this one.
The situation:
I have a wrapper JSX component which holds my React hook (useState). In this WrapperComponent I have the array state which holds the objects I loop over and generate the child components in the JSX code. I pass down my onChangeUpHandler which gets called every time I want to delete a child component from the array.
Wrapper component:
export const WrapperComponent = ({ component }) => {
// ID for component
const { odmParameter } = component;
const [wrappedComponentsArray, setWrappedComponentsArray] = useState([]);
const deleteChildComponent = (uuid) => {
// Logs to array "before" itsself
console.log(wrappedComponentsArray);
/*
Output: [{"uuid":"acc0d4c-165c-7d70-f8e-d745dd361b5"},
{"uuid":"0ed3cc3-7cd-c647-25db-36ed78b5cbd8"]
*/
setWrappedComponentsArray(prevState => prevState.filter(item => item !== uuid));
// After
console.log(wrappedComponentsArray);
/*
Output: [{"uuid":"acc0d4c-165c-7d70-f8e-d745dd361b5",{"uuid":"0ed3cc3-
7cd-c647-25db-36ed78b5cbd8"]
*/
};
const onChangeUpHandler = (event) => {
const { value } = event;
const { uuid } = event;
switch (value) {
case 'delete':
// This method gets hit
deleteChildComponent(uuid);
break;
default:
break;
}
};
const addOnClick = () => {
const objToAdd = {
// Generate uuid for each component
uuid: uuid(),
onChangeOut: onChangeUpHandler,
};
setWrappedComponentsArray(wrappedComponentsArray => [...wrappedComponentsArray, objToAdd]);
// Have also tried this solution with no success
// setWrappedComponentsArray(wrappedComponentsArray.concat(objToAdd));
};
return (
<>
<div className='page-content'>
{/*Loop over useState array*/}
{
wrappedComponentsArray.length > 0 &&
<div>
{wrappedComponentsArray.map((props) => {
return <div className={'page-item'}>
<ChildComponent {...props} />
</div>;
})
}
</div>
}
{/*Add component btn*/}
{wrappedComponentsArray.length > 0 &&
<div className='page-button-container'>
<ButtonContainer
variant={'secondary'}
label={'Add new component'}
onClick={() => addOnClick()}
/>
</div>
}
</div>
</>
);
};
Child component:
export const ChildComponent = ({ uuid, onChangeOut }) => {
return (
<>
<div className={'row-box-item-wrapper'}>
<div className='row-box-item-input-container row-box-item-header'>
<Button
props={
type: 'delete',
info: 'Deletes the child component',
value: 'Delete',
uuid: uuid,
callback: onChangeOut
}
/>
</div>
<div>
{/* Displays generated uuid in the UI */}
{uuid}
</div>
</div>
</>
)
}
As you can see in my UI my adding logic works as expected (code not showing that the first element in the UI are not showing the delete button):
Here is my problem though:
Say I hit the add button on my WrapperComponent three times and adds three objects in my wrappedComponentsArray gets rendered in the UI via my mapping in the JSX in the WrapperComponent.
Then I hit the delete button on the third component and hit the deleteChildComponent() funtion in my parent component, where I console.log my wrappedComponentsArray from my useState.
The problem then occurs because I get this log:
(2) [{…}, {…}]
even though I know the array has three elements in it, and does not contain the third (and therefore get an undefined, when I try to filter it out, via the UUID key.
How do I solve this issue? Hope my code and explanation makes sense, and sorry if this question has already been posted, which I suspect it has.
You provided bad filter inside deleteChildComponent, rewrite to this:
setWrappedComponentsArray(prevState => prevState.filter(item => item.uuid !== uuid));
You did item !== uuid, instead of item.uuid !== uuid
Please try this, i hope this works
const deleteChildComponent = (uuid) => {
console.log(wrappedComponentsArray);
setWrappedComponentsArray(wrappedComponentsArray.filter(item => item !== uuid));
};
After update
const deleteChildComponent = (uuid) => {
console.log(wrappedComponentsArray);
setWrappedComponentsArray(wrappedComponentsArray.filter(item => item.uuid !== uuid)); // item replaced to item.uuid
};
Huge shoutout to #Jay Vaghasiya for the help.
Thanks to his expertise we managed to find the solution.
First of, I wasn't passing the uuid reference properly. The correct was, when making the objects, and pushing them to the array, we passed the uuid like this:
const addOnClick = () => {
const objToAdd = {
// Generate uuid for each component
uuid: uuid(),
parentOdmParameter: odmParameter,
onChangeOut: function(el) { onChangeUpHandler(el, this.uuid)}
};
setWrappedComponentsArray([...wrappedComponentsArray, objToAdd]);
};
When calling to delete function the function that worked for us, was the following:
const deleteChildComponent = (uuid) => {
setWrappedComponentsArray(item => item.filter(__item => __item.uuid !== uuid)); // item replaced to item.uuid
};

Can't update array state using information from another state

I have two state objects. One is personnel, an array of 1-object arrays like this: [[{}],[{}],[{}],[{}],...]. Another is rowItems which I am trying to fill by pulling out all the objects from the inner arrays of the big personnelarray.
My end goal is to use the rowItems to create a material-ui data grid. Right now, the data grid is empty and not rendering any data, but shows the correct number of personnel items I expect (253) in the pagination display, which is weird.
Here's my code:
const [personnel, setPersonnel] = useState([]);
const [rowItems, setRowItems] = useState([]);
const handleCallback = (data) => {
setPersonnel((prevData) => [...prevData, data]);
};
useEffect (() => {
console.log("personnel:", personnel) // I see all 253 arrays printed
setRowItems((rowItems => [...rowItems, {id: '59686', first_name: 'vbn',}])) // This was for testing only, somehow hardcoding this works
personnel?.map((row) => {
console.log("row", row[0]); // I see the item being printed
setRowItems(rowItems => [...rowItems, row[0]]);
console.log("row items", rowItems) // this is empty. WHYYYY
})
}, [personnel])
return (
<div> // This is where I get personnel items and pass to callback
{props.personnel.edges.map(({ node }) => {
return (
<Personnel
key={node.__id}
personnel={node}
parentCallback={handleCallback}
/>
);
})}
</div>
<DataGrid
columns={cols}
rows={rowItems}
pageSize={12}
/>
)
I took jsN00b's suggestion and tried to move the setRowItems() outside of the map function like so:
useEffect(() => setRowItems(prev => ([ ...prev, ...personnel?.map(row => ({...row[0]}))])), [personnel]);
and it worked! Thanks a million!

Saving api response to State using useState and Axios (React JS)

I'm having an issue when trying to save to State an axios API call. I've tried
useState set method not reflecting change immediately 's answer and many other and I can't get the state saved. This is not a duplicate, because I've tried what the accepted answer is and the one below and it still doesn't work.
Here's the (rather simple) component. Any help will be appreciated
export const Home = () => {
const [widgets, setWidgets] = useState([]);
useEffect(() => {
axios
.get('/call-to-api')
.then((response) => {
const data = response.data;
console.log(data); // returns correctly filled array
setWidgets(widgets, data);
console.log(widgets); // returns '[]'
});
}, []); // If I set 'widgets' here, my endpoint gets spammed
return (
<Fragment>
{/* {widgets.map((widget) => { // commented because it fails
<div>{widget.name}</div>;
})} */}
</Fragment>
);
};
Welcome to stackoverflow, first thing first the setting call is incorrect you must use spread operator to combine to array into one so change it to setWidgets([...widgets, ...data]); would be correct (I assume both widgets and data are Array)
second, react state won't change synchronously
.then((response) => {
const data = response.data;
console.log(data); // returns correctly filled array
setWidgets(widgets, data);
console.log(widgets); // <--- this will output the old state since the setWidgets above won't do it's work till the next re-render
so in order to listen to the state change you must use useEffect hook
useEffect(() => {
console.log("Changed Widgets: ", widgets)
}, [widgets])
this will console log anytime widget changes
the complete code will look like this
export const Home = () => {
const [widgets, setWidgets] = useState([]);
useEffect(() => {
axios
.get('/call-to-api')
.then((response) => {
const data = response.data;
setWidgets([...widgets, ...data])
});
}, []);
useEffect(() => {
console.log("Changed Widgets: ", widgets)
}, [widgets])
return (
<Fragment>
{/* {widgets.map((widget) => { // commented because it fails
<div>{widget.name}</div>;
})} */}
</Fragment>
);
};
Try:
setWidgets(data);
istead of
setWidgets(widgets, data);
Your widgets.map() probably fails because there isn't much to map over when the component is being rendered.
You should update it with a conditional like so, just for clarity:
widgets.length>0 ? widgets.map(...) : <div>No results</div>
And your call to setWidgets() should only take one argument, the data:
setWidgets(data)
or if you want to merge the arrays use a spread operator (but then you need to add widgets as the dependency to the useEffect dependency array.
setWidgets(...widgets, ...data)
You might also have to supply the setWidgets hook function to the useEffect dependency array.
Let me know if this helps..

Pulling information from OpenWeatherMap API when state (country) changes

Currently, I am only able to pull information when the name of the country is already loaded, I add the code, save it, then it's updated on localhost. But when I type in another country I get the error of Uncaught TypeError: Cannot read properties of undefined (reading 'temp'). For example, my code {weather.main.temp} works but only when the state = "france" is already set before.
My sub component:
const WeatherInfo = ({ country }) => {
const [weather, setWeather] = useState({});
useEffect(() => {
axios
.get(`https://api.openweathermap.org/data/2.5/weather?q=${country.capital}&appid=${api_key}&units=imperial`)
.then(response => {
console.log(response.data)
setWeather(response.data)
})
}, [country])
return (
<div>
<h3>Weather in {country.capital}</h3>
<p><b>Temperature</b>{weather.main.temp}</p>
</div>
)
}
weather is initialized as an empty object when WeatherInfo is created. Therefore weather.main is undefined and you get the error when you try to get the attribute temp from undefined.
You can resolve this by having some conditional chaining or some ternary operators depending on what you want displayed when weather is an empty object.
return (
<div>
<h3>Weather in {country.capital}</h3>
<p><b>Temperature</b>{weather.main?.temp}</p>
</div>
)

Categories

Resources