This function is returning the error:
"Objects are not valid as a React child (found: [object Promise]). If you meant to render a collection of children, use an array instead."
and I can't figure out why. It shouldn't be returning an object, it should be mapping over an array, right? At the top of the code I'm using a hook for setState like so:
const [expSymbolData, setExpSymbolData] = useState([])
I have a similar code that's working properly. Also, when an error like this happens, is there a way to see what object it's returning instead of React just saying "nope, use an array!"? It would help me troubleshoot.
const GetSymbol = async () => {
const rezsymbol = await fetch("https://api.scryfall.com/sets/83491685-880d-41dd-a4af-47d2b3b17c10")
const jsonsymbol = await rezsymbol.json()
setExpSymbolData(jsonsymbol)
{
expSymbolData.map((expStuff) => {
return(
<span>{expStuff.icon_svg_url}</span>
)}
)
}
}
useState setter is not a synchronous action. values will be updated on next render so either you can use useEffect to render based on updated values or simply use the data returned by api
const GetSymbol = async () => {
const rezsymbol = await fetch("https://api.scryfall.com/sets/83491685-880d-41dd-a4af-47d2b3b17c10")
const jsonsymbol = await rezsymbol.json()
setExpSymbolData(jsonsymbol)
return jsonsymbol.map((expStuff) => <span>{expStuff.icon_svg_url}</span>);
}
// Alternate
const GetSymbol = async () => {
const rezsymbol = await fetch("https://api.scryfall.com/sets/83491685-880d-41dd-a4af-47d2b3b17c10")
const jsonsymbol = await rezsymbol.json()
setExpSymbolData(jsonsymbol)
}
// add a useEffect hook
useEffect(() => {
expSymbolData.map((expStuff) => <span>{expStuff.icon_svg_url}</span>);
}, [expSymbolData]);
In 2nd scenario, handle rending based on new logic
Related
Im using Next.js and Typescript.
I'm trying to implement a custom hook for reading my data and outputting the object. I'm using the firebase realtime database. My data is an array of objects. Firebase returns an object of objects with .val() which I then convert to an array of objects in the custom hook, but I cannot pass it to my component for some reason. I can console.log the new array of objects, but can't return it to my component which reads undefined when I console.log it at the component.
I notice that sometimes when I edit the code and it auto-reloads, the console.log for my component works.
Here's my custom hook snippet and component trying to call it. Thanks!
Custom Hook Snippet:
// ... rest of hook is above this snippe (the hook is useFirebaseAuth())
const readBlogData = () => {
const db = getDatabase(app);
const dbRef = ref(db, "blogs");
let newArrObj;
onValue(dbRef, (snapshot) => {
newArrObj = Object.values(snapshot.val());
console.log(newArrObj); // I can see the array of objects in console.log
});
return newArrObj;
};
return { login, logout, loginStatus, writeBlogData, readBlogData };
}
Component:
export default function BlogSection() {
const { readBlogData } = useFirebaseAuth();
console.log(readBlogData()); // console.log shows undefined
return (
<section className={styles.blogSection}>
<Card className={styles.blogsContainer}>
<h5>Blogs</h5>
<Paginate blogs={DUMMY_DATA} />
</Card>
</section>
);
}
The code in the callback for onValue happens after the return newArrObj line.
You probably want to set it on state in your hook and return that state so the consumer can react to it.
const [blogs, setBlogs] = useState([]);
// then
onValue(dbRef, (snapshot) => {
newArrObj = Object.values(snapshot.val());
setBlogs(newArrObj);
});
// then
return { login, logout, loginStatus, writeBlogData, readBlogData, blogData: blogs };
Program the consumer to fetch the data and display the response
const Consumer = () => {
const { blogData, readBlogData } = useWhatever();
useEffect(() => readBlogData(), []); // Tell the hook to get some data
// blogData will initially be an empty array
// and then eventually will have some elements
return blogData.length ? <p>{JSON.stringify(blogData)</p> : null;
};
I am pretty much familiar with the async await but with back end nodejs. But there is a scenario came across to me where I have to use it on front end.
I am getting array of objects and in that objects I am getting lat lng of the places. Now using react-geocode I can get the place name for a single lat lng but I want to use that inside the map function to get the places names. SO as we know it async call I have to use async await over there.
Here is the code
import Geocode from "react-geocode";
render = async() => {
const {
phase,
getCompanyUserRidesData
} = this.props
return (
<div>
<tbody>
await Promise.all(_.get(this.props, 'getCompanyUserRidesData', []).map(async(userRides,index) => {
const address = await Geocode.fromLatLng(22.685131,75.873468)
console.log(address.results[0].formatted_address)
return (
<tr key={index}>
<td>
{address.results[0].formatted_address}
</td>
<td>Goa</td>
<td>asdsad</td>
<td>{_.get(userRides,'driverId.email', '')}</td>
<td>{_.get(userRides,'driverId.mobile', '')}</td>
</tr>
)
}))
</tbody>
</div>
)
}
But when I use async with the map function here it doesn't return anything. Can anyone please help me where I going wrong?
You should always separate concerns like fetching data from concerns like displaying it. Here there's a parent component that fetches the data via AJAX and then conditionally renders a pure functional child component when the data comes in.
class ParentThatFetches extends React.Component {
constructor () {
this.state = {};
}
componentDidMount () {
fetch('/some/async/data')
.then(resp => resp.json())
.then(data => this.setState({data}));
}
render () {
{this.state.data && (
<Child data={this.state.data} />
)}
}
}
const Child = ({data}) => (
<tr>
{data.map((x, i) => (<td key={i}>{x}</td>))}
</tr>
);
I didn't actually run it so their may be some minor errors, and if your data records have unique ids you should use those for the key attribute instead of the array index, but you get the jist.
UPDATE
Same thing but simpler and shorter using hooks:
const ParentThatFetches = () => {
const [data, updateData] = useState();
useEffect(() => {
const getData = async () => {
const resp = await fetch('some/url');
const json = await resp.json()
updateData(json);
}
getData();
}, []);
return data && <Child data={data} />
}
With the wrapper function below, delayed_render(), you can write asynchronous code inside a React component function:
function delayed_render(async_fun, deps=[]) {
const [output, setOutput] = useState()
useEffect(async () => setOutput(await async_fun()), deps)
return (output === undefined) ? null : output
}
This wrapper performs delayed rendering: it returns null on initial rendering attempt (to skip rendering of this particular component), then asynchronously calculates (useEffect()) the proper rendering output through a given async_fun() and invokes re-rendering to inject the final result to the DOM. The use of this wrapper is as simple as:
function Component(props) {
return delayed_render(async () => { /* any rendering code with awaits... */ })
}
For example:
function Component(props) {
return delayed_render(async () => {
const resp = await fetch(props.targetURL) // await here is OK!
const json = await resp.json()
return <Child data={json} />
})
}
UPDATE: added the deps argument. If your async_fun depends on props or state variables, all of them must be listed in deps to allow re-rendering. Note that passing deps=null (always re-render) is not an option here, because the output is a state variable, too, and would be implicitly included in dependencies, which would cause infinite re-rendering after the async_fun call completes.
This solution was inspired by, and is a generalization of, the Jared Smith's one.
My component relies on local state (useState), but the initial value should come from an http response.
Can I pass an async function to set the initial state? How can I set the initial state from the response?
This is my code
const fcads = () => {
let good;
Axios.get(`/admin/getallads`).then((res) => {
good = res.data.map((item) => item._id);
});
return good;
};
const [allads, setAllads] = useState(() => fcads());
But when I try console.log(allads) I got result undefined.
If you use a function as an argument for useState it has to be synchronous.
The code your example shows is asynchronous - it uses a promise that sets the value only after the request is completed
You are trying to load data when a component is rendered for the first time - this is a very common use case and there are many libraries that handle it, like these popular choices: https://www.npmjs.com/package/react-async-hook and https://www.npmjs.com/package/#react-hook/async. They would not only set the data to display, but provide you a flag to use and show a loader or display an error if such has happened
This is basically how you would set initial state when you have to set it asynchronously
const [allads, setAllads] = useState([]);
const [loading, setLoading] = useState(false);
React.useEffect(() => {
// Show a loading animation/message while loading
setLoading(true);
// Invoke async request
Axios.get(`/admin/getallads`).then((res) => {
const ads = res.data.map((item) => item._id);
// Set some items after a successful response
setAllAds(ads):
})
.catch(e => alert(`Getting data failed: ${e.message}`))
.finally(() => setLoading(false))
// No variable dependencies means this would run only once after the first render
}, []);
Think of the initial value of useState as something raw that you can set immediately. You know you would be display handling a list (array) of items, then the initial value should be an empty array. useState only accept a function to cover a bit more expensive cases that would otherwise get evaluated on each render pass. Like reading from local/session storage
const [allads, setAllads] = useState(() => {
const asText = localStorage.getItem('myStoredList');
const ads = asText ? JSON.parse(asText) : [];
return ads;
});
You can use the custom hook to include a callback function for useState with use-state-with-callback npm package.
npm install use-state-with-callback
For your case:
import React from "react";
import Axios from "axios";
import useStateWithCallback from "use-state-with-callback";
export default function App() {
const [allads, setAllads] = useStateWithCallback([], (allads) => {
let good;
Axios.get("https://fakestoreapi.com/products").then((res) => {
good = res.data.map((item) => item.id);
console.log(good);
setAllads(good);
});
});
return (
<div className="App">
<h1> {allads} </h1>
</div>
);
}
Demo & Code: https://codesandbox.io/s/distracted-torvalds-s5c8c?file=/src/App.js
I have a problem because whenever I try to return data from my custom useValidation hook it returns it correctly after second call.
For example here is my component that uses custom hook.
const { runValidation, errors } = useValidation();
return (<button onClick={runValidation}>Test</button>) // Returns {} on first click and finally {...data} on second
Then inside my useValidation hook I have state for errors:
const [errors, setErrors] = useState({});
const validate = () => {
setErrors(data);
}
const runValidation = () => {
validate();
};
return { runValidation, errors };
How can I fix this issue?
This is the expected behaviour.
Your problem is that you access the data directly after setting the state, that is before the component is updated. Therefore you'll only see the previous data.
Try to display the state in your component rather than logging it.
You'll see the state correctly updated.
const { runValidation, errors } = useValidation();
// will display the correct state after update
return (<button onClick={runValidation}>{errors.key}</button>);
const useValidation = () => {
const [errors, setErrors] = useState({});
const validate = () => {
setErrors({ key: 123 });
};
const runValidation = () => {
validate();
console.log(errors); // will log {} immediately after
};
return { runValidation, errors };
};
When you update the sate, react doesn't immediately update it. The value is passed into a queue and then updated on the next render. You are actually always printing the previous value, which you can't notice in your example, because the value is always the same.
When you call setState what react does is, it throws away your whole component and renders it again with the new value. You can't access the new value in the same step. Since you do the console.log in the same function/step with setErrors, it will show the previous value. This is because the component hasn't been updated with new values yet.
I am using deckofcardsapi.com and I cannot pass my deck_id in props to fetch url.
Props are passing ok because when I use them in line 40 they display as normal string. And when I paste this string to variable deckId in line 23 it is giving me list of cards.
But when instead of copying string to deckId i use props.deckId there is error "TypeError: cards.cards is undefined"
On the other hand props.quantity works well.
Here is my code
import React, { useState, useEffect } from 'react';
function NewDeck(props) {
const [deck, setDeck] = useState({ deck_id: [] });
async function draw() {
const result = await fetch(`https://deckofcardsapi.com/api/deck/new/shuffle/?deck_count=1`);
const json = await result.json();
setDeck(json);
};
useEffect(() => {
draw();
}, []);
return(
<Draw deckId={deck.deck_id} quantity={props.quantity} />
);
}
function Draw(props) {
const [cards, setCards] = useState({ cards: [] });
var deckId = props.deckId; //when i put here for egzample "l31hmefvilqe" it is working
async function draw() {
const result = await fetch(`https://deckofcardsapi.com/api/deck/${deckId}/draw/?count=${props.quantity}`);
const json = await result.json();
setCards(json);
};
useEffect(() => {
draw();
}, []);
return(
<ul>
{cards.cards.map(item => (
<li>{item.code}</li>
))}
<li>deckId: {props.deckId}</li>
<li>quantity: {props.quantity}</li>
</ul>
);
}
export default NewDeck;
How to pass props.deckId to my fetch's url
I was searching for answer but with no result. This is probably my stupid mistake but i can't find it.
Thank you in adwance
The problem is that at the first render of NewDeck the value of deck_id isn't set and it pass an empty array. You can fix it rendering the Draw component conditionally to the deck_id having a value . https://codesandbox.io/s/brave-margulis-olgxt In my example i set the deck_id to null and render draw only when deck_id exists.
PD: props.quantity is undefined and maybe you meant deck.remaining? Also check the draw() dependency for useEffect in the component, maybe you need to useCallback() (I0m not so sure of this because I'm still learning hooks)