async/await in react with redux - reactjs

Hi guys I am quite new to programming and trying to understand one thing with redux and await/async functions. Basically I have this function:
//nh functions
const onSubmit = async data => {
try{
await dispatch(Login(data))
if (auth.logged != false){
addToast(content, { appearance: 'success', autoDismiss: true, })
history.push('/')
} else if (auth.logged == false){
addToast(content2, { appearance: 'error', autoDismiss: true, })
}
}finally{
console.log('Tada')
}
}
which should first authenticate an account and then push a notification. However, the await is not working at all, and it proceeds immediately to the if statement. Any tips?

Wht dave said is true. If you want to do something like that, you should dispatch your data and get the result in a props. Then you can use a useEffect to listen to this prop and do your things. Somethink like:
useEffect(() => {
// Do your things after your dispatch here
}, [yourProp]);

The "normal" pattern is to write asynchronous pseudo-actions: async function dispatching classical synchronous actions :
// reducer
// define 3 synchronous actions : loginActionBegin, loginActionSuccess,
// loginActionFailure which update your state at your convenience, for example setting a boolean flag to inform components that request is
// flying, or add user infos in store when loginActionSuccess is dispatched...
// async pseudo action
export const loginActionAsync = (loginInfos: any): any => {
return (dispatch: Dispatch, getState: any): any => {
dispatch(loginActionBegin());
return loginService.login(loginInfos)
.then(result: any): any => {
// request succeeded, add toast, user feedback...
dispatch(loginActionSuccess(result));
})
.catch((error: any) => {
// request failed, add toast, user feedback...
dispatch(loginActionFailure(error));
});
};
}
Then in a component:
// grab infos from store
const user = useSelector(state => state.user)
// on login form submit
dispatch(loginActionAsync({username:..., password:...}));
You will need async middleware to do so like https://github.com/reduxjs/redux-thunk
See :
https://redux.js.org/advanced/async-actions
https://www.digitalocean.com/community/tutorials/redux-redux-thunk
https://redux-toolkit.js.org/usage/usage-with-typescript#createasyncthunk

I'm assuming that auth is a prop that is mapped from the redux state to the component?
If so you could get around having to create an async functionality for the submit button and handling the redirect and/or state change when the component is updated with a new value from the store.
I would recommend this as you should rather be handling any error in the action itself, that way the component can be kept simple and mainly focuses on what to display.
useEffect(() => {
if (auth.logged != false){
addToast(content, { appearance: 'success', autoDismiss: true, })
history.push('/')
}
else if (auth.logged == false){
addToast(content2, { appearance: 'error', autoDismiss: true, })
}
}, [auth.logged]);
const onSubmit = data => dispatch(Login(data))

Related

Calling an action within another one in Redux using TypeScript

Suppose that I have these 2 actions (as an example) for "creating category" and "loading all categories". I need to load all categories every time I create a new category successfully, so, I need to call "loadAllCategories" action within "createCategory". I usually do that like this while using TypeScript with Redux:
// Loading all categories
export const loadAllCategories = () => async (dispatch: Dispatch) => {
try {
// omitted for brevity
dispatch<ILoadAntdTreeSelectCompatibleCategoriesAction>( {
type: TaxonomyActionTypes.LOAD_ANTD_TREESELECT_COMPATIBLE_CATEGORIES,
payload: {
catTreeSelectLoading: false,
catTreeSelectRegistry
}
})
} catch (error) {
// omitted for brevity
}
}
// Creating a category
export const createCategory = (taxonomy: ITaxonomy) => async (dispatch: Dispatch) => {
try {
await agent.Taxonomies.create(taxonomy);
dispatch<any>(loadAllCategories()); <--- Dispatching above action within this one
dispatch<ICreateCategoryAction>({
type: TaxonomyActionTypes.CREATE_CATEGORY,
payload: {
loadingInitial: false
},
})
} catch (error) {
// omitted for brevity
}
}
I wanted to know, using dispatch with "any" type is the only way to call another action within the current one or there is a better way of doing that?
Could I use a more specific type instead of "any"?
Needless to say without using dispatch(action), just by calling the action's name it doesn't change the state so we have to use dispatch.
What is the best practice for doing that?
There is a simpler way to do this when you create a category lets say you use an API for that, make that API return the value you added, in response, then add that category to category list in Redux. use the following function in the reducer.
const addToList = (oldList:any, doc:any) => {
let newList:any = oldList;
newList.push(doc);
return newList;
}
and in the reducer function call it like
case TaxonomyActionTypes.CREATE_CATEGORY:
return { ...state, categories: addToList(state.categories, action.payload) }
Edit
The Answer to your question is
dispatch<Array>
Example
interface Category {
name: String,
}
let x:Array<Category>

