get value from async function in useState - javascript

I'm using async storage to retrieve my data but how do I use it with useState()?
Example:
async function getdata(){
let d= await AsyncStorage.getItem('Name');
return d;
}
export default function App() {
const [name, setName] = useState(() => getdata());
return (<View>...</View>)
}
but this doesn't work since getdata() is async, so how do I solve this problem?
Edit:
I forgot to mention I tried the useEffect() Hook like this:
async function getdata(){
let d= await AsyncStorage.getItem('Name');
console.log('retrieved Data!');
return d;
}
const [name, setName] = useState(()=>0);
useEffect(() => {
getData().then(setName);
console.log('moving on...');
}, []);
but the order of execution was:
'Moving on...'
'retrieved Data!'
where as it should have been the other way around

You'll initialize your state to some empty value. Maybe an empty string, maybe a null. If you like, you can have your component render something different during this time, such as a loading indicator. Then once the data loads, you can set state to render again.
export default function App() {
const [name, setName] = useState(null);
useEffect(() => {
getData().then(value => setName(value));
}, []);
if (name === null) {
return <Text>Loading...</Text>
} else {
return (<View>...</View>)
}
}

You have to use useEffect
export default function App() {
const [name, setName] = useState('');
useEffect(() => {
const bootstrap = async () => {
const name = await getdata();
setName(name);
};
bootstrap();
}, []);
return <View>...</View>;
}

I think the best solution here is to initialize name as false, and then use 'setName' in a .then() callback. setName (and those setter functions that come out of useState generally) is meant to be a way of updating that value asynchronously.
I'm setting name to the boolean 'false' here and checking it strictly. If there is a case where that name field is empty it can just be set to an empty string without causing an infinite loop.
async function getdata(){
let d= await AsyncStorage.getItem('Name');
return d;
}
export default function App() {
const [name, setName] = useState(false);
useEffect(() => {
if (name === false) {
getData().then((name) => {
setName(name);
});
}
}, []);
return (<View>...</View>)
}
Keep in mind that your view has to be able to deal with a false name value. How it does that is up to you.
^^^The above solution should effectively solve your problem but I'll make a quick note here: If your component is relatively complex it might be re-rendering a number of times for other reasons and will consequently run the if(name === false) condition as it waits for the callback to resolve. This won't break things but it'll hammer async storage more than necessary and might affect performance. If you find that to be the case, look into 'debouncing' callbacks using the helpful article below.
https://medium.com/#gabrielmickey28/using-debounce-with-react-components-f988c28f52c1

Related

Is there any way to use async await in SetState

