`react-query` mutate onSuccess function not responding - javascript

query,
I'm making a bulletin board right now.
So, I put the values ​​of title, content in the Form information into the react-query function onSuccess. In the value, console.log does not react.
export const useAddFAQPost = () => {
return useMutation(FaqPost)
}
export function FaqPost(data: FAQ) {
return axios.post<FAQ>('/add', data, {
})
}
const { mutate } = useAddFAQPost()
const onSubmit = useCallback((event: React.ChangeEvent<FormEvent>) => {
event.preventDefault();
return mutate({ title, type } as FAQ), {
onSuccess: async (data: string, context: string) => {
console.log(data);
console.log('why not?');
},
onError: async (data: string, context: string) => {
console.log(data);
}
};
}, [title, type])
return (
<>
<form onSubmit={onSubmit}>
<input type="text" name="title" value={title} ... />
<option value="faq">FAQ</option>
</form>
</>
)
If onSubmit succeeds or fails, the console.log in onSuccess, onError should be recorded, but it is not being recorded. Why are you doing this?
onSuccess, onError doesn't seem to respond.
I don't know why. Help

The accepted answer is incorrect. The onSuccess/onError handler is also available on the 'mutate' method (https://react-query.tanstack.com/reference/useMutation).
The catch here is that those additional callbacks won't run if your component unmounts before the mutation finishes. So, you have to make sure the component where you are doing the mutation does not unmount. In your case:
const {
mutate
} = useAddFAQPost()
const onSubmit = useCallback((event: React.ChangeEvent < FormEvent > ) => {
event.preventDefault();
return mutate({
title,
type
}
as FAQ), {
onSuccess: async(data: string, context: string) => {
console.log(data);
console.log('why not?');
},
onError: async(data: string, context: string) => {
console.log(data);
},
onSettled: () => {
// Some unmounting action.
// eg: if you have a form in a popup or modal,
// call your close modal method here.
// onSettled will trigger once the mutation is done either it
// succeeds or errors out.
}
};
}, [title, type])

You can also use mutateAsync, wait for the async to finish, and get the response if needed in a particular scenario.

The onSuccess / onError and so on method should be define in the useMutation hook creation and not when you call the mutate function as per documention.
https://react-query.tanstack.com/guides/mutations
You'll have to adapt to your need but the idea is:
export const useAddFAQPost = () => {
return useMutation(FaqPost, {
onSuccess: async (data: string, context: string) => {
console.log(data);
console.log('why not?');
},
onError: async (data: string, context: string) => {
console.log(data);
}
})
}
You could also pass it when you call useAddFAQPost()

Related

Is it wrong to use an action's payload inside a component with react-redux?

I'd like to keep track of API requests that I make with react-redux. To do this I'd like to generate a request Id inside the action and pass that along to middleware and reducers through the payload. Then when I'm dispatching the action from my component I can capture the request Id and use it for updating the component as the request progresses.
Here's some example code
State
export interface State {
[requestId: number]: Request;
}
export interface Request {
status: string;
error?: string;
}
Action
export function createRequest(): Action {
return {
type: "request",
payload: {
requestId: Math.random () // Make a random Id here
}
};
}
Reducer
export function createRequestReducer(state: State): State {
return {
...state,
...{ state.payload.requestId: { status: "pending" } }
};
}
Component
interface props {
getRequestById: (id: number) => Request;
createRequest: () => number;
}
const component = (props: testProps): JSX.Element => {
const getRequestById = props.getRequestById;
const [requestId, setRequestId] = useState(null);
const [request, setRequest] = useState(null);
useEffect(() => {
if (requestId !== null) {
setRequest(getRequestById(requestId));
}
}, [requestId]);
return <div>The request status is {(request && request.status) || "Not started"}</div>;
}
function mapStateToProps(state: State) {
return {
getRequestById: (requestId: number): Request => {
getRequestById(state, requestId)
}
};
}
function mapDispatchToProps(dispatch: Dispatch) {
return {
createRequest: (): number => {
const action = createRequest();
dispatch(action);
return action.payload.requestId;
}
};
}
export default connect(mapStateToProps, mapDispatchToProps)(component);
I expect this will work but it may be a massive anti pattern. Is this not advised and, if so, is there an alternative?
I think your approach works technically totally fine. Only "logically" it might make sense to make a some changes:
Yes, the "action" is something that is supposed to be sent to the reducer (and not used anywhere else, although there is technically no problem with that).
But what you can do:
1. separate action and values
Inside the action creator function, you can do whatever you want.
So you can create and use the action and the requestId seperately.
This is technically exact the same as what you did, but logically separated.
E.g.:
function createRequest(){
const requestId = createUniqueId();
const action = { type: "request", payload: { requestId: requestId } };
return {
requestId: requestId, // <-- request id independent of the action
action: action, // <-- action independent of the request id
};
}
function mapDispatchToProps( dispatch: Dispatch ){
return {
createRequest: (): number => {
const { requestId, action } = createRequest();
dispatch( action ); // <-- action independent of the request id
return requestId; // <-- request id independent of the action
}
};
}
2. "action dispatchers"
I (and apparently others as well) like to use what I call "action dispatchers".
This is an extra step and more code, but I think when you got used to this concept, it eliminates any doubts where code like that has to be put.
E.g.:
// Create the action, and nothing else:
const createRequestActionCreator = function( requestId ){
return { type: "request", payload: { requestId: requestId } };
};
// Preper some data needed to create the action:
const createRequestActionDispatcher = function( dispatch ){
return function(){
const requestId = createUniqueId();
dispatch( createRequestActionCreator( requestId ) );
return requestId;
};
};
//
function mapDispatchToProps( dispatch: Dispatch ) {
return {
createRequest: (): number => {
const requestId = createRequestActionDispatcher( dispatch )();
return requestId;
}
};
}
2.a
Additionally you could pass such an "action dispatcher" directly as a prop, if you want.
In this case it basically replaces your function in mapDispatchToProps, but is reusable, e.g.:
function mapDispatchToProps( dispatch: Dispatch ) {
return {
createRequest: createRequestActionDispatcher( dispatch ),
};
}
2.b
Some people prefer to use a fat-arrow-function here, which I find more confusing, not less, but it looks cleaner as soon as you got used to that pattern:
const createRequestActionDispatcher = (dispatch: Dispatch) => (maybeSomeValue: MyType) => {
const requestId = createUniqueId();
dispatch( createRequestActionCreator( requestId ) );
return requestId;
};
Remark:
I generally prefer to be consistent, for which I should always (or never) use these "action dispatchers",
but I found that most of the time I don't need one, but sometimes I find them very useful.
So I'm actually using dispatch( myAction ) in some places and myActionDispatcher(value)(dispatch) in others.
I don't like that, but it works well, and I don't have a better idea.

debouncing a redux dispatch not working as expected

Im trying to debounce a api action which is a dispatch call to reducer.The api call in the browser should debounce after a particular delay given as a single api , but its going as multiple api calls after the delay ,the code is as follows.
please refer the screenshot also
const apiCall = (args) => {
dispatch(getECByStatus({status: 'PENDING_APPROVAL', search: args}))
}
const debounce = (apiFunc, delay) => {
let inDebounce
return function () {
const context = this
const args = arguments
clearTimeout(inDebounce)
inDebounce = setTimeout(() => {
inDebounce = null
apiFunc.apply(context, args)
},delay);
}
}
const optimizedVersion = debounce(apiCall, 600)
const handleSearchChange = (value) => {
optimizedVersion(value)
}
the handleSearchChange is the onchange event fired from the input box on typing the input.getECByStatus is a redux action creator, which calls api with the search param,
export const getECByStatus = (params) => async (dispatch) => {
let editCheckType = params?.type ? `/${params.type}` : ''
let searchParams = params?.search ? `&search=${params.search}` : ''
try {
dispatch({
type: actionType.GET_EC_BY_STATUS_REQUEST,
payload: {
load: true,
},
})
let study_id = getItem('study_id')
const { data } = await DataService.get(
`/edit-checks${editCheckType}?status=${params.status}&study_id=${study_id}${searchParams}`
)
dispatch({
type: actionType.GET_EC_BY_STATUS_SUCCESS,
payload: {
load: false,
data: data.data,
}
},
})
} catch (error) {
console.error('Get EC by status error', error)
dispatch({
type: actionType.GET_EC_BY_STATUS_FAIL,
payload: {
load: false,
},
})
}
}
Thanks in advance!
This is due to the UI re-render after dispatch event get fired, since the input box is a controlled one , we have to pass the updated value to the input box component, so we can make pass a ref for not re-rendering the UI.

