glazjoon glazjoon - 1 month ago 18
React JSX Question

Sending error message from reducer to user

I'm new to React and Redux and I'm trying to write a simple application where a person can submit a URL for an image and it will show up on the page. Note that there is no backend to the application as of yet.

export const addImage = (url) => {
return {
type: ADD_IMAGE,
key: Guid.create().toString(),
payload: url
}
}


Adding an image creates an action of type ADD_IMAGE and my reducer updates the state consequently. However I also check if the URL is already in the list.

switch (action.type) {
case ADD_IMAGE:
if (state.find(image => image.url === action.payload)) {
return state;
} else {
return(
[
...state,
{key: action.key, url: action.payload}
]
);
}
break;
default:
}


The problem is that when I deny a post because the URL is already in the state I also want to convey that message to the user by showing it in a div next to the form. From what I've read I think I'm not supposed to try to access React state from reducers (if that is even possible) and... well.. I'm just stuck. I've been trying to find a simple guide on how to do this but I find nothing I can quite understand. After adding a database I guess I will have to do this as part of the async process but as I have it now I guess there should be some kind of simple solution.

Answer

You are starting to introduce logic into your reducer and this will inevitably lead to situation where you need to mutate some state outside of the reducer's scope.

The solution is to transfer your reducer logic into a thunk using a middleware package such redux-thunk (or similar package). This allows you to treat special kinds of actions as functions which means you can extend a plain action with specific action-related logic. The example you give of needing to dispatch an error action under certain conditions is an excellent use-case for redux-thunk.

Below is a example of how you might pull the logic out of your reducer into a thunk. You should note that, unlike reducers, thunks explicitly support fetching state and dispatching subsequent actions via the getState and dispatch functions.

Thunk example

export const addImage = (url) => {
  return (dispatch, getState) => {
    const key = Guid.create().toString()
    dispatch({
      type: ADD_IMAGE,
      key,
      payload: url
    })
    const state = getState()

    // you would want to use a `selector` here to locate the existing image
    // within the state tree 

    const exists = selectors.images.exists(state, url)

    if (exists) {
      dispatch(actions.ERROR_IMAGE_EXISTS({key, url}))
    }
  }
}

A note on selectors

You will see that I am using a selector to determine if the image exists. In the same way that thunks are the place to put your dispatch logic, a selector is the place to put your state-traversal logic. They are used to return portions of the state-tree or provide simple state-utilities such as the exists function shown above. Packages are available to help, for example reselect.