Wait for redux action to finish dispatching when using redux saga

I have a redux saga setup which works fine. One of my dispatches is to create a new order, then once that has been created I want to do things with the updated state.
// this.props.userOrders = []
dispatch(actions.createOrder(object))
doSomethingWith(this.props.userOrders)
Since the createOrder action triggers a redux saga which calls an API, there is a delay, so this.props.userOrders is not updated before my function doSomethingWith is called. I could set a timeout, but that doesn't seem like a sustainable idea.
I have read the similar questions on Stack Overflow, and have tried implementing the methods where relevant, but I can't seem to get it working. I'm hoping with my code below that someone can just add a couple of lines which will do it.
Here are the relevant other files:
actions.js
export const createUserOrder = (data) => ({
type: 'CREATE_USER_ORDER',
data
})
Sagas.js
function * createUserOrder () {
yield takeEvery('CREATE_USER_ORDER', callCreateUserOrder)
}
export function * callCreateUserOrder (newUserOrderAction) {
try {
const data = newUserOrderAction.data
const newUserOrder = yield call(api.createUserOrder, data)
yield put({type: 'CREATE_USER_ORDER_SUCCEEDED', newUserOrder: newUserOrder})
} catch (error) {
yield put({type: 'CREATE_USER_ORDER_FAILED', error})
}
}
Api.js
export const createUserOrder = (data) => new Promise((resolve, reject) => {
api.post('/userOrders/', data, {headers: {'Content-Type': 'application/json'}})
.then((response) => {
if (!response.ok) {
reject(response)
} else {
resolve(data)
}
})
})
orders reducer:
case 'CREATE_USER_ORDER_SUCCEEDED':
if (action.newUserOrder) {
let newArray = state.slice()
newArray.push(action.newUserOrder)
return newArray
} else {
return state
}
This feels like an XY Problem. You shouldn't be "waiting" inside a component's lifecycle function / event handler at any point, but rather make use of the current state of the store.
If I understand correctly, this is your current flow:
You dispatch an action CREATE_USER_ORDER in your React component. This action is consumed by your callCreateUserOrder saga. When your create order saga is complete, it dispatches another "completed" action, which you already have as CREATE_USER_ORDER_SUCCEEDED.
What you should now add is the proper reducer/selector to handle your CREATE_USER_ORDER_SUCCEEDED:
This CREATE_USER_ORDER_SUCCEEDED action should be handled by your reducer to create a new state where some "orders" property in your state is populated. This can be connected directly to your component via a selector, at which point your component will be re-rendered and this.props.userOrders is populated.
Example:
component
class OrderList extends React.PureComponent {
static propTypes = {
userOrders: PropTypes.array.isRequired,
createOrder: PropTypes.func.isRequired,
}
addOrder() {
this.props.createOrder({...})
}
render() {
return (
<Wrapper>
<Button onClick={this.addOrder}>Add Order</Button>
<List>{this.props.userOrders.map(order => <Item>{order.name}</Item>)}</List>
</Wrapper>
)
}
}
const mapStateToProps = state => ({
userOrders: state.get('userOrders'),
})
const mapDispatchToProps = {
createOrder: () => ({ type: 'CREATE_ORDER', payload: {} }),
}
export default connect(mapStateToProps, mapDispatchToProps)(OrderList)
reducer
case 'CREATE_USER_ORDER_SUCCEEDED':
return state.update('userOrders',
orders => orders.concat([payload.newUserOrder])
)
If you really do need side-effects, then add those side-effects to your saga, or create a new saga that takes the SUCCESS action.

