I'm trying to use an array returned from Node in a React code.
Here's the Node's router code:
router.get('/prodlist', async (req, res) => {
try {
await Product.find({}).exec((err, result) => {
res.status(200).send({ express: result })
})
} catch (e) {
console.log(e)
res.status(400).send(e)
}
})
The React's component code:
import React, { useState, useEffect } from 'react';
function App() {
const [datas, setDatas] = useState(null)
useEffect(() => {
var curURL = window.location.href
var calledRoute = curURL.substr(8).substr(curURL.substr(8).indexOf("/"))
callBackendAPI(calledRoute)
.then(res => setDatas(prevData => res.express))
.catch(err => console.log(err));
}, [])
async function callBackendAPI(routePath) {
const response = await fetch(routePath);
const body = await response.json();
if (response.status !== 200) {
throw Error(body.message)
}
console.log(body.express)
return body;
}
return (
<div className="App">
{{datas}}
</div>
)
}
export default App;
And the array content (seen from Chrome's developer tools):
(3) [{…}, {…}, {…}]
0:
prodDesc: "Prod1"
prodID: "1"
prodImage: "prod1.jpg"
prodPrice: 350
prodRebate: 100
prodReviews: []
prodStock: 31
prodThumb: "prod1.jpg"
__v: 0
_id: "5eb04a6439eec26af4981541"
__proto__: Object
1: {prodID: "2", prodDesc: "Prod2", prodThumb: "prod2.jpg", prodImage: "prod2.jpg", prodPrice: 1500.79, …}
2: {prodID: "3", prodDesc: "Prod3", prodThumb: "prod3.jpg", prodImage: "prod3.jpg", prodPrice: 280.79, …}
length: 3
__proto__: Array(0)
When I execute this code I get the error: Objects are not valid as a React child
So I tried to map data's content with a line like this:
{datas.map((item) => <p>{item}</p>)}
Executing this code with a map I get the error : Cannot read property 'map' of null.
Although datas contains an array, it seems React sees it as null.
Anybody understands this issue?
EDIT: I didn't realized that this question is about two different issues. Norbitrial's answer fixed the problem about synchronicity but I still get the error "Objects are not valid as a React child [...] use an array instead".
EDIT2: Solved the issue by forwarding the mapping to a REACT component with the following line:
{datas && datas.map(item => <ShopCart key={item.prodID} item={item} />)}
The problem is your backend API returns the result asynchronously so it might take few seconds while your state value is null.
The solution can be checking for null value first then using the .map() on your state called datas with && in your code.
Try as the following:
{datas && datas.map((item) => <p>{item}</p>)}
I hope this helps!
The code runs before datas is populated from your api, you need to first check to make sure it is populated like this.
{datas && datas.length && datas.map((item) => <p>{item}</p>)}
The problem is that your component gets mounted before you receive the data.
What I would do(and most people) is to create a loading component that will show a spinner or something like that when the array is empty and after it gets filled it should render the component with the data.
But a quick solution to this is to do a conditional render
{datas.length && datas.map((item) => <p>{item}</p>)}
Related
Hi am trying to create a simple multi file component, But the files state is not behaving as expected.
The FileUpload component works fine and when the user chooses a file and it starts uploading the onStart prop method is called. And then when it finishes successfully the 'onFinish' prop method is called. This code seems fine to the point of consoling the object in the onfinish method. it consoles an old value of the object before it was modified by the onStart Method. I expected the file object value in the console to include the buffer key since it was added when the onStart method was called but it's not there.
Example initial state files should be [] when the use effect is called on the state files should be updated to [{_id:"example_unique_id"}] then a button for upload will appear and when user chooses a file and onStart modifies the object and the state should be updated to [{_id:"example_unique_id", buffer:{}] and finally when it finishes files should be [{_id:"example_unique_id", buffer:{}] but instead here it returns [{_id:"example_unique_id"}].
What could I be missing out on?
Also, I have React Dev tools installed and it seems the state is updated well in the dev tools.
import React, { useState } from 'react'
import { useEffect } from 'react';
import unique_id from 'uniqid'
import FileUpload from "./../../components/FileUpload";
const InlineFileUpload = ({ onFilesChange }) => {
const [files, setFiles] = useState([]);
function onFinish(file, id) {
const old_object = files.filter((file) => file._id == id)[0];
console.log("old object on after upload", old_object);
}
const addFile = (file, id) => {
const old_object = files.filter((file) => file._id == id)[0];
const index = files.indexOf(old_object);
const new_files = [...files];
new_files.splice(index, 1, { ...old_object, buffer: file });
setFiles(new_files);
};
useEffect(() => {
const new_attachments = files.filter(({ buffer }) => buffer == undefined);
if (new_attachments.length == 0) {
setFiles([...files, { _id: unique_id() }]);
}
const links = files.filter((file) => file.file !== undefined);
if (links.length !== 0) {
onFilesChange(links);
}
}, [files]);
return (
<>
{files.map((file) => {
const { _id } = file;
return ( <FileUpload
key={_id}
id={_id}
onStart={(e) => addFile(e, _id)}
onFinish={(e) => onFinish(e, _id)}
/>
);
})}
</>
);
};
export default InlineFileUpload
I think the problem is caused by the fact that your this code is not updating the state:
const addFile = (file, id) => {
const old_object = files.filter((file) => file._id == id)[0];
const index = files.indexOf(old_object);
const new_files = [...files];
new_files.splice(index, 1, { ...old_object, buffer: file });
setFiles(new_files);
}
files looks like an array of objects.
Spread operator will not do a deep copy of this array. There are a lot of examples on the internet, here is one.
let newArr = [{a : 1, b : 2},
{x : 1, y : 2},
{p: 1, q: 2}];
let arr = [...newArr];
arr[0]['a'] = 22;
console.log(arr);
console.log(newArr);
So your new_files is the same array. Splice must be making some modifications but that is in place. So when you are doing this setFiles(new_files);, you are basically setting the same reference of object as your newState. React will not detect a change, and nothing gets updated.
You have the option to implement a deep copy method for your specific code or use lodash cloneDeep.
Looking at your code, this might work for you : const new_files = JSON.parse(JSON.stringify(files)). It is a little slow, and you might lose out on properties which have values such as functions or symbols. Read
The reason you are getting the old log is because of closures.
When you do setFiles(new_files) inside addFiles function. React updates the state asynchronously, but the new state is available on next render.
The onFinish function that will be called is still from the first render, referencing files of the that render. The new render has the reference to the updated files, so next time when you log again, you will be getting the correct value.
If it's just about logging, wrap it in a useEffect hook,
useEffect(() => {
console.log(files)
}, [files);
If it's about using it in the onFinish handler, there are answers which explore these option.
Working on a project here and ran into an issue. I haven't had this problem before but now I do for some reason.
So I am making an GET request to ASOS API, but it is acting very strange. Some of these, such as name is received upon page refresh, but mainly the other things like information about the brand becomes undefined. Now, the brand is in another object inside of the API. But I have had other things at other parts of the page before that were also in objects. But I did not have any issue there.
Here is how the API call looks like:
And here is my code for the API fetch:
const FetchAPI = (props) => {
const [product, setProduct] = useState({});
const [brand, setBrand] = useState({});
const [price, setPrice] = useState({});
const [params, setParams] = useState({
id: "23363645",
lang: "en-US",
store: "US",
sizeSchema: "US",
currency: "USD",
});
useEffect(() => {
const options = {
method: "GET",
url: "https://asos2.p.rapidapi.com/products/v3/detail",
params: params,
headers: {
"x-rapidapi-key": "",
"x-rapidapi-host": "",
},
};
axios
.request(options)
.then(function (response) {
console.log(response.data);
setProduct(response.data);
setBrand(response.data.brand);
setPrice(response.data.price.current);
})
.catch(function (error) {
console.error(error);
});
}, []);
return (
<div>
<Product
name={product.name}
price={price.text}
brand={brand.description.replace( /(<([^>]+)>)/ig, '')}
/>
</div>
);
};
I am sending the data over to Product which is the product page of the item I am requesting. But whenever I refresh the page, I get TypeError: Cannot read property 'replace' of undefined. I did remove replace, and it worked fine. And if I placed replace back into the brand.description and saved, still worked fine. But on the page refresh, it crashes.
Is it perhaps trying to load my return before the useEffect? If so, how do I solve this?
I think the issue here is that while data from API is being fetched, brand.description is undefined and there is no replace method on undefined. You can either do this - >
<div>
<Product
name={product.name}
price={price.text}
brand={brand.description ? brand.description.replace( /(<([^>]+)>)/ig, '') : ""}
/>
</div>
or
const [brand, setBrand] = useState({ description: ""});
and keep the remaining code same.
First of all, unrelated to your question, you have many superfluous state variables. You have product which stores all the data of the product and then price and brand which stores subsets of the same data.
Consider using only the product state variable, or if you want to keep the names do something like
const price = product.price.current;
Second, your default value for brand is an empty object, meaning brand.description is undefined.
You can solve this with optional chaining like so:
<Product
name={product?.name}
price={price?.text}
brand={brand?.description?.replace( /(<([^>]+)>)/ig, '')}
/>
The useEffect hook gets called once the component is rendered so in this case initially when your API is not called your brand.description will be undefined and when you are trying to use replace on undefined the error is coming. So you can always add a check using optional chaining(?.) so even if we don't get the description in the brand it will not break the website and you should also use a loader till the API call is through.
<div>
<Product
name={product.name}
price={price.text}
brand={brand.description?.replace( /(<([^>]+)>)/ig, '')}
/>
</div>
useEffect gets called only after the component is rendered . So when the component is rendered for the first time you have the state brand as an empty object . So what you are trying to do is {}.description -> . This is undefined .
This is the reason why its a good practice to always have a loading state when the component is making an api call.
const [ loading, setLoading ] = useState(true);
const getProductDetails = async() => {
const options = {
method: "GET",
url: "https://asos2.p.rapidapi.com/products/v3/detail",
params: params,
headers: {
"x-rapidapi-key": "",
"x-rapidapi-host": "",
},
};
try {
const { data } = await axios.request(options);
setProduct(data);
setBrand(data.brand);
setPrice(data.price.current);
}
catch(error){
console.log(error)
} finally {
setLoading(false)
}
}
useEffect(() => {
getProductDetails();
}, []);
if(loading)
return <p> Loading ... </p>
return (
// return your JSX here
)
My problem is that item stored in object (in an array of objects inside a state hook) is not being rendered on page, but it gets printed with console.log
I fetched some data from the server and it worked as expected, returning an array of two items, one of which is an object containing blog data(blog heading, creator, etc) an another is an array of 'sections' of that blog. Here is how I did it,
This is the initialization
// Get the blog id
const {blog_id} = useParams();
// Declaring our state hooks
const initial_blog_state = {
blog_id: blog_id,
heading: '',
creator: {},
created: '',
section: [],
}
const [blog_state, updateBlogState] = useState(initial_blog_state);
Here is the fetching of data from the server
useEffect(() => {
// Fetching data
Promise.all([
fetch(`http://127.0.0.1:8000/api/blog/${blog_id}`),
fetch(`http://127.0.0.1:8000/api/section/full/${blog_id}`)
]).then(responses => {
// Get a JSON object from each of the responses
return Promise.all(responses.map(response => {
return response.json()
}))
}).then(function (data) {
// Log the data to the console
console.log(data);
// Update state
updateBlogState({
...blog_state,
heading: data[0].heading,
creator: data[0].creator,
created: data[0].created,
section: data[1]
})
}).catch(function (error) {
// if there's an error, log it
console.log(error);
});
}, []);
I think the way I'm updating the section inside the hook can be a problem(although I'm not sure how), because I saw in a stackoverflow answer that objects must always be initialized (which I'm not doing when declaring an array of 'objects')
And here is the thing that needs to be rendered
return (
<div className="container">
<h1>{blog_state.heading}</h1>
<p className="text-small text-muted">{blog_state.creator.username}</p>
{blog_state.section.map(item => {
{console.log(item.paragraph)}
<p>{item.paragraph}</p>
})}
</div>
)
Here blog_state.heaing and blog_state.creator.username are being rendered as desired and also console.log(item.paragraph) prints the correct paragraph on the console window, but item.paragraph doesn't show anything on the page.
Nothing is being returned from your map.
i.e you need to add a return line before the <p>{item.paragraph}</p>
So:
{blog_state.section.map(item => {
console.log(item.paragraph)
return <p>{item.paragraph}</p>
})}
or as an inline return:
{blog_state.section.map(item => <p>{item.paragraph}</p>)}
I have a javascript object and I want to map through one of its properties which is an array. But for some reason when I map through it or even call .length on the property which I know is an array it treats the property as if it's not an array.
Object is being created from an axios get request in an asynchronous object.
useEffect(() => {
async function fetchData() {
const dummyData = await axios.get(
'http://localhost:3000/data',
);
dummyData.data.videos.map(video => {
return (
setCurrentEdition(video.currentEdition[0])
);
});
}
fetchArtPiece();
return () => console.log('clean artPiece detail page');
}, []);
currentEdition: {
videoAssociated: {id: "oYu5J4TQbJ728c45ExWopZicY9LrCxNNTEZ3"}
bids: []
id: "Kr3fjeo51"
owners: (2) [{…}, {…}]
}
All im trying to do for now is .map() and it throws an undefined error
currentEdition.owners.map(owners => console.log(owners))
even when i check currentEdition.owners.length it throws an undefined error and when I log currentEdition.owners the proto: is an array and it has the square brackets and everything so why is this not being treated as an array when I try to operate on it?
Thank You
Well, since you are working with asynchronous data you should always make sure that you have your data and then start playing with it around.
So to fix this you need to make a condition for your JSX like this:
<Grid item xs={7}> {Array.isArray(currentEdition?.owners) && console.log(currentEdition.owners.length)} </Grid>
I get an object from a Symfony rest API. This object has a property "shooting" which is an array and this array is full when I console.log it but when i try to access it, it is empty
This is my fetch request
const getProjectsAvailable = async () => {
const data = await fetch(
`${process.env.GATSBY_CORE_URI}/api/dashboard/supplier/projects/available`,
{
headers: {
[`X-Auth-Token`]: `${token}`,
[`Accept`]: `application/json`,
},
}
);
return data;
};
Here is the project object that i get back from fetch request
0: {id: 258, name: "Project26-1", reference: "A6568", isOfferValidated: null, source: "dashboard", …}
It has a shooting key which contains an array and it is not empty
shootings: Array(1)
0:
addressCity: "Paris"
addressCountry: {id: 76}
But when i set this object to my component state, all values stay the same except the shooting key which becomes an empty array
const [projects, setProjects] = useState([]);
useEffect(() => {
getProjectsAvailable().then(res =>
res.json().then(data => {
setProjects(data);
})
);
}, []);
I have no idea why does it act like that.
Thanks in advance
EDIT :
For example, the first line with console.log gives me the response object with a full shooting array while the second one sets it to my state but shooting array is empty
useEffect(() => {
getProjectsAvailable().then(response => console.log(response));
getProjectsAvailable().then(response => setProjects(response));
}, []);
Ok it is my bad. Somewhere else in the code, there was a .split() on the shooting property which mutates the array so the props changed and shooting array got empty