Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RSAA action-creator factories #169

Open
ckreiling opened this issue Jan 31, 2018 · 7 comments
Open

RSAA action-creator factories #169

ckreiling opened this issue Jan 31, 2018 · 7 comments

Comments

@ckreiling
Copy link

Hello,

The below example is a quick whip-up, but if you have any suggestions for improvement please let me know. In my case, and I'm guessing in many others', it's common to send one 'entity' to the backend in a request (like an object's ID), so I made this nice little RSAA action creator to abstract away that functionality. The 'apiRequestObject' can be any of the other valid RSAA action properties besides 'body' and 'types'. I then use Javascript's convenient bind() method to make more specific action creators.

So what does everyone think about incorporating an action creator factory for RSAAs?

/**
 * Function for creating an RSAA action that concerns a certain entity. A lot of requests to the
 * backend have just one entity sent in the POST body of the request, so
 * this function abstracts away the entity and consolidates code.
 * Use the Javascript bind() function to make action creators for requests that take a certain entity.
 * @param entityName A string representing the name of the entity, to be used as the key in action payloads.
 * @param apiRequestObject Other RSAA data for the request besides 'body' and 'types'
 * @param fetchAction The name of the action to be dispatched for a waiting request.
 * @param successAction The name of the action to be dispatched for a successful request.
 * @param failureAction The name of the action to be dispatched for a failed request.
 * @param entity The entity object to be passed in the POST body of the request.
 * @returns {{}} The RSAA action constructed with the function parameters.
 */
export const entityRsaaActionCreator = (entityName,
                                               apiRequestObject,
                                               fetchAction,
                                               successAction,
                                               failureAction,
                                               entity) => ({
  [RSAA]: {
    ...apiRequestObject,
    body: JSON.stringify({[entityName]: entity}),
    types: [
      {
        type: fetchAction,
        payload: (action, state) => ({[entityName]: entity})
      },
      {
        type: successAction,
        payload: (action, state, response) => {
          if (response) {
            return response.json().then(({data}) => ({[entityName]: entity, data}))
          }
          else {
            return ({[entityName]: entity}); // if there's no response just return the entity
          }
        }
      },
      {
        type: failureAction,
        payload: (action, state, response) => ({[entityName]: entity})
      }
    ]
  }});
@agraboso
Copy link
Owner

@ckreiling Could you provide an example or two of how you use this entityRsaaActionCreator? At some point I did have the idea of creating a "recipes" section — though these days I don't do much for redux-api-middleware: it is @nason that maintains it —, and your snippet might fit into it.

@ckreiling
Copy link
Author

ckreiling commented Feb 1, 2018

@agraboso Absolutely, see below.

My app is an admin UI for managing an organization's messaging service, similar to Slack. I have an ApiConfig.js file in the root of my app that maintains all of the URL's requests are made to, and it looks like:

const apiVersion = '/v1';

const BASE_URL = 'https://www.fakeurl.com/api' + apiVersion; // base URL for all requests

/**
 * Makes a standard config object for a specific request. Just replace the member of the type
 * array that corresponds to any action you might want to assign a payload to.
 * @param url the URL to be tacked on to the {@code BASE_URL} string
 */
const standardPostConfigFactory = (url) => ({
  endpoint: BASE_URL + url,
  method: 'POST',
  credentials: 'include'
});

