React / Graphql / Apollo, Not refetch data after update states and change routes - reactjs

I have a graphql query that shows me a list of users, when I update the list with a mutation, the state change well, but when I change my route and I come back to the old one, it return the old state.I have to do a hard refresh on my browser to have the new list.
This is my query :
export const group = (id) => {
const data = Client.query({
query: gql`
query group($id: Int) {
group(_id: $id) {
_id
name
lat
lng
is_private
creation_date
}
}
})
This is my component :
async componentWillMount() {
try {
var data = await group(632);
var result = data.data.group[0];
this.setState({
group: result
});
} catch (e) {
console.log(e);
}
}
updateGroup = async() => {
try {
var data = await groupUpdate(501, 632, {
name: this.state.group.name,
is_private: this.state.group.is_private,
address: this.state.group.address,
creation_date: this.state.group.creation_date,
nbrEmployees: this.state.group.nbrEmployees
});
notifyUser(NOTIFY.success, "Done");
this.toggleInfo();
} catch (e) {
notifyUser(NOTIFY.error, "Error Serveur");
}
}
Any help please ?

Related

send multiple RTK query request in response of another RTK Query

I am new to RTK Query and when I fetch some data from an endpoint I get a response of an array of objects for each id of item in the list I have to call another API to get the details of each item.
but I do not know have to achieve this.
for example:
query: () => '/posts'; // response is ==> [{id: 21, title:'Hello world'}]
and the for the details of the post with an id of 21
query: (id) => `post/${id}/detail`; // response { description:'', img:'', ... }
I need to show all posts with details. and for that, I have to get all the details on the list first and then return the result from query to later show it on the page.
i came up with this so far
import { createApi } from '#reduxjs/toolkit/query/react';
import axiosBaseQuery from 'api/axiosBaseQuery';
import axios from 'services/request';
export const postsApi = createApi({
reducerPath: 'api/portfolio',
baseQuery: axiosBaseQuery(),
endpoints: (builder) => ({
getUserPosts: builder.query({
async queryFn() {
// get a random user
const postsList = await axios.get('/posts');
if (postsList.error) return { error: postsList.error };
// const result = await axios.get(`/market/instruments/${item.refId}/summary`);
const endpoints = postsList.data.map((item) =>
axios.get(`/post/${item.id}/details`)
);
let error = null;
let data = null;
try {
data = await Promise.all(endpoints).then((res) => {
return res.map((item, index) => {
return { ...item.data, ...postsList.data[index] };
});
});
} catch (err) {
error = err;
}
return data ? { data } : { error };
}
})
})
});
export const { useGetUserPostsQuery } = postsApi;

Recoil Async data request with atomFamily

I'm using an atomFamily with a default value of a selectorFamily to get some order data:
export const orderState = atomFamily<Order | undefined, string>({
key: 'orderFamily',
default: selectorFamily({
key: 'orderSelectorFamily',
get:
orderId =>
async ({ get }) => {
try {
const response = await getOrder(orderId);
return response.data;
} catch (e) {
console.log('error', e);
}
},
}),
});
This is used when the page loads and id is captured from the URL and used in a React component:
export const useGetOrderValue = (orderId: string) => {
return useRecoilValue_TRANSITION_SUPPORT_UNSTABLE(orderState(orderId));
};
And in the Component
const order = useGetOrderValue(id);
I also need to be able to get the order data from an order search that'll then redirect to the order page. So I'm getting the order data from a request and setting it manually using a useRecoilCallback function:
const getOrder = useRecoilCallback(
({ set }) =>
async (orderId: string) => {
try {
const response = await requestGetOrder({ orderId });
set(orderState(orderId), response.data);
} catch (e) {
console.log('error', e);
}
},
[],
);
It all seems to work fine but I feel like I'm duplicating effort within the useRecoilCallback. Is there a better way to do this?

apollo client 3 cache doesn't update after mutation

First of all, i'd like to apologize if this is a duplicate but none of the existing answers in similar questions helped me out.
I am using nextjs 9.5.3 and apollo client 3.2.2 . I have a form where a user fills it out to update their profile details. On submission, the data saved is saved in the database and a response returned back to the client. The issue is that the response is unable to update the cache but it can be found inside ROOT_MUTATION according to apollo devtools.
I use a query to initially load the user's data then update the cache with the result during the mutation. Below are the local query and mutation.
// fragments
export const editUserProfileFragment= gql`
fragment EditUserProfileFields on ProfileInterface {
id
type
slug
name
location {
name
address
country
latitude
longitude
}
settings
createdAt
isActive
}
`;
// query
export const editUserProfileQuery = gql`
query EditUserProfile($slug: String) {
Profile(slug: $slug) {
...EditUserProfileFields
}
}
${editUserProfileFragment}
`;
// mutation
export const editUserProfileMutation = gql`
mutation EditUserProfile($id: ObjectID!, $profile: ProfileInput!) {
editProfile(id: $id, profile: $profile) {
...EditUserProfileFields
}
}
${editUserProfileFragment}
`;
Here's how i use the query and mutation:
// the query
const { error, data, loading } = useQuery(editUserProfileQuery, {
variables: { slug },
fetchPolicy: "network-only",
})
// data returns a `Profile` object
// the mutation
const [editUserProfileMutate] = useMutation(editUserProfileMutation)
...
// save data
try {
const response = await editUserProfileMutate({
variables: { id, profile: inputdata },
// update: (cache, { data }) => {
// const cachedData: any = cache.readQuery({
// query: editUserProfileQuery,
// variables: { slug: newProfile.slug }
// });
// const cacheId = cache.identify(data.editProfile) // to see the id, i wanted to try cache.modify() but didn't how to proceed.
// console.log('update.cachedData', cachedData);
// console.log('update.cachedData.Profile', cachedData.Profile);
// console.log('update.data', data);
// const newData = { ...cachedData.Profile, ...data.editProfile }; // i first used [] but
// console.log('newData', newData);
// // cache.writeQuery({
// // query: editUserProfileQuery,
// // variables: { slug: newProfile.slug },
// // data: { editProfile: newData }
// // })
// // cache.modify({
// // id: cacheId,
// // })
// },
// tried the below but didn't work
// refetchQueries: [{
// query: editProfilePageQuery,
// variables: { slug: newProfile.slug },
// }],
// awaitRefetchQueries: true
});
const updatedProfile = response.data.editProfile;
console.log('updatedProfile', updatedProfile);
....
} catch (error) {
....
} // trycatch
Also the below apollo client is mainly based on nextjs with-apollo example:
...
let apolloClient;
...
const cache = new InMemoryCache({
// https://www.apollographql.com/docs/react/data/fragments/#using-fragments-with-unions-and-interfaces
dataIdFromObject: result => `${result.__typename}:${result._id || result.id || result.name || result.slug || Math.floor(Math.random() * 1000000)}`,
possibleTypes: {
ProfileInterface: ["Star", "User"],
},
// #see https://www.apollographql.com/docs/react/caching/cache-field-behavior/#merging-non-normalized-objects from console warnings
typePolicies:
User: {
fields: {
location: {
merge(_existing, incoming) {
return incoming;
},
},
},
},
},
});
function createClient() {
const link = makeLink();
return new ApolloClient({
cache,
link,
connectToDevTools:typeof window !== 'undefined',
ssrMode: typeof window === 'undefined',
});
}
export function initializeApollo(initialState = null) {
const _apolloClient = apolloClient ?? createClient()
if (initialState) {
const existingCache = _apolloClient.extract()
console.log('existingCache', existingCache);
// _apolloClient.cache.restore({ ...existingCache, ...initialState }) // commented out on purpose
_apolloClient.cache.restore(initialState)
}
if (typeof window === 'undefined') return _apolloClient
if (!apolloClient) apolloClient = _apolloClient
return _apolloClient
}
export function useApollo(initialState) {
const store = useMemo(() => initializeApollo({ initialState }), [initialState])
return store
}
So after going through my backend source code, i found out that i wasn't returning the updated values from the database but rather the old ones instead. I fixed it and it works as it should.

Query data doesn't update after successful apollo cache write

I have a query on my App.js:
import { gql } from 'apollo-boost';
const ALL_ITEMS_QUERY = gql`
query ALL_ITEMS_QUERY {
challenges {
id
title
}
goals {
id
title
completed
createdAt
updatedAt
steps {
id
completed
title
}
}
}
`;
And i am looking to write a simple deleteGoal mutation:
const DeleteWrapper = (props) => {
const [deleteGoal, { data }] = useMutation(DELETE_ITEM_MUTATION, {
update(cache, payload) {
const data = cache.readQuery({ query: ALL_ITEMS_QUERY });
data.goals = data.goals.filter(
(goal) => goal.id !== payload.data.deleteGoal.id
);
cache.writeQuery({ query: ALL_ITEMS_QUERY, data });
},
});
}
The function returns the modified array correctly, but the item never disappears from the frontend list. I have a hunch that this is related to querying multiple categories at once (goals and challenges, rather than goals only).
Even though the cache seems to be modified correclty, why does the item never disappear, why does the re-render never happen?
After some trial and error I found out that I have to lay out the exact data object to the writeQuery function. I don't really understand why, since the challenges object was left untouched after the query. I have not been able to make this work otherwise.
const DeleteWrapper = (props) => {
const [deleteGoal] = useMutation(DELETE_ITEM_MUTATION, {
update(cache, { data: { deleteGoal} }) {
const { goals, challenges } = cache.readQuery({ query: ALL_ITEMS_QUERY });
const newArr = goals.filter((goal) => goal.id !== deleteGoal.id);
cache.writeQuery({
query: ALL_ITEMS_QUERY,
data: { challenges, goals: newArr },
});
},
});
}

How to set route params for CRUD application using Redux and API server

I'm working on a React/Redux application that needs to make a simple GET request to an API server endpoint (/contents/{id}). Right now I have an action set up to fetch this data:
export const fetchInfoPage = id => {
return async dispatch => {
try {
const res = await fetch(`${server}/contents/${id}`)
if (res.ok) {
const json = await res.json()
await dispatch(fetchPageRequest())
await setTimeout(() => {
dispatch(fetchPageSuccess(json.data))
}, 1000)
} else {
const json = await res.json()
console.log(res, json)
}
} catch (error) {
dispatch(fetchPageFailure(error))
console.log(error)
}
}
}
And here's what fetchPageSuccess looks like:
const fetchPageSuccess = content => {
const { id, attributes } = content
return {
type: FETCH_PAGE_SUCCESS,
isFetching: false,
id: id,
name: attributes.name,
content: attributes.content,
created_by: attributes.created_by,
updated_by: attributes.updated_by,
created_at: attributes.created_at,
updated_at: attributes.updated_at
}
}
I am firing off this action inside of componentDidMount in my InfoPage component by using fetchInfoPage(match.params.name). The match.params.name is set up to match the parameters in the React Route (i.e. /:name/information). I want to instead change this to fetch the data by using an ID number from the JSON while still displaying :name as the route parameter.
I feel like I'm close in getting this wired up but there's a gap in my logic somewhere. Is it possible to do what I'm trying to do here? I also have access to a GET endpoint at /contents/slug/{slug}.
It's perfectly fine what you are trying to do.
Just map the id using your name in the fetchInfoPage from your json or you can actually send the id to your fetchInfoPage function from component. It has nothing to do with your route params. All you are doing is getting the name from your param and getting the corresponding id using your name. I assume you have a name: id map somewhere.
export const fetchInfoPage = name => {
return async dispatch => {
try {
const id = getIdFromName(name); // Write a helper function
const res = await fetch(`${server}/contents/${id}`)
if (res.ok) {
const json = await res.json()
await dispatch(fetchPageRequest())
await setTimeout(() => {
dispatch(fetchPageSuccess(json.data))
}, 1000)
} else {
const json = await res.json()
console.log(res, json)
}
} catch (error) {
dispatch(fetchPageFailure(error))
console.log(error)
}
}
}
Your route will still be /:name/information
What I ended up doing was fetching by slug instead. On the components where I fetched the data, I created the slug name in componentDidMount by using match.params.name from my route, then fired off fetchInfoPage(slugName) to get the data. I also cleaned up the code quite a bit so here's what fetchInfoPage looks like now:
export const fetchInfoPage = slug => {
return async dispatch => {
try {
dispatch(fetchPageRequest())
const res = await fetch(`${server}/contents/slug/${slug}`)
const contentType = res.headers.get('content-type')
if (contentType && contentType.includes('application/vnd.api+json')) {
const json = await res.json()
if (res.ok) {
dispatch(fetchPageSuccess(json))
} else {
printError(res, json)
dispatch(fetchPageFailure(res.body))
dispatch(push('/error'))
}
} else {
console.log('Not valid JSON')
dispatch(fetchPageFailure(res.body))
dispatch(push('/error'))
}
} catch (error) {
dispatch(fetchPageFailure(error))
dispatch(push('/error'))
console.log(`Network error: ${error.message}`)
}
}
}
And a componentDidMount example:
componentDidMount() {
const { match, fetchInfoPage } = this.props
const slugName = `${NAMESPACE}-${match.params.name}`
fetchInfoPage(slugName)
}

Resources