How to update react useState without delay? - reactjs

When I call fetchProducts I want productPage state to be updated before it runs the next line of code since the next line requires the updated state.
The state only gets updated after the function has finished running. No matter where in the fetchProducts function I put console.log(productPage) it returns the state as it was before the function was called.
const [productPage, setProductPage] = useState(1)
const [displayProducts, setDisplayProducts] = useState([])
const fetchProducts = async () => {
setProductPage(productPage + 1) // DOES NOT UPDATE UNTIL AFTER FUNCTION RUNS
const newProductsData = await fetch(`/api/getproducts?page=${productPage}`)
const newProductsArr = await newProductsData.json()
setDisplayProducts(displayProducts.concat(newProductsArr))
}
Is this possible? I've found workarounds to the problem but it feels hacky and not ideal.
I'm using next.js also, I'm not sure if that would make a difference. I can't use normal variables as they reset on each render.

setProductPage will update the state and trigger a new render. productPage is not the state, it's just a variable holding the value of the state the moment you used useState. It will never change, it will always have the same value. You think it changes, but in reality, the function is simply executed again, but now productPage assigned with the new value.
So what can you do? You have two options.
The one it use useEffect, that will see if productPage changes (every time the function is executed = re-renders), and if it is, it will fetch the data and use setDisplayProducts to re-render the component with a new value in displayProducts. This is nice if you plan to have other ways of updating the productPage and you want them to also trigger the fetch.
const [productPage, setProductPage] = useState(1)
const [displayProducts, setDisplayProducts] = useState([])
const fetchProducts = async () => {
setProductPage(productPage + 1);
}
useEffect(() => {
const getDisplayProducts = async () => {
const newProductsData = await fetch(`/api/getproducts?page=${productPage}`)
const newProductsArr = await newProductsData.json()
setDisplayProducts(displayProducts.concat(newProductsArr))
};
getDisplayProducts();
}, [productPage]);
The second one is just store the new value in a variable, pass the variable to the setProductPage but use the variable in the fetch and not productPage.
const [productPage, setProductPage] = useState(1)
const [displayProducts, setDisplayProducts] = useState([])
const fetchProducts = async () => {
const nextProductPage = productPage + 1;
setProductPage(nextProductPage);
const newProductsData = await fetch(`/api/getproducts?page=${nextProductPage}`)
const newProductsArr = await newProductsData.json()
setDisplayProducts(displayProducts.concat(newProductsArr));
};

Setting a state is an async operation and you can't use it like a sync operation.
So, you must use useEffect hook, which runs after state changes.
import React, {useState, useEffect} from 'react';
const [productPage, setProductPage] = useState(1)
const [displayProducts, setDisplayProducts] = useState([])
const fetchProducts = async () => {
setProductPage(productPage + 1)
}
useEffect(() => {
const newProductsData = await fetch(`/api/getproducts?page=${productPage}`)
const newProductsArr = await newProductsData.json()
setDisplayProducts(displayProducts.concat(newProductsArr))
}, [productPage]); // <-- monitor productPage for changes

Related

How can I stop react from making multiple network request simultaneously

