Wait for action to update state in react-native and redux - javascript

I have a simple react-native application with a redux store set up. Basically I want to add a new story, dispatch the redux action and transition to this new story after it has been created.
I have the following code in my Container Component, which runs when the user taps on an add button.
addStory() {
this.props.actions.stories.createStory()
.then(() => Actions.editor({ storyId: last(this.props.stories).id }); // makes the transition)
}
And the following action creator.
export const createStory = () => (dispatch) => {
dispatch({ type: CREATE_STORY, payload: { storyId: uniqueId('new') } });
return Promise.resolve();
};
As you see, I return a promise in the action creator. If I don't return a promise here, the transition will be made before the state has been updated.
This seems a little odd to me - why do I have to return a resolved Promise here? Aren't dispatches meant to be synchronous?

As discussed in comments
Callbacks Example:
addStory() {
this.props.actions.stories.createStory( (id) => {
Actions.editor({ storyId: id })
});
}
export const createStory = ( callback ) => (dispatch) => {
const _unique_id = uniqueId('new');
dispatch({ type: CREATE_STORY, payload: { storyId: _unique_id } });
callback(_unique_id);
};
Timeout Example:
Here we're assuming the state would have updated by now.. that's not the case most of the times.
addStory() {
this.props.actions.stories.createStory()
setTimeout( () => {
Actions.editor({ storyId: last(this.props.stories).id });
}, 500);
}
export const createStory = () => (dispatch) => {
dispatch({ type: CREATE_STORY, payload: { storyId: uniqueId('new') } });
};
Promise:
this can take a sec or a minute to complete.. it doesn't matter. you do everything you have to do here and finally resolve it so the app/component can perform next actions.
export const createStory = () => (dispatch) => {
return new Promise( (resolve, reject) => {
// make an api call here to save data in server
// then, if it was successful do this
dispatch({ type: CREATE_STORY, payload: { storyId: uniqueId('new') } });
// then do something else
// do another thing
// lets do that thing as well
// and this takes around a minute, you could and should show a loading indicator while all this is going on
// and finally
if ( successful ) {
resolve(); // we're done so call resolve.
} else {
reject(); // failed.
}
});
};
And now, checkout http://reactivex.io/rxjs/

Related

Calling another action after one is completed in React

I'm using Redux Thunk.
I got an async operation (updating message to db), and I want to wait for it to complete and then get the updated messages array from the db.
I tried:
const handleWriteMessage = async (e) => {
e.preventDefault()
await dispatch(
writeMessage({
sender: data.sender,
subject: data.subject,
receiver: data.receiver,
message: data.message,
created: date
})
)
dispatch(getMessages())
}
But it doesn't mind the await and runs getMessages() immediately when handleWriteMessage is called.
I tried to do it in the action itself after it's completed:
axios
.post('http://localhost:4000/api/messages/writeMessage', msg, config)
.then((res) => {
getMessages()
dispatch({
type: WRITE_MESSAGE_SUCCESS
})
})
But it's not working too.
What am I missing?
It seem that handleWriteMessage should not by async, it must return function that will accept dispatch and may execute async function, see redux-thunk docs.
See snippet below and its output.
var thunk = createThunkMiddleware();
var log = (state = [], action) => state.concat(action.message || action.type);
var store = Redux.createStore(log, [], Redux.applyMiddleware(thunk, logger));
(async() => {
store.dispatch(asyncAC('async message 1.a'))
.then(() => asyncAC('async message 1.b'))
.then(store.dispatch);
store.dispatch(syncAC('sync 1'));
await store.dispatch(asyncAC('await async message 2.a'));
store.dispatch(syncAC('sync 2'));
store.dispatch(asyncAC('await async message 3.a'))
.then(() => store.dispatch(asyncAC('then async message 3.b')));
})();
function syncAC(m) {
return {
type: 'log',
message: m
}
}
function asyncAC(m) {
return (dispatch) => {
return new Promise(resolve => setTimeout(resolve, 1000, syncAC(m)))
.then(dispatch);
}
}
// redux-thunk itself
function createThunkMiddleware(extraArgument) {
return function(_ref) {
var dispatch = _ref.dispatch,
getState = _ref.getState;
return function(next) {
return function(action) {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
};
};
}
// logger middleware
function logger({
getState
}) {
return next => action => {
console.log('will dispatch', action)
// Call the next dispatch method in the middleware chain.
const returnValue = next(action)
console.log('state after dispatch', getState())
// This will likely be the action itself, unless
// a middleware further in chain changed it.
return returnValue
}
}
<script src="https://unpkg.com/redux#4.0.5/dist/redux.js"></script>

adding resolve() to vuex action breaks data loading in component

I have the following action in my VueX store
Adding resolve() makes component unable to load store data
fetch_resources({ commit, rootState }) {
return new Promise((resolve, reject) => {
const url = '/api/resource/';
rootState.axios_api.get(url)
.then((response) => {
commit('SET_RESOURCES', response.data);
resolve(response.data); // Adding this line breaks my component
}).catch((error) => {
reject(setErrorServer(error));
});
});
},
Data is loaded and mutated in the store but is not loaded whitin the component called by this action on mount.
How should I fix my then().catch() to make it work ?
mounted() {
this.fetch_resources().then(() => {
}).catch((error) => {
showErrorModal(error);
});
},
``̀̀̀`
Axios themself returns promise, so you dont need to wrap it:
fetch_resources({ commit, rootState }) {
const url = '/api/resource/';
// also you may make a closure to param variable
let com = commit
return rootState.axios_api.get(url)
.then((response) => {
commit('SET_RESOURCES', response.data);
// com('SET_RESOURCES', response.data);
resolve(response.data); // Adding this line breaks my component
}).catch((error) => {
reject(setErrorServer(error));
});
},

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.

Unit testing Redux async actions

I am trying to add unit test cases to my redux actions.
I have tried this, this & this
I am using thunk, promise-middleware in my actions
one of my action is like this
export function deleteCommand(id) {
return (dispatch) => {
dispatch({
type: 'DELETE_COMMAND',
payload: axios.post(`${Config.apiUrl}delete`, { _id: id })
})
}
}
unit test for this is
import configureMockStore from "redux-mock-store"
const middlewares = [thunk, promiseMiddleware()];
const mockStore = configureMockStore(middlewares)
it('creates DELETE_COMMAND_FULFILLED after deleting entry', (done) => {
nock('http://localhost:8081/')
.post('/delete',{
_id: 1
})
.reply(200, {})
const expectedActions = [{
type: 'DELETE_COMMAND_FULFILLED',
payload: {}
}]
const store = mockStore({
command: {
commands: []
}
})
store.dispatch(actions.deleteCommand({_id: 1}).then(function () {
expect(store.getActions())
.toEqual(expectedActions)
done();
})
})
I am using nock,redux-mock-store configured with thunk, promise middleware
It gives then of undefined
then I changed the action to return the promise, then I got unhandled promise reject exception, I added a catch to the action dispatch call.
Now I am getting Network Error because nock is not mocking the call.
Also tried moxios, changed axios to isomorphic-fetch, whatwg-fetch. Doesn't seem to be working
Where am I doing it wrong?.
I know It's from 2017,
But maybe someone will have the same issue.
So the problem is that the arrow function inside deleteCommand returns undefined.
That's why you get
It gives then of undefined
TLDR:
if you using redux-thunk with redux-promise-middleware
You must return the inner dispatch
here is the official redux-promise-middleware docs
The solution is very simple :
Option 1.
Add return inside the arrow function
export function deleteCommand(id) {
return (dispatch) => {
return dispatch({
type: 'DELETE_COMMAND',
payload: axios.post(`${Config.apiUrl}delete`, { _id: id })
})
}
}
Option 2.
Remove the curly braces from the arrow function
export function deleteCommand(id) {
return (dispatch) =>
dispatch({
type: 'DELETE_COMMAND',
payload: axios.post(`${Config.apiUrl}delete`, { _id: id })
})
}
Now you can do
store.deleteCommand(<some id>).then(...)
In general arrow functions and return
Return a plain object
// option 1
const arrowFuncOne = () => {
return {...someProps}
}
// option 2
const arrowFuncTwo = () => ({
...someProps
})
// This means that you returning an expression
//same as
// return a result of other function
const arrowFuncCallFunOne = () => callSomeOtherFunc()
// or
const arrowCallFunkTwo = () => {return callSomeOtherFunc()}
// However
// wrong
const arrowCallFunkNoReturn = () => {callSomeOtherFunc()}
// in this case the curly braces are just the body of the function
// and inside this body there is no return at all

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