redux action is not a promise thus cannot use .then method - reactjs

when my props.postToAllData and props.postToUserData methods are called, it takes a while for the fetch method to make its course. Thus, I am being pushed to another webpage and the new information doesn't show since Im pushing to a new page quicker than my post method. I cant use .then method since this isn't a promise. What can i do to push to after the post method finishes?
const onSubmit = (event) => {
event.preventDefault();
if (ingredientsArray.length !== 0 && instructionsArray.length !== 0) {
props.postToAllData(
{
...recipe,
ingredients: ingredientsArray,
instructions: instructionsArray,
img: isSelectedFile,
author: user.username,
},
),
props.postToUserData(
{
...recipe,
ingredients: ingredientsArray,
instructions: instructionsArray,
img: isSelectedFile,
author: user.username,
},
),
resetForm();
history.push('/dashboard/global');
} else setErrorForm('Please fill in all fields');
};

You can use a middleware for redux called thunk. Thunk allows you to write async actions, which you can't with regular redux.
It'll work like this:
Declare an async action with your api call
const myAsyncAtion = () => {
return async () => {
const response = await apiCall();
return response;
}
}
And just dispatch it in your code like any other action. Now it returns a promise, which you can chain with .then or await. Cheers!
const save = async () => {
await props.myAsyncAction();
console.log('hooray')
// or
props.myAsyncAction().then(() => console.log('hooray'))
}

Related

React and Redux toolkit - reject after promise

I'm working on a React Native app. I have a signup screen which has a button, onclick:
const handleClick = (country: string, number: string): void => {
dispatch(registerUser({ country, number }))
.then(function (response) {
console.log("here", response);
navigation.navigate(AuthRoutes.Confirm);
})
.catch(function (e) {
console.log('rejected', e);
});
};
The registerUser function:
export const registerUser = createAsyncThunk(
'user/register',
async ({ country, number }: loginDataType, { rejectWithValue }) => {
try {
const response = await bdzApi.post('/register', { country, number });
return response.data;
} catch (err) {
console.log(err);
return rejectWithValue(err.message);
}
},
);
I have one of my extraReducers that is indeed called, proving that it's effectively rejected.
.addCase(registerUser.rejected, (state, {meta,payload,error }) => {
state.loginState = 'denied';
console.log(`nope : ${JSON.stringify(payload)}`);
})
But the signup component gets processed normally, logging "here" and navigating to the Confirm screen. Why is that?
A thunk created with createAsyncThunk will always resolve but if you want to catch it in the function that dispatches the thunk you have to use unwrapResults.
The thunks generated by createAsyncThunk will always return a resolved promise with either the fulfilled action object or rejected action object inside, as appropriate.
The calling logic may wish to treat these actions as if they were the original promise contents. Redux Toolkit exports an unwrapResult function that can be used to extract the payload of a fulfilled action or to throw either the error or, if available, payload created by rejectWithValue from a rejected action:
import { unwrapResult } from '#reduxjs/toolkit'
// in the component
const onClick = () => {
dispatch(fetchUserById(userId))
.then(unwrapResult)
.then(originalPromiseResult => {})
.catch(rejectedValueOrSerializedError => {})
}

How does this code work in this specifics regarding async wait

