Set default state after object has been loaded - reactjs

I am using custom made useFirestore hook to retrieve file objects from my firestore collection.
import { useState, useEffect } from "react";
import { app } from "../firebase/firebase_storage";
const useFirestore = (collection) => {
const [songs, setSongs] = useState([]);
useEffect(() => {
const unsub = app
.firestore()
.collection(collection)
.onSnapshot((snap) => {
let songsArr = [];
snap.forEach((song) => {
songsArr.push({ ...song.data(), id: song.id });
});
setSongs(songsArr);
});
return () => unsub();
}, [collection]);
return { songs };
};
export default useFirestore;
Then I am storing this collection into a variable
const tracks = useFirestore("songs");
console.log(tracks) is giving
{songs: Array(2)}
I want to set the first track to be the default one which will be loaded into the waveform and I am using useState.
const [selectedTrack, setSelectedTrack] = useState(tracks.songs[0]);
This is not working because when the default state is set, the value I am passing is undefined because I am assuming it is not loaded. When I set some objects with placeholder data this works.
So my idea was that somehow postpone assigning default state until the object loads and it is not undefined.
Does anyone have an idea on how to achieve this?

You can use useEffect and set default state after tracks is initialized.
Try:-
const tracks = useFirestore("songs");
const [selectedTrack, setSelectedTrack] = useState('');
useEffect(() => {
setSelectedTrack(tracks.songs[0]);
}, [tracks])

Related

when i reload the page local storage will be empty so i want to store data after reloading in react

const [contacts, setContacts] = useState([]);
//Fetch the Value from Local Storage
useEffect(() => {
const retrieveContacts = localStorage.getItem(LOCAL_STORAGE_KEY);
if (retrieveContacts) {
setContacts(JSON.parse(retrieveContacts));
}
}, []);
//Storage the Value
useEffect(() => {
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(contacts));
}, [contacts]);
i tried to fetch data from local storage but it's not working
The use of useEffect hooks is the issue. At the second useffect setting the 'contacts' s not happeing since the first one hasn't yet been set. So the value of setting the localstorage goes null. Instead have the value set at the point of useState hooks. Also, when you are using localStorage, it's important to handle the initial state as well. Try the code below.
import React from 'react'
import { useState, useEffect } from 'react';
const App2 = () => {
const LOCAL_STORAGE_KEY = 'key-1'
const [contacts, setContacts] = useState(
localStorage.getItem(LOCAL_STORAGE_KEY) ? JSON.parse(localStorage.getItem(LOCAL_STORAGE_KEY)) : {name:'james', count:0}
);
useEffect(() => {
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(contacts));
}, [contacts]);
const handleButtonClickEvent = () => {
setContacts({name:'james', count:contacts.count + 1})
}
return (
<>
<div>Name: {contacts.name}</div>
<div>Country: {contacts.count}</div>
<input type='button' value='button' onClick={(e) => handleButtonClickEvent()}></input>
</>
)
}
export default App2
Your local storage will be empty after every reload because your default 'contacts' value is an empty array. Your second useEffect then saves this empty array to local storage when the component mounts, overwriting whatever value was previously saved there.
You can resolve this by getting rid of your first useEffect, and loading the default 'contacts' value from local storage:
const retrieveContacts = () => localStorage.getItem(LOCAL_STORAGE_KEY) || []; // Outside of component
...
const [contacts, setContacts] = useState(() => retrieveContacts()); // Inside of component
useEffect(() => {
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(contacts));
}, [contacts]);

Unsubscribe from listener in hook or in screen component?

