React hook renders [object object] - reactjs

I am new in React and I am not sure what I do wrong. I am trying to use useEffect and save list in useState. But I am getting [object object] back.
I am building simple weather app and I managed to save 1 result into useState, but now I wanna have 3 constant cities showing weather on page load, not depending on what user enteres. Here is the code
const [query, setQuery] = useState('');
const [weather, setWeather] = useState({});
const [weatherConst, setWeatherConst] = useState([]); <-- not working
useEffect(() => {
fetch(`${api.base}group?id=3413829,6618983,2759794&units=metric&APPID=${api.key}`)
.then(res => res.json())
.then(result => {
setWeatherConst(result)
console.log("new list" + result)})
}, []) <-- not working
function apiCall() {
fetch(`${api.base}weather?q=${query}&unit=metric&APPID=${api.key}`)
.then(res => res.json())
.then(result => {
setWeather(result)
setQuery('')
console.log(result)
})
}
When I console log "new list" i get [object object], but when I run link by itself in browser I get list of 3 cities back
Image of result getting back

Here is a quick snippet illustrating the sequence.
We set the initial state to [] as you have.
On the first render we check state.length and because our initial array is empty it renders <h2>Loading...</h2>.
The useEffect runs and waits 1 second before callingsetState([...loadedState]) which triggers a render (the useEffect cleanup runs here and clears the timer).
We check state.length again and now because our array is no longer empty we render state[0].name, state[1].name, state[2].name. (For a known index or limited number of indexes this is ok, but you'll usually want to use state.map())
<script src="https://unpkg.com/react#16/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.26.0/babel.min.js"></script>
<script type="text/babel">
const { useState, useEffect } = React
const loadedState = [{name: 'foo'},{name: 'bar'},{name: 'baz'}]
function App() {
const [state, setState] = useState([])
useEffect(() => {
const timer = setTimeout(() => {
setState([...loadedState]);
}, 1000);
return () => clearTimeout(timer);
},[])
return (
<div className="container">
{state.length ?
(<div>
<p>{state[0].name}</p>
<p>{state[1].name}</p>
<p>{state[2].name}</p>
</div>) : (<h2>Loading...</h2>)
}
</div>
);
}
ReactDOM.render(<App />, document.getElementById('root'));
</script>
<div id="root"></div>
Note
The answer to your actual question was given in the comments by jonrsharpe.
"new list" + result is string concatenation, which will implicitly call result.toString(), which gives "[object Object]". Use console.log("new list", result) instead, or if you want to make a more readable string form try e.g. JSON.stringify(result, null, 2).

If you would like another example of explanation:
I would like to explain by simple words. When we use "useEffect" or "setSmth (created by useState hook)" hook we need to understand that this is not a simple function which we just call. This is some kind of async function which calls when it needs.
So, for example :
useEffect(() => {
axios
.get(
"https://api.oceandrivers.com:443/v1.0/getForecastPoints/cnarenal/language/en"
)
.then((res) => res.data.data)
.then((res) => {
setWeather(res);
console.log(weather);
})
.catch((e) => {
console.table(e);
});
}, []);
We can not see results in "console.log(weather)" , because setWeather(res) didn't finish work. We see result only after render of component. So, if we create a list:
let weatherList = weather.map((el) => {
return <li key={el.name}> {el.name} </li>;
});
return (
<div className="App">
<ul>{weatherList}</ul>
</div>
);
we'll see all information that we need.
If you would like to see all code: https://codesandbox.io/s/wonderful-feynman-n0h39?file=/src/App.js:503-677
Sorry for my English. If I said smth wrong tell me please, I'll be very appreciated!

Related

React useState overwritten even with spread

So I have a component where I have to make an API call to get some data that has IDs that I use for another async API call. My issue is I can't get the async API call to work correctly with updating the state via spread (...) so that the checks in the render can be made for displaying specific stages related to specific content.
FYI: Project is a Headless Drupal/React.
import WidgetButtonMenu from '../WidgetButtonMenu.jsx';
import { WidgetButtonType } from '../../Types/WidgetButtons.tsx';
import { getAllInitaitives, getInitiativeTaxonomyTerm } from '../../API/Initiatives.jsx';
import { useEffect } from 'react';
import { useState } from 'react';
import { stripHTML } from '../../Utilities/CommonCalls.jsx';
import '../../../CSS/Widgets/WidgetInitiativeOverview.css';
import iconAdd from '../../../Icons/Interaction/icon-add.svg';
function WidgetInitiativeOverview(props) {
const [initiatives, setInitiatives] = useState([]);
const [initiativesStages, setInitiativesStage] = useState([]);
// Get all initiatives and data
useEffect(() => {
const stages = [];
const asyncFn = async (initData) => {
await Promise.all(initData.map((initiative, index) => {
getInitiativeTaxonomyTerm(initiative.field_initiative_stage[0].target_id).then((data) => {
stages.push({
initiativeID: initiative.nid[0].value,
stageName: data.name[0].value
});
});
}));
return stages;
}
// Call data
getAllInitaitives().then((data) => {
setInitiatives(data);
asyncFn(data).then((returnStages) => {
setInitiativesStage(returnStages);
})
});
}, []);
useEffect(() => {
console.log('State of stages: ', initiativesStages);
}, [initiativesStages]);
return (
<>
<div className='widget-initiative-overview-container'>
<WidgetButtonMenu type={ WidgetButtonType.DotsMenu } />
{ initiatives.map((initiative, index) => {
return (
<div className='initiative-container' key={ index }>
<div className='top-bar'>
<div className='initiative-stage'>
{ initiativesStages.map((stage, stageIndex) => {
if (stage.initiativeID === initiative.nid[0].value) {
return stage.stageName;
}
}) }
</div>
<button className='btn-add-contributors'><img src={ iconAdd } alt='Add icon.' /></button>
</div>
<div className='initiative-title'>{ initiative.title[0].value } - NID ({ initiative.nid[0].value })</div>
<div className='initiative-description'>{ stripHTML(initiative.field_initiative_description[0].processed) }</div>
</div>
);
}) }
</div>
</>
);
}
export default WidgetInitiativeOverview;
Here's a link for video visualization: https://vimeo.com/743753924. In the video you can see that on page refresh, there is not data within the state but if I modify the code (like putting in a space) and saving it, data populates for half a second and updates correctly within the component.
I've tried using spread to make sure that the state isn't mutated but I'm still learning the ins and outs of React.
The initiatives state works fine but then again that's just 1 API call and then setting the data. The initiativeStages state can use X amount of API calls depending on the amount of initiatives are returned during the first API call.
I don't think the API calls are necessary for this question but I can give reference to them if needed. Again, I think it's just the issue with updating the state.
the function you pass to initData.map() does not return anything, so your await Promise.all() is waiting for an array of Promise.resolve(undefined) to resolve, which happens basically instantly, certainly long before your requests have finished and you had a chance to call stages.push({ ... });
That's why you setInitiativesStage([]) an empty array.
And what you do with const stages = []; and the stages.push() inside of the .then() is an antipattern, because it produces broken code like yours.
that's how I'd write that effect:
useEffect(() => {
// makes the request for a single initiative and transforms the result.
const getInitiative = initiative => getInitiativeTaxonomyTerm(
initiative.field_initiative_stage[0].target_id
).then(data => ({
initiativeID: initiative.nid[0].value,
stageName: data.name[0].value
}))
// Call data
getAllInitaitives()
.then((initiatives) => {
setInitiatives(initiatives);
Promise.all(initiatives.map(getInitiative))
.then(setInitiativesStage);
});
}, []);
this code still has a flaw (imo.) it first updates setInitiatives, then starts to make the API calls for the initiaives themselves, before also updating setInitiativesStage. So there is a (short) period of time when these two states are out of sync. You might want to delay setInitiatives(initiatives); until the other API requests have finished.
getAllInitaitives()
.then(async (initiatives) => {
const initiativesStages = await Promise.all(initiatives.map(getInitiative));
setInitiatives(initiatives);
setInitiativesStage(initiativesStages)
});

Infinite Loop from Promise and useState Hook

I've managed to accidentally create an infinite loop out of a fetch promise and a useState hook in react. Can anyone point out why this is repeating? My grasp of both pieces is a little weak, and I imagine that a clear explanation with correctly functioning code would help me see how both work.
FYI - the loreReturn variable is an object with some transaction ids, and the getData function goes and grabs some text from the web using those ids.
export function TextExample(){
let testText = "no itemLore yet";
const [text, textAdd] = useState(testText);
const txs = loreReturn.data.transactions.edges;
txs.forEach ((tx) => {arweave.transactions.getData(tx.node.id, {decode: true, string: true}).then(data => {
console.log(data);
textAdd(text + data);
});
});
return (
<div>
<p>{text}</p>
<
/div>
);
};
Put your API calls inside a useEffect hook and add necessary dependencies. This will make sure you are not repeatedly running the API calls every time the component reloads
export function TextExample(){
let testText = "no itemLore yet";
const [text, textAdd] = useState(testText);
useEffect(()=>{
const txs = loreReturn.data.transactions.edges;
txs.forEach ((tx) => {
arweave.transactions.getData(tx.node.id, {decode: true, string: true})
.then(data => {
console.log(data);
textAdd(text + data);
});
});
}, [])
return (
<div>
<p>{text}</p>
</div>
);
};

In ReactJS, facing problem in rendering the data from an API due to its odd structure

This is my code:
import React, { useState, useEffect } from "react";
import axios from 'axios';
function App() {
const [arrName, setName] = useState();
useEffect(() => {
axios.get('URL')
.then( response => {
// console.log( response.data )
setName( response.data )
});
}, [])
const foo = () => {
console.log(arrName.data[0].data[0]._id)
// following two console statements need not to be in the code. I am putting them only to show the structure of API
console.log(arrName)
console.log(arrName.data)
}
return(
<div>
Hi, I'm the component
<button onClick={foo}> console </button>
</div>
)
}
export default App;
Console response screenshot
I want to simplify the following statement. So that I can easily iterate the API and print anything from the API. However, I am printing only an id here.
console.log(arrName.data[0].data[0]._id)
Following command breaks the code
<div>
arrName.data[0].data.map((item) => <li>{item._id}</li> ) }
</div>
Kindly, Help me what changes should I make to my code.
Based on the replied comment, you would do it like so:
arrName?.data &&
arrName.data.map((item) => (
<ul>
{item.data.map((chidlItem) => (
<li> {chidlItem._id} </li>
))}
</ul>
));
The ? is Optional Chaining which is to check if the reference in the chain is valid, so it will skip the map function if arrName or arrName.data is null/undefined
Can't you just iterate this part?
arrName.data[0].data
for (const d of arrName.data[0].data) {
console.log(d._id)
}
I guess the real problem is from the backend API sending the data structured as an array of objects inside another array of objects. One solution could be saving the value needed in an array that would be more accessible to you.
const [arrName, setName] = useState();
useEffect(() => {
axios.get('URL')
.then( response => {
// console.log( response.data )
setName( response.data[0].data )
});
}, [])
but that will only be a workaround.

