I am trying to immediately update a boolean state variable in react native. the await keyword does not work however. because state setter functions are asynchronous, how can I do this using async / await? In vscode, the await in front of the setLike setter function has a message : "await has no effect on this type of expression"
const likeIt = async () => {
console.log('like pressed');
console.log('liked? before set', like); //**false**
await setLike(like => !like);
console.log('liked? after set', like); //**should be true but getting false**
const axiosAuth = await axiosWithAuth();
const userToken = axiosAuth.defaults.headers.Authorization;
if (like) {
axiosAuth.post(`https://url`,{})
.then(res => {
console.log('response from post like: ', res.data);
})
.catch(err => console.log('error in post like', err.message))
} else {
axiosAuth.delete(`https://url`)
.then(res => console.log('res from unlike', res.data))
.catch(err => console.log('err from unlike', err))
}
}
If we talk about react hooks, you should to know that useState() return array of two values. The second value is a dispatch function which we use to change value of state. And this functions are synchronous.
In your example this dispatch function is setLike() (and it is synchronous function). So await keyword do not work for them actually.
React has special system in the hood to work with changing state. This system wait for batch of changing states and then update our state and rerender component. Before that update of state and rerender of component, our likeIt() function will be done.
You could use useEffect() lifecycle method to handle change of like state.
For example:
const likeIt = async () => {
console.log('like pressed');
console.log('liked? before set', like); //**false**
await setLike(like => !like);
}
useEffect(() => {
console.log('liked? after set', like); //**true**
...
}, [like]);
await useState(); will not work.
Here is a possible solution that rather works with a temp variable in your likeIt() function.
function App() {
const [like, setLike] = useState(false);
const likeIt = async () => {
let temp = !like;
const axiosAuth = await axiosWithAuth();
const userToken = axiosAuth.defaults.headers.Authorization;
if (!temp) {
axiosAuth.delete(`https://url`)
.then(res => console.log('res from unlike', res.data))
.catch(err => console.log('err from unlike', err))
} else {
axiosAuth.post(`https://url`,{})
.then(res => {
console.log('response from post like: ', res.data);
})
.catch(err => console.log('error in post like', err.message))
}
setLike(temp);
}
return (
<div className="App">
<button onClick={likeIt}>{like === true ? 'Liked' : 'Not Liked'}</button>
</div>
);
}
Are you getting compiling errors due to await setLike()?
If not, it is a small issue that would be confusing for some people on VScode.
Please kindly check https://github.com/microsoft/vscode/issues/80853
Related
I'm kind of confused about how useEffect is triggered and how it work. I wrote a function like this but the useEffect doesn't run at all. I want to fetch the data from the API and then render a page based on the data. But it doesn't trigger the useEffect. If I don't use the useEffect, it will render the page three times.
async function getData() {
var tmpArrData = [];
await fetch("this API is hidden due to the privacy of the company - sorry")
.then((res) => res.json())
.then((data) => {
console.log("data", data);
tmpArrData = data;
});
console.log("tmpData ", tmpArrData);
return tmpArrData;
}
function App() {
const [arrData, setArrData] = useState();
const [loadData, setLoadData] = useState(false);
useEffect(() => {
console.log("if it works, this line should be shown");
const tmpArrData = getData();
setArrData(tmpArrData);
}, [arrData]);
const data = arrData[0];
console.log(data);
return (
<GifCompoment
id = {data.id}
name = {data.name}
activeTimeTo = {data.activeTimeTo}
activeTimeFrom = {data.activeTimeFrom}
requiredPoints = {data.requiredPoints}
imageUrl = {data.imageUrl}
/>
);
}
export default App;
The useEffect hook is guaranteed to run at least once at the end of the initial render.
getData is an async function and the useEffect callback code is not waiting for it to resolve. Easy solution is to chain from the implicitly returned Promise from getData and access the resolved value to update the arrData state. Make sure to remove the state from the useEffect's dependency array so that you don't create a render loop.
The getData implementation could be clean/tightened up by just returning the fetch result, no need to save into a temp variable first.
async function getData() {
return await fetch(".....")
.then((res) => res.json());
}
useEffect(() => {
console.log("if it works, this line should be shown");
getData().then((data) => {
setArrData(data);
});
}, []); // <-- empty dependency so effect called once on mount
Additionally, since arrData is initially undefined, arrData[0] is likely to throw an error. You may want to provide valid initial state, and a fallback value in case the first element is undefined, so you don't attempt to access properties of an undefined object.
const [arrData, setArrData] = useState([]);
...
const data = arrData[0] || {}; // data is at least an object
return (
<GifCompoment
id={data.id}
name={data.name}
activeTimeTo={data.activeTimeTo}
activeTimeFrom={data.activeTimeFrom}
requiredPoints={data.requiredPoints}
imageUrl={data.imageUrl}
/>
);
You should call state setter insede of Promise
function App() {
const [arrData, setArrData] = useState();
function getData() {
fetch("/api/hidden")
.then((res) => res.json())
.then((data) => setArrData(data));
}
useEffect(() => {
console.log("if it works, this line should be shown");
getData();
}, []);
return ...
}
By combining the answer from Drew Reese and Artyom Vancyan, I have solved my problem. I think the key points are setState right in the then function .then((data) => setArrData(data)) ,don't put the dependency in the useEffect, and await inside the useEffect. Thank you guy super ultra very much. Big love
useEffect(() => {
console.log("if it works, this line should be shown");
const getData = async () => {
await fetch("hidden API")
.then((ref) => ref.json())
.then((data) => {
setArrData(data);
});
}
getData();
}, []);
function App() {
const [arrData, setArrData] = useState([]);
const [loadData, setLoadData] = useState(false);
const async getData=()=> {
var tmpArrData = [];
await fetch("this API is hidden due to the privacy of the company - sorry")
.then((res) => res.json())
.then((data) => {
console.log("data", data);
setArrData(tmpArrData);
});
console.log("tmpData ", tmpArrData);
return tmpArrData;
}
useEffect(() => {
console.log("if it works, this line should be shown");
const callApi =async()=>{
await getData();
}
}, [arrData]);
const data = arrData[0];
console.log(data);
return (
<GifCompoment
id = {data.id}
name = {data.name}
activeTimeTo = {data.activeTimeTo}
activeTimeFrom = {data.activeTimeFrom}
requiredPoints = {data.requiredPoints}
imageUrl = {data.imageUrl}
/>
);
}
export default App;
Page will be rendered three to four times it's normal.
I have written an axios request in react-native useEffect.The request is succesfull in backend and returning a the right response in terminal.But the useEffect hook is not working according to it .It is still returning product as undefined and not changing the state.
If it all works well the product would contain the product variable.
It only works when I save it again and then it shows the product . Am I missing something here ?
Thanks in Advance !!
const [product, setProduct] = useState();
useEffect( () => {
getproductinfo()
if (props.editMode) {
AsyncStorage.getItem("jwt")
.then((res) => {
setToken(res);
})
.catch((error) => console.log(error));
}
console.log(product, "this is product");
},
[],
)
this is my function
const getproductinfo = async () => {
await axios
.get(`${baseURL}products/get/product/${props.product}`)
.then((res)=> {setProduct(res.data)
})
.catch((error)=> {
console.log(error);
console.log("this is order card product card error ")
});
}
getproductinfo is an async function and you don't use await in the useEffect hook so the code continues to run while the axios request is not yet resolved. However you can't use an async function as useEffect so I suggest the following approach
useEffect( () => {
const asyncFunction = async() {
await getproductinfo();
console.log(product, "this is product");
}
asyncFunction();
// Rest of your code....
},
[],
)
I am using gitbeaker to get a project from gitlab API, after fetching the project, I used useState to save the project object, now I want to fetch another API whose URL is in that object, but whenever I try to access that URL, an error appears "Cannot read property 'issues' of undefined".
Here's my code:
const [project, setProject] = useState<any>({});
const api = new Gitlab({
host: "https://example.com",
token: "my token",
});
useEffect(() => {
(async () => {
const projectsPromises = await api.Projects.all().then((allprojects) => {
return allprojects;
});
Promise.all(projectsPromises).then((data) => {
setProject(data.find((element) => element.id === 338));
});
})();
return () => {};
}, []);
console.log(project);
console.log(project._links.issues);
fetch(project._links.issues).then((res) => console.log(res));
console.log(project); gives me {} and after some time it prints the object, that's why when I try to use project._links.issues it is undefined as I think it isn't resolved yet but I don't know how to make it work.
I solved it by fetching the data in the useEffect hook and saving the response of the api in the state so that I can access it later in my code, like that
const [issues, setIssues] = useState<any>([]);
Promise.all(projectsPromises).then((data) => {
const celoProject: any = data.find((element) => element.id === 338);
setProject(celoProject);
const projectIssues = fetch(celoProject._links.issues)
.then((res) => res.json())
.then((allIssues) => {
setIssues(allIssues);
});
});
If someone has a better way or an explanation why I couldn't access it outside the useEffect, please tell me.
Anything inside the useEffect hook will only execute when the page first loads (because you provided an empty array as the second argument). Anything outside of it will execute on every render (every time props or state changes). That is why it logs {} the first time because the effect is asynchronous and hasn't completed before the component is rendered.
You should run the second fetch in the useEffect hook after the first API request completes. Need more information to determine what exactly is happening beyond this.
const [project, setProject] = useState<any>({});
const api = new Gitlab({
host: "https://example.com",
token: "my token",
});
useEffect(() => {
(async () => {
const projectsPromises = await api.Projects.all().then((allprojects) => {
return allprojects;
});
Promise.all(projectsPromises).then((data) => {
const projectResponse = data.find((element) => element.id === 338)
setProject(projectResponse)
fetch(projectResponse._links.issues).then((res) => {
console.log(res)
// Do something with this response
});
});
})();
return () => {};
}, []);
console.log(project);
console.log(project._links.issues);
I'm trying to get the value of googleToken inside a <div> in the return of my React Component. The value is already updated, but it's the initial state here in the return, therefore, it always shows null
const Layout = props => {
let googleToken = null;
useEffect( () => {
fetchGoogleToken();
}, [])
const fetchGoogleToken = async () => {
await api
.get("/google_token")
.then((response) => {
console.log('google_token: ' + response.data.google_token);
googleToken = response.data.google_token;
console.log('google_token updated: ' + googleToken);
})
.catch((error) => console.log(error));
};
const getGoogleToken = (res) => {
console.log(res);
setGoogleToken(res.accessToken);
saveGoogleTokenInDB();
};
const saveGoogleTokenInDB = async () => {
await api
.post("/fit-auth", {google_token : googleToken})
.then((response) => {
console.log(response);
})
.catch((error) => console.log(error));
};
return (
<div className={classes.googleButton} style={{display: googleToken === null ? 'block' : 'none'}}>
<h3>{googleToken}</h3>
<div/>
}
Any ideas on why I can't get the updated value?
It is right to use useEffect hook for fetching. But the result must be kept into state. And when you ask react to update state, you can not watch changes on the next line of code using console.log because setState is async function and it will be executed later on.
// this will never work as you might be expected:
setState(newState)
console.log(state)
To catch state updates always use useEffect hook as in example below:
useEffect(() => {
console.log(state)
}, [state])
Also, avoid using inline styles for showing / hiding your components. Check the official conditional rendering recommendations.
The final code is going to look like this:
const Layout = props => {
const [googleToken, setGoogleToken] = useState(null)
useEffect( () => {
fetchGoogleToken();
}, [])
// you can watch how state changes only using useEffect:
useEffect(() => {
console.log('google_token updated: ' + googleToken)
}, [googleToken])
const fetchGoogleToken = async () => {
await api
.get("/google_token")
.then((response) => {
console.log('google_token: ' + response.data.google_token);
setGoogleToken(response.data.google_token);
})
.catch((error) => console.log(error));
};
// conditional rendering:
if (!googleToken) return <span>Fetching token...</span>
return (
<div className={classes.googleButton}>
<h3>{googleToken}</h3>
<div/>
)
}
Hope you will find this helpful.
The issue is that when the value of googleToken is updated the component is not notified about this. Since the component is not notified about the change it still believes that the value is still the initial value and it does need to change anything in DOM.
To solve this issue try using states or just force a rerender once the function is executed.
I'm very much new to react native currently i'm building small app for just getting an idea about this. I'm facing an issue in mapping the data from API. This is the json response returning from the api
{"data":[{"digit":300,"countsum":"52"},{"digit":301,"countsum":"102"},{"digit":302,"countsum":"27"},{"digit":303,"countsum":"201"},{"digit":500,"countsum":"101"}]}
When i tried to map this data i'm facing some issues. I stored the response from API to the state and when i tried to display the state data using map function it's showing the state value is null. This the code i tried till now
const [listdata, setListData] = useState(null)
useEffect(() => {
// Run! Like go get some data from an API.
getListData();
}, []);
const getListData = async () => {
const token = await AsyncStorage.getItem("#userToken")
axios
.get(constants.BASE_URL + "getlist?token=" +token)
.then(response => setListData(response.data))
.catch(error => {
console.log(error);
});
listdata.map(item => <Text>{item.digit}</Text>)
}
Do it like this,
export default function ComponentName () {
const [listdata, setListData] = useState([])
useEffect(() => {
// Run! Like go get some data from an API.
getListData();
}, []);
const getListData = async () => {
const token = await AsyncStorage.getItem("#userToken")
axios
.get(constants.BASE_URL + "getlist?token=" +token)
.then(response => setListData(response.data))
.catch(error => {
console.log(error);
});
}
return (<>
listdata.map(item => <Text>{item.digit}</Text>)
</>
);
}
You have to wait the fetch execution and later do the list map.
// wait for it
await axios
.get(constants.BASE_URL + "getlist?token=" +token)
.then(response => setListData(response.data))
.catch(error => {
console.log(error);
});
listdata.map(item => <Text>{item.digit}</Text>)
If you want to map the data then do that inside return statement of your code ,like:
return(
{listData?listdata.map(item => return <Text>{item.digit}</Text>):""}
);
This is a sample of a meant in my comment above:
Try console.log listdata at this stage, you will find that it is still
null, in other words, the value of the updated value of the
listdata:useSate will be ready after the render take place. You can
make another function outside of the current one. then use useEffect
with listdata to update your text views
const [listdata, setListData] = useState(null)
useEffect(() => makeRemoteRequest(), [listdata])
makeRemoteRequest = () => {
const url = `your-url-of-data-here`;
fetch(url)
.then(res => res.json())
.then(res => {
setListData(res.data);
})
.catch(error => {
console.log(error)
});
};
You could try the following:
const [listdata, setListData] = useState([])
useEffect(() => {
// Run! Like go get some data from an API.
getListData();
}, []);
const getListData = async () => {
const token = await AsyncStorage.getItem("#userToken")
try {
const dataResponse = await axios.get(constants.BASE_URL + "getlist?token=" +token);
setListData(dataResponse.data || [] );
} catch(error) {
console.log(error);
}
}
return (<>
listdata.map(item => <Text>{item.digit}</Text>)
</>);