I am making a get request to get data from my rest API, and when I have the data react will keep on making the same request simultaneously multiple times.
this is the code:
export default function Transaction() {
const [transactions, setTransaction] = useState([]);
const [loading, setLoading] = useState(true)
const { id } = useParams();
// this is where I am getting the problem
useEffect(() => {
const fetchTransc = async () => {
const res = await axios.get(`http://localhost:4000/api/get-records/${id}`);
setTransaction(res.data)
setLoading(false)
console.log(res.data)
};
fetchTransc();
},[id,transactions]);
The second argument of the UseEffect hook is the dependency array. It tells react which variables to watch for changes. When a dependency changes or when the component is mounted for the first time, the code inside the hook is executed.
In your case the array indicates that each time “id” or “transations” change, the hook should be executed.
Since setTransation is called in the fetch function, that will trigger the hook again because “transations” is present in hook’s the dependency array.
Each time the transactions state variable is set with a brand a new object fetched from the url, react will trigger the useEffect hook.
If “transations” is removed from the hook’s dependency array, this should work fine. Maybe also adding an IF to check the “id” value could be useful to prevent errors.
useEffect(() => {
const fetchTransc = async () => {
if(id != null) {
const res = await axios.get(`http://localhost:4000/api/get-records/${id}`);
setTransaction(res.data)
setLoading(false)
console.log(res.data)
}
};
fetchTransc();
},[id]);

React Native I can not store an array with AsyncStorage

I am newbie in React Native and I am trying to store and get an array with AsyncStorage in ReactNative.
I have two problems.
First, I do not know why but when I storage data, it only works the second time but I am calling first the set of useState.
const handleAddTask = () => {
Keyboard.dismiss();
setTaskItems([...taskItems, task]);
storeData(taskItems);
};
Second, how can I call the getData function to get all the data and show it? Are there something like .onInit, .onInitialize... in ReactNative? Here is my full code
const [task, setTask] = useState();
const [taskItems, setTaskItems] = useState([]);
const handleAddTask = () => {
Keyboard.dismiss();
setTaskItems([...taskItems, task]);
storeData(taskItems);
};
const completeTask = (index) => {
var itemsCopy = [...taskItems];
itemsCopy.splice(index, 1);
setTaskItems(itemsCopy);
storeData(taskItems);
}
const storeData = async (value) => {
try {
await AsyncStorage.setItem('#tasks', JSON.stringify(value))
console.log('store', JSON.stringify(taskItems));
} catch (e) {
console.log('error');
}
}
const getData = async () => {
try {
const value = await AsyncStorage.getItem('#tasks')
if(value !== null) {
console.log('get', JSON.parse(value));
}
} catch(e) {
console.log('error get');
}
}
Updating state in React is not super intuitive. It's not asynchronous, and can't be awaited. However, it's not done immediately, either - it gets put into a queue which React optimizes according to its own spec.
That's why BYIRINGIRO Emmanuel's answer is correct, and is the easiest way to work with state inside functions. If you have a state update you need to pass to more than one place, set it to a variable inside your function, and use that.
If you need to react to state updates inside your component, use the useEffect hook, and add the state variable to its dependency array. The function in your useEffect will then run whenever the state variable changes.
Even if you're update state setTaskItems([...taskItems, task]) before save new data in local storage, storeData(taskItems) executed before state updated and save old state data.
Refactor handleAddTask as below.
const handleAddTask = () => {
Keyboard.dismiss();
const newTaskItems = [...taskItems, task]
setTaskItems(newTaskItems);
storeData(newTaskItems);
};

Not awaiting for data in useEffect

I have a useEffect in my component that is waiting for data from the context so that it can set it in state. But its not waiting for the state and is moving on to the next line of code to set isLoading to false.
I'd like it to wait for the data so that I can render the loading.
I tried setting the isFetchingData in the context but I had run into problems where if another component calls it first it would set the isFetchingData state to false.
First call to ReactContext is setting the isLoading sate to false
It is fine for results to come back with no records. The component would render 'No records found'. Therefore, I cannot check the length on state to say if length is zero then keep loading.
Following is my code:
Context
const [activeEmployees, setActiveEmployees] = useState([]);
const [terminatedEmployees, setTerminatedEmployees] = useState([]);
useEffect(() => {
getEmployees()
.then(response => {
/// some code...
setActiveEmployees(response.activeEmployees)
setTerminatedEmployees(response.terminatedEmployees)
});
});
Component
const EmployeesTab = () => {
const { activeEmployees, terminatedEmployees } = useContext(BlipContext);
//Component states
const [isFetchingData, setIsFetchingData] = useState(true);
const [newEmployees, setNewEmployees] = useState([]);
const [oldEmployees, setOldEmployees] = useState([]);
useEffect(() => {
async function getData() {
await setNewEmployees(activeEmployees);
await setOldEmployees(terminatedEmployees);
setIsFetchingData(false);
}
getData();
}, [activeEmployees, terminatedEmployees, isFetchingData]);
if(isFetchingData) {
return <p>'Loading'</p>;
}
return (
// if data is loaded render this
);
};
export default EmployeesTab;
Since you have useState inside your useContext, I don't see the point of storing yet again the activeEmployees in another state.
If you want to have a local loading variable it could something like:
const loading = !(activeEmployees.length && terminatedEmployees.length);
This loading will update whenever getEmployees changes.
And to answer you question, the reason await is not having an effect is because setState is synchronous.

Looping set state react + firestore

I'm trying to create a user profile page for my app, for this page I'm trying to get a doc with some user information from firestore, and populate fields that the user can click on and to change. When I added the setTags function to the db call below, it resulted in an infinite loop. Can anyone explain why this happens and how to fix it? I'm very new to using react hooks.
const UserProfile = ({history}) => {
const description = useRef('');
const name = useRef('');
const {currentUser} = useContext(AuthContext);
let [tags, setTags] = useState('');
let uData;
db.collection('Users').doc(currentUser.uid).get().then(doc => {
console.log("checking db")
console.log(doc.data())
uData = doc.data();
description.current = uData.description;
name.current = uData.name
setTags(uData.tags)
})
}
Functional components are run on every render. That means that on every rerender, you open the database to search for the current user, then set the tags. Crucially, updating the state always causes a rerender! This is a core feature of React that allows implicit updating and is one of the main reasons to use React in the first place.
If you want something to be run only once, you can still do that, but you need to simulate componentDidMount() from a class component. So, you can use useEffect:
// Add this import in
import { useEffect } from 'react';
const UserProfile = ({history}) => {
const description = useRef('');
const name = useRef('');
const {currentUser} = useContext(AuthContext);
let [tags, setTags] = useState('');
let uData;
useEffect(() => {
db.collection('Users').doc(currentUser.uid).get().then(doc => {
console.log("checking db")
console.log(doc.data())
uData = doc.data();
description.current = uData.description;
name.current = uData.name
setTags(uData.tags)
})
}, []); // Empty dependency list means this will only run on first render
}

React/Redux How to set value from redux store to state of component

I need to change my received data from redux store to another variable (and then modify it).
At the moment, I receive data from store after API call and it is stored offices, but it is not set to my officeData variable. Does anyone how can I solve that?This is my code:
const dispatch = useDispatch();
const offices = useSelector((state) => state.office.offices)
const [officeData, setOffices] = useState(offices);
debugger;
useEffect(()=> {
dispatch(getOffices());
setOffices(offices);
debugger
}, [dispatch])
If you don't even enter your useEffect i think it's because you give dispatch as a dependency.
Effect are triggered when component mount but also when component update (prop or state). So you could do something like that :
import { useEffect } from "react";
const dispatch = useDispatch();
const offices = useSelector((state) => state.office.offices);
const [officeData, setOffices] = useState(undefined);
const [didMount, setDidmount] = useState(false);
// When component mount, load your Offices data
useEffect(() => {
if(!didMount){
dispatch(getOffices());
setOffices(offices);
} else {
setDidmount(true);
}
});
useEffect(() => {
if(didMount) {
// When you update officeData, do your thing
}
}, [officeData]);
I don't know the behavior of useSelector, but i guess it does not trigger a rendering. Maybe you could have a useEffect with offices as dependency, just be careful not to loop !

Resources