How to rewrite a basic action/reducer to use promises - javascript

I'm having trouble understanding how to rewrite normal action/reducer code to make use of redux-thunk or redux-promise-middleware so I can use promises.
I want to wait for my updatePhone to finish updating my state.user.information.phone before it starts testUserPhone. So obviously I need a promise to be returned from updatePhone.
this.props.updatePhone('+1**********')
.then(() => this.props.testUserPhone(this.props.user.information.phone))
action
export const updatePhone = (phone) => ({
type: UPDATE_PHONE,
payload: phone
})
and reducer
export default (state = INITIAL_STATE, action) => {
switch(action.type) {
case 'UPDATE_PHONE':
return {...state,
information: {
...state.information,
phone: action.payload
}
}
default:
return state
}
}
Should I write something like this as well as the basic function, or can I somehow combine them into one? Because I need the action to fully complete its cycle through my reducer and update phone before it comes back, but I don't want to break my reducer because now it won't be able to access payload and such since it's inside of a returned function -- super confused with how you start off using these libraries.
export function updatePhoneAsync(phone) {
return dispatch({
type: UPDATE_PHONE,
payload: phone
})
}
EDIT: So I've got this now for my action creators
export const updatePhone = (phone) => ({
type: UPDATE_PHONE,
payload: phone
})
export function updatePhoneAsync(phone) {
return function (dispatch) {
dispatch(updatePhone(phone))
}
}
Outside in my component;
this.props.updatePhoneAsync('+1**********')
.then(() => this.props.testUserPhone(this.props.user.information))
Which gives me an error 'cannot read property then of undefined'