async function not waiting for the await to end

I'm trying to add an async/await in my code to have the app wait for the execution of a function to invoke an other one, but I can't seem to be able to figure out where my code is wrong.
I have an API call in a redux action, this is the action
export const editSecondaryCategory = (text) => {
return (dispatch) => {
let obj = {
text
};
axios
.put(
`${ROOT_URL}/...`,
obj
)
.then(() => {
dispatch({ type: EDIT_NOTE, payload: [...annotations] });
dispatch(loadAllAnnotations(cid, uuid));
})
.catch((error) =>
dispatch(
notifSend({
message: error.message,
kind: "danger",
dismissAfter: 2000,
})
)
);
};
};
I want, in my component, to wait after this action is completed to call an other function to update the state. Ideally, it should be something like this (I guess?):
async updateCategory() {
// do the thing I have to wait for
const propsUpdate = await this.props.editSecondaryCategory(text);
// if you've done the thing then call the function to update the state
if (updatedCat) {
this.updateStateArray();
}
}
And I'd call this.updateCategory() inside my component after the user is done editing the information.
Clearly, this code does not work. I know it's wrong, I just don't know why. Basically I have no clue what to write inside updateCategory() to make this work.
Please help lol
You need to rewrite editSecondaryCategory function to make it async.
export async function editSecondaryCategory(text){
return (dispatch) => {
let obj = {
text
};
axios
.put(
`${ROOT_URL}/...`,
obj
)
.then(() => {
dispatch({ type: EDIT_NOTE, payload: [...annotations] });
dispatch(loadAllAnnotations(cid, uuid));
})
.catch((error) =>
dispatch(
notifSend({
message: error.message,
kind: "danger",
dismissAfter: 2000,
})
)
);
};
};
Currently, your function is not an async function do the above changes and check.