I created a hook to manipulate users data and one function is listener for users collection.
In hook I created subscriber function and inside that hook I unsubscribed from it using useEffect.
My question is is this good thing or maybe unsubscriber should be inside screen component?
Does my approach has cons?
export function useUser {
let subscriber = () => {};
React.useEffect(() => {
return () => {
subscriber();
};
}, []);
const listenUsersCollection = () => {
subscriber = firestore().collection('users').onSnapshot(res => {...})
}
}
In screen component I have:
...
const {listenUsersCollection} = useUser();
React.useEffect(() => {
listenUsersCollection();
}, []);
What if I, by mistake, call the listenUsersCollection twice or more? Rare scenario, but your subscriber will be lost and not unsubscribed.
But generally speaking - there is no need to run this useEffect with listenUsersCollection outside of the hook. You should move it away from the screen component. Component will be cleaner and less chances to get an error. Also, easier to reuse the hook.
I prefer exporting the actual loaded user data from hooks like that, without anything else.
Example, using firebase 9 modular SDK:
import { useEffect, useMemo, useState } from "react";
import { onSnapshot, collection, query } from "firebase/firestore";
import { db } from "../firebase";
const col = collection(db, "users");
export function useUsersData(queryParams) {
const [usersData, setUsersData] = useState(undefined);
const _q = useMemo(() => {
return query(col, ...(queryParams || []));
}, [queryParams])
useEffect(() => {
const unsubscribe = onSnapshot(_q, (snapshot) => {
// Or use another mapping function, classes, anything.
const users = snapshot.docs.map(x => ({
id: x.id,
...x.data()
}))
setUsersData(users);
});
return () => unsubscribe();
}, [_q]);
return usersData;
}
Usage:
// No params passed, load all the collection
const allUsers = useUsersData();
// If you want to pass a parameter that is not
// a primitive or a string
// memoize it!!!
const usersFilter = useMemo(() => {
return [
where("firstName", "==", "test"),
limit(3)
];
}, []);
const usersFiltered = useUsersData(usersFilter);
As you can see, all the loading and cleaning-up logic is inside the hook, and the component that uses this hook is as clear as possible.

Why component is not refreshing in react hooks?

I tried to dispatch the API call using redux in useEffect hooks. After the response came to redux-saga response goes to reducer and the reducer updated the state successfully but my component is not refreshing.
import React, { useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import SubscriptionComponent from '../../Components/Subscription/Subscription';
import SubscriptionActions from '../../Redux/Subscription/Actions';
import {
getMySubscriptions,
getMySubscriptionByName,
getMySubscriptionByGroup,
} from '../../Redux/Subscription/Selectors';
const Subscription = (props) => {
const { navigation } = { ...props };
const [visible, setVisible] = useState(false);
const subscriptionList = useSelector((state) => getMySubscriptions(state));
const dispatch = useDispatch();
const [data, setData] = useState(subscriptionList);
const payload = {
memberId: '604f2ad047bc495a0a7fad26',
vendorId: '5fd484c39590020dc0dfb82a',
vendorOrgId: '5fd484439590020dc0dfb829',
};
useEffect(() => {
dispatch(SubscriptionActions.fetchMySubscriptions(payload));
}, [data]);
const onHandleSubscriptionByName = () => {
setVisible(false);
const subscription = getMySubscriptionByName(data);
setData(subscription);
};
const onHandleSubscriptionByGroup = () => {
setVisible(false);
const subscription = getMySubscriptionByGroup(data);
setData(subscription);
};
return (
<SubscriptionComponent
list={data}
navigation={navigation}
onPressList={(val) =>
navigation.navigate('SubscriptionDetails', { _id: val._id, name: val.name })
}
visible={visible}
openMenu={() => setVisible(!visible)}
closeMenu={() => setVisible(!visible)}
sortByName={() => onHandleSubscriptionByName()}
sortBySub={() => onHandleSubscriptionByGroup()}
/>
);
};
export default Subscription;
used reselect to get the state from redux.
export const getMySubscriptions = createSelector(mySubscriptionSelector, (x) => {
const mySubscriptions = x.map((item) => {
return {
_id: item._id,
image: 'item.image,
description: item.description,
name: item.name,
subscriptionGroup: item.subscriptionGroup,
subscriptionAmount: item.subscriptionAmount,
status: item.status,
delivery: item.delivery,
product: item.product,
};
});
return mySubscriptions ;
});
Why component is not refreshing.
Thanks!!!
You're storing the selection result in local state.
const subscriptionList = useSelector((state) => getMySubscriptions(state));
const [data, setData] = useState(subscriptionList);
useState(subscriptionList) will only set data initially not on every update.
EDIT:
Your setup is a little odd:
useEffect(() => {
dispatch(SubscriptionActions.fetchMySubscriptions(payload));
}, [data]);
Using data in the dependency array of useEffect, will cause refetching the data, whenever data is updated. Why? I looks like your sorting is working locally, so no need to refetch?
I would suggest to store the sort criteria (byName, byGroup) also in Redux and eliminate local component state, like that:
// ToDo: rewrite getMySubscriptions so that it considers sortCriteria from Redux State
const subscriptionList = useSelector(getMySubscriptions);
const dispatch = useDispatch();
};
useEffect(() => {
dispatch(SubscriptionActions.fetchMySubscriptions(payload));
// Empty dependency array, so we're only fetching data once when component is mounted
}, []);
const onHandleSubscriptionByName = () => {
dispatch(SubscriptionActions.setSortCriteria('byName'));
};
const onHandleSubscriptionByGroup = () => {
dispatch(SubscriptionActions.setSortCriteria('byGroup'));
};
As mentioned in the comments you will need to add a new action setSortCriteria and reducer to handle the sorting and adjust your selector, so that it filters the subscription list when a sortCriteria is active.
You do not update data after fetching new subscription.
const [data, setData] = useState(subscriptionList);
Only initializes data, but does not update it, you need to add useEffect to update data:
useEffect(() => {
setData(subscriptionList);
}, [JSON.stringify(subscriptionList)]);
JSON.stringify only used for deep compare complex objects, since useEffect only runs shallow compare and might miss, changes in objects.
-----EDIT------
Other problem might be that your getMySubscriptions function might need deep compare, since useSelector by itself doesn't do that, example might be:
import { useSelector, shallowEqual } from 'react-redux';
const subscriptionList = useSelector((state) => getMySubscriptions(state), shallowEqual);
Note that both solutions must be used.

