useState is not rendering as expected - javascript

I have a state named chats in which i am storing all messages from firebase and i'm trying to render it in app. but it does not render
here is my component's state:
const [chats, setChats] = useState([]);
I am bringing messages in UseEffect hook to get it on running of app
useEffect(() => {
let merged_uid = uid_merger(current_user.id, chat_user.uid);
database()
.ref('/')
.child(`chats/${merged_uid}`)
.on('child_added', (msgs) => {
console.log(msgs);
chats.push(msgs.val());
setChats(chats);
});
}, []);
but id does not render.

since chats is a state you can't mutate it using chats.push(msgs.val()); instead what you need to do is to replace
chats.push(msgs.val());
setChats(chats);
with
setChats([...chats, msgs.val()])

Related

onSnapshot in firebase fires multiple times [duplicate]

I use a lot of firestore snapshots in my react native application. I am also using React hooks. The code looks something like this:
useEffect(() => {
someFirestoreAPICall().onSnapshot(snapshot => {
// When the component initially loads, add all the loaded data to state.
// When data changes on firestore, we receive that update here in this
// callback and then update the UI based on current state
});;
}, []);
At first I assumed useState would be the best hook to store and update the UI. However, based on the way my useEffect hook is set up with an empty dependency array, when the snapshot callback gets fired with updated data and I try to modify the current state with the new changes, the current state is undefined. I believe this is because of a closure. I am able to get around it using useRef with a forceUpdate() like so:
const dataRef = useRef(initialData);
const [, updateState] = React.useState();
const forceUpdate = useCallback(() => updateState({}), []);
useEffect(() => {
someFirestoreAPICall().onSnapshot(snapshot => {
// if snapshot data is added
dataRef.current.push(newData)
forceUpdate()
// if snapshot data is updated
dataRef.current.find(e => some condition) = updatedData
forceUpdate()
});;
}, []);
return(
// JSX that uses dataRef.current directly
)
My question is am I doing this correct by using useRef along with a forceUpdate instead of useState in a different way? It doesn't seem right that I'm having to update a useRef hook and call forceUpdate() all over my app. When trying useState I tried adding the state variable to the dependency array but ended up with an infinite loop. I only want the snapshot function to be initialized once and the stateful data in the component to be updated over time as things change on the backend (which fires in the onSnapshot callback).
It would be better if you combine useEffect and useState. UseEffect will setup and detach the listener, useState can just be responsible for the data you need.
const [data, setData] = useState([]);
useEffect(() => {
const unsubscribe = someFirestoreAPICall().onSnapshot(snap => {
const data = snap.docs.map(doc => doc.data())
this.setData(data)
});
//remember to unsubscribe from your realtime listener on unmount or you will create a memory leak
return () => unsubscribe()
}, []);
Then you can just reference "data" from the useState hook in your app.
A simple useEffect worked for me, i don't need to create a helper function or anything of sorts,
useEffect(() => {
const colRef = collection(db, "data")
//real time update
onSnapshot(colRef, (snapshot) => {
snapshot.docs.forEach((doc) => {
setTestData((prev) => [...prev, doc.data()])
// console.log("onsnapshot", doc.data());
})
})
}, [])
I found that inside of the onSnapshot() method I was unable to access state(e.g. if I console.log(state) I would get an empty value.
Creating a helper function worked for, but I'm not sure if this is hack-y solution or not but something like:
[state, setState] = useState([])
stateHelperFunction = () => {
//update state here
setState()
}
firestoreAPICall.onSnapshot(snapshot => {
stateHelperFunction(doc.data())
})
use can get the currentState using callback on set hook
const [state, setState] = useState([]);
firestoreAPICall.onSnapshot(snapshot => {
setState(prevState => { prevState.push(doc.data()) return prevState; })
})
prevState will have Current State Value

Loading when fetching firestore data

I want to set a loading state whenever the page is fetching the firestore data .
I am using this :
const [isLoading, setIsLoading] = React.useState(true)
const fetchGames=async()=>{
let promises = []
const dataDB = await db.collection('event')
const eventsFromDb = []
const DB =await db.collection('event').get()
DB.docs.forEach((item,index)=>{
const promise = dataDB
eventsFromDb[index]= item.data()
promises.push(promise);
})
setEvents(eventsFromDb)
Promise.all(promises)
.then(setIsLoading(false));
}
useEffect(()=>
{
fetchGames()
if(!isLoading)
{
console.log("HEY")
}
}, [])
I cannot get the HEY in my console , how to fix this ?
As it stands, your useEffect method only runs on component mount. You need it to run when state is updated as well.
You just need to add your state as a parameter within the useEffect array argument. Like so:
useEffect(()=>
{
fetchGames()
if(!isLoading)
{
console.log("HEY")
}
}, [isLoading])
This will run the effect on mount and when the isLoading state is updated.

React Components "flickers" on load, since state gets overriden

I work on a todo app with React and things become clearer, but I struggle to undersand the "lifecycle". In VueJS I know a ComponentDidMount() hook, which would help me to solve this issue if I guess, but in React I canĀ“t find it out.
I have an array of todos like this: const todos = [{description: "walk dog", done: false}]
This is the initial state of my app:
const [alltodos, handleTodos] = useState([]);
On load I use this useEffect hook to get data from localStorage.
useEffect(() => {
const items = localStorage.getItem("todos");
const parsed = JSON.parse(items);
handleTodos(parsed);
}, []);
I count my todos with this function:
const countTodos = () => {
const donetodos = alltodos.filter((item) => {
return !item.done;
});
countOpen(donetodos.length);
};
I update the count if a dependency changes:
useEffect(() => {
countTodos();
localStorage.setItem("todos", JSON.stringify(alltodos));
}, [alltodos]);
So what happens is that the counter starts with 0 and than "flickers" for a milisecond before it shows the number of todos which I get from localstorage.
Is there a way to prevent that behaviour? As far as I know the component gets rendered FIRST and then the useEffect hook gets triggered. How I render my component AFTER the data is pulled from localstorage?
The best way to do this would be with a lazy initial state. Also, cleaning up the variables and using a standardized [variable, setVariable] will save you headache debugging in the future.
const [alltodos, setAlltodos] = useState(() => {
const items = localStorage.getItem("todos");
const parsed = JSON.parse(items);
return parsed || "";
});
Initialize the allTodos state with null. As long as this state is null, render a notification or just return null to render nothing.
You can calculate the open todos count directly from the current alltodos state, without the need of useEffect.
const [alltodos, handleTodos] = useState(null);
useEffect(() => {
const items = localStorage.getItem("todos");
const parsed = items ? JSON.parse(items) : [];
handleTodos(parsed);
}, []);
useEffect(() => {
localStorage.setItem("todos", JSON.stringify(alltodos));
}, [alltodos]);
if(alltodos === null) return 'Loading todos list';
// this is derived from state, so you don't have to create a state for it
const openTodosCount = alltodos.reduce((acc, o) => acc + !o.done, 0);
The fastest way would be to add new state that would be responsible for loading.
For example
const [isLoading, setIsLoading] = useState(true);
set it initially on true and then after you do all your calculations change it to false.
Then you can depend on that state and show div with a text "Loading" or anything else but when isLoading would go to false it will show component elements.

how to save array with useEffect React Native

I want save array data using react useEffect. Follow Example with class:
async componentDidMount() {
const users = await AsyncStorage.getItem('users');
if (users) {
this.setState({ users: JSON.parse(users) });
}
}
componentDidUpdate(_, prevState) {
const { users } = this.state;
if (prevState.users !== users) {
AsyncStorage.setItem('users', JSON.stringify(users));
}
}
how to implement the logic with React Hooks?
For componentDidMount logic you can use useEffect hook:
useEffect(() => {
const asyncFetch = async () => {
const users = await AsyncStorage.getItem("users");
if (users) {
// setter from useState
setUsers(JSON.parse(users));
}
};
asyncFetch();
}, []);
For componentDidMount use useEffect with dep array and useRef reference.
const prevUsers = useRef();
useEffect(() => {
const prevUsers = prevUsers.current;
// Some equal check function
if (!areEqual(prevUsers, users)) {
AsyncStorage.setItem("users", JSON.stringify(users));
}
prevUsers.current = users;
}, [users]);
Notice that in your current code, prevState.users !== users is always truley, you comparing two objects and in JS {} !== {} always results true.
You can try like below and you can use hooks in functional based component not class based component
//state declaration similar to class based component
const [usersdata,setUsers] = useState([]);
const users = await JSON.parse(AsyncStorage.getItem('users'));
//whenever the value of users changes useEffect will reset the value of users in state useEffect handle the lifecycle in function based component
useEffect(()=>{
if(users){
setUsers(JSON.parse(users));
}
},[users])
For hooks the logic changes slightly, you would have to "hook" your effect with a state in order to update the component, so the component would update (componentDidUpdate) when the hooked state has been updated, you can obviously hook multiple states.
If you choose to not hook any state, the effect would execute only at the mounting of the component just like (componentDidMount())
I don't see the logic that makes you decide when to update the user state since you always get it from the storage, so I will assume that you have some kind of a trigger that makes you verify if the users value has changed in the storage.
so you can refactor your code like this:
const [users, setUsers] = useState([]);
const [userHasChanged, setUserHasChanged] = useState(false);
usEffect(async () => {
// comparing the old users with the new users is not useful since you always fetch the users from the storage, so the optimal is to always set the new array/ object to users, this way you avoid comparing the two objects which is a bit costly.
const newUsers = await AsyncStorage.getItem("users");
setUsers(JSON.parse(newUsers));
setUserHasChanged(false);
}, [userHasChanged])
// some code that triggers userHasChanged, you use setUserHasChaned(true)

getting undefined from useSelector react-redux hook

I have a component that dispatches action to the reducer using useDispatch hook inside useEffect. I only wants it to happen once on first component render. Then when I try to set the results from the store using useSelector hook I am getting undefined. Here is my code:
const SingleHotel = props => {
//GETTING HOTEL ID FROM REACT-ROUTER-DOM PROPS
const {
match: {
params: { id }
}
} = props
//INITIALIZE USE DISPATCH REDUX HOOK.
const dispatch = useDispatch()
//ACCESSING HOTEL DETAILS STATE FROM THE REDUX STORE.
const hotelDetails = useSelector(state => state.SingleHotel)
//DISPATCHING HOTEL DETAILS TO THE REDUCER.
useEffect(() => {
dispatch(fetchSingleHotel(id))
}, [])
return <div>Single hotel page {console.log(hotelDetails)}</div>
}
I am console logging state from the store in the JSX but I get 'undefined'.
However, when I check the results in the Redux dev tools it appears correctly.
Here is how it looks:

Categories