Get value and not [object Promise] from async function in React Component - reactjs

I have a function which is async and returns a value.
How do I get this value when I call that function within a React function component?
For me it always shows [object Promise].
I already tried some stuff with the Effect Hook.
function Home() {
const { publicKey } = useWallet();
const [buttonClicked, setClick] = useState(false);
let tokenAccounts;
useEffect(() => {
if (buttonClicked) {
//This is the async function that I am calling
tokenAccounts = getTokenAccounts(publicKey);
console.log("token account: " + tokenAccounts);
readyToShow = true;
}
})
function handleButClick() {
setClick(true);
}
let display;
if (buttonClicked) {
display = <div>test{tokenAccounts}</div>
} else {
display = (publicKey && <button onClick={handleButClick}>click</button>);
}
return (
<div>
{display}
</div>
);
}
(It shows button and after the button is clicked it does show test and should also show the variable tokeAccounts which in my case is a [object Promise])
Putting an await in front of the function call would be my intentional solution but then it says Unexpected reserved word 'await'

Try this:
const Home = () => {
const { publicKey } = useWallet();
const [tokenAccounts, setTokenAccounts] = useState(undefined);
const handleButClick = () => {
getTokenAccounts(publicKey)
.then(result => setTokenAccounts(result));
// Depending on API may need result.data or other
}
return (
<React.Fragment>
{tokenAccounts === undefined && publicKey &&
<button onClick={handleButClick}>click</button>
}
{tokenAccounts && <div>test{tokenAccounts}</div>}
</React.Fragment>
);
}
When the component first loads the tokenAccounts state variable is undefined so your button element will be displayed. When the button is clicked the event handler calls your async action and accesses the return value via the .then statement (also check out .catch and .finally). The tokenAccounts state variable is then set and the state change causes the component to refresh. Since tokenAccounts now has a value it is displayed and the button element hidden.

function Home() {
const { publicKey } = useWallet();
const [buttonClicked, setClick] = useState(false);
let tokenAccounts;
useEffect(() => {
const getTokenAccountsLocally = async () => {
if (buttonClicked) {
tokenAccounts = await getTokenAccounts(publicKey);
console.log("token account: " + tokenAccounts);
}
}
getTokenAccountsLocally();
})
function handleButClick() {
setClick(true);
}
let display;
if (buttonClicked) {
//runs through this before tokenAccounts is loaded with the value
display = <div>this is the {tokenAccounts}</div>
} else {
display = (publicKey && <button onClick={handleButClick}>click</button>);
}
return (
<div>
{display}
</div>
);
}
Wrapping the async function call around another async function locally and then call it so I can add wait for the other async function call kind of works. tokenAccounts is now loaded with the returned value from the async function call. BUT it gets shown before it actually loaded the value.

Related

Reference to MediaRecorder object is undefined in function, despite being initialized in previously called function

