keep the data in app even when i navigate to other pages and back - reactjs

I'm using React and Next.js with Firestore. On one page I get data from Firebase with useEffect only once the page is rendered. But since the get is kind of costly (lots of read), I want to persist the data fetched even when the user navigates to other pages and back to this page, so that I don't need to fetch again. How can I do that? Thanks!
useEffect(() => {
var newObj = {};
FB.getAllFiles()
.then((snapshot) => {
snapshot.forEach((doc) => {
const data = doc.data();
newObj[data["name"]] = data["count"];
});
})
.then(() => {
setCounts(newObj);
})
.catch((e) => console.log(e));
}, []);

There are multiple ways but one good way would be to use Context, create a data store context which would store your data and then you can read from it as a single source of truth.

As explaied in the other answers use the context and to make it even better you can persist the realtime listeners by controlling when you want to start or stop theme. Here is an example of Providers made for Firebase that do exactly the same. They also enable offline working for PWAs and persist state between reloads.
It's importand to understand that if you don't persist the realtime listeners you won't have any benefit from storing the data into any local state. Calling the listener again would load all data again and cost you the same. The only benefit from that would be that the user has the local data initial so it looks like the app is faster.

Related

How to clear & invalidate cache data using RTK Query?

I was facing a problem for sometime, that was I'm unable to clear cache using RTK query.
I tried in various ways but cache data is not clear.
I used invalidatesTag in my mutation query and it called the api instantly. But in this case I want to refetch multiple api again, but not from any rtk query or mutation. I want to make the api call after some user activity like click.
How can I solve this problem?
I made a separate function where I return api.util.invalidateTags(tag) or api.util.resetApiState().
this is my code-snipet:-
` const api = createApi({.....})
export const resetRtkCache = (tag?: String[]) => {
const api =
if (tag) {
return api.util.invalidateTags(tag)
} else {
return api.util.resetApiState()
}
}`
& I called it using dispatch method from other files
`const reloadData = () => {
dispatch(resetRtkCache())
}`
but here cache data is not removed.I think dispatch funtion is not working. I don't see the api call is being sent to server in the browser network.
But in this case I want to refetch multiple api again, but not from
any rtk query or mutation. I want to make the api call after some user
activity like click. How can I solve this problem?
So if I understood correctly what you want to achieve is to fetch some api that you have in RTK only after some kind of user interaction?
Can't you just define something like this?
const { data } = useGetYourQuery({ skip: skipUntilUserInteraction })
Where skipUntilUserInteraction is a component state variable that you will set to true and update to false based on the user interaction you need? (e.g. a click of a button).
So essentially on component render that specific endpoint will be skipped but will be fetched after the interaction that you want will happen?
wow, you actually asking so many questions at once. but I think you should definitely read the documentation because it covers all the questions you have.
so trying to answer your questions one by one.
I used invalidatesTag in my mutation query and it called the api instantly.
invalidating with Tags is one of the ways to clear the cache.
you should first set the tagTypes for your API then use those tags in mutation queries and tell the RTK query which part of entities you want to clear.
I want to refetch multiple APIs again
you can customize the query inside of a mutation or query like this example and by calling one function query you can send multiple requests at once and if you want to fetch the API again after the cache removed you do not need to do anything because RTK query will do it for you.
I want to make the API call after some user activity like click
every mutation gives u a function that you can pass to onClick like below:
import { use[Mymutation]Mutation } from 'features/api';
const MyComponenet() {
const [myMutationFunc, { isLoading, ...}] = use[Mymutation]Mutation();
return <button type='button' onClick={myMutationFunc}>Click for call mutaion</button>
}
and remember if you set providesTags for your endpoint which you were defined in tagTypes by clicking on the button and firing up the myMutationFunc you will be clearing the cache with those tags.
and if you looking for an optimistic update for the cache you can find your answer in here.
async onQueryStarted({ id, ...patch }, { dispatch, queryFulfilled }) {
const patchResult = dispatch(
api.util.updateQueryData('getPost', id, (draft) => {
Object.assign(draft, patch)
})
)
try {
await queryFulfilled
} catch {
patchResult.undo()
}
}

Firebase data deleted upon page refresh in React

I have been stumped on a work around for this problem for a while now and was hoping someone could help.
I am currently working on a React UI that sends info to the backend Firebase for a budgeting app.
When the page first loads, I pull in the data using this:
const [incomeSources, setIncomeSources] = React.useState([]);
/////////////////////////////////
// PULL IN DATA FROM FIREBASE //
///////////////////////////////
async function getData() {
const doc = await getDoc(userCollectionRef);
const incomesData = doc.data().incomeSources;
// const expensesData = doc.data().expenses;
// const savingsData = doc.data().savingsAllocation;
// SET STATES //
if (incomesData.length > 0) {
setIncomeSources(incomesData);
}
}
then when I want to add a new object to the state array I use a input and button. The issue I currently have is that I have it set up like this:
async function updateFirebaseDocs(userID, stateName, state) {
const userRef = doc(db, "users", userID);
try {
await setDoc(userRef, { [stateName]: state }, { merge: true });
} catch (err) {
console.error("error adding document", err);
}
}
React.useEffect(() => {
updateFirebaseDocs(userID, 'incomeSources', incomeSources)
},[incomeSources])
this works so long as I don't refresh the page, because upon page refresh, incomeSources defaults back to an empty array on render. Causing firebase docs to become an empty array again which deletes firestore data.
I can't for the life of me figure out the workaround even though I know its probably right in front of me. Can someone point me in the right direction please.
Brief summary: I am able to pull in data from backend and display it, but I need a way to keep the backend database up to date with changes made in the Frontend. And upon refreshing the page, I need the data to persist so that the backend doesn't get reset.
Please advise if more information is needed. First time posting.
I have tried using the above method using useEffects dependency, I have also tried using localstorage to work around this but also don't can't think of a way of implementing it. I feel I am tiptoeing around the solution.