Needs Help To Troubleshoot Fetching Single Document From Firebase Database As Detailed Page

I'm try to get single document as detail information from Firebase database under collection "books", however my array method map does not recognize as function due to the render produce "undefined". Somehow render again and produce the object value in log. I posted the screenshot of the log above, hoping somebody help me out, thanks!!!!!
import React, {useState, useEffect} from 'react'
import {Link} from 'react-router-dom'
import firebase from '../config/fbConfig'
const BookDetails = (props) => {
const [books, setBooks] = useState([])
useEffect(() => {
const db = firebase.firestore()
const id = props.match.params.id
var docRef = db.collection("books").doc(id);
docRef.get().then(doc => {
if(doc.exists){
const data = doc.data()
console.log("Document data:", data)
setBooks(data)
}else {
console.log("No such document!");
}
}).catch(error => {
console.log("Error getting document:", error);
})
}, [])
console.log('this log is before return', books.title)
return (
<div className="book_details">
<Link to="/"><h2>Home</h2></Link>
{console.log("this log is in the return method", books.title)}
<h1>The Summary Of the Book </h1>
{books.map( book => <ul key = "book.id" >
<li>Book Title: {book.title}</li>
<li>Book Author: {book.author}</li>
<li>Book Summery: {book.brief}</li>
</ul>)}
</div>
)
}
export default BookDetails
Because you are testing whether books is undefined and only call the map function if it is defined (i.e. {books && books.map( [...] )}), the problem must lie somewhere else.
You are fetching a single document from your Firebase database. Therefore, the returned data will not be an array but an object which does not have the map function in its prototype. You can verify this from your console logs.
Your component renders twice because you are changing its state inside the useEffect via setBooks(data).
const db = firebase.firestore()
const id = props.match.params.id
First of all move these lines inside of useEffect.
Coming to the problem
You are fetching a single doc(object) from firebase and saving it in a state which is an array. Change your useState to
const \[book, setBook\] = useState(undefined) // or useState({})
Change your return to
return (
<div className="book_details">
<Link to="/"><h2>Home</h2></Link>
{console.log("this log is in the return method", books.title)}
<h1>The Summary Of the Book </h1>
{book && <div key={book.id}> {book.brief} </div>}
</div>
)
// or {Object.keys(book).length !== 0 && <div key={book.id}> {book.brief} </div>}
if you have used empty object in useState.