How can I make an action be reusable? ReactJS with Redux

I need to do a dynamic action. In other words, it can be reused for differents actions.
I tried to create a function that loads type and payload, but an error appears.
I'm trying make this function works:
export function getData(url, type) {
const request = Server.get(url)
return (dispatch) =>
request.then((response) => {
dispatch({
type: type,
payload: response.data
})
}).catch(function (error) {
console.log(error)
});
}
But I got an error when I call this function this way:
export function getClientes() {
Actions.getData('ClientesEFornecedores', GET_CLIENTES)
}
It's showing:
Uncaught Error: Actions must be plain objects. Use custom middleware for async actions.
I'm Calling the getClientes() function this way:
function ClientesTable(props)
{
const dispatch = useDispatch();
const clientes = useSelector(({erpCliente}) => erpCliente.clientes.data);
useEffect(() => {
dispatch(Actions.getClientes());
}, [dispatch]);
How can I make an action be reusable?
Try something like this
export const getData=(url, type) =>async dispatch=>{
try{
const response = await Server.get(url);
dispatch({ type: type,payload: response.data })
} catch(err){
console.log(err)
}
}
getClientes function
export const getClientes=() => dbActions.getData('ClientesEFornecedores', GET_CLIENTES);
In fact I had almost succeeded.
All that remained was to return the function call.
This is the way that works:
export function getClientes() {
return dbActions.getData('ClientesEFornecedores', GET_CLIENTES)
}

React Redux dispatch action after another action

I have an async action, which fetch data from REST API:
export const list = (top, skip) => dispatch => {
dispatch({ type: 'LIST.REQUEST' });
$.get(API_URL, { top: top, skip: skip })
.done((data, testStatus, jqXHR) => {
dispatch({ type: 'LIST.SUCCESS', data: data });
});
};
A sync action, which changes skip state:
export const setSkip = (skip) => {
return {
type: 'LIST.SET_SKIP',
skip: skip
};
};
Initial state for top = 10, skip = 0. In component:
class List extends Component {
componentDidMount() {
this.list();
}
nextPage() {
let top = this.props.list.top;
let skip = this.props.list.skip;
// After this
this.props.onSetSkip(skip + top);
// Here skip has previous value of 0.
this.list();
// Here skip has new value of 10.
}
list() {
this.props.List(this.props.list.top, this.props.list.skip);
}
render () {
return (
<div>
<table> ... </table>
<button onClick={this.nextPage.bind(this)}>Next</button>
</div>
);
}
}
When button Next at first time clicked, value of skip which uses async action not changed.
How I can to dispatch action after sync action?
If you are using redux thunk, you can easily combine them.
It's a middleware that lets action creators return a function instead of an action.
Your solution might have worked for you now if you don't need to chain the action creators and only need to run both of them.
this.props.onList(top, newSkip);
this.props.onSetSkip(newSkip);
If you need chaining(calling them in a synchronous manner) or waiting from the first dispatched action's data, this is what I'd recommend.
export function onList(data) {
return (dispatch) => {
dispatch(ONLIST_REQUEST());
return (AsyncAPICall)
.then((response) => {
dispatch(ONLIST_SUCCESS(response.data));
})
.catch((err) => {
console.log(err);
});
};
}
export function setSkip(data) {
return (dispatch) => {
dispatch(SETSKIP_REQUEST());
return (AsyncAPICall(data))
.then((response) => {
dispatch(SETSKIP_SUCCESS(response.data));
})
.catch((err) => {
console.log(err);
});
};
}
export function onListAndSetSkip(dataForOnList) {
return (dispatch) => {
dispatch(onList(dataForOnList)).then((dataAfterOnList) => {
dispatch(setSkip(dataAfterOnList));
});
};
}
Instead of dispatching an action after a sync action, can you just call the function from the reducer?
So it follows this flow:
Sync action call --> Reducer call ---> case function (reducer) ---> case function (reducer)
Instead of the usual flow which is probably this for you:
Sync action call --> Reducer call
Follow this guide to split the reducers up to see what case reducers are.
If the action you want to dispatch has side affects though then the correct way is to use Thunks and then you can dispatch an action after an action.
Example for Thunks:
export const setSkip = (skip) => {
return (dispatch, getState) => {
dispatch(someFunc());
//Do someFunc first then this action, use getState() for currentState if you want
return {
type: 'LIST.SET_SKIP',
skip: skip
};
}
};
also check this out redux-sequence-action
Thanks for the replies, but I made it this way:
let top = this.props.list.top;
let skip = this.props.list.skip;
let newSkip = skip + top;
this.props.onList(top, newSkip);
this.props.onSetSkip(newSkip);
First I calculate new skip and dispatch an async action with this new value. Then I dispatch a syns action, which updates skip in state.
dispatch({ type: 'LIST.SUCCESS', data: data, skip: The value you want after sync action });

Categories