React Redux best practices for performing actions

Where should I perform actions (redirecting or adding/removing something to/in the localstorage) in React (and Redux)? So after the password is successfully updated I want to redirect the user to another page. Should I redirect after the dispatch method, should I do it in the component or are there other options?
Example action:
export function updateAccountPassword(encryptedPassword) {
return dispatch => {
axios.post(API_URL + '/account/recovery/update', {
_id: getSignedInUserID(),
password: encryptedPassword
}).then(() => {
dispatch(updateUserPasswordSuccess())
}).catch(() => {
dispatch(updateUserPasswordFailError());
})
}
}
function updateUserPasswordSuccess() {
return({
type: RECOVERY_UPDATE_SUCCESS
})
}
function updateUserPasswordFailError() {
return({
type: RECOVERY_UPDATE_FAIL_ERROR,
payload: 'Something went wrong, please try again'
})
}
The way I am doing it is by passing the this.props.history.push as a callback to the action creator, and calling it, as you suggested, in the action creator, after dispatch.
Here is an example from my code:
In the component form's submission, calling the action creator:
this.props.ACTION_CREATOR(formPayload, () => {
this.props.history.push(`ROUTING_TARGET`);
});
And, then, in the action creator, when the proper condition has been met, calling the callback (rerouting).

Display loading state and change route when API call is successfull