why is useEffect not updating the value after the api call?

im calling an api that does a fetch , but when using setHotSalesArray, the hotSalesArray is empty after useEffect Finishes
i have tried calling another function sending the data, also tried putting the data inside a variable to try and update it that way
here is the part im having trouble with
const [isActive, setIsActive] = useState(true);
const [hotSalesArray, setHotSales] = useState({});
useEffect(()=>{
let infoData;
API.getToken().then(
(data) =>API.getHotSale(data).then(info => (
infoData = info,
setHotSales(infoData),
setIsActive(false),
console.log(hotSalesArray,info, infoData)
),
)
)
},[])
this is what i get on the console :
{} {hotsales: Array(1)} {hotsales: Array(1)}
and the result im expecting is this:
{hotsales: Array(1)} {hotsales: Array(1)} {hotsales: Array(1)}
Update:
so i learned that i need a re-render to happen so i can have my new value inside the variable, my problem is i have this in my return
<div className="hotSalesContainer">
<h1 className="activitiesLabelHot">
Ventas Calientes
</h1>
{hotSales}
{ hotSales !== undefined ? hotSales.map((hotsale,index) => <PropertyElement data={hotsales} key={index.toString()} /> ) : ""}
</div>```
so i need the object hotSales to have something before i return that, i used to do this call before with componentWillMount, but im trying to learn how to use hooks
When you do console.log(hotSalesArray, info, infoData), hotSalesArray will have the value of the current render, which is {} (the initial value). It won't be until a re-render occurs that hotSalesArray will have the value of { hotsales: [...] }, but you won't see that in the console because you're only logging when the response comes back from your API.
Add a console log directly in the render and you'll see.
I fixed the syntax errors and did some cleanup. Besides the syntax errors though, you had the right idea. This could be improved upon with useReducer perhaps, but I didn't want to overcomplicate the answer :)
const MyComponent = () => {
const [isActive, setIsActive] = useState(true);
const [hotSales, setHotSales] = useState({});
useEffect(
() => {
API.getToken()
.then(data => API.getHotSale(data))
.then(info => {
setHotSales(info);
setIsActive(false);
})
},
[]
);
const { hotsales = [] } = hotSales;
return (
<div className='hotSalesContainer'>
<h1 className='activitiesLabelHot'>Ventas Calientes</h1>
{ hotsales.length &&
hotsales.map((sale, index) =>
<PropertyElement data={sale} key={index} />
)
}
</div>
)
}

Resources