I'm developing a React application to record both audio and video from the user and then fetch it to a backend.
In the MediaRecorderComponent below, the function initializeDevice asks the user to allow audio or video recording (it's called from clicking a button since our users need our application UI to be as clear and explicit as possible) and, if granted, initializes the mediaRecorder variable to a new MediaRecorder object, receiving as argument the response from the navigator.mediaDevices.getUserMedia promise.
import React, { useEffect, useState } from 'react';
import { RecordAudioButton } from '../Components';
type Props = {
mediaConstraints: MediaStreamConstraints;
};
function MediaRecorderComponent({ mediaConstraints }: Props) {
const [permissionGranted, setPermissionGranted] = useState(false);
const [stream, setStream] = useState<null | MediaStream>(null);
const [isRecording, setIsRecording] = useState(false);
let mediaRecorder: MediaRecorder;
const initializeDevice = async () => {
try {
const response = await navigator.mediaDevices.getUserMedia(
mediaConstraints
);
setStream(response);
setPermissionGranted(true);
mediaRecorder = new MediaRecorder(response);
} catch (err) {
setPermissionGranted(false);
console.log(err);
}
};
const handleRecording = () => {
if (isRecording) {
mediaRecorder.stop();
setIsRecording(false);
}
mediaRecorder.start();
setIsRecording(true);
};
return (
<div>
{permissionGranted ? (
<RecordAudioButton
isRecording={isRecording}
onClick={handleRecording}
/>
) : (
<>
<p>
CLIENT INSTRUCTIONS
</p>
<button
type="button"
onClick={() => initializeDevice()}
>
Let's start!
</button>
</>
)}
</div>
);
}
Later on, the handleRecording is passed as a prop to another component, which calls it when its div is clicked on:
function RecordAudioButton({ isRecording, onClick }: Props) {
return (
<div
className="bg-error text-base-100 rounded-full p-5 shadow-lg"
onClick={() => onClick()}
>
///... and so on
However, when the button is clicked I receive a Uncaught TypeError: Cannot read properties of undefined (reading 'start') triggered when the handleRecording function calls mediaRecorder.start(), and the mediaRecorder variable appears as undefined, even although it was initialized when the user granted permission in the initializeDevice function.
Why would the reference be uninitialized?
Use a ref to store your value:
import React, { useEffect, useState, useRef } from 'react';
...
const mediaRecorder = useRef(null);
const initializeDevice = async () => {
try {
const response = await navigator.mediaDevices.getUserMedia(
mediaConstraints
);
setStream(response);
setPermissionGranted(true);
mediaRecorder.current = new MediaRecorder(response);
} catch (err) {
setPermissionGranted(false);
console.log(err);
}
};
const handleRecording = () => {
if (isRecording) {
mediaRecorder.current.stop();
setIsRecording(false);
}
mediaRecorder.current.start();
setIsRecording(true);
};
...
Changing the values in your ref will not cause a rerender and will persist the value even when a render occurs provided that the component is not unmounted.
useRef returns a mutable ref object whose .current property is initialized to the passed argument (initialValue). The returned object will persist for the full lifetime of the component.

Can't set the location to the state

I'm trying to set the location to the state, but the first time i click the button the state is null not the current location. Why?
function Geolocation() {
const [myLocation, setMyLocation] = useState(null)
const getLocation = () => {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(setMyLocation);
console.log(myLocation)
} else {
alert("Geolocation is not supported by this browser.")
}
}
return (
<div>
<button onClick={getLocation}>Get geolocation</button>
</div>
)
}
export default Geolocation
There is not a propblem. Just Console.log works faster than getLocation() function. You can see it in this code:
function Geolocation() {
const [myLocation, setMyLocation] = useState(null)
const getLocation = () => {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(setMyLocation);
console.log(myLocation)
} else {
alert("Geolocation is not supported by this browser.")
}
}
return (
<div>
<button onClick={getLocation}>{myLocation?.timestamp || "null"}</button>
</div>
)
}
export default Geolocation
but let me guess, in the second time you click it log the current location.
setMyLocation() does not immediately mutate myLocation but creates a pending state transition. Accessing myLocation after calling this method can potentially return the existing value. There is no guarantee of synchronous operation of calls to setMyLocation() and calls may be batched for performance gains.
you can think of your myLocation state as a "snapshot" of the state related to the current render. after mutate the state, the component will re-render with the new state you set.
in your example you are trying to use the state you changed the line before but it will update just after the next render.
In conclusion, the state indeed changing. you can check it this way:
function Geolocation() {
const [myLocation, setMyLocation] = useState(null);
useEffect(() => {
console.log(myLocation);
}, [myLocation]);
const getLocation = () => {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(setMyLocation);
} else {
alert("Geolocation is not supported by this browser.");
}
};
return (
<div>
<button onClick={getLocation}>Get geolocation</button>
</div>
);
}

Value not defined in first render but defined in next render

I recently started using useEffect hook, and I'm facing some issues. This is a simple App component that renders an input field and submit button. On hitting submit selectItem is called which in turn calls an async function getNames(). getNames function checks if there is an existing entry, if so it returns, otherwise it calls 3rd party API getNewNames() to get newNames. I tried setting the state with this newNames field, but it seems like it is undefined in first render. But after the first render it is defined. How do I make sure that I have newNames field, so that it doesn't return undefined in any renders?
const App = () => {
const [namesArr, setNamesArr] = useState([])
const [name, setName] = useState('')
useEffect (()=> {
console.log('Inside use Effect')
}, [namesArr])
const changeInput = (val) => {
setName(val)
}
const selectItem = async() => {
const returnedVal = await getNames()
// ReturnVal is empty in first render, but filled in second render.
/
}
const getNames = async() =>{
const existingNames = namesArr.find((name)=> name === name)
if(existingNames){
return 'We have an entry'
}
else{
console.log(`Names are not reloaded properly, need to re-render`)
const newNames = await getNewNames() // this is
setName((oldNames)=> [...oldNames, newNames])
return namesArr
}
}
return <div>
<input value={name} onChange={(e)=> changeInput(e.target.value)}></input>
<button onClick={()=> selectItem()}></button>
</div>
}
I think your problem is related to the setName method. In the line after setName console.log(namesArr) will be undefined. So how can we fix this?
const getNames = async() =>{
const existingNames = namesArr.find((name)=> name === name)
if(existingNames){
return 'We have an entry'
}
else{
const newNames = await getNewNames();
// We created our new list outside
// If x is a list, prepend an ellipsis ...newNames
const newList = [...namesArr, newNames];
// setName string is a state. Incorrect state updating.
// setNamesArr instead of setName
setNamesArr(newList)
return newList;
}
}
Now there are two possibilities here.
returnedVal === array or returnedVal === 'We have an entry'
const selectItem = async() => {
const returnedVal = await getNames();
console.log(returnedVal); // array or string.
}