While working on a side project, I faced an issue with react-router-dom.
What I want to implement is: When I submit a Form, I need to save the data on my server. While the request is pending, I need to display a loading indicator. Once the server says everything is ok, I need to redirect the user on a new page
action.js
export const addNotification = value => async dispatch => {
dispatch(addNotificationPending())
try {
const response = await client.createNotification(values)
dispatch(addNotificationSuccess(response))
} catch(e) {
dispatch(addNotificationFailure())
}
}
component.js
class CreateNotificationForm extends Component {
onSubmit = (values) => {
this.props.addNotification(parameters, history)
}
render() {
const { isCreating } = this.props
const submitBtnText = isCreating ? 'Creating...' : 'Submit'
return (
<Form>
// content omitted
<Submit value={submitBtnText} />
</Form>
)
}
}
const mapStateToProps = (state) => ({
isCreating: getIsFetching(state)
})
const mapDispatchToProps = (dispatch) => ({ // omitted })
connect(mapStateToProps, mapDispatchToProps)(CreateNotificationForm)
So far so good: When I submit my form, the form's submit button shows a Creating... text.
However, how do I tell react-router to load a new path once the request is successful?
Right now, I've done that by using withRouter and using this.props.history as a second argument for this.props.addNotification.
It works great, but it seems really wrong
I've seen solutions using react-router-redux, but I don't really want to add a new middleware to my store.
Should I make the API call inside my component and use a Promise?
Any help?
Update:
After working a little on my own React project, and thinking about similar situations where I handle route changes there, I decided I want to change my original answer. I think the callback solution is OK, but the solution that you already mentioned of making the API call inside your component and using a promise is better. I realized that I've actually been doing this in my own app for a while now.
I use redux-form in my app, and it provides onSubmitSuccess/onSubmitFail functions that you can use to handle the submit result, and each of those rely on you returning a promise (usually from your action creator).
I think the fact that one of the most popular packages for form submission in React/Redux supports this pattern is an indication that it's probably a good pattern to use. Also, since react-router passes history into your component, it seems logical that they expect most people to do a lot of their programmatic route changes inside the component.
Here's an example of what the promise solution would look like with your code:
action.js
export const addNotification = value => dispatch => {
return new Promise(async (resolve, reject) => {
dispatch(addNotificationPending())
try {
const response = await client.createNotification(values)
dispatch(addNotificationSuccess(response))
resolve(response)
} catch(e) {
dispatch(addNotificationFailure())
reject(e)
}
})
}
component.js
onSubmit = async () => {
try {
await this.props.addNotification(parameters)
this.props.history.push('/new/route')
} catch(e) {
// could use a try/catch block here to display
// an error to the user here if addNotification fails,
// or go to a different route
}
}
Old Answer:
A simple solution would be to allow addNotification() to accept a callback function as an optional second argument.
export const addNotification = (value, callback=null) => async dispatch => {
dispatch(addNotificationPending())
try {
const response = await client.createNotification(values)
dispatch(addNotificationSuccess(response))
(typeof callback === 'function') && callback()
} catch(e) {
dispatch(addNotificationFailure())
}
}
Then inside your component use the router to go to the new route.
onSubmit = (values) => {
this.props.addNotification(parameters, () => {
this.props.history.push('/new/route')
})
}
You should not write your asynchronous calls in reducers or actions as the documentation clearly suggests them to be pure functions. You will have to introduce a redux-middleware like redux-thunk or redux-saga (I personally prefer sagas)
All your async calls will happen inside the middleware, and when it succeeds, you can use react-routers history .replace() or .push() methods to update your route. Let me know if it makes sense
You can use one popular package axios
See Here https://www.npmjs.com/package/axios
and you can implement your login like
axios.post('/user', {
firstName: 'Fred',
lastName: 'Flintstone'
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
You can write your loader login while calling api
and then you can hide your loader in .then

React-redux cross access state value

For last two weeks I have been working with redux and I'm facing an issue where I want to access/change a state value of another reducer. How can I achieve that?
For example: I have two components 'A-Component' and 'Message-component'
which has 'A-actions', 'Message-actions' and 'A-reducer', 'Message-reducer' respectively
When an action of 'A-Component' is called it will call the corresponding reducer function where I need to update the Message-reducer state value which will display the message box
A-action
export function add(data) {
return {
types: [types.ONADD, types.ONADDSUCCESS, types.ONADDFAIL],
payload: {
response: api.add(data).then(response => response),
data
}
};
}
A-reducer
export default createReducer(initialState, {
[types.ONADD](state) {
return {
...state,
message: 'Updating Records'
};
}
});
The above mentioned message state value is message reducer's state value. I want to update the message state value from A-reducer
which in turn updates the message component. Is this possible in redux?
I tried with various middleware but failed.
Thank in advance!
I think you're approaching this the wrong way. You should normalize your data as much as you can, and then maybe use the connect decorator to compose the state you need for your UI. For example, Messages could be nested under a "Friend"'s node, but it's better to have them in their own store, and then make a function that selects the messages from a friend based on a relationship. This gives you aggregations (You have 3 unread messages) for free. Take a look at reselect for a way to do this in a nice (and cached) way.
Edit:
You could write middleware which dispatches multiple actions:
export default (store) => (next) => (action) => {
if(!action.types){
return next(action);
}
action.types.forEach(type => {
next({
type,
payload: action.payload
})
});
}
Then call it from an Action Creator like so:
export function addMessage(message){
return {
types: ['ADD_MESSAGE', 'UPDATE_USER'],
payload: message
}
}
If you already have a update action in Message-actions
I think you can just directly dispatch the update action when ONADDSUCCESS is triggered.
// Message action
export function MessageUpdate (data) {
return {
type: ...,
data,
}
}
// A action
export function add(data) {
return dispatch => {
dispatch({
type: types.ONADD
});
// code for your add event
api.add(data).then( response => {
(() => {
dispatch(MessageUpdate(response));
return dispatch({
type: types.ONADDSUCCESS,
})
})()
});
}
}
Hope this answer to your question.

Resources