Am I using Redux correctly? - redux

this is just a question,
I'd love to double check if I'm doing things right. I'm coming from ages of different frameworks, and I def want to avoid bad practices in the early stage.
I'm using this boilerplate: https://github.com/erikras/react-redux-universal-hot-example
and I'm writing in ES7.
I created:
- 1 reducer: earlyUser.js
- 1 container: landingPage.js
- 1 component: registrationForm.js
In the landingPage, I'm including the methods from reducer in this way:
import { saveEmail, savePreferences, checkEmailExists } from 'redux/modules/earlyUser';
and I declare some handles
handleSubmitEmail(data) {
this.props.saveEmail(data);
}
handleSubmitPreferences(data) {
//some data manipulation
this.props.savePreferences(data);
}
and in the JSX part I just pass to my component the handlers:
<registrationForm submitEmail= {::this.handleSubmit} etc... >
Now inside the component, I linked the form submission to this handler:
submitEmail() {
if (this.validateEmail(this.state.email)) {
this.props.submitEmailHandler(this.state);
}
}
Now my question is, where should I attach the .then and .catch of the promise returned ?
Ideally I'd like to do inside the component.js something like
this.props.submitEmailHandler(this.state).then( function emailRegisterCallback(){
// move to next step
}).catch( function errorHandler(error){
// there was an error
}
Is that correct?
Also, is there the right syntax to handle promises in ES7 ?

You normally handle the async aspects in your action creators:
See this code from the async redux example:
function fetchPosts(reddit) {
return dispatch => {
dispatch(requestPosts(reddit));
return fetch(`http://www.reddit.com/r/${reddit}.json`)
.then(response => response.json())
.then(json => dispatch(receivePosts(reddit, json)));
};
}
When the promise resolves, you should dispatch another action to update the state with the successful result or the error.

Related

Running an action creator in response to an action (without saga)

Is there a library for redux that allows a function to be run in response to a specific action type and have that function's return value (which may be an action/thunk/promise) dispatched? I know that saga allows for this, but saga is a bit full featured for this basic requirement.
I understand that this would be fairly trivial to write myself, but if someone else has already done it then there's no need to reinvent the wheel.
I would write my own middleware like this:
export default ({ dispatch }) => (next) => (action) => {
if (action.type === 'MY_ACTION') {
const anotherAction = someOtherFunction();
dispatch(anotherAction); // can use next() if you want to skip all middleware before this one.
} else {
next(action);
}
}

How to organize Redux Action-Creators for use in Redux-React

I'm using react-redux & redux-thunk for my project.
I have to inject my actions to a component by using connect.
connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])
My task is one level up. I don't want just to inject multiple actions in this form:
{
doThis(),
doThat()
}
But in this form:
{
this: {
doThis1(),
doThis2()
}
that: {
doThat()
}
}
So basically my problem is that I want to dispatch multiple action-creator files because I want them organized as such.
I tried this version which obviously doesn't work because dispatch is not injected in each Thunk Action Creator:
import * as actions from './actions'
const mapDispatchToProps = (dispatch) => {
return {
dataActions: {
...actions.dataActions
}
};
}
export default connect(null, mapDispatchToProps)(Component);
So my final question is:
Am I even supposed to use Redux this way? Can I organize my files this way, if so how?
If instead of having one property per action creator, you want to structure your bound action creators in a couple of properties that each contain a group of action creators, you can do something like this:
import { bindActionCreators, .. } from 'redux';
..
const mapDispatchToProps = (dispatch) => {
return {
dataActions: bindActionCreators(actions.dataActions, dispatch),
otherActions: bindActionCreators(actions.otherActions, dispatch),
..
};
};
The first argument to bindActionCreators is an object containing action-creator functions (e.g. an imported module that exports only action creators). In your actual component, you should then be able to use this.props.dataActions.someDataAction(..).
If the question is just about whether you can keep different action creators in different files, you might not even want to group the action creators and just do this:
return {
...bindActionCreators(actionCreatorsFromOneModule, dispatch),
...bindActionCreators(actionCreatorsFromAnotherModule, dispatch),
..
};

How to make sure that one Saga completes before the other?

