React state is empty inside useEffect - reactjs

Currently I'm building a pusher chat app with react. I'm trying to keep a list of online users. I'm using this code below:
const [users, setUsers] = useState([]);
useEffect(() => { // UseEffect so only called at first time render
window.Echo.join("server.0")
.here((allUsers) => {
let addUsers = [];
allUsers.map((u) => {
addUsers.push(u.name)
})
setUsers(addUsers);
})
.joining((user) => {
console.log(`User ${user.name} joined`);
setUsers([users, user]);
})
.leaving((user) => {
console.log(`User ${user.name} left`);
let addUsers = users;
addUsers.filter((u) => {
return u !== user.name;
})
setUsers(addUsers);
})}, []);
Whenever I subscribe to the pusher channel, I receive the users that are currently subscribed and the state is set correctly. All subscribed users are showing. However when a new user joins/leaves, the .joining/.leaving method is called and the users state is empty when I console log it. This way the users state is being set to only the newly added user and all other users are being ignored. I'm new to react so there is probably a simple explanation for this. I was not able to find the answer myself tough. I would really appreciate your help.

I saw the problem in joining. You need to update setState like this: setUsers([...users, user.name]);
And leaving also need to update:
const addUsers = users.filter((u) => {
return u !== user.name;
});
setUsers(addUsers);
here should also rewrite:
let addUsers = allUsers.map((u) => u.name);
setUsers(addUsers);

I found the issue. The problem is that when accessing state from within a callback funtion, it always returns the initial value. In my case an empty array. It does work when using a reference variable. I added the following lines:
const [users, _setUsers] = useState([]);
const usersRef = React.useRef(users);
const setUsers = data => {
usersRef.current = data;
_setUsers(data);
}
Each time I update the users state, I use the setUsers function. Now when I acces the state from inside my callback function with usersRef.current I get the latest state.
Also I used the code from the answer of #Viet to update the values correctly.

Related

usestate can change the state value after axios in useEffect

I expected to get the url with category=business,but the web automatically reset my state to the url that dosent have the category.I dont know the reason behind
let {id}=useParams()
const [newsurl,setNewsurl]=useState(()=>{
const initialstate="https://newsapi.org/v2/top-headlines?country=us&apiKey=c75d8c8ba2f1470bb24817af1ed669ee"
return initialstate;})
//console.log(id);
const [articles, setActicles] = useState([]);
useEffect( ()=>{
if(id === 2)
console.log("condition")
setNewsurl("https://newsapi.org/v2/top-headlines?country=de&category=business&apiKey=c75d8c8ba2f1470bb24817af1ed669ee")},[])
useEffect(() => {
const getArticles = async () => {
const res = await Axios.get(newsurl);
setActicles(res.data.articles);
console.log(res);
};
getArticles();
}, []);
useEffect(() => {
console.log(newsurl)
// Whatever else we want to do after the state ha
s been updated.
}, [newsurl])
//return "https://newsapi.org/v2/top-headlines?country=us&apiKey=c75d8c8ba2f1470bb24817af1ed669ee";}
return (<><Newsnavbar />{articles?.map(({title,description,url,urlToImage,publishedAt,source})=>(
<NewsItem
title={title}
desciption={description}
url={url}
urlToImage={urlToImage}
publishedAt={publishedAt}
source={source.name} />
)) } </>
)
one more things is that when i save the code the page will change to have category but when i refresh it ,it change back to the inital state.Same case when typing the url with no id.May i know how to fix this and the reason behind?
Setting the state in React acts like an async function.
Meaning that the when you set the state and put a console.log right after it, it will likely run before the state has actually finished updating.
You can instead, for example, use a useEffect hook that is dependant on the relevant state in-order to see that the state value actually gets updates as anticipated.
Example:
useEffect(() => {
console.log(newsurl)
// Whatever else we want to do after the state has been updated.
}, [newsurl])
This console.log will run only after the state has finished changing and a render has occurred.
Note: "newsurl" in the example is interchangeable with whatever other state piece you're dealing with.
Check the documentation for more info about this.
setState is an async operation so in the first render both your useEffetcs run when your url is equal to the default value you pass to the useState hook. in the next render your url is changed but the second useEffect is not running anymore because you passed an empty array as it's dependency so it runs just once.
you can rewrite your code like the snippet below to solve the problem.
const [articles, setActicles] = useState([]);
const Id = props.id;
useEffect(() => {
const getArticles = async () => {
const newsurl =
Id === 2
? "https://newsapi.org/v2/top-headlines?country=de&category=business&apiKey=c75d8c8ba2f1470bb24817af1ed669ee"
: "https://newsapi.org/v2/top-headlines?country=us&apiKey=c75d8c8ba2f1470bb24817af1ed669ee";
const res = await Axios.get(newsurl);
setActicles(res.data.articles);
console.log(res);
};
getArticles();
}, []);