You should write something like this if you use redux-thunk:
Action creators:
function update (params if you need them) {
return function (dispatch) {
send request here
.then(data =>
dispatch(phoneUpdated(data));
}
function phoneUpdated(phone) {
return {type: 'PHONE_UPDATED', phone};
}
Then, feel free to grab this action in your reducer and update the state as you wish.
Also, you can enhance it with additional actions in case when promise will be rejected, or at the start of request to show loader animations

Related

How can we await in dispatch for a particular value in redux action creator?

Here I am setting a flag as true(initially flag=false) and I want that flag in another reducer and stop and untill the flag is true
filterReduer.js
const filterReducer = (state = intialState, action) => {
switch(action..type){
case actionTypes.FILTER_SUCCESS:
return {
...state,
filtereData: action.data.messages,
flag: true,
};
default state;
}
}
OtherAction.js
export const mySolutions = (userId) => {
return async (dispatch, getState) => {
let flag = await getState().filter.flag; // I am getting flag info from different reducer
let data = getState().filter.channelMetaData;
console.log("data", data);
dispatch(mySolutionStarts());
}
}
My flag is false unable to wait untill flag is true
My intenton is when flag is set true the api call and data is updated to state and where I can use the state info for further call but unable to wait
Timeout is not a good idea where flag vaue is updated based on api call
is there any different approcah ?
First, in case you pasted your code here, you should change
switch(action..type){
// ...
filtereData: action.data.messages,
// ...
}
to
switch(action.type){
// ...
filterData: action.data.messages,
// ...
}
As for dispatching an action on successful API response: I have to assume a lot, but let's hope that you have an initial action with the API call. If so, you should simply dispatch the second action within the same function that dispatches the first. Something like:
const myAsyncAction = () => async (dispatch, getState) => {
dispatch({ type: FIRST_ACTION }); // perhaps you want to set the flag here
// async API call here
if (apiRes.status === 200) { // if successful response
dispatch({ type: SECOND_ACTION }) // set flag again
}
}
As far as I know, you can dispatch as many different actions as you like in there. Don't forget to import the actions if needed.

Callback after Redux Thunk function: Uncaught TypeError: Cannot read property 'then' of undefined

Problem
I have an async function in redux (with redux thunk) that set a value in the redux store. It is used many times throughout my app but, I want different things to happen after the redux function runs using the new values that the redux function sets. Seems like a good reason for a callback, right? So I tried doing just this, but I got the following error:
Uncaught TypeError: Cannot read property 'then' of undefined
Logic in Attempt to fix
I thought the reason might be that although the redux function dispatches actions, it doesn't actually return anything. Therefore, I added returns in the redux function thinking that the function would return something back to the component that called it, which would resolve the error I am receiving. No Luck.
Question:
Does anyone know how I can perform functions after an async redux function (with redux thunk) runs and finishes setting a new value in the redux store?
My Most Recent Attempt to Solve
Component
import {fetchUser} from './../actions/index.js';
class MyComponent extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
// Attempt #1: this.props.fetchUserData()); // originally I attempted just running one function and then the rest without a callback but the new value was not set in redux store before the next function that needed it ran
// Attempt #2: this.props.fetchUserData().then(function() => { // I also tried running just a standard .then(function()
// Attempt #3: (line below)
this.props.fetchUserData().then((resultFromReduxFunction) => {
console.log("fetchUserData result = " + resultFromReduxFunction);
// do some stuff with the new data
console.log("this.props.reduxData.userHairColor = " + this.props.reduxData.userHairColor);
console.log("this.props.reduxData.userHeight = " + this.props.reduxData.userHeight);
});
}
}
function mapStateToProps(state) {
return {
reduxData: state.user
};
}
function matchDispatchToProps(dispatch) {
return bindActionCreators({
fetchUserData: fetchUserData
}, dispatch)
}
export default connect(mapStateToProps, matchDispatchToProps)(MyComponent);
Action Creator
export const set = (idToSet, payloadToSet) => {
return {
type: 'SET',
id: idToSet,
payload: payloadToSet
}
}
export const fetchUserData = (callbackFunction) => {
console.log("fetchUserData triggered...");
return (dispatch, getState) => {
firebase.auth().onAuthStateChanged(function (user) {
if (!user) {
console.log("no user logged in");
return false // Added in attempt to fix callback
} else {
// fetch some more user data
firebase.database().ref().child('users').child(user.uid).on('value', function(snapshot) {
var userHairColor = snapshot.child(userHairColor).val();
var userHeight = snapshot.child(userHeight).val();
dispatch(set('userHairColor', userHairColor));
dispatch(set('userHeight', userHeight));
return true // Added in attempt to fix callback
})
}
})
}
}
Redux Store
const initialState = {
userHairColor: "",
userHeight: ""
}
export default function (state=initialState, action) {
switch(action.type) {
case 'SET':
return {
...state,
[action.id]: action.payload
}
default:
return {
...state
}
}
}
Thanks in advance for any help
To use .then(...), your thunk has to return a Promise. Firebase's onAuthStateChanged seems to returns an unsubscribe function, not a Promise, so even if you returned that it wouldn't allow you to chain additional callbacks at your action creator call site. And you can't return from within the callback you pass to onAuthStateChanged, because you're in a different call stack at that point (it's asynchronous).
What you're going to have to do is pass a callback function to your action creator, which it needs to call from within the success callback of your data fetching.
Something like this:
export const fetchUserData = (callbackFunction) => {
return (dispatch, getState) => {
firebase.auth().onAuthStateChanged(function (user) {
if (!user) {
console.log("no user logged in");
// You can pass false to your callback here, so that it knows the auth was unsuccessful
callbackFunction(false);
} else {
// fetch some more user data
firebase.database().ref().child('users').child(user.uid).on('value', function(snapshot) {
var userHairColor = snapshot.child(userHairColor).val();
var userHeight = snapshot.child(userHeight).val();
dispatch(set('userHairColor', userHairColor));
dispatch(set('userHeight', userHeight));
// Passing true so your callback knows it was a success
callbackFunction(true);
})
}
})
}
}
Then to use it as desired:
this.props.fetchUserData((success) => {
console.log("fetchUserData result = " + success);
// do some stuff with the new data
console.log("this.props.reduxData.userHairColor = " + this.props.reduxData.userHairColor);
console.log("this.props.reduxData.userHeight = " + this.props.reduxData.userHeight);
});
There's a bit of callback hell going on here, but to avoid that you'd have to use a "promisified" version of Firebase's API, in which case you could also return the promise from your thunk so that anything using your action creator could attach a .then(...).
The callback would work if you actually explicitly called it inside the thunk, but it really feels like an anti-pattern.
Instead you can dispatch another thunk that can deal with any further logic.
export const fetchUserData = (callbackFunction) => {
console.log("fetchUserData triggered...");
return (dispatch, getState) => {
firebase.auth().onAuthStateChanged(function (user) {
...do stuf...
dispatch(doMoreStuff())
})
}
}
Or, if you need to react to the result inside your react component dispatch an action that will modify your redux state. That will in turn call the react life cycle methods and render and you can react to the change there based on the new state.
To improve the answer it would help to know what you want to do after the action is done.

Prevent Redux-Thunk from re-requesting data that already is in store

I'm trying to use redux thunk to make asynchronous actions and it is updating my store the way I want it to. I have data for my charts within this.props by using:
const mapStateToProps = (state, ownProps) => {
const key = state.key ? state.key : {}
const data = state.data[key] ? state.data[key] : {}
return {
data,
key
}
}
export default connect(mapStateToProps)(LineChart)
Where data is an object within the store and each time I make an XHR call to get another piece of data it goes into the store.
This is the the async and the sync action
export function addData (key, data, payload) {
return {
type: ADD_DATA,
key,
data,
payload
}
}
export function getData (key, payload) {
return function (dispatch, getState) {
services.getData(payload)
.then(response => {
dispatch(addData(key, response.data, payload))
})
.catch(error => {
console.error('axios error', error)
})
}
}
And the reducer:
const addData = (state, action) => {
const key = action.key
const data = action.data.results
const payload = action.payload
return {
...state,
payload,
key,
data: {
...state.data,
[key]: data
}
}
}
In the tutorial I have been following along with (code on github), this seems like enough that when a piece of data that already exists within the store, at say like, data['some-key'] redux will not request the data again. I'm not entirely sure on how it's prevented but in the course, it is. I however am definitely making network calls again for keys that already exist in my store
What is the way to prevent XHR for data that already exists in my store?
Redux itself does nothing about requesting data, or only requesting it if it's not cached. However, you can write logic for that. Here's a fake-ish example of what a typical "only request data if not cached" thunk might look like:
function fetchDataIfNeeded(someId) {
return (dispatch, getState) => {
const state = getState();
const items = selectItems(state);
if(!dataExists(items, someId)) {
fetchData(someId)
.then(response => {
dispatch(loadData(response.data));
});
}
}
}
I also have a gist with examples of common thunk patterns that you might find useful.

return promise from store after redux thunk dispatch

I am trying to chain dispatches with redux thunk
function simple_action(){
return {type: "SIMPLE_ACTION"}
}
export function async_action(){
return function(dispatch, getState){
return dispatch(simple_action).then(()=>{...});
}
}
How do I get the dispatch to return a promise from the store?
MORE SPECIFICALLY:
I am probably just not understanding something here, but in all the examples with redux-thunk, they call a separate async event (like fetch), which obviously returns a Promise.
What I'm specifically looking for is when I dispatch an action to the store: How do I make certain the store has processed that action completely before anything else happens in the function action_creator() above.
Ideally, I would like the store to return some sort of promise, but I don't understand how or where that happens?
Here you have an example on how to dispatch and chain async action. https://github.com/gaearon/redux-thunk
The thunk middleware knows how to turn thunk async actions into actions, so you just have to have your simple_action() to be a thunk and the thunk middleware will do the job for you, if the middleware see a normal action, he will dispatch this action as normal action but if it's an async function it will turn your async action into normal action.
So your simple_action need to be a thunk ( A thunk is a function that returns a function.) Like this for example:
function makeASandwichWithSecretSauce(forPerson) {
return function (dispatch) {
return fetchSecretSauce().then(
sauce => dispatch(makeASandwich(forPerson, sauce)),
error => dispatch(apologize('The Sandwich Shop', forPerson, error))
);
};
}
When using the makeASandwichWithSecretSauce function you can use the dispatch function
store.dispatch(
makeASandwichWithSecretSauce('Me')
);
And even
// It even takes care to return the thunk’s return value
// from the dispatch, so I can chain Promises as long as I return them.
store.dispatch(
makeASandwichWithSecretSauce('My wife')
).then(() => {
console.log('Done!');
});
Here a complete example on how you can write action creators that dispatch actions and async actions from other action creators, and build your control flow with Promises.
function makeSandwichesForEverybody() {
return function (dispatch, getState) {
if (!getState().sandwiches.isShopOpen) {
// You don’t have to return Promises, but it’s a handy convention
// so the caller can always call .then() on async dispatch result.
return Promise.resolve();
}
//Do this action before starting the next one below
dispatch(simple_action());
// We can dispatch both plain object actions and other thunks,
// which lets us compose the asynchronous actions in a single flow.
return dispatch(
makeASandwichWithSecretSauce('My Grandma')
).then(() =>
Promise.all([
dispatch(makeASandwichWithSecretSauce('Me')),
dispatch(makeASandwichWithSecretSauce('My wife'))
])
).then(() =>
dispatch(makeASandwichWithSecretSauce('Our kids'))
).then(() =>
dispatch(getState().myMoney > 42 ?
withdrawMoney(42) :
apologize('Me', 'The Sandwich Shop')
)
);
};
}
//apologize and withdrawMoney are simple action like this for example
return {
type: "END_SUCESS"
}
//usage
store.dispatch(
makeSandwichesForEverybody()
).then(() =>
console.log("Done !");
);
To create you own promises you can use a library like bluebird.
//EDIT :
To be sure that the store has processed that action completely before anything else happens in the function action_creator() you can dispatch this simple_action before action_creator(); // I added this comment to the code //Do this action before starting the next one below
This is a pattern I've been using recently:
export const someThenableThunk = someData => (dispatch, getState) => Promise.resolve().then(() => {
const { someReducer } = getState();
return dispatch({
type: actionTypes.SOME_ACTION_TYPE,
someData,
});
});
When you dispatch(someThenableThunk('hello-world')), it returns a Promise object that you can chain further actions to.
dispatch will return whatever the action/function it calls returns; so if you want to chain certain activities (as per your example), your action would need to return a Promise.
As #Aaleks mentions, if your action were a thunk you can create a scenario where you return a Promise, then you could do as you mention.
BTW I think naming your thunk action_creator is a bit misleading, as simple_action is actually an Action Creator in Redux parlance - have edited accordingly :)
What you will need to do is create trunkate action which returns Promise. The dispatch function return what you have added as argument to it's call. For example, if you want dispatch to return Promise you'd have to add Promise as argument to the call.
function simple_action() {
return { type: 'SIMPLE_ACTION' };
}
export function async_action(dispatch, getState) {
return function () {
return Promise.resolve(dispatch(simple_action()));
}
}
const boundAction = async_action(dispatch, getState);
boundAction().then(() => {});
Asynchronous action and how to call an action from a component when using redux and thunk
Without Promise
action.js
export function shareForm(id) {
return function (dispatch) {
dispatch({
type: 'SHARE_FORM',
payload: source.shareForm(id)
})
}
}
SomeComponent.js
dispatch(shareForm(id))
With Promise
action.js
export function shareForm(id, dispatch) {
return new Promise((resolve, reject) => {
dispatch({
type: 'SHARE_FORM',
payload: source.shareForm(id)
})
.then(res => resolve(res))
.catch(err => reject(err))
})
}
SomeComponent.js
shareForm(id, dispatch)
.then(res => console.log('log on success', res))
.catch(err => console.log('log on failure', err))
PS: Let me know in comments if you need more explanations