I learn some React and Redux and have a beginner question.
In the GitHub code below there are two method calls getInitUsers() and getMoreUsers().
Here is Original GitHub code for the below code
....
useEffect(() => {
const getUsers = async () => {
await getInitUsers();
}
getUsers();
}, [getInitUsers]);
const addMoreUsers = () => {
if(!isGettingMoreUsers) {
getMoreUsers(prevDoc);
}
}
....
const mapDispatchToProps = dispatch => ({
getInitUsers: () => dispatch(asyncGetInitUsers()),
getMoreUsers: prevDoc => dispatch(asyncGetMoreUsers(prevDoc))
})
...
The Redux action for the above getInitUsers() and getMoreUsers() are this two:
Here is the original GitHub code for the below code
export const asyncGetInitUsers = () => {
return async dispatch => {
try {
dispatch(getUsersStart());
const usersRef = firestore.collection("users").orderBy("registrationDate", "desc").limit(30);
usersRef.onSnapshot(docSnapShot => {
let users = [];
docSnapShot.docs.forEach(doc => {
users.push({id: doc.id, data: doc.data()});
});
dispatch(getUsersSuccess(users));
const lastDoc = docSnapShot.docs[docSnapShot.docs.length - 1];
dispatch(setPrevDoc(lastDoc));
});
} catch (errorMsg) {
dispatch(getUsersFailure(errorMsg));
}
}
}
export const asyncGetMoreUsers = prevDoc => {
return async dispatch => {
try {
dispatch(getMoreUsersStart());
const usersRef = firestore.collection("users").orderBy("registrationDate", "desc").startAfter(prevDoc).limit(30);
usersRef.onSnapshot(docSnapShot => {
let users = []
docSnapShot.docs.forEach(doc =>{
users.push({id: doc.id, data: doc.data()})
});
dispatch(getMoreUsersSuccess(users));
const lastDoc = docSnapShot.docs[docSnapShot.docs.length - 1];
dispatch(setPrevDoc(lastDoc));
});
} catch (e) {
dispatch(getMoreUsersFailure(e));
}
}
}
I understand placing the getInitUsers() in the useEffect() will make it run once on Component creation. What I want to ask is what does this await do on this line:
await getInitUsers();
If you look at the getMoreUsers() it does not have the await and if you look at the two action asyncGetInitUsers() and asyncGetMoreUsers() abowe they have the same logic and starts with:
return async dispatch => {...
So what is the difference here? getInitUsers() and getMoreUsers()??
I created a CodeSandbox to try to understand the await
In this case, the await does nothing different, there's actually no point in using async await because you aren't doing anything after the await or returning a value from it.
So, the getInitUsers bit could be simplified to:
useEffect(() => {
getInitUsers();
}, [getInitUsers]);
For example, if you wanted to run some code after the getInitUsers finished. For example, a loading boolean:
const [loading, setLoading] = useState(false);
useEffect(() => {
setLoading(true);
const getUsers = async () => {
await getInitUsers();
setLoading(false);
};
getUsers();
}, [getInitUsers]);
Though due to the simplicity of the code, this could be simplified by using promises directly:
const [loading, setLoading] = useState(false);
useEffect(() => {
setLoading(true);
getInitUsers().then(() => {
setLoading(false);
});
}, [getInitUsers]);
For some documentation on async await and what it does for us: https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Async_await, but here's a bit of an intro:
Essentially, async/await is a combination of features that allow for easier asynchronous flows in JS.
These features basically act as syntactic sugar on top of promises,
making asynchronous code easier to write and to read afterwards. They
make async code look more like old-school synchronous code, so they're
well worth learning. This article gives you what you need to know.
async functions always return a promise, and they do so the moment they are called.
When the async function finishes execution, then that promise resolves to the value returned from it (undefined if nothing is returned) or is rejected with an error.
await only works in async functions (though there is a proposal that's in stage 3 for Top Level Await). await takes a promise and waits for it to be resolved or rejected before continuing execution and unwrapping the promise.
So, without async/await, you need to use the .then or .catch functions of promises, but with async/await, you can do a lot to reduce callback hell.
Here's some very contrived examples to show how using the async/await syntatic sugar can make code easier to read and reason through. Though the biggest danger is also that async/await makes the code look synchronous even though it's not.
// Setting up a couple promises
let promise = new Promise(resolve => resolve(42));
let promise2 = new Promise(resolve => resolve(8));
// Using promises to multiply them together
Promise.all([promise, promise2])
.then(([value, value2]) => value * value2)
.then(value => console.log('promises', value))
// Setting up a couple promises
let promise3 = new Promise(resolve => resolve(42));
let promise4 = new Promise(resolve => resolve(8));
// Using async/await to multiply them together
(async() => {
let value = await promise3 * await promise4;
console.log('async/await', value);
})()

Call async method in Office Fabric CommandBar onClick event using react hooks?

I have a react hook style component in typescript. I'm using office uifabric as ui framework. I want to get the following pattern to work in a "best practice" manner:
I have a component with an onClick event (in my case a CommandBar)
User clicks on the action
I make an async call to a backend api (the async part is whats causing me trouble here I think, and this is unfortunately a requirement).
When the async call is complete, or fails. I want to show a MessageBar with information.
All in all using as little code as possible, react hooks seems to have the ability to produce nice and condence code together with TypeScript. But I'm new to both things so I may be out on a limb here...
Creating a dummy fetch method that is NOT async causes my example code to work as expected. But when I have the async call, something gets lost and I do not get a re-render and visibility of the MessageBar. The api call is made though!
const UserProfile = () => {
const [apiStatusCode, setApiResponseCode] = useState(0);
const [showMessageBar, setShowMessageBar] = useState(false);
const startAsync = () => {
// With await here I get a compiler
// error. Without await, the api call is made but
// the messagebar is never rendered. If the call is not async, things
// work fine.
myAsyncMethod();
};
const toggleMessageBar = (): (() => void) => (): void => setShowMessageBar(!showMessageBar);
const myAsyncMethod = async () => {
try {
const responseData = (await get('some/data'))!.status;
setApiResponseCode(responseData);
toggleMessageBar();
} catch (error) {
console.error(error);
setApiResponseCode(-1);
toggleMessageBar();
}
};
const getItems = () => {
return [
{
key: 'button1',
name: 'Do something',
cacheKey: 'button1', //
onClick: () => startAsync()
}
];
};
return (
<Stack>
<Stack.Item>
<CommandBar
items={getItems()}
/>
</Stack.Item>
{showMessageBar &&
<MessageBar messageBarType={MessageBarType.success} onDismiss={toggleMessageBar()} dismissButtonAriaLabel="Close">
Successfully retrieved info with status code: {apiStatusCode}
</MessageBar>
}
// Other ui parts go here
</Stack>
)
};
export default UserProfile;
Assigning an async method to the onClick event gives compiler error:
Type 'Promise<void>' is not assignable to type 'void'. TS2322
Not awaiting the async call causes react not re-rendering when call is complete.
A common pattern for this sort of thing it to update your state in the callback as you are doing, and then respond to the new data with useEffect:
useEffect(()=>{
setShowMessageBar(!showMessageBar)
},[apiStatusCode, showMessageBar])
const myAsyncMethod = async () => {
try {
const responseData = (await get('some/data'))!.status;
setApiResponseCode(responseData);
// toggleMessageBar(); <-- delete me
} catch (error) {
console.error(error);
setApiResponseCode(-1);
// toggleMessageBar(); <-- delete me
}
};
Found the answer eventually, the key was to use reacts useEffect mechanism. This answer led me to the solution:
Executing async code on update of state with react-hooks
Key changes in my code:
Create a state to track execution
const [doAsyncCall, setDoAsyncCall] = useState(false);
Set the state in the onClick event:
const getItems = () => {
return [
{
key: 'button1',
name: 'Do something',
cacheKey: 'button1', //
onClick: () => setDoAsyncCall(true)
}
];
};
Wrap the async functionality in a useEffect section that depends on the state:
useEffect(() => {
async function myAsyncMethod () {
try {
const responseData = (await get('some/data'))!.status;
setApiResponseCode(responseData);
setShowMessageBar(true);
} catch (error) {
console.error(error);
setApiResponseCode(-1);
setShowMessageBar(true);
}
}
if (doAsyncCall) {
myAsyncMethod();
}
}, [doAsyncCall]);

How to mock out async actions

I'm trying to test this function:
function login(username, password) {
let user = { userName: username, password: password };
return dispatch => {
localStorageService.login(username, password).then((response) => {
dispatch(resetError());
dispatch(success( { type: userConstants.LOGIN, user} ));
}, (err) => {
dispatch(error(err));
});
};
function success(user) { return { type: userConstants.LOGIN, payload: user } };
};
Here is my test
const mockStore = configureStore([thunk]);
const initialState = {
userReducer: {
loggedInUser: "",
users: [],
error: ""
}
};
const store = mockStore(initialState);
jest.mock('./../../services/localStorageService');
describe("Login action should call localstorage login", () => {
let localStorage_spy = jest.spyOn(localStorageService, 'login');
store.dispatch(userActions.login(test_data.username, test_data.password)()).then( () => {
expect(localStorage_spy).toHaveBeenCalled();
});
});
The error I get:
Actions must be plain objects. Use custom middleware for async actions.
A lot of resources online keep telling me to use thunk in my test for these actions but it's not working. The last thing it calls is dispatch(resetError()); and it breaks. I've never really found a resource online which is similar enough to my problem. My function returns a dispatch which returns a promise which returns another dispatch when the promise resolves. I'm just trying to get the function to return. I've put a spy on localStorageService.login and also mocked it out and I have an expect to make sure it was called. But of course the function is not returning

Dispatch async redux action from non-react component with thunk middleware

I am building an react / redux webapp where I am using a service to make all my API calls. Whenever the API returns 401 - Unauthorized I want to dispatch a logout action to my redux store.
The problem is now that my api-service is no react component, so I cannot get a reference to dispatch or actions.
What I did first was exporting the store and calling dispatch manually, but as I read here How to dispatch a Redux action with a timeout? that seems to be a bad practice because it requires the store to be a singleton, which makes testing hard and rendering on the server impossible because we need different stores for each user.
I am already using react-thunk (https://github.com/gaearon/redux-thunk) but I dont see how I can injectdispatch` into non-react components.
What do I need to do? Or is it generally a bad practice to dispatch actions outside from react components?
This is what my api.services.ts looks like right now:
... other imports
// !!!!!-> I want to get rid of this import
import {store} from '../';
export const fetchWithAuth = (url: string, method: TMethod = 'GET', data: any = null): Promise<TResponseData> => {
let promise = new Promise((resolve, reject) => {
const headers = {
"Content-Type": "application/json",
"Authorization": getFromStorage('auth_token')
};
const options = {
body: data ? JSON.stringify(data) : null,
method,
headers
};
fetch(url, options).then((response) => {
const statusAsString = response.status.toString();
if (statusAsString.substr(0, 1) !== '2') {
if (statusAsString === '401') {
// !!!!!-> here I need to dispatch the logout action
store.dispatch(UserActions.logout());
}
reject();
} else {
saveToStorage('auth_token', response.headers.get('X-TOKEN'));
resolve({
data: response.body,
headers: response.headers
});
}
})
});
return promise;
};
Thanks!
If you are using redux-thunk, you can return a function from an action creator, which has dispatch has argument:
const doSomeStuff = dispatch => {
fetch(…)
.then(res => res.json())
.then(json => dispatch({
type: 'dostuffsuccess',
payload: { json }
}))
.catch(err => dispatch({
type: 'dostufferr',
payload: { err }
}))
}
Another option is to use middleware for remote stuff. This works the way, that middle can test the type of an action and then transform it into on or multiple others. have a look here, it is similar, even if is basically about animations, the answer ends with some explanation about how to use middleware for remote requests.
maybe you can try to use middleware to catch the error and dispatch the logout action,
but in that case, the problem is you have to dispatch error in action creator which need to check the log status
api: throw the error
if (statusAsString === '401') {
// !!!!!-> here I need to dispatch the logout action
throw new Error('401')
}
action creator: catch error from api, and dispatch error action
fetchSometing(ur)
.then(...)
.catch(err => dispatch({
type: fetchSometingError,
err: err
})
middleware: catch the error with 401 message, and dispatch logout action
const authMiddleware = (store) => (next) => (action) => {
if (action.error.message === '401') {
store.dispatch(UserActions.logout())
}
}
You should have your api call be completely independent from redux. It should return a promise (like it currently does), resolve in the happy case and reject with a parameter that tells the status. Something like
if (statusAsString === '401') {
reject({ logout: true })
}
reject({ logout: false });
Then in your action creator code you would do:
function fetchWithAuthAction(url, method, data) {
return function (dispatch) {
return fetchWithAuth(url, method, data).then(
({ data, headers }) => dispatch(fetchedData(data, headers)),
({ logout }) => {
if(logout) {
dispatch(UserActions.logout());
} else {
dispatch(fetchedDataFailed());
}
);
};
}
Edit:
If you don't want to write the error handling code everywhere, you could create a helper:
function logoutOnError(promise, dispatch) {
return promise.catch(({ logout }) => {
if(logout) {
dispatch(UserActions.logout());
}
})
}
Then you could just use it in your action creators:
function fetchUsers() {
return function (dispatch) {
return logoutOnError(fetchWithAuth("/users", "GET"), dispatch).then(...)
}
}
You can also use axios (interceptors) or apisauce (monitors) and intercept all calls before they goes to their handlers and at that point use the
// this conditional depends on how the interceptor works on each api.
// In apisauce you use response.status
if (response.status === '401') {
store.dispatch(UserActions.logout())
}

Resources