How to navigate to the update/detail page after a POST in RTK query - reactjs

I've got a form with a POST in REACT RTK-query and then it should navigate to the second step, but for that I need to know the id of the newly created record. The id is not sent in POST but AutoIncremented in the backend. I don't want to navigate to the list-view to get that id but directly.
const [addMyModel] = useNewMyModelMutation();
const handleSubmit = (e: { preventDefault: () => void; }) => {
e.preventDefault();
const my_model = {
"user_id": user_id,
"date_time_field": new Date(),
"icm": icmChoice,
...
};
dispatch(setUniqueFilters(unique_filters))
//localStorage.setItem('unique_filters', JSON.stringify(unique_filters))
//localStorage.setItem('selection_positions', JSON.stringify(positions))
//localStorage.setItem('my_model', JSON.stringify(my_model))
addMyModel(my_model)
push(`/icm/mymodels/list`) // push(`/icm/mymodels/${answer_question}`);
What could be the best solution?
try to send the id in response from the backend after POST (Django Rest Framework),
change the primary key in the backend as such that I will know the id before handleSubmit (did that before but forgot the reason why I set everything back.)
saving step1 in the localStorage (but then there will be problems with updating previous records),
saving step1 in the state (but than I cannot refresh)

It's simple, but only in case your API returns a newly created item on POST response (which is expected for RESTfull API).
If so - just await the answer from hook:
const data = await addMyModel(my_model).unwrap()
// id should be in `data`
Considering that mutation "trigger" function returns a Promise, you can use it as usual:
addMyModel(my_model).then(newItem => {
if ("data" in newItem) {
const expectedId = item.data.id;
// use you expectedId
}
});

const [addMyModel, result] = useNewMyModelMutation();
...
const handleSubmit = (e: any) => {
e.preventDefault();
const my_model = {
"user_id": user_id,
"date_time_field": new Date(),
"icm": icmChoice,
...
};
addMyModel(my_model)}
result.status === "fulfilled" && push(`/icm/mymodels/update/step2/${result.data["id"]}`)

Related

React hooks and Axios - cancel request ( AbortController ) is not triggered in the expected order

I have a problem that I can't seem to figure out why is happening.
I created a custom hook that makes an API call to the BE every time a "filter" or "page" is changed.
If the user changes some filter before the response is received, I successfully cancel the API call with the "old" filters and trigger the new correct api call with the updated filters.
I have a Redux store, and in it I have a "loading" property that keeps track if the API call si pending or finished. Just normal, plain no fancy redux store implementation with React.
Below is the custom hook that I created, that works as expected in terms of "canceling" the request when the filters/page change. I
export const useGetPaginatedDataWithSearch = (getFunction, page, searchValue, filters={}) => {
const dispatch = useDispatch();
useEffect(()=>{
const controller = new AbortController();
const abortSignal = controller.signal;
// to the initial filters,
// we want to add the page and search value
let params = {...filters};
params.page = page
params.search = searchValue;
dispatch(getFunction(params, abortSignal))
return () => {
controller.abort()
}
}, [dispatch, page, searchValue, getFunction, filters])
}
The "getFunction" parameter, is something like this.
export const getDrivers = (params= {}, abortSignal, endpoint) => {
return (dispatch) => {
dispatch(driversApiCallStart());
const url = endpoint ? endpoint : '/drivers';
return api.get(url, {params:params, signal: abortSignal})
.then((response) => {
let data = response.data;
dispatch(getDriversSuccess(data));
})
.catch((error) => {
const actionType = 'getDrivers';
throw parseError(dispatch, driversApiCallFail, error, actionType);
});
};
};
driversApiCallStart and parseError are some functions used across all the actions for the drivers store slice, and just update the "loading" or "error" state in redux store. Redux store uses the "immer" package, this is where the "draft" comes from in the below code.
case actionTypes.DRIVERS_API_CALL_START:
draft.loading = true;
break;
case actionTypes.DRIVERS_API_CALL_FAIL:
draft.loading = false;
draft.error = {
apiCall: action.payload.apiCall,
message: action.payload.message
};
break;
Now my problem is the following. This is the expected normal order of the "actions/trigers" whatever:
the cancel request is triggered when the filters change (OK)
cancel request goes in the .catch((error) block (OK)
.catch((error) block trigers "DRIVERS_API_CALL_FAIL" => draft.loading = false => spinner stops (OK)
new api call is made with the new filters "DRIVERS_API_CALL_START" => draft.loading = true; => spinner start (OK)
My problem is that the order in the app is:
1 => 2 => 4 => 3 (spinner is not displayed although the API call is "in progress").
Please check below print screen:
redux order in the app
Expected order behavior:
1 => 2 => 3 => 4 (spinner to be displayed white the API call is "in progress").

How to fetch with parameters using React Query?

For the sake of this question let's first assume existence of such entity:
export interface Event {
id: number;
date: Date;
}
Then let's assume there's backend with such endpoints:
GET /events -> returns all events
GET /events?startDate=dateA&endDate=dateB -> returns all events between dateA and dateB
I create hook containing 4 methods (one for each CRUD operation) in my frontend code like this:
export function useEvents() {
const getEvents() = async () => {
const response = await axios.get(`events`);
return response.data;
}
const postEvent()...
const updateEvent()...
const deleteEvent()...
const query = useQuery('events', getEvents);
const postMutation = ...
const updateMutation = ...
const deleteMutation = ...
return { query, postMutation, updateMutation, deleteMutation }
}
This architecture works like a charm but I got to the point where I would like to conditionaly fetch events based on currently chosen month in my Calendar.tsx component.
How would I inject this information into useQuery() and getEvents()?
the query key should contain all "dependencies" that you need for your fetch. This is documented in the official docs here, and I've also blogged about it here.
So, in short:
const getEvents(month) = async () => {
const response = await axios.get(`events/${month}`);
return response.data;
}
const query = useQuery(['events', month], () => getEvents(month));
The good thing is that react-query will always refetch when the key changes, so data for every month is cached separately, and if the month changes, you'll get a fetch with that month.

refetch in reactQuery is not return the data

I am using reactQuery in my react application. I need to call one get API in button click. for that i am using refetch option in reactQuery. API call is working fine but my response data is coming undefined. I checked in browser network there i can see the response.
My API call using reactQuery
const { data: roles, refetch: roleRefetch } = useQuery('getRoles', () => api.getRoles('ID_234'), { enabled: false });
My click event
const handleAdd = (e) => { roleRefetch(); console.log(roles) }
My action call using axios
export const getRoles = (name) => axios.get(roles/list?sa_id=${name}, { headers: setHeader }).then(res => res);
const handleAdd = (e) => { roleRefetch(); console.log(roles) }
this not how react works, and it's not react-query specific. calling a function that updates some state will not have your state be available in the next line. It will make it available in the next render cycle. Conceptually, you want this to work, which cannot with how react is designed:
const [state, setState] = React.useState(0)
<button onClick={() => {
setState(1)
console.log(state)
}}
here, the log statement will log 0, not 1, because the update doesn't happen immediately, and this is totally expected.
With react-query, what you can do is await the refetch, because its async, and it will give you the result back:
const handleAdd = async (e) => {
const { data } = await roleRefetch();
console.log(data)
}
or, depending on what you actually want to do, you can:
use data in the render function to render something - it will always be up-to-date.
use theonSuccess callback of useQuery to trigger side-effects whenever data is fetched
spawn a useEffect in the render function that does the logging:
const { data: roles, refetch: roleRefetch } = useQuery('getRoles', () => api.getRoles('ID_234'), { enabled: false });
React.useEffect(() => {
console.log(roles)
}, [roles])
on a more general note, I think disabling a query and then calling refetch on a button click is very likely not idiomatic react-query. Usually, you have some local state that drives the query. in your case, that's likely the id. Dependencies of the query should go to the queryKey, and react-query will trigger a refetch automatically when the key changes. This will also give you caching by id. You can use enabled to defer querying when your dependencies are not yet ready. Here's what I would likely do:
const [id, setId] = React.useState(undefined)
const { data: roles } = useQuery(['getRoles', id], () => api.getRoles(id), { enabled: !!id });
const handleAdd = (e) => { setId('ID_234') }
of course, id doesn't have to come from local state - it could be some other form of client state as well, e.g. a more global one.

Return value from Google Places getDetails callback asynchronously

Is it possible to make a Google Places callback from getDetails function asynchronus or return it's value?
Essentially, in my react app, I am trying to do the following:
pass a placeId to google places api
wait for google to bring back some place information (address, photos, etc)
make a call to my local api to do something with this data from google
What happens though is
google goes to get the data
the call to my api is made but google has not brought back the data yet
I have tried using useState however, the state is not updated right away and thus I am not sure how to "wait" for google places to finish getting data before continuing my call
example of what I am trying to do (obviously wrong)
const [value, setValue] = useState('')
const foo = async (placeId) => {
var service = new window.google.maps.places.PlacesService(
predictionsRef.current,
)
await service.getDetails(
{
placeId,
fields: ['photos'],
},
async place =>
await setValue(
place.photos[0].getUrl({ maxWidth: 1280, maxHeight: 720 }),
),
)
// I want 'value' to have google place data before the axios call
await axios.post(...)
}
I have looked at the following links so far but can't piece together what I need:
google places library without map
https://gabrieleromanato.name/javascript-how-to-use-the-google-maps-api-with-promises-and-async-await
useState set method not reflecting change immediately
You could make it this way,
const [value, setValue] = useState('')
const [placeID, setPlaceID] = useState(null)
a function to return a promise from google placeDetails API,
const getPlaceDetails = (ref) => {
return new Promise(function (resolve, reject) {
let placesService = new window.google.maps.places.PlacesService(ref);
placesService.getDetails(
{
placeId,
fields: ['photos'],
},
(place) => {
resolve(place.something);
}
);
});
};
an effect that triggers upon placeID change
useEffect(() => {
async function doStuff() {
let placesResponse = await getPlaceDetails(ref);
let myResponse = await yourAPICall(placesResponse);
setValue(placesResponse);
}
if(placeID !==null){
doStuff();
}
}, [placeID]);
I haven't tested this code, but hope this approach helps.

Firebase On Value get state react hooks

i'm using firebase and react hooks, my problem is that I need to check if the driverId that i have is still in the drivers list, but the problem is that inside the event, inputs.driverId is null, even if it already have a value, and if a "print" the inputs variable all the flelds are like when i declared the initial state.
const initialState = {
driverId:'',
drivers: []
};
const [inputs, setInputs] = useState(initialState);
const getDrivers = () => {
const self = this;
const nameRef = firebase.database().ref().child('drivers').orderByChild('status');
nameRef.on('value', snapshot => {
var drivers = snapshot.val();
setInputs(inputs => ({...inputs, ['drivers']: drivers}));
console.log('dtiverId', inputs.driverId) // console response: dtiverId
})
}
anyvody that can help me, i need when the event excutes check if the select driver (driverId) is still in the list, but when i check driverId i get null, only inside the event
firebase.database() is an asynchronous call. You need to add async and await like so
const getDrivers = aysnc () => {
const self = this;
const nameRef = await firebase.database().ref().child('drivers').orderByChild('status');
nameRef.on('value', snapshot => {
var drivers = snapshot.val();
setInputs(inputs => ({...inputs, ['drivers']: drivers}));
console.log('dtiverId', inputs.driverId) // console response: dtiverId
})
}
What is happening in your code is that you are trying to use driverId before it is returned from firebase (which is why it is null). Async will block until firebase returns then your code will resume executing.
The firebase realtime database works asynchronously.
You should make use of async await or promises to get the data.
The examples in the
firebase docs show how to get the data using promises.

Resources