How to undo Redux Async Action? (multiple steps back in the state)

if I have an async action with api call, which could either be an action returns a function:
export function asyncAction(itemId) {
return (dispatch) => {
dispatch(requestStarted());
return sendRequest(itemId).then(
(result) => dispatch(requestSuccess()),
(error) => dispatch(requestFail())
);
};
}
or one returns an object and uses middleware to intercept it and do stuff:
export function asyncAction(itemId) {
return {
type: [ITEM_REQUEST, ITEM_REQUEST_SUCCESS, ITEM_REQUEST_FAILURE],
promise: sendRequest(itemId),
userId
};
}
// same middleware found in https://github.com/rackt/redux/issues/99
export default function promiseMiddleware() {
return (next) => (action) => {
const { promise, ...rest } = action;
if (!promise) {
return next(action);
}
next({ ...rest, readyState: 'request' );
return promise.then(
(result) => next({ ...rest, result, readyState: 'success' }),
(error) => next({ ...rest, error, readyState: 'failure' })
);
};
}
Now my question is: How do I rollback to the state before the asyncAction is dispatched, which essentially means two steps back in the state(success/failure => request) w/ an api call to undo last api call.
For example, after delete a todo item(which is an async action), a popup snackbar shows with an undo option, after click it the deleted todo item will be added back to UI along with an api call to add it back to db.
I've tried redux-undo but I feel it's not intended to solve problems like this.
Or I should forget about 'undo' and just dispatch a brand new addTodo action when user clicks undo option?
Thanks in advance :-)
Redux Optimist might be what you need.

Categories