I'm a noob in redux-sagas, so please bear with me if I miss out something obvious!
I have a scenario wherein I'm using redux-saga to fetch an access_token from an OIDC provider and store it in the browser's localStorage.
I also use Sagas to fetch some data from an API endpoint.
But I've been experiencing problems with this approach because the Saga calling the external API gets invoked before the Auth Saga can resolve with an access_token.
My Auth Saga :
export function * handleFetchTokens () {
try {
const token = yield cps(getToken)
localStorage.setItem('token', token)
const isAuthenticated = !!(token)
yield put(actions.checkAuthSuccess(isAuthenticated))
} catch (e) {
yield put(actions.checkAuthFailure(e))
}
}
export default function * sagas () {
yield fork(takeLatest, actions.CHECK_AUTH, handleFetchTokens)
}
My API Saga :
export function * handleFetchItems () {
try {
const response = yield call(getItems)
yield put(actions.fetchItemsSuccess(response))
} catch (e) {
yield put(actions.fetchItemsFailure(e.errors))
}
}
export default function * sagas () {
yield fork(takeLatest, actions.FETCH_ITEMS, handleFetchItems)
}
My root Saga :
export default function * root () {
yield fork(items.sagas)
yield fork(authentication.sagas)
}
What should be the proper way of overcoming this problem?
Personally, I'd make sure the token is received before allowing any part of my app to actually call the FETCH_ITEMS action. Assuming you don't want to introduce such logic you will have to decide what to do with FETCH_ITEMS actions before you get the token.
The easiest approach would be to just ignore them, but that also probably isn't the most feasible way to go.
So what remains is to buffer the FETCH_ITEMS actions. You can do this using actionChannel. Since you are using takeLatest you will also want to define a sliding buffer of size 1.
It could look ruffly like this:
export default function * sagas () {
const chan = yield actionChannel(actions.FETCH_ITEMS, buffers.sliding(1))
yield take('TOKEN_RECEIVED') // wait for action informing us token was received
chan.close()
yield fork(takeLatest, chan, handleFetchItems)
yield fork(takeLatest, actions.FETCH_ITEMS, handleFetchItems)
}
More about actionChannels here https://redux-saga.js.org/docs/advanced/Channels.html
Another approach - with a bit less writing but also a bit less control - instead of buffering is to wait in the fetching saga itself:
export function * handleFetchItems () {
while (!token) yield take('TOKEN_RECEIVED');
...
}
Either way, both ways rely on waiting for a TOKEN_RECEIVED action you need to dispatch once the token is received.

redux-promise with Axios, and how do deal with errors?