Nextjs/React : Skip useEffect function on particular state change

I built chart view page which display sales data.
This chart will updated through REST API when user change dimension, filter value and period.
useEffect(async () => {
let [startDate, endDate] = getReportPeriod({target: { innerText: period}}) // get formatted date string
getReportData(startDate, endDate) // function that call REST API to get chart data
}, [filteredDimensions, dimensions, dimensionTime])
Also, user can save and load chart setting.
const loadCustomReport = async(reportName) => {
let res = await axios.post('/api/customReport/loadReport', {
report_name: reportName
}) // get report setting
let setting = JSON.parse(res.data[0].report_setting) // parse report setting as json
setRevenue(setting.metrics.revenue)
setImpression(setting.metrics.impression)
setClicks(setting.metrics.clicks)
setCPM(setting.metrics.CPM)
setCTR(setting.metrics.CTR)
setDimensions(setting.dimensions)
setFilteredDimensions(setting.filteredDimensions)
}
The problem is the useEffect function run twice(both of setDimensions and setFilteredDimensions) and response times vary, so sometimes I got report data that a setting is not fully applied.
Is there any way to skip useEffect function running on setDimensions?
I think put both dimension setting and filter setting on one state might be works but I just curious that there are other solutions.
Please advise me any suggestion to solve the problem.
#Gabriele Petrioli give me the solution actually I wanted.
#man.in.the.jukebox and #Brandon Pelton-Cox's advice give me a great point.
Currently my code is as below(I changed before I saw #Gabriele Petrioli's answer)
useEffect(async () => {
let [startDate, endDate] = getReportPeriod({target: { innerText: period}})
getReportData(startDate, endDate)
}, [filteredDimensions, dimensionTime])
// button handler that add dimension to current dimensions state
const addDimension = (dimension) => {
setDimensions([
...dimensions,
dimension
])
let [startDate, endDate] = getReportPeriod({target: { innerText: period}})
getReportData(startDate, endDate)
}
// button handler that remove dimension from current dimensions state
const removeDimension = (idx) => {
let tempDimensions = [...dimensions]
tempDimensions.splice(idx, 1)
setDimensions(tempDimensions)
let [startDate, endDate] = getReportPeriod({target: { innerText: period}})
getReportData(startDate, endDate)
}
// load report setting
const loadCustomReport = async(reportName) => {
let res = await axios.post('/api/customReport/loadReport', {
report_name: reportName
})
let setting = JSON.parse(res.data[0].report_setting)
setRevenue(setting.metrics.revenue)
setImpression(setting.metrics.impression)
setClicks(setting.metrics.clicks)
setCPM(setting.metrics.CPM)
setCTR(setting.metrics.CTR)
setDimensions(setting.dimensions)
setFilteredDimensions(setting.filteredDimensions)
}
Thank you all for give me great advices.
Since your state updates are fired from an async function, react does not batch them by default
(applies to React versions before 18, as in 18 all updates are batched regardless of where they originate).
You need to opt-in to unstable_batchedUpdates, so
const loadCustomReport = async(reportName) => {
let res = await axios.post('/api/customReport/loadReport', {
report_name: reportName
}) // get report setting
let setting = JSON.parse(res.data[0].report_setting) // parse report setting as json
React.unstable_batchedUpdates(() => {
setRevenue(setting.metrics.revenue)
setImpression(setting.metrics.impression)
setClicks(setting.metrics.clicks)
setCPM(setting.metrics.CPM)
setCTR(setting.metrics.CTR)
setDimensions(setting.dimensions)
setFilteredDimensions(setting.filteredDimensions)
});
}
By including dimensions in the dependency array for the useEffect function, you are telling React to re-render the page and execute the function in that useEffect block whenever setDimensions is called. Therefore, if you want to prevent that function from being executed when dimensions is updated, you need to remove from the dependencies array, like so
useEffect(async () => {
...
}, [filteredDimensions, dimensionTime])

Results from realtime db not available when called

Im having some trouble with realtime database and react native.
I have the following useEffect in a component that is supposed to listen for changes and then update state as required, I then use that state to populate a list.
The component gets a data object passed as a prop, the data object contains a string array called members that contains uuids, I am trying to iterate over those to get the attached user from realtime db and then save those objects to a state array.
const myComponent = ({ data }) => {
const [users, setUsers] = useState([]);
useEffect(() => {
const userArr = [];
data.map(item => {
item.members.forEach((username: string) => {
database()
.ref(`users/${username}`)
.on('value', snapshot => {
userArr.push(snapshot.val());
});
});
});
setUsers(userArr);
};
}, []);
return (
<>
{users} <----- this is in a flatlist
</>
);
}
It works eventually after refreshing the screen about 5 times. Any help would be greatly appreciated.
The simplest way to get some data to show is to move update the state right after you add an item to the array:
useEffect(() => {
const userArr = [];
data.map(item => {
item.members.forEach((username: string) => {
database()
.ref(`users/${username}`)
.on('value', snapshot => {
userArr.push(snapshot.val());
setUsers(userArr); // 👈
});
});
});
};
}, []);
Now the UI will update each time that a user is loaded from the database.
I also recommend reading some more about asynchronous loading, such as in Why Does Firebase Lose Reference outside the once() Function?
Your database call may be asynchronous, which is causing the code inside the useEffect to act a little funny. You could push all those database calls (while iterating through item.members) into an array, and then do Promise.all over the array. Once the promises are resolved, you can then set the users.
Hope this helps!
add an async function inside useEffect and call it
useEffect(() => {
const getUsers = async () => {
const userArr = [];
data.....
//wait for it with a promise
Promise.all(userArr).then(array => setUsers(array))
})
getUsers()
}, [])
not sure if the function needs to be async