Async/await specifically with Firestore forEach (React Native with Hooks)

Can someone provide some guidance on getting a specific item within a Firestore collection before the rest of the code moves on? I want to get the user item in the users collection before iterating through the reviews collection, and this is best achieved through using async/await as far as I know, but everywhere I look on stack overflow and elsewhere, they achieve it using setups/syntax much different to what I have been using.
Here's what I've got:
import React, { useState, useEffect } from "react";
import { Image, Text, TextInput, TouchableOpacity, View } from "react-native";
import styles from "./model/AccountStyles.js";
import { KeyboardAwareScrollView } from "react-native-keyboard-aware-scroll-view";
import { firebase } from "../firebase/config";
export default function AccountScreen({navigation }) {
const [existingReviewsArray, setExistingReviewsArray] = useState([]);
useEffect(() => {
let userID;
//get userID
firebase.auth().onAuthStateChanged((user) => {
const usersRef = firebase.firestore().collection("users");
usersRef
.doc(user.uid)
.get()
.then((document) => {
const data = document.data();
userID = data.id
});
});
//get reviews made by userID to useState
var addToReviewsArray = [];
const reviewsRef = firebase.firestore().collection('reviews');
reviewsRef.get().then((querySnapshot) => {
querySnapshot.forEach(snapshot => {
if (snapshot.data().userID == userID){
var existingReview = snapshot.data();
addToReviewsArray = ([...addToReviewsArray , existingReview]);
}
}
)
setExistingReviewsArray(addToReviewsArray);
});
}, []);
What would be the best way to go about this? Would appreciate any guidance here.
Explanation of the code below
useEffect hooks are async by design, and the Firebase API is too, so what I try to do clearly with this example is show how to implement a reactive programming approach. I don't use the async/await pattern here. This may also not be the best way of doing things - it's just the first thing that came to mind. :)
The code below "reacts" to the change of the user's auth state because initially the userId is undefined:
const [userId, setUserId] = useState(); // this means userId is undefined
The userId variable is populated in the first useEffect, which has no dependencies (the empty []), meaning that it should only run once when the component is mounted.
There is then a second useEffect hook created to listen to changes in the userId variable, which runs a function that can then use the newly populated variable.
Here's how I would do it. :)
import { useState, useEffect } from "react";
export default function AccountScreen({ navigation }) {
const [userId, setUserId] = useState();
const [existingReviewsArray, setExistingReviewsArray] = useState([]);
// Create a useEffect [with no dependencies] that runs once to get the userId
useEffect(() => {
firebase.auth().onAuthStateChanged((user) => {
const usersRef = firebase.firestore().collection("users");
usersRef
.doc(user.uid)
.get()
.then((document) => {
const data = document.data();
// Use the set state hook here to trigger the second useEffect below
setUserId(data.id);
});
});
}, []);
// Create a useEffect that is triggered whenever userId is changed
useEffect(() => {
// get reviews made by userID to useState
var addToReviewsArray = [];
const reviewsRef = firebase.firestore().collection("reviews");
reviewsRef.get().then((querySnapshot) => {
querySnapshot.forEach((snapshot) => {
if (snapshot.data().userID == userID) {
var existingReview = snapshot.data();
addToReviewsArray = [...addToReviewsArray, existingReview];
}
});
setExistingReviewsArray(addToReviewsArray);
});
}, [userId]);
// Use your `existingReviewsArray` in the render
}