// These are certain URLs for the API, we're writing the actual requests inside the actions in order
// to have a better form of verification. These are each paired with a method that defines what
// type of HTTP request is made.
export const apiRequestObjects = {
  // Auth endpoints
  LOGIN: standardPostConfigFactory('/auth/login'),
  LOGOUT: standardPostConfigFactory('/auth/logout'),
  AUTH_STATUS: standardPostConfigFactory('/auth/status'),
  // System calls for getting big payloads for the user
  GROUPS_GET_ALL: standardPostConfigFactory('/system/getgroups'), // gets all the user's Group objects
  USERS_GET_ALL: standardPostConfigFactory('/system/getusers'), // gets all the user's User objects
  MEMBERS_GET_ALL: standardPostConfigFactory('/system/getmembers'), // gets all the user's User objects
  // Group endpoints
  GROUPS_CLEAR: standardPostConfigFactory('/groups/clear'), // removes all users from the group
  GROUPS_NUKE: standardPostConfigFactory('/groups/nuke'), // nuke a group
  GROUPS_FIX: standardPostConfigFactory('/groups/fix'), // fix a group
  GROUPS_CREATE: standardPostConfigFactory('/groups/create'), // create a new group
  GROUPS_DESTROY: standardPostConfigFactory('/groups/destroy'), // destroy a group
  GROUPS_ADD: standardPostConfigFactory('/groups/add'), // add a certain user to the group
  GROUPS_EVAL: standardPostConfigFactory('/groups/eval'), // check for illegal members
  GROUPS_SHOW: standardPostConfigFactory('/groups/show'),
  GROUPS_ABOVE_CURRENT: standardPostConfigFactory('/groups/abovecurrent'),
  GROUPS_MEMBERS: standardPostConfigFactory('/groups/members'), // get all the members for a group
  // User endpoints
  USER_GET: standardPostConfigFactory('/users/get'), // get the user by User ID
};

My Redux state is then cut into slices, stored in their own directories, with RSAA actions tied to each endpoint. For instance, the /groups/nuke endpoint nukes a group, and takes only a group ID as a member of the POST body. The /groups/fix endpoint fixes a group, and the endpoint also only takes a group ID as a member of the POST body. So the function I posted above is used like this:

// Create the new action creator by binding it to the abstracted one
const groupEntityRsaaActionCreator = entityRsaaActionCreatorFactory.bind(this, 'group');

// Create another new action creator, again using bind()
const nuke = groupEntityRsaaActionCreator.bind(this,
  apiRequestObjects.GROUPS_NUKE,
  ActionTypes.GROUP_NUKE_FETCH,
  ActionTypes.GROUP_NUKE_SUCCESS,
  ActionTypes.GROUP_NUKE_FAILURE
);

const fix = groupEntityRsaaActionCreator.bind(this,
  apiRequestObjects.GROUPS_FIX,
  ActionTypes.GROUP_FIX_FETCH,
  ActionTypes.GROUP_FIX_SUCCESS,
  ActionTypes.GROUP_FIX_FAILURE
);

I find this recipe to be very extensible. Please let me know what you think as I'd love to improve upon my existing implementation!

@ckreiling
Copy link
Author

ckreiling commented Feb 27, 2018

@nason I'm really interested in helping with a "recipes" section, as I only started loving the library as I found ways to consolidate my code. I also think it should be made more evident in the docs that chaining RSAA dispatches using the Promises returned from Redux's dispatch() function call would be great.

@nason
Copy link
Collaborator

nason commented Feb 28, 2018

I'm really interested in helping with a "recipes" section, as I only started loving the library as I found ways to consolidate my code.

@ckreiling awesome! I've been thinking about documentation in general, and think that some recipes or even example apps would be great additions.

Do you want to make a PR to kick this off?

I also think it should be made more evident in the docs that chaining RSAA dispatches using the Promises returned from Redux's dispatch() function call would be great.

Great point. I agree this should be in the docs.

@geminiyellow
Copy link

is anything changing?

@ckreiling
Copy link
Author

@geminiyellow I'd like to add a recipes section to the documentation. It's been awhile since I had a use-case for this project, but I loved using it when I did.

Let me know what you think. I want to create a recipes section demonstrating the use of higher-order functions in order to build action-creators for different entities.

@geminiyellow
Copy link

geminiyellow commented Sep 28, 2018

@ckreiling 👍 , or you can create a new doc who's title is RSAA action-creator or you can create a total new helper npm package.

i created one, redux-api-actions , but it is not very good.
so hope there is a official creator here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants