How to refresh the value stored in context on an action? - reactjs

So, i am fetching data from api and doing some manipulation on it and providing it to a component from context.
After I update the form i want to fetch new values and do the same thing again but its not being called automatically
const { isLoading: assayMatrixIsLoading, data: matrixData } = useQuery('Assay Matrix Data', fetchAssayMatrixes);
useEffect(() => {
// console.log('context mount');
}, []);
useEffect(() => {
if (!assayMatrixIsLoading) {
const unflattenedData = getDataForAutocomplete(unflattenObject(matrixData));
setDataForAutoComplete(unflattenedData);
}
}, [assayMatrixIsLoading]);
return dataForAutoComplete ? (
<AssayContext.Provider
value={{
dataForAutoComplete,
validation,
...
<AssayContextProvider>
<CreateAssayStepper />
</AssayContextProvider>
so how can i auto call it let say on route change or on submit like that?

Related

How to call search api from form submit and change the data value for table?

Trying to have a search field for a table where upon submit I form the payload and pass that payload to API for getting relevant result set and change the data value passed to table for new.
Following is my code where i am stuck as how to call api and change the data for it
const {
isLoading: accessioningListLoading,
data: accessioningData,
isError: accessioningRetrievalError,
error: accessioningRetrievalErrorMsg
} = useAccessioningListData(onAccessioningRetrievalError);
export const useAccessioningListData = (onError) => {
// const transformFn = mapperFn ? mapperFn : mapAccessioningData;
return useQuery('accessioning-order-pending-list', () => AccessioningApi.getOrderPendingList(''), {
//the select function allows you to manipulate the data coming in from the fetch
select: mapAccessioningData,
onError
});
};
...
const formik = useFormik({
initialValues: orderpendingListSearchInitialValues,
onSubmit: async (values, actions) => {
const params = {
//payload data
};
var queryString = Object.keys(params)
//#ts-ignore
.map((key) => key + '=' + params[key])
.join('&');
const formatValues: string = '?' + queryString;
console.log(formatValues);
//fetch data via api hit but getting cant call hooks here
}
});
....
<ReactTable
columns={columns}
csvFilename={'OrderPending'}
minimizedColumns={customColumns}
//#ts-ignore
data={accessioningData || []}
enableHiddenColumns={true}
hiddenOnLoad={hiddenOnLoad}
getHeaderProps={(column: any) => column.getSortByToggleProps()}
/>
any help will be appreciated to how to approach it
Thanks

React component uses old data from previous API call

I am using React Query to fetch data from an API I have built. The component is rendering the old data from the previous api call and not updating with new the data from the new api call.
The new data is only rendering when I refresh the page.
Component:
export const ProfilePageStats = (props: {
user: User;
id: number;
}) => {
const { chatId } = useParams();
const { status: subscribeStatus, data: subscribeData } =
useSubscriptionsWithType(
chatId ? chatId : "",
props.id,
props.user.id,
"SUBSCRIBE"
);
const { status: unsubscribeStatus, data: unsubscribeData } =
useSubscriptionsWithType(
chatId ? chatId : "",
props.id,
props.user.id,
"UNSUBSCRIBE"
);
if (unsubscribeStatus == "success" && subscribeStatus == "success") {
console.log("Working", unsubscribeData);
return (
<ProfilePageStatsWithData
user={props.user}
subscribed={Object.keys(subscribeData).length}
unsubscribed={Object.keys(unsubscribeData).length}
/>
);
}
if (unsubscribeStatus == "error" && subscribeStatus == "error") {
console.log("error");
return <ProfilePageStatsLoading />;
}
if (unsubscribeStatus == "loading" && subscribeStatus == "loading") {
console.log("loading");
return <ProfilePageStatsLoading />;
}
return <ProfilePageStatsLoading />;
};
export const useSubscriptionsWithType = (
chatId: string,
id: number,
userId: number,
type: string
) => {
return useQuery(
["subscriptionsWithType"],
async () => {
const { data } = await api.get(
`${chatId}/subscriptions/${id}/${userId}?type=${type}`
);
return data;
},
{
enabled: chatId > 0 && userId > 0,
refetchOnWindowFocus: false,
}
);
};
The component should update to show the new user values but shows the previous user values. If I click out and select a different user entirely it then shows the values for the previously clicked user.
I can see that React Query is fetching with the correct values for the query but the component still renders the old user data?
It turns out that the fetchStatus value is changing to "fetching" but it not actually calling the api. Hence, why its only using the old values?
Your key part of the useQuery is what tells the hook when to update.
You only use ["subscriptionsWithType"] as key, so it will never know that you need to refetch something.
If you add userId there, it will update when that changes.
So, using
return useQuery(
["subscriptionsWithType", userId],
async () => {
...
will work.
It is likely, that you want all the params, that you use in the url, to be added there.
I solved it by adding a useEffect and refetching based on the changing user id.
useEffect(() => {
refetch();
}, [props.user.id]);

useState not updating rendered values after getting document snapshots

I am having a problem assigning data to useState by fetching data using reference type value from firebase.
const [preOil, setPreOil] = useState([]);
const [oilChange, setOilChange] = useState([]);
useEffect(() => {
getDocs(query(collection(db, "oil"), orderBy("timestamp"))).then(
(snapshot) => {
setPreOil(
snapshot.docs.map((doc) => ({
id: doc.id,
...doc.data(),
}))
);
}
);
}, []);
useEffect(() => {
let current = preOil.length > 0 ? [...preOil] : [];
current.map((_oil, i) => {
getDoc(_oil.oilReference).then((oilRef) => {
current[i].oilReference = oilRef.data();
});
});
setOilChange(current);
}, [preOil]);
In the first useEffect, the data is fetched successfully in this form,
preOil = {
id:"RxbAOAIs8d3kGOHNskJ4",
oilReference: Ta {converter: null, _key: ut, type: 'document', firestore: Na},
customerName: "USAMA",
}
In the second useEffect based on [preOil], I need to reassign the oilReference with the data fetched from firestorm through its reference(oilReference), The data is successfully fetched from the database and assigned to current but The main problem is when I set to state setOilChange(current) it updates my oilChange state when I inspect in inspect tools in chrome but in JSX the changes don't reflect
I am updating the state in useEffect
I am having desired data assigned in a local variable and assign that variable to the state
Then What am I missing?
In your second useEffect(), more specifically, in
current.map((_oil, i) => {
getDoc(_oil.oilReference).then((oilRef) => {
current[i].oilReference = oilRef.data();
});
});
setOilChange(current);
You are mutating the content of the current variable. This mutation, because it is async, will happen after the setOilChange call. Such mutation will thus not trigger a re-render.
What you need is to instead first wait for all the docs to be loaded and only after that set the state. Example:
const _docs = current.map((_oil, i) => {
return getDoc(_oil.oilReference).then((oilRef) => { // changed here
return { // changed here
...current[i], // changed here
oilReference: oilRef.data() // changed here
} // changed here
}); // changed here
});
Promise.all(_docs).then(() => {
setOilChange(_docs);
});
Also notice I didn't mutate current, rather I returned a new object. This is not mandatory but just a best practice overall.

How can i auto refresh or render updated table data form database in material UI table after doing any operation in React?

Here useVideos() give us all videos form database. After adding a new video the new entry is not append in the Material UI table , but if I refresh the page then it's showing that new entry. Now I want to show this new entry after add operation. Please help me to do this! Thanks in Advance.
const initialState = [{}];
const reducer = (state, action) => {
switch (action.type) {
case "videos":
const data = [];
let cnt = 1;
action.value.forEach((video, index) => {
data.push({
sl: cnt,
noq: video.noq,
id: index,
youtubeID: video.youtubeID,
title: video.title,
});
cnt = cnt + 1;
});
return data;
default:
return state;
}
};
export default function ManageVideos() {
const { videos, addVideo, updateVideo, deleteVideo } = useVideos("");
const [videoList, dispatch] = useReducer(reducer, initialState);
useEffect(() => {
dispatch({
type: "videos",
value: videos,
});
}, [videos]);
const columns = [
{ title: "Serial", field: "sl" },
{ title: "Title", field: "title" },
{ title: "Number of Qusetion", field: "noq" },
{ title: "Youtube Video Id", field: "youtubeID" },
];
return (
<div>
<MaterialTable
title="Videos Table"
data={videoList}
columns={columns}
editable={{
onRowAdd: (newData) =>
new Promise((resolve, reject) => {
setTimeout(() => {
addVideo(newData);
resolve();
}, 1000);
}),
onRowUpdate: (newData) =>
new Promise((resolve, reject) => {
setTimeout(() => {
updateVideo(newData);
resolve();
}, 1000);
}),
}}
/>
</div>
);
}
Since the information provided is a bit lacking, I'll assume that the useEffect hook is not working when you update your videos (check it with consle.log("I'm not working") and if it does work then you can just ignore this answer).
You can define a simple state in this component, let's call it reRender and set the value to 0, whenever the user clicks on the button to add a video you should call a function which adds 1 to the value of reRender (()=>setReRender(prevState=>prevState+1)). In your useEffect hook , for the second argument pass reRender. This way, when the user clicks to submit the changes , reRender causes useEffect to run and dispatch to get the new data.
If this doesn't work , I have a solution which takes a bit more work. You will need to use a state manager like redux or context api to store your state at a global level. You should store your videos there and use 1 of the various ways to access the states in this component (mapStateToProps or store.subscribe() or ...). Then pass the video global state as the second argument to useEffect and voilĂ , it will definitely work.

Firestore: calling collections.get() inside promise()

useEffect(() => {
if (!stop) {
// get current user profile
db.collection('events').get(eventId).then((doc) => {
doc.forEach((doc) => {
if (doc.exists) {
let temp = doc.data()
let tempDivisions = []
temp["id"] = doc.ref.id
doc.ref.collection('divisions').get().then((docs) => {
docs.forEach(doc => {
let temp = doc.data()
temp["ref"] = doc.ref.path
tempDivisions.push(temp)
});
})
temp['divisions'] = tempDivisions
setEvent(temp)
setStop(true)
// setLoading(false);
} else {
// doc.data() will be undefined in this case
console.log("No such document!");
<Redirect to="/page-not-found" />
}
})
})
}
}, [stop, eventId]);
I am curious if this is the properly way to extract nested data from Cloud Firestore.
Data model:
Collection(Events) -> Doc(A) -> Collection(Divisions) -> Docs(B, C, D, ...)
Pretty much I'm looking to get metadata from Doc(A), then get all the sub-collections which contain Docs(B, C, D, ...)
Current Problem: I am able to get meta data for Doc(A) and its subcollections(Divisions), but the front-end on renders metadata of Doc(A). Front-End doesn't RE-RENDER the sub-collections even though. However, react devtools show that subcollections(Divisions) are available in the state.
EDIT 2:
const [entries, setEntries] = useState([])
useEffect(() => {
let active = true
let temp = []
if (active) {
divisions.forEach((division) => {
let teams = []
let tempDivision = division
db.collection(`${division.ref}/teams`).get().then((docs) => {
docs.forEach((doc, index) => {
teams.push(doc.data())
})
tempDivision['teams'] = teams
})
setEntries(oldArray => [...oldArray, temp])
})
}
return () => {
active = false;
};
}, [divisions]);
is there any reason why this is not detecting new array and trigger a new state and render? From what I can see here, it should be updating and re-render.
Your inner query doc.ref.collection('divisions').get() doesn't do anything to force the current component to re-render. Simply pushing elements into an array isn't going to tell the component that it needs to render what's in that array.
You're going to have to use a state hook to tell the component to render again with new data, similar to what you're already doing with setEvent() and setStop().

Resources