React JS: Get Context Data After getting success on API Call

I am stuck at getting context data.
I have a context and a component which uses its data.
I need to get the updated data of context's variable on API call success in my component.
so How can I do that ?
Here what I have tried.
context.js
import React, { useState, createContext,useEffect } from 'react';
import {getData} from './actionMethods';
const NewContext = createContext();
function newContextProvider(props) {
const [dataValue, setData] = useState([])
useEffect(() => {
const fetchMyData = async () => {
const dataValue = await getData(); // this is an API call
setData(dataValue)
};
fetchMyData();
}, []);
return (
<NewContext.Provider
value={{
state: {
dataValue
},
actions: {
}
}}
>
{props.children}
</NewContext.Provider>
);
}
const newContextConsumer = newContext.Consumer;
export { newContextProvider, newContextConsumer, newGridContext };
myComponent.js
import React, { useState, useContext } from 'react'
import context from './context'
import deleteAPI from './actionMethods'
function myComponent(props) {
const id= 10
const {state,actions} = useContext(context)
deleteAPI(id).then(res => {
if (res){
// what should I write here to get the updated Data from the context which will call an API to get the updated data.
}
})
}
Any help would be great.
Thank You.
As a generic example, one option is to fetch the data from the server when the app loads in the front-end. From there you can send requests to modify the server data and at the same time update your local version. Something like:
Fetch data and save it to the local store: [{id: 0, name: 'first'},{id: 1, name: 'second'}]
Modify the data sending a request to the server. For example deleting an item. id: 0
Once the server responds confirming the operation was successful you can modify that data in the local store. [{id: 1, name: 'second'}]
You can handle the data using a Redux store or a React Context. For example, using a Context:
export const ItemsContext = createContext([]);
export const ItemsContextProvider = props => {
const [items, setItems] = useState([]);
const deleteItem = id => {
deleteItemsAxios(id).then(() => {
setItems(items => items.filter(item => item.id !== id));
});
};
useEffect(() => {
const fetchItems = async () => {
const items_fetched = await fetchItemsAxios();
if (items_fetched) {
setItems(items_fetched);
} else {
// Something went wrong
}
};
fetchItems();
}, []);
return (
<ItemsContext.Provider
value={{
items,
deleteItem
}}
>
{props.children}
</ItemsContext.Provider>
);
};
We define a Component that will manage the data fetch. The data items are inside a state. When the Component mounts we fetch the items and save them in the state. If we want to delete an item we first call the corresponding fetch function. Once it finishes, if it was successful, we update the state and remove that item. We use React Context to pass the items data, as well as the deleteItem function, to any component that needs them.
Let me know if you need more explanation.

Resources