How to Avoid Fetching Data Twice When Using React Hooks

I'm trying to fetch a set of questions from a Firestore database, and then store them in a local array for use later on in a quiz. As I'm working to fetch the data, it's running asynchronously. I've dealt with this before, and the solution was a useEffect() hook and conditionally rendering component.
My code currently looks like this:
var questions = [];
var collection = useRef('Planning');
var [loading, setLoading] = useState(true);
useEffect(() => {
if (props.track === 'Testing & Deployment') {
collection.current = 'Test_Deploy';
} else {
collection.current = props.track;
}
if(questions.length !== 0){
setLoading(false)
} else {
var questionSet = db.collection('Quizzes').doc(collection.current).collection('Questions')
questionSet.withConverter(questionConverter).get().then(function(response) {
response.forEach(document => {
var question = document.data();
// running asynch - need to address
console.log('Question in')
questions.push(question)
})
})
setLoading(false)
}
}, [props.track, questions])}
Depending on where setLoading() is, the component either won't re-render at all, or will double fetch the data from Firestore. I've tried playing around with useCallback() to no avail, and have also seen that this might be an issue with React StrictMode.
Any advice on how to fix my code in order to re-render the page after fetching all of the questions, without double fetching them?
To fetch data once on component mount you can specify an empty dependency array. I also suggest moving the questions object into local component state. Since the state is updated in a loop you should use a functional state update to add each new question to the array.
const [questions, setQuestions] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
const questionSet = db
.collection("Quizzes")
.doc(collection.current)
.collection("Questions");
questionSet
.withConverter(questionConverter)
.get()
.then(function (response) {
response.forEach((document) => {
const question = document.data();
setQuestions(questions => [...questions, question]);
});
});
setLoading(false);
}, []);
If the linter complains about missing dependencies then you need to either add them and place the appropriate conditional check on the data fetch, or add a comment to ignore/disable the linter for the line with the dependency array, // eslint-disable-next-line react-hooks/exhaustive-deps.
It will rerender again when the props change or update including props.track and questions everytime when you push item into it. So if you want to get rid of it rerender then just remove from useEffect like
useEffect(() => {
.......
}, [loading])} // remove from here and add `loading` here just

ReactJS: Maximum update depth exceeded when fetching data from local-storage token

I am getting the above error on the following code. Will anyone help me find out what could be wrong with this code? Thanks
const [userData, setUserData] = useState({});
useEffect(() => {
const user = auth.getCurrentUser();
setUserData(user);
});
This is the getCurrentUser function
export function getCurrentUser() {
try {
const jwt = localStorage.getItem(tokenKey);
return jwtDecode(jwt);
} catch (ex) {
return null;
}
}
You should be very cautious while using useEffect.
Below is what you have used in your implementation. Which is also the problem why the error.
useEffect(() => {
const user = auth.getCurrentUser();
setUserData(user);
});
To understand what exactly happening just a background of useEffect. When useEffect is being used without any dependencies it'll execute every time the component gets rendered. Please have a look here for more info.
So in this case in useEffect you are trying to update the state of the component which will cause the component to re-render, as discussed again useEffect will get called again state gets updated and the loop goes on.
setUserData() -> component re-render -> setUserData() -> component re-render -> setUserData() -> component re-render ....the cycle goes on
So ideally you should be getting the current user details only once after the component mounted and this can be done by passing [] as dependency to useEffect as below
useEffect(() => {
const user = auth.getCurrentUser();
setUserData(user);
}, []);
Hope this helps.
You have to try this
useEffect(() => {
function getData() {
const user = auth.getCurrentUser();
setUserData(user);
}
getData()
},[]);

Resources