So, I see on an error, redux-promise hands me back error: true, along with the payload, but that is once it hits the reducer... to me, decoupling the request AND error condition is a bit odd, and seems inappropriate. What is an effective way to also deal with error condition when using axios w/ reduc-promise (middleware).. here is the gist of what i have..
in action/
const request = axios(SOME_URL);
return {
type: GET_ME_STUFF,
payload: request
}
in reducer/
const startState = {
whatever: [],
error: false
}
case GET_ME_STUFF:
return {...state, startState, {stuff:action.payload.data, error: action.error? true : false}}
etc... then I can deal with the error.. so, my api call is now split into two seperate areas and that seems wrong.... there must be something I am missing here. I would think in the /actions I can pass in a callback that handles a new action etc.. or something, but not split it.
I've had to go through a similar situation. The challenge is that you likely won't be able to evaluate the results of the promise until it is at the reducer. You could handle your exceptions there but it's not the best pattern. From what I've read reducers are meant only to return appropriate pieces of state based on action.type and do nothing else.
So, enter an additional middleware, redux-thunk. Instead of returning an object, it returns a function, and it can coexist with promise.
It's explained quite well at http://danmaz74.me/2015/08/19/from-flux-to-redux-async-actions-the-easy-way/ [archived here]. Essentially, you can evaluate the promise here and dispatch through the other action creators before the promise result hits the reducers.
In your actions file, add additional action creators that would handle the success and error (and any other) states.
function getStuffSuccess(response) {
return {
type: GET_ME_STUFF_SUCCESS,
payload: response
}
}
function getStuffError(err) {
return {
type: GET_ME_STUFF_ERROR,
payload: err
}
}
export function getStuff() {
return function(dispatch) {
axios.get(SOME_URL)
.then((response) => {
dispatch(getStuffSuccess(response))
})
.catch((err) => {
dispatch(getStuffError(err))
})
}
}
return null
This is roughly to how you might translate your pseudocode to what is explained at the link. This handles evaluating the promise directly in your action creator and firing off the appropriate actions and payloads to your reducers which follows the convention of action -> reducer -> state -> component update cycle. I'm still pretty new to React/Redux myself but I hope this helps.
The accepted answer doesn't make use of redux-promise. Since the question is actually about handling errors using redux-promise I provide another answer.
In the reducer you should inspect the existence of the error attribute on the action object:
// This is the reducer
export default function(previousState = null, action) {
if (action.error) {
action.type = 'HANDLE_XHR_ERROR'; // change the type
}
switch(action.type) {
...
And change the type of the action, triggering a state change for an error handling component that you have set up for this.
You can read a bit more about it here on github.
It looks like you can catch the error where you make the dispatch, then make an separate error dispatch if it happens. It's a bit of a hack but it works.
store.dispatch (function (dispatch) {
dispatch ({
type:'FOO',
payload:axios.get(url)
})
.catch (function(err) {
dispatch ({
type:"FOO" + "_REJECTED",
payload:err
});
});
});
and in the reducer
const reducer = (state=initialState, action) => {
switch (action.type) {
case "FOO_PENDING": {
return {...state, fetching: true};
}
case "FOO_REJECTED": {
return {...state, fetching: false, error: action.payload};
}
case "FOO_FULFILLED": {
return {
...state,
fetching: false,
fetched: true,
data: action.payload,
};
}
}
return state;
};
Still using redux-promises you can do something like this which I think is an elegant way to deal with this problem.
First, set a property in the redux state that will hold any ajax errors that may occurred.
ajaxError: {},
Second, setup a reducer to handle ajax errors:
export default function ajaxErrorsReducer(state = initialState.ajaxError, action) {
if (action.error) {
const { response } = action.payload;
return {
status: response.status,
statusText: response.statusText,
message: response.data.message,
stack: response.data.stack,
};
}
return state;
}
Finally, create a very simple react component that will render errors if there are any (I am using the react-s-alert library to show nice alerts):
import React, { Component } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import Alert from 'react-s-alert';
class AjaxErrorsHandler extends Component {
constructor(props, context) {
super(props, context);
this.STATUS_GATE_WAY_TIMEOUT = 504;
this.STATUS_SERVICE_UNAVAILABLE = 503;
}
componentWillReceiveProps(nextProps) {
if (this.props.ajaxError !== nextProps.ajaxError) {
this.showErrors(nextProps.ajaxError);
}
}
showErrors(ajaxError) {
if (!ajaxError.status) {
return;
}
Alert.error(this.getErrorComponent(ajaxError), {
position: 'top-right',
effect: 'jelly',
timeout: 'none',
});
}
getErrorComponent(ajaxError) {
let customMessage;
if (
ajaxError.status === this.STATUS_GATE_WAY_TIMEOUT ||
ajaxError.status === this.STATUS_SERVICE_UNAVAILABLE
) {
customMessage = 'The server is unavailable. It will be restored very shortly';
}
return (
<div>
<h3>{ajaxError.statusText}</h3>
<h5>{customMessage ? customMessage : ajaxError.message}</h5>
</div>
);
}
render() {
return (
<div />
);
}
}
AjaxErrorsHandler.defaultProps = {
ajaxError: {},
};
AjaxErrorsHandler.propTypes = {
ajaxError: PropTypes.object.isRequired,
};
function mapStateToProps(reduxState) {
return {
ajaxError: reduxState.ajaxError,
};
}
export default connect(mapStateToProps, null)(AjaxErrorsHandler);
You can include this component in your App component.
This might not be the best approach but it works for me. I pass the 'this' of my component as var context. Then when i get response back i just execute the methods defined in my components context. In my component i have successHdl and errorHdl. From there i can trigger more redux actions as normal. I checked all the previous answers and seem too daunting for such a trivial task.
export function updateJob(payload, context){
const request = axios.put(UPDATE_SOMETHING, payload).then(function (response) {
context.successHdl(response);
})
.catch(function (error) {
context.errorHdl(error);
});;
return {
type: UPDATE_SOMETHING,
payload: payload,
}
}
Don't use redux-promise. It overcomplicates something that's actually super simple to do yourself.
Instead read the redux docs: http://redux.js.org/docs/advanced/AsyncActions.html
It'll give you a much better understanding of how to handle this kind of interactions and you'll learn how to write something (better than) redux-promise yourself.

React-redux project - chained dependent async calls not working with redux-promise middleware?

I'm new to using redux, and I'm trying to set up redux-promise as middleware. I have this case I can't seem to get to work (things work for me when I'm just trying to do one async call without chaining)
Say I have two API calls:
1) getItem(someId) -> {attr1: something, attr2: something, tagIds: [...]}
2) getTags() -> [{someTagObject1}, {someTagObject2}]
I need to call the first one, and get an item, then get all the tags, and then return an object that contains both the item and the tags relating to that item.
Right now, my action creator is like this:
export function fetchTagsForItem(id = null, params = new Map()) {
return {
type: FETCH_ITEM_INFO,
payload: getItem(...) // some axios call
.then(item => getTags() // gets all tags
.then(tags => toItemDetails(tags.data, item.data)))
}
}
I have a console.log in toItemDetails, and I can see that when the calls are completed, we eventually get into toItemDetails and result in the right information. However, it looks like we're getting to the reducer before the calls are completed, and I'm just getting an undefined payload from the reducer (and it doesn't try again). The reducer is just trying to return action.payload for this case.
I know the chained calls aren't great, but I'd at least like to see it working. Is this something that can be done with just redux-promise? If not, any examples of how to get this functioning would be greatly appreciated!
I filled in your missing code with placeholder functions and it worked for me - my payload ended up containing a promise which resolved to the return value of toItemDetails. So maybe it's something in the code you haven't included here.
function getItem(id) {
return Promise.resolve({
attr1: 'hello',
data: 'data inside item',
tagIds: [1, 3, 5]
});
}
function getTags(tagIds) {
return Promise.resolve({ data: 'abc' });
}
function toItemDetails(tagData, itemData) {
return { itemDetails: { tagData, itemData } };
}
function fetchTagsForItem(id = null) {
let itemFromAxios;
return {
type: 'FETCH_ITEM_INFO',
payload: getItem(id)
.then(item => {
itemFromAxios = item;
return getTags(item.tagIds);
})
.then(tags => toItemDetails(tags.data, itemFromAxios.data))
};
}
const action = fetchTagsForItem(1);
action.payload.then(result => {
console.log(`result: ${JSON.stringify(result)}`);
});
Output:
result: {"itemDetails":{"tagData":"abc","itemData":"data inside item"}}
In order to access item in the second step, you'll need to store it in a variable that is declared in the function scope of fetchTagsForItem, because the two .thens are essentially siblings: both can access the enclosing scope, but the second call to .then won't have access to vars declared in the first one.
Separation of concerns
The code that creates the action you send to Redux is also making multiple Axios calls and massaging the returned data. This makes it more complicated to read and understand, and will make it harder to do things like handle errors in your Axios calls. I suggest splitting things up. One option:
Put any code that calls Axios in its own function
Set payload to the return value of that function.
Move that function, and all other funcs that call Axios, into a separate file (or set of files). That file becomes your API client.
This would look something like:
// apiclient.js
const BASE_URL = 'https://yourapiserver.com/';
const makeUrl = (relativeUrl) => BASE_URL + relativeUrl;
function getItemById(id) {
return axios.get(makeUrl(GET_ITEM_URL) + id);
}
function fetchTagsForItemWithId(id) {
...
}
// Other client calls and helper funcs here
export default {
fetchTagsForItemWithId
};
Your actions file:
// items-actions.js
import ApiClient from './api-client';
function fetchItemTags(id) {
const itemInfoPromise = ApiClient.fetchTagsForItemWithId(id);
return {
type: 'FETCH_ITEM_INFO',
payload: itemInfoPromise
};
}

Resources