React RTK query Mutation can return value? - reactjs

Is is possible to get the response of endpoint in React RTK Query Mutation .
I have a one mutation which insert into DB and I need to get the ID which inserted. in my api :
addRecord: build.mutation({
query(data) {
return {
url: base + 'myControler/SaveDataAsync',
method: 'post',
data: data,
}
},
}),
and in my component after import my hook I call it like
const [addRecord] = useAddRecordMutation();
and then in my submit function is use it like
const handleSubmitCustom = async (values) => {
await addRecord(values);
}
which I need the return value of await addRecord(values);

You can just do
const handleSubmitCustom = async (values) => {
try {
const returned = await addRecord(values).unwrap();
} catch (error) {
// you can handle errors here if you want to
}
}

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;

Handling axios errors in child components?

My React app has an api client component which handles axios calls to the back end. So, in the api component I have:
async function getData(path: string, params?: any) {
const object: AxiosRequestConfig = {
...obj,
method: 'GET',
headers: {
...obj.headers,
},
params,
};
const response: AxiosResponse = await axios.get(
`${baseUrl}${path}`,
object
);
return response;
});
}
(obj contains the headers and is defined earlier in the file; baseUrl is a constant which I import).
So, if I have a useEffect to retrieve data from the endpoint '/user/{userId}' whenever the state variable userId changes, I do this:
React.useEffect(() => {
const controller = new AbortController();
const getData = async () => {
try {
const url = `user/${userId}`;
let res = await Client.getData(url, {
signal: controller.signal,
});
... Do things with results ...
} catch (e) {
// Show error
if (!controller.signal.aborted) console.log('Error: ', e);
}
};
getData();
return () => {
controller.abort();
};
}, [state.userId]);
I'm just a bit confused about how errors will be handled in this code. So, if there's an error when the axios call is made (eg no network connection, the endpoint is wrong, or the user isn't found or whatever) will the catch block get called in the getData function? Or do I need a try...catch in the api component too?

How to implement multiple api call in a single query with RTK query

I'm new in RTK Query and I'm struggling with a use case I have to implement.
Scenario:
I have to merge the results from two API calls: the first API call is a private API call while the second one is a public API call. I need to merge the responses from these two APIs and write the computed result into the RTK cache so the UI can update accordingly.
Problem:
I'seeing that as soon as the await queryFullfilled is invoked, RTK Query immediately write into its cache the response from that API call and then when I make my calculation and try to update the RTK cache with apiSlice.util.updateQueryData the cache will change again. That's means that the UI will render twice, the first time using a wrong value (an array of persons) and the second time with the correct value (the JSON composed by ids and entities).
Question:
Is there a way to have just 1 write into the RTK cache so I can have just the computed value I need ? Because what is happening is that for some instances I'm having into the cache an array while I need the {ids: [...], entities: {}} JSON.
import { createEntityAdapter } from '#reduxjs/toolkit';
import axios from 'axios';
export const personsAdapter = createEntityAdapter();
const permitsInitialState = personsAdapter.getInitialState();
export const apiSlice = myServiceApi.injectEndpoints({
endpoints: (builder) => ({
getPersons: builder.query({
query: () => ({ url: '/persons', method: 'get' }),
onQueryStarted: async (_, { dispatch, queryFulfilled }) => {
try {
// Resolving the private API call
const { data: persons } = await queryFulfilled;
// Just a random public API call
const { data: todos } = await axios('https://jsonplaceholder.typicode.com/todos');
const enhancedPersons = /** Here the logic that merge the todos and the persons */
const state = personsAdapter.setAll(permitsInitialState, enhancedPermits);
dispatch(
apiSlice.util.updateQueryData('getPersons', _, (draft) => {
Object.assign(draft, state);
})
);
} catch (e) {
console.error(e);
}
},
}),
}),
});
That is one of the use cases of queryFn: Performing multiple requests with a single query
import {
createApi,
fetchBaseQuery,
FetchBaseQueryError,
} from '#reduxjs/toolkit/query'
import { Post, User } from './types'
const api = createApi({
baseQuery: fetchBaseQuery({ baseUrl: '/ ' }),
endpoints: (build) => ({
getRandomUserPosts: build.query<Post, void>({
async queryFn(_arg, _queryApi, _extraOptions, fetchWithBQ) {
// get a random user
const randomResult = await fetchWithBQ('users/random')
if (randomResult.error) throw randomResult.error
const user = randomResult.data as User
const result = await fetchWithBQ(`user/${user.id}/posts`)
return result.data
? { data: result.data as Post }
: { error: result.error as FetchBaseQueryError }
},
}),
}),
})

React Query useQuery & Axios

I'm trying to create an API function with a help of React Query and Axios.
When I'm using useQuery with vanilla fetch function - it all works perfectly.
export const useGetDebts = async () => {
const { families } = appStore.user;
const res = useQuery("getDebts", async () => {
const res = await fetch(`${API_URL}/api/family/${families[0]}/debts`, {
method: "GET",
headers: {
Authorization: `Bearer ${appStore.token ?? ""}`,
},
});
const parsedBody: DebtsResponse = await res.json();
return parsedBody;
});
return res;
};
But when I switch the vanilla fetch function to Axios - I get an error status of 500 (not sure if it comes from React Query or Axios).
export const useGetDebts = async () => {
const { families } = appStore.user;
const res = useQuery("getDebts", async () => {
const res = await axiosInstance.get<DebtsResponse>(`/api/family/${families[0]}/debts`);
return res.data;
});
return res;
};
Thanks in advance for any explanations/suggestions.
P.s. The axiosInstance works fine with the useMutation hook. So it only makes me more confused. =(
export const useGetDebt = () => (
useMutation(async (id: number) => {
const { families } = appStore.user;
const res = await axiosInstance.get<DebtResponse>(`/api/family/${families[0]}/debts/${id}`);
return res.data;
})
);
P.s.s. I'm working with React Native if it's somehow relevant.
react-query doesn't give you any 500 errors because react-query doesn't do any data fetching. It just takes the promise returned from the queryFn and manages the async state for you.
I'm not sure if the fetch code really works because it doesn't handle any errors. fetch does not transform erroneous status codes like 4xx or 5xx to a failed promise like axios does. You need to check response.ok for that:
useQuery(['todos', todoId], async () => {
const response = await fetch('/todos/' + todoId)
if (!response.ok) {
throw new Error('Network response was not ok')
}
return response.json()
})
see Usage with fetch and other clients that do not throw by default.
So my best guess is that the fetch example also gives you a 500 error code, but you are not forwarding that error to react-query.

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