I have a functional component, ListKeys. When it loads I want to set an empty array of keys equal to a list of all keys extracted from storage. Here is what I've got at the moment:
const ListKeys = props => {
const [keys, setKeys] = useState([]);
const [areKeysLoaded, setAreKeysLoaded] = useState(false);
useEffect(() => {
if(!areKeysLoaded){
loadSavedKeys();
setAreKeysLoaded(true);
console.log(keys)
}
});
async function loadSavedKeys(){
try {
var allKeys = await AsyncStorage.getAllKeys();
console.log(allKeys);
setKeys(allKeys);
}
catch {
console.log("Error: Cannot access saved data.");
}
}
return (
<View></View>
);
};
export default ListKeys;
This code correctly gets the list of keys and outputs it to the console. This is done on line 16: console.log(allKeys);
However, when I then setKeys(allKeys);, this doesn't work. I know this because line 9: console.log(keys) outputs an empty array.
I'm guessing I can't just set a state value array to another array but I'm not experienced enough with JS or React Native to know why.
Can someone tell me how to properly set the keys array to the allKeys array?
You need to wait for the loadSavedKeys to resolve first before trying to set since it is async:
const ListKeys = props => {
const [keys, setKeys] = useState([]);
const [areKeysLoaded, setAreKeysLoaded] = useState(false);
useEffect(() => {
if (areKeysLoaded) return
AsyncStorage
.getAllKeys()
.then((keys) => {
setAreKeysLoaded(true)
setKeys(keys)
})
.catch(e => console.error(e));
}, []);
return (
<View></View>
);
};
export default ListKeys;
Note I simplified the code a bit since it seemed a little more verbose then necessary. They key take away is you need to wait for getAllKeys to resolve before you'll get the keys since it is asynchronous.
Also, you prob don't need areKeysLoaded if you just want this to run once, but I guess you could use it instead for a loading indicator?
Related
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
When I try to execute the following react code, the axios.get() executed multiple times.
I have attached the screenshot of the log. Console Logs.
Can anyone please help me regarding this.
const CaskList = () =>{
const [casklist,getCaskList] = useState('');
const [searchCaskName, getCaskForSearch] = useState('');
const [searchResultCaskName, setSearchResultCaskName] = useState('');
const getCaskForSearchFromInput = (event) =>{
console.log(event.target.value);
getCaskForSearch(event.target.value);
};
useEffect(()=>{
const func = async() =>{
const resultCasks = await axios.get('http://localhost:3001/getAllApps');
const actualData = resultCasks.data;
console.log("**********************" + actualData);
getCaskList(actualData);
}
func();
})
const caskToBeRendered = [];
for(let i=0;i<casklist.length;i++){
caskToBeRendered.push(<Cask allCasks={casklist[i]} >);
};
const options = {
includeScore: false,
findAllMatches : true,
threshold : 0.3
};
const fuse = new Fuse(casklist,options);
const result = fuse.search(searchCaskName);
setSearchResultCaskName(result);
return (
<div>
{caskToBeRendered}
</div>
);
}
you need to pass a second argument to hook useEffect. You can read about that
If you want to run an effect and clean it up only once (on mount and
unmount), you can pass an empty array ([]) as a second argument. This
tells React that your effect doesn’t depend on any values from props
or state, so it never needs to re-run. This isn’t handled as a special
case — it follows directly from how the dependencies array always
works.
useEffect(()=>{
const func = async() =>{
const resultCasks = await axios.get('http://localhost:3001/getAllApps');
const actualData = resultCasks.data;
getCaskList(actualData);
}
func();
},[])
You need to add a empty dependency array.
If you want to fire useEffect once on initial mount only. Like
useEffect(() => {
//your code goes here
}, []);
If you want useEffect to fire on initial mount and every re-render, you don't pass any dependency array. Like
useEffect(() => {
//your code goes here
});
I am trying to add multiple objects to a state array using a forEach loop:
const App = () => {
const [pokemon, setPokemon] = useState([]);
axios.get('https://pokeapi.co/api/v2/pokemon') // Gives me 20 pokemon
.then(res => {
res.data.results.map(p => p.url).forEach(url => { // Loops over endpoints of pokemon
axios.get(url)
.then(response => {
setPokemon(response.data)
})
});
})
}
As you can probably guess, only the last item from the forEach loop is shown when I console.log pokemon as it's the last one to be set in the state.
The api I'm working with: https://pokeapi.co/.
I start with making a call to https://pokeapi.co/api/v2/pokemon which gives me 20 pokemon, each pokemon object comes with an endpoint to get more information about it and it's the information from these endpoints I want to store as objects in the state.
Thanks for any help, if you know of a better way I can do this feel free to let me know.
I suggest you use Promise.all() inside of a useEffect() hook. By using useEffect, you can run your fetch code once when the component mounts (not each time it renders). Using Promise.all() allows you to pass an array of promises (which is what axios.get() returns), and resolve each Promise to one. This one Promise can resolve to an array of responses that each axios.get() call resulted in. This allows you to only set your state once.
See working example:
const {useState, useEffect} = React;
const App = () => {
const [pokemon, setPokemon] = useState([]);
useEffect(() => {
axios.get('https://pokeapi.co/api/v2/pokemon')
.then(res => Promise.all(res.data.results.map(pokemon => axios.get(pokemon.url))))
.then(arr => setPokemon(arr.map(response => response.data)));
}, []);
return (<div>
{pokemon.map(({id, species}) => <p key={id}>{species.name}</p>)}
</div>);
}
ReactDOM.render(<App />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.0/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.21.1/axios.min.js" integrity="sha512-bZS47S7sPOxkjU/4Bt0zrhEtWx0y0CRkhEp8IckzK+ltifIIE9EMIMTuT/mEzoIMewUINruDBIR/jJnbguonqQ==" crossorigin="anonymous"></script>
Just create a temporary array, add all the data which you get from the API to that, and later set the state using that variable. You don't have to set the state every time - you can set it at once.
const App = () => {
const [pokemon, setPokemon] = useState([]);
const tempArr = [];
axios.get('https://pokeapi.co/api/v2/pokemon') // Gives me 20 pokemon
.then(res => {
res.data.results.map(p => p.url).forEach(url => { // Loops over endpoints of pokemon
axios.get(url).then(response => {
tempArr.push(response.data);
})
});
});
setPokemon(tempArr);
}
Try setPokemon([...pokemon , response.data] )
You should probably use
setPokemon (pokemon.concat(response.data)
I have this code userPage that is suppose to fetch data from an online JSONplaceholder of users and output it based on ID. Currently my i am fetching the data correctly, but when I try to render it, I get the error "TypeError: Cannot read property 'map' of undefined" for some reason. Not sure why, I feel like it is a small error but I have been racking my brain for hours trying to figure it out.
UserPage
export const UserPage = ({match}) => {
const [data, setData] = useState({ hits: [] });
useEffect(() => {
function getFetchUrl() {
return `https://jsonplaceholder.typicode.com/users/${match.params.id}`;
}
async function fetchData() {
const result = await axios(getFetchUrl());
setData(result.data);
}
fetchData();
}, [match.params.id]);
console.log(data)
return (
<>
<ul>
{data.hits.map(item => (
<li key={item.id}>
<a href={item.url}>{item.title}</a>
</li>
))}
</ul>
</>
);
}
all of the api data is stored inside hits, but this is how the data in hits looks like when I console.log(data) after making the API call in useEffect
as you can see, the data is correct, but the first time it runs, hits: Array{0}. The data is the correct output after that call. I feel like this might be the problem, but I'm not quite sure and I don't know how to fix it. Also, I'm not sure why it made the call 3 times, but I don't know if that matters either. I am new to React.JS so I'm figuring out all these things as I go along.
Thank you!
If you want the state to be an object, then you need to update it like one. As per your current code, you are replacing the object in state data with what you get from api which causes your model to break
async function fetchData() {
const result = await axios(getFetchUrl());
setData({hits: result.data}); // pass an object
}
Also note that state updates with hooks do not merge the state, so say you have multiple keys in the object and you would like to update only one, you need to merge and update it like
setData(prev => ({
...prev,
hits: result.data
}))
Add a boolean variable , initialise it as false then set it to true once axios promise is recieved. And console your data when this boolean is true.
export const UserPage = ({match}) => {
const [data, setData] = useState({ hits: [] });
const [fetched, setFetched] = useState(false);
useEffect(() => {
function getFetchUrl() {
return `https://jsonplaceholder.typicode.com/users/${match.params.id}`;
}
async function fetchData() {
const result = await axios(getFetchUrl());
setData(result.data);
setFetched(result.data && true);
}
fetchData();
}, [match.params.id]);
fetched && console.log(data)
i hope this will solve the error with console log.
I have tried many things and can't seem to understand why setTypes won't update the 'types' array??
import { useState, useEffect } from 'react';
import { PostList } from './post-list';
import * as api from '../utils/api';
export const PostSelector = (props) => {
const [posts, setPosts] = useState([]);
const [loading, setLoading] = useState(false);
const [type, setType] = useState('post');
const [types, setTypes] = useState([]);
const fetchTypes = async () => {
setLoading(true);
const response = await api.getPostTypes();
delete response.data.attachment;
delete response.data.wp_block;
const postTypes = response.data;
console.log(response.data); // {post: {…}, page: {…}, case: {…}}
setTypes(postTypes);
console.log(types); // []
// Why types remain empty??
}
const loadPosts = async (args = {}) => {
const defaultArgs = { per_page: 10, type };
const requestArgs = { ...defaultArgs, ...args };
requestArgs.restBase = types[requestArgs.type].rest_base; // Cannot read property 'rest_base' of undefined
const response = await api.getPosts(requestArgs);
console.log(response.data);
}
useEffect(() => {
fetchTypes();
loadPosts();
}, []);
return (
<div className="filter">
<label htmlFor="options">Post Type: </label>
<select name="options" id="options">
{ types.length < 1 ? (<option value="">loading</option>) : Object.keys(types).map((key, index) => <option key={ index } value={ key }>{ types[key].name }</option> ) }
</select>
</div>
);
}
Please, take a look at the console.log and notice the different responses.
What I am trying to do is to load list of types, in this case 'post', 'page' and 'case' and then render a list of posts based on the current 'type'. The default type is 'post'.
If I add [types] to useEffect. I finally get the values but the component renders nonstop.
Thanks to everyone for your comments. Multiple people have pointed out the problem, being that, the fact that we set the state doesn't mean it will set right away because it it asynchronous.
How do we solve this problem then? Regardless of the reasons, how do we get it done? How do we work with our state at any point in time and perform calculations based on our state if we don't know when it will become available? How do we make sure we wait whatever we need to and then use the values we expect?
For any one coming here and not being able to set/update a useState array you need to use a spread operator (...) and not just the array e.g. "[...initState]" instead of "initState" ... in Typescript
//initialise
const initState: boolean[] = new Array(data.length).fill(false);
const [showTable, setShowTable] = useState<boolean[]>([...initState]);
// called from an onclick to update
const updateArray = (index: number) => {
showTable[index] = !showTable[index];
setShowTable([...showTable]);
};
It seems like useState is asynchronous and does not update the value instantly after calling it.
Review this same case here
useState's setTypes is an asynchronous function so the changes do not take effect immediately. You can use useEffect to check if anything changes
useEffect(()=>{
const defaultArgs = { per_page: 10, type };
const requestArgs = { ...defaultArgs, ...args };
requestArgs.restBase = types;
console.log("types updated",types)
},[types])
You can remove loadPosts because now useEffect will run whenever types change
You have declared your types to be an array, yet you are passing a dictionary of dictionaries through to it.
Try this:
const [types, setTypes] = useState({});
You also do not need to call
loadPosts()
becuase the useState hook will re-render your component, only updating what is needed.
Ok, The short answer is due to Closures
It not due to asynchronous as other answers said !!!
Solution (☞゚ヮ゚)☞
You can check the changes by console.log at return function like this.
return (
<div> Hello World!
{
console.log(value) // this will reference every re-render
}
</div>
);
or create a new one useEffect with value as a dependency like below
React.useEffect(() => {
console.log(value); // this will reference every value is changed
}, [value]);
function App() {
const [value, Setvalue] = React.useState([]);
React.useEffect(() => {
Setvalue([1, 2, 3]);
console.log(value); // this will reference to value at first time
}, []);
return (
<div> Hello World!
{
console.log(value) // this will reference every re-render
}
</div>
);
}
ReactDOM.render(<App />, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="app"></div>
Read here in more detail: useState set method not reflecting change immediately