React Redux dispatch action after another action - javascript

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 });

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.

Async does wait for data to be returned in a redux-thunk function

I've being trying populate my redux store with data that comes from my mongo-db realm database.
Whenever I run the function below it will execute fine but the problem is data will be delayed and ends up not reaching my redux store.
My thunk function:
export const addItemsTest = createAsyncThunk(
"addItems",
async (config: any) => {
try {
return await Realm.open(config).then(async (projectRealm) => {
let syncItems = await projectRealm.objects("Item");
await syncItems.addListener((x, changes) => {
x.map(async (b) => {
console.log(b);
return b;
});
});
});
} catch (error) {
console.log(error);
throw error;
}
}
);
and my redux reducer:
extraReducers: (builder) => {
builder.addCase(addItemsTest.fulfilled, (state, { payload }: any) => {
try {
console.log("from add Items");
console.log(payload);
state.push(payload);
} catch (error) {
console.log(error)
}
});
}
Expected Results:
My redux store should have these data once addItemsTest return something:
[{
itemCode: 1,
itemDescription: 'Soccer Ball',
itemPrice: '35',
partition: 'partitionValue',
},
{
itemCode: 2,
itemDescription: 'Base Ball',
itemPrice: '60',
partition: 'partitionValue',
}
]
Actual Results:
Mixed Syntaxes
You are combining await/async and Promise.then() syntax in a very confusing way. It is not an error to mix the two syntaxes, but I do not recommend it. Stick to just await/async
Void Return
Your action actually does not return any value right now because your inner then function doesn't return anything. The only return is inside of the then is in the x.map callback. await syncItems is the returned value for the mapper, not for your function.
Right now, here's what your thunk does:
open a connection
get items from realm
add a listener to those items which logs the changes
returns a Promise which resolves to void
Solution
I believe what you want is this:
export const addItemsTest = createAsyncThunk(
"addItems",
async (config: any) => {
try {
const projectRealm = await Realm.open(config);
const syncItems = await projectRealm.objects("Item");
console.log(syncItems);
return syncItems;
} catch (error) {
console.log(error);
throw error;
}
}
);
Without the logging, it can be simplified to:
export const addItemsTest = createAsyncThunk(
"addItems",
async (config: any) => {
const projectRealm = await Realm.open(config);
return await projectRealm.objects("Item");
}
);
You don't need to catch errors because the createAsyncThunk will handle errors by dispatching an error action.
Edit: Listening To Changes
It seems that your intention is to sync your redux store with changes in your Realm collection. So you want to add a listener to the collection that calls dispatch with some action to process the changes.
Here I am assuming that this action takes an array with all of the items in your collection. Something like this:
const processItems = createAction("processItems", (items: Item[]) => ({
payload: items
}));
Replacing the entire array in your state is the easiest approach. It will lead to some unnecessary re-renders when you replace item objects with identical versions, but that's not a big deal.
Alternatively, you could pass specific properties of the changes such as insertions and handle them in your reducer on a case-by-case basis.
In order to add a listener that dispatches processItems, we need access to two variables: the realm config and the redux dispatch. You can do this in your component or by calling an "init" action. I don't think there's really much difference. You could do something in your reducer in response to the "init" action if you wanted.
Here's a function to add the listener. The Realm.Results object is "array-like" but not exactly an array so we use [...x] to cast it to an array.
FYI this function may throw errors. This is good if using in createAsyncThunk, but in a component we would want to catch those errors.
const loadCollection = async (config: Realm.Configuration, dispatch: Dispatch): Promise<void> => {
const projectRealm = await Realm.open(config);
const collection = await projectRealm.objects<Item>("Item");
collection.addListener((x, changes) => {
dispatch(processItems([...x]));
});
}
Adding the listener through an intermediate addListener action creator:
export const addListener = createAsyncThunk(
"init",
async (config: Realm.Configuration, { dispatch }) => {
return await loadCollection(config, dispatch);
}
);
// is config a prop or an imported global variable?
const InitComponent = ({config}: {config: Realm.Configuration}) => {
const dispatch = useDispatch();
useEffect( () => {
dispatch(addListener(config));
}, [config, dispatch]);
/* ... */
}
Adding the listener directly:
const EffectComponent = ({config}: {config: Realm.Configuration}) => {
const dispatch = useDispatch();
useEffect( () => {
// async action in a useEffect need to be defined and then called
const addListener = async () => {
try {
loadCollection(config, dispatch);
} catch (e) {
console.error(e);
}
}
addListener();
}, [config, dispatch]);
/* ... */
}