Update React Component With Updated Data From Firestore

I have a chrome extension that stores data in Firestore and populates that data to the frontend. I always have to refresh the page to see newly added data, which isn’t a user friendly experience. How can I update the UI to show the newly updated data without having to refresh the page?
So far, I've tried using useEffect to get the data. Inside of it, I'm using a function that gets data from Firestore cached inside of chrome local storage.
Here is my code
const getFolderData = () => {
getDataFromChrome("docId").then((res: any) => {
setDocId(res.docId);
});
getDataFromChrome("content").then((res: any) => {
//console.log("getting in mainfolder",res);
// for (const item of res.content) {
// if (item.type.toLowerCase() === "subfolder") {
// // console.log(item)
// getSubFolder(item.id);
// }
// }
for (const item of res.content) {
setTiersContent((pre: any) => [...pre, item]);
}
});
};
useEffect(() => {
getFolderData();
}, []);
I also get this error. I'm also using the chrome extension API to communicate with a background script. It could be related to the problem
Uncaught (in promise) Error: A listener indicated an asynchronous response by returning true, but the message channel closed before a response was received
I've never used firebase so I'm not sure what your functions do, I can only guess. A few things wrong from what I can see:
Your useEffect is set to only run on page load since the dep array is empty, I assume you want to refetch on some condition.
If any of the 2 functions is supposed to be a subscription, your useEffect needs to return a cancel function.
Refetch data when needed is not a new problem, packages like React Query has tools that optimize your requests and refetch when needed. I suggest you give it a shot if your app has more than 2-3 fetch requests.

update state of the parent screen if an update occurs in current screen

I am using react-navigation 6 and react-native CLI, to make a chat application. I want to achieve this feature which is common in every chat application that when we send a message to someone, and go back to homescreen of the app (where all conversations are listed), the last message sent, can be seen.
Like if I sent message and pressed the back button, it will navigate me to home screen where all my conversations are, and it should update the conversation where I sent the message.
I have tried route.params, but it gives a warning that non-serializable values found.
React navigation warning
Also, I have heard that passing setState function to child component is very bad practice as it is mentioned here
I also tried navigation_events along with useEffect , this was a surprise to me that it didn't work either.
When I refresh the screen manually, or logout and log in, then it refreshes completely, but doesn't when I go back from application.
React.useEffect(() => {
navigation.addListener('focus', e => {
fetchConvos();
});
return () => {};
}, []); //also tried [navigation] instead of []
const fetchConvos = () => {
fetch('http://localhost:15000/' + id + '/conversations', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
redirect: 'follow',
referrerPolicy: 'no-referrer',
})
.then(res => res.json())
.then(received => {
if (received?.response !== false) {
setConversations(received);
}
});
};
I have checked the id , received and even setConversations, they all are updating, but my screen still rendering the old messages.
Also, I don't want to use Async Storage or redux for this simple problem like this.
I can share the complete code if this isn't enough.
EDIT
I figured out one more way to update it may help clarify the situation more.
React.useEffect(() => {
navigation.addListener('focus', e => {
setConversations([]); //first setting convos to empty
fetchConvos(); //then fetching new data
});
return () => {};
}, []);
But this method is quite slow as I am updating the state of conversations twice.
I would appreciate if someone can help me here.
By taking the last 2 samples of code, I'd go down the route of setting the state to change the data. Like, I don't know the structure of your code completely. But i'm assuming you're using useEffect inside some component, right? In that case, React Context might be what you're looking for:
How to use react hooks on react-native with react-navigation
It allows to share informations, without having to build a structured store like redux. You should probably working on redesign a bit the code as, if you're following the current logic, you're going to split data pool of the conversation in the menu and load them when the "back" navigation event occurs, right?
Whilst the conversation data should be shared and available to both components, regardless where you're.
At least I'd rethink it this way to allow consistent data throughout the whole application.
Unless you've to do something specific and on-spot, of course.

React state updating on its own after axios request?

I'm currently working on a React app where I'm using React's context API to put together the business logic of the app.
Inside the context all CRUD functions make axios requests to the API and return promises that can be used by components to deal with error handling and input. This is the context https://github.com/luckyrose89/notes-app/blob/master/src/AppContext.js
My app has a notebooks array that will contain individual notebook objects. And each of these objects has a notes array that refers to the notes made in each notebook. This is the APIs controller https://github.com/luckyrose89/notebook-app-backend/blob/master/controllers/notebook.js
As long as I was creating, reading, updating and deleting the notebooks, I had to make changes to the state in app context for things to get updated in my app. But when I create, read, update or delete anything in the notes array of the notebooks, I do not have to make those changes to the state. I return the promise and the state changes on its own. Can anyone help me understand why is this happening?
For example when I create a new note in a notebook and submit it using this:
handleSubmit = event => {
event.preventDefault();
this.props
.createNotepage(this.props.match.params.id, this.state)
.then(response => {
this.clearInputs();
this.props.history.push("/viewbooks");
})
.catch(err => {
this.setState({
errorMessage: err.response.data.error
});
});};
I don't have to change the state in the app's context by manually adding the response from the api to the notebook's notes array. Can anyone please explain this.
instead of passing this.state, try passing {...this.state} and see what happens

Resources