I'm trying to SetState but inside it, I have to get some data that needs async/await and I know it can't be done so I wonder if is there any way I can do it properly
codesanbox: https://codesandbox.io/s/infallible-mendeleev-6641iw?file=/src/App.js:0-614
Edit: It's hard for me to get data before setState because If I only want to get data If the newValue satisfies a condition so Get data force to inside setState
import "./styles.css";
import { useEffect, useState } from "react";
export default function App() {
const [value, setValue] = useState([]);
const run = async () => {
setValue((oldValue) => {
const newValue = [...oldValue];
// do something makes newValue changes
if (newValue == true) { // if newValue satisfy a condition
const res = fetch(`https://fakestoreapi.com/products/${newValue.length}`);
const result = res.json();
newValue.push(result.title);
}
return newValue;
});
};
return (
<div className="App">
<button onClick={run}>get result</button>
{value.map((item, index) => {
return <h2 key={index}>{value[index]}</h2>;
})}
</div>
);
}
Why don't you run first the async code, and when the data are available set the state ?
const run = async (x) => {
const res = await fetch(`https://fakestoreapi.com/products/${x}`);
const result = await res.json();
setValue((oldValue) => {
// you have access to the fetched data here
const newValue = [...oldValue];
console.log(result.title);
return newValue;
});
};
And ofcourse the click handler should be
onClick={() => run(2)}
There's a React component called Suspense. I think it first appeared in v16, but in all honesty I have only used it with React v18, so unsure if it will work for you.
I'll refer you to a live demo I have: wj-config Live Demo
Here I use <Suspense> to wrap a component that requires data that is asynchronously obtained, just like when you use fetch().
Suspense works like this:
It attempts to load the inner children but places a try..catch in said loading process.
If an error is caught, and the caught error is a promise then Suspense will instead render the component in its fallback property.
After rendering what's in fallback, React awaits the caught promise.
Once the caught promise resolves, Suspense retries rendering the child components.
I hear there are frameworks that provides the necessary mechanisms to use Suspense in a simple and expedite manner, but in my live demo I did it all myself. Is not too bad I think.
The procedure to use this is:
Create a readXXX function that is a suspender function (a function that throws a promise).
Call this function at the beginning of your inner Suspense component's code.
Program the rest of your inner component as if the readXXX function has worked and returned the needed data.
Here's the code I have in the live demo to create suspender functions:
function suspenderWrapper(promise) {
let isPending = true;
let outcome = null;
const suspender = promise.then(
(r) => {
outcome = r;
isPending = false;
},
(e) => {
outcome = e;
isPending = false;
}
);
return () => {
if (isPending) {
throw suspender;
}
return outcome;
};
}
Open up my live code's demo and look for App.js where all the magic is shown.
You can get the old value from the value variable which is always storing the current state, and do the if check on it instead.
And if the if condition on value was true - then you can call fetch and after that call setState and update the state.
If the condition was not true, there is no need to update the state since it stayed the same.
See the code below:
const [value, setValue] = useState([]);
const run = async () => {
//some condition on the current value
if (value) {
const res = await fetch( `https://fakestoreapi.com/products/${newValue.length}`);
const result = await res.json();
// And here apply the changes on the state
setValue((oldValue) => {
const newValue = [...oldValue];
newValue.push(result.title);
return newValue;
});
}
//Outside the if block - no need to change the state.
};

Issues upgrading async componentDidMount() to async useEffect()