How can I pass state to action to fetch API?

so im new in redux and I need bit of help with my homework. I have a drop down with couple of choices and the choice that user select needs to be passed to state (already have this working and state is updating when user select something new) and then to action that can fetch data with '/stats/${userChoice}'. But i have no idea how to do this at all.
actions/index.js:
export const fetchAuthorsStats = () => async dispatch => {
const response = await myAPI.get(`/stats/${userChoice}`);
dispatch({ type: 'FETCH_AUTHORS_STATS', payload: response.data })
};
components/Dropdown.js:
onAuthorSelect = (e) => {
this.setState({selectAuthor: e.target.value})
};
.
.
.
const mapStateToProps = state => {
return {
authors: state.authors,
selectAuthor: state.selectAuthor,
authorsStats: state.authorsStats
}
};
export default connect(mapStateToProps, { fetchAuthors, selectAuthor, fetchAuthorsStats })(Dropdown)
under "selectAuthor" I have my state that I need to pass to this action API
You already map dispatch to fetchAuthorsStats thunk in your component so that means you can just use it in onAuthorSelect (or anywhere else you need - like on form submit) and pass it a parameter with the selectedAuthor.
// Added a userChoice param here:
export const fetchAuthorsStats = (userChoice) => async dispatch => {
const response = await myAPI.get(`/stats/${userChoice}`);
dispatch({ type: 'FETCH_AUTHORS_STATS', payload: response.data })
};
onAuthorSelect = (e) => {
this.setState({selectAuthor: e.target.value})
this.props.fetchAuthorsStats(e.target.value);
};
You can achieve this by calling the API directly with the event target value :
/// first you update your API call to receive the selected author
export const fetchAuthorsStats = (userChoice) => async dispatch => {
const response = await myAPI.get(`/stats/${userChoice}`);
dispatch({ type: 'FETCH_AUTHORS_STATS', payload: response.data })
};
//then you update your handler function
onAuthorSelect = (e) =>{
this.props.fetchAuthorsStats(e.target.value)
}
if you wish to still save it on the react state you can do the setState first and then the API call with (this.state.selectedAuthor) instead of (e.target.value)

Action must be plain object. Use custom middleware

What would be the problem?
Uncaught Error: Actions must be plain objects. Use custom middleware for async actions.
Configure Store:
export default configureStore = () => {
let store = compose(applyMiddleware(ReduxThunk))(createStore)(reducers);
return store;
}
Action
export const menulist = async ({ navigation }) => {
return async dispatch => {
try {
dispatch({ type: types.MENULIST_REQUEST_START })
let response = await menuListByCategories();
dispatch({ type: types.MENULIST_SUCCESS })
} catch (error) {
dispatch({ type: types.MENULIST_FAILED })
}
}
}
You are using it the wrong way,
in Redux every action must return an object, and this is a must!
so, your dispatch, which is a function, should be called this way.
Besides you only need to declare async the function which returns dispatch. The async keyword determines that the following function will return a promise. As your first function (menulist) is returning the promise returned by the second function (dispatch one) you don't have to specify it.
export const menulist = ({ navigation }) => async (dispatch) => {
try {
dispatch({ type: types.MENULIST_REQUEST_START })
let response = await menuListByCategories();
dispatch({ type: types.MENULIST_SUCCESS })
} catch (error) {
dispatch({ type: types.MENULIST_FAILED })
}
}
}

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

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/

Categories