How to turn a function into an ASYNC AWAIT

Currently, I'm passing a value from a Child Component to a Parent in React.
const ParentComponent = () => {
const [callback, setCallback] = useState(null);
const handleCallback = (childData) => {
console.log(childData);
return childData;
};
let childDataType = handleCallback();
if (childDataType === "success") {
setCallback(true)
} else if (childDataType === "error") {
setCallback(true)
}
return (
<div dataValue={dataValue(callback)}/>
)
}
const ChildComponent = ({dataValue}) => {
let callback = thisData[index].value
return (
<div dataValue={dataValue(callback)}/>
)
}
I'm trying to use that value to set a state. If the value from the child equals a string then the state is true else false.
Right now I'm able to get the data to console.log(childData) inside handleCallback. However, the data comes back undefined at first then sets to a value. Because of this, it sets childDataType to undefined. Which in turn sets my state to undefined.
I need to have the variable childDataType to wait for the function to run and return a defined value before trying to set the callback's state. How do I get handleCallback to await a defined value before returning its initial value?
What I like to do is the following
const handleCallback = (childData) => {
return new Promise((resolve,reject)=>{
resolve(childData)
})
};
then u can do
let childDataType = await handleCallback();
This is a nice approach because if any error happens in the function u just call reject(error).
You can change the function to an async function and await for it to return the value.
const handleCallback = async (childData) => {
console.log(childData);
return childData;
};
let childDataType = await handleCallback();
Did you try to use the "await" expression?
let childDataType = await handleCallback();
Check this for more info:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await

Can't set state in a onClick function in React Hook

Hi i cant set state in a using a function i defined for onClick. All other lines are working except setting the state.
export default function SaleProducts(props) {
const [currentSelected, setSelected] = useState(props.location.state.pid);
useEffect(() => {
superagent
.post("url")
.set("Content-Type","application/x-www-form-urlencoded")
.send({"access_token":token})
.set('accept', 'json')
.end((error, response) => {
if(error){
console.log("Error")
}
else{
var json = response.body;
json.products.map((res) => {
var array = [res.title,"Not yet published",res.variants[0].price,<Button onClick={(event) => handleItemDeletion(event,res.id)}>Delete Item</Button>];
arr.push(array);
})
,[currentSelected]}
const handleItemDeletion = (event,id) =>{
event.preventDefault();
var cSelected = currentSelected.replace(id,'');
setSelected((currentSelected) => cSelected); //this is not working
console.log("Current Selected : ",currentSelected)
}
return(<arr>); //this is only for representation
OnClick function is getting called but only that setSelected line is not working. The state is not changing it is still like before.
You should pass value to setSelected, not a function. Something like this: setSelected(cSelected);
Setting the state is not correct. Try this,
export default function SaleProducts(props) {
const [currentSelected, setSelected] = useState(props.location.state.pid);
useEffect(() => {
superagent
.post("url")
.set("Content-Type","application/x-www-form-urlencoded")
.send({"access_token":token})
.set('accept', 'json')
.end((error, response) => {
if(error){
console.log("Error")
}
else{
var json = response.body;
json.products.map((res) => {
var array = [res.title,"Not yet published",res.variants[0].price,<Button onClick={(event) => handleItemDeletion(event,res.id)}>Delete Item</Button>];
arr.push(array);
})
,[currentSelected]}
const handleItemDeletion = (event,id) =>{
event.preventDefault();
var cSelected = currentSelected.replace(id,'');
setSelected(cSelected); // check here
console.log("Current Selected : ",currentSelected)
}
return(<arr>);

Resources