// UPDATE: The issue was using the state immediately after setting it inside useEffect(). See my answer HERE for details.
I'm trying to upgrade one of my React app pages from class component to functional component with Hooks. However, I have some issues due to some async functions.
The way the old page behaves is that in componentDidMount() some data is async fetched from the database and displayed. It works properly, myName and myValue are displayed correctly.
// OLD APPROACH - CLASS COMPONENT
class MyPage extends Component {
constructor(props) {
super(props);
this.state = {
myName: null,
myValue: undefined,
}
}
componentDidMount = async () => {
try {
const myName = await getNameFromDatabase();
const myValue = await getValueFromDatabase();
this.setState({ myName, myValue });
} catch (error) {
alert(
"Some errors occured when fetching from DB"
);
console.error(error);
}
}
render() {
return (
<div>
<h1>{this.state.myName}</h1>
<h1>{this.state.myValue}</h1>
</div>
)
}
export default MyPage
I tried to update the page by carefully following this response.
// NEW APPROACH - FUNCTIONAL COMPONENT WITH HOOKS
function MyPage() {
const [myName, setMyName] = useState(null);
const [myValue, setMyValue] = useState(undefined);
useEffect(() => {
async function fetchFromDatabase() {
const myName = await getNameFromDatabase();
const myValue = await getValueFromDatabase();
setMyName(myName);
setMyValue(myValue);
}
fetchFromDatabase();
}, [])
return (
<div>
<h1>{myName}</h1>
<h1>{myValue}</h1>
</div>
)
}
However, when I do this, they no longer get displayed. I supposed they remain "null" and "undefined". Apparently if I do a console.log(), they eventually get fetched, but only after the page is rendered without them, which is not what was happening in the first case.
Why exactly is this happening? Why is it getting displayed correctly in the first case but not in the second? As far as I know, useEffect() does the same thing as componentDidMount(). Should I proceed another way if I wish to call async functions inside useEffect()?
The useEffect hook and state updates are fine. Function components are instanceless though, so the this is just undefined. Fix the render to just reference the state values directly.
It's also good practice to handle errors when working with asynchronous code.
function MyPage() {
const [myName, setMyName] = useState(null);
const [myValue, setMyValue] = useState(undefined);
useEffect(() => {
async function fetchFromDatabase() {
try {
const myName = await getNameFromDatabase();
const myValue = await getValueFromDatabase();
setMyName(myName);
setMyValue(myValue);
} catch(error) {
// handle any rejected Promises and thrown errors
}
}
fetchFromDatabase();
}, []);
return (
<div>
<h1>{myName}</h1>
<h1>{myValue}</h1>
</div>
);
}
First of all, you are giving the same name for your response as your useState(). Try using different names. Then, put just empty string into your useState() default value instead of null or undefined. Finally, you no longer need to use this but instead access directly the value. It should be something like this :
function MyPage() {
const [myName, setMyName] = useState('');
const [myValue, setMyValue] = useState('');
useEffect(() => {
async function fetchFromDatabase() {
const name = await getNameFromDatabase();
const value = await getValueFromDatabase();
setMyName(name);
setMyValue(value);
}
fetchFromDatabase();
}, [])
return (
<div>
<h1>{myName}</h1>
<h1>{myValue}</h1>
</div>
)
}
function MyPage() {
const [myName, setMyName] = useState(null);
const [myValue, setMyValue] = useState(undefined);
useEffect(() => {
(async () => {
const myName = await getNameFromDatabase();
const myValue = await getValueFromDatabase();
setMyName(myName);
setMyValue(myValue);
})();
}, []);
return (
<div>
<h1>{myName}</h1>
<h1>{myValue}</h1>
</div>
);
}
Alright, so the code in the original post is correct, as other remarked. However, it is a very simplified/abstract version of the actual code I'm working on.
What I was doing wrong is that I was using the state in useEffect() immediately after setting it there.
Something like that:
// WRONG
let fetchedName= getNameFromDatabase();
setMyName(fetchedName);
if(myName==="something") {
setMyValue(1000);
}
The conclusion is: Never use the state immediately after setting it in useEffect() or componentWillMount(), use an intermediary variable.
Instead do:
// CORRECT
let fetchedName= getNameFromDatabase();
setMyName(fetchedName);
if(fetchedName==="something") {
setMyValue(1000);
}

Rendering async promises through function components

Sorry if the post is duplicated i just find examples for class components.
I have this code:
export const getUniPrice = async () => {
const pair = await Uniswap.Fetcher.fetchPairData(HOKK, Uniswap.WETH[ETH_CHAIN_ID]);
const route = new Uniswap.Route([pair], Uniswap.WETH[ETH_CHAIN_ID]);
const priceUni = route.midPrice.toFixed(9);
return priceUni
}
It does work, answer me the promise object:
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: "1106278.001628948"
What i would like to know is, how can i properly work with this object in order to be able to render it through function components? I'm doing something like this which obviously will not work because react doesn't render objects.
const Price = () => {
const { state, dispatch } = useContext(AppContext);
return(<>
{state.dex === 'uni' ? getUniPrice() : state.dex === 'cake'
? getCakePrice() : getMDexPrice()
}
</>)
}
Could someone give me a hint? This function is running outside a function component so I can't just use useState
You're right. The result of getUnitPrice() is a Promise, not a value, so what React does is it prints out the stringified version of that Promise. If you need the fulfilled value, you need a state value that will re-render the page if updated. Something like this:
const [price, setPrice] = useState('');
useEffect(() => {
getUnitPrice().then((p) => setPrice(p));
}, []);
...
<div>Price: {price}</div>
If you're using a class component, you can initialize the state the same way like this:
state = {
price: '',
}
async componentDidMount() {
const p = await getUniPrice();
this.setState({ price: p });
}

React infity loop on function call

i've a (certainly) stupid problem.
my function getDataTbable is called in infinity loop I don't understand why... So the request is infinitive called.
export const TableResearch = ({setSelectedSuggestion,setImages}) => {
const [research, setResearch] = useState('');
const [suggestions, setSuggestions] = useState ([]);
const [table, setTable]= useState ([]);
const getDataTable = async () => {
const {data} = await jsonbin.get('/b/5f3d58e44d93991036184474');
setTable(data);
console.log(table)
};
getDataTable();
That is because the TableResearch function is called multiple times (every time this component is rendered). If you want to run a function only when the component is mounted, you'll have to use useEffect. Here is an example:
useEffect(() => {
const {data} = await jsonbin.get('/b/5f3d58e44d93991036184474');
setTable(data);
}, []);
The second parameter [] passed to useEffect is important. It makes the function run only once.
You can learn more about useEffect from HERE
The component re-renders every time you change it's state (setTable).
You should use useEffect to only execute your function the first time it renders.
Also you might encounter this warning:
Warning: Can't perform a React state update on an unmounted component.
if the async call finishes after component has unmounted. To account for that, write useEffect like this:
// outside component
const getDataTable = async () => {
const { data } = await jsonbin.get("/b/5f3d58e44d93991036184474");
return data;
};
useEffect(() => {
let mounted = true;
getDataTable()
.then(data => {
if (!mounted) return;
setTable(data);
});
return () => {
mounted = false;
};
}, []);
You can try this
useEffect(() => {
const getDataTable = async () => {
const { data } = await jsonbin.get("/b/5f3d58e44d93991036184474");
setTable(data);
console.log(table);
};
getDataTable();
}, []); // [] makes it runs once
Yes, getDataTable(); is being executed everytime the view is rendered, including when the data is returned.
Try wrapping getDataTable() like this:
if (!table.length) {
getDataTable()
}
But you will need to handle the case for if the requests returns no results, in which case it will still run infinitely:
const [table, setTable]= useState([]);
const [loading, setLoading]= useState();
const getDataTable = async () => {
setLoading(true);
const {data} = await jsonbin.get('/b/5f3d58e44d93991036184474');
setTable(data);
setLoading(false);
};
if (typeof loading === 'undefined' && !table.length) {
getDataTable();
}

Return component data using switch and Hooks

I'm trying to do a switch inside a function and i'm use react hooks.
The switch works fine but i cannot return a component..why?
The idea is that as I go through the array i will load the corresponding component whit all his data at that moment.
export default function Content({content}) {
const [contentBooks, setContentBooks] = useState(null);
const [contentFilms, setContentFilms] = useState(null);
async function data() {
return await Promise.all(content.map(element => element.content).map(async item => {
if (item.type == 'DETAIL') {
switch (item.type) {
case 'BOOKS':
const bookstype = await axios.get(`url`)
setContentBooks(bookstype)
return <Componen1 info={contentBooks} // --> not work
case 'FILMS':
const filmstype = await axios.get(``)
setContentFilms(filmstype)
return <Componen2 info={contentFilms} // --> not work
default:
return null;
}
}
}))
}
useEffect(() => {
const fetchData = async () => {
const result = await data()
};
fetchData();
}, [content]);
return (
<React.Fragment></React.Fragment>
)
}
You probably need this resource: https://www.robinwieruch.de/react-pass-props-to-component/
I don't think that is the correct way of calling a component in react
there is alot wrong with this. first your components have no closing tag not sure how this even compiles.
<Componen2 info={contentFilms}/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.4.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.4.0/umd/react-dom.production.min.js"></script>
next you are going an extremely round about way of calling the component.
load it once, not in the useeffect.

Categories