Over a million developers have joined DZone.

Real-World ReactJS and Redux (Part 2)

This is the second in a series of blog posts about real-world ReactJS usage and what we've learned scaling our app at Threat Stack.

· Web Dev Zone

Start coding today to experience the powerful engine that drives data application’s development, brought to you in partnership with Qlik.

This is the second in a series of blog posts about real-world ReactJS usage and what we've learned scaling our app at Threat Stack.

In this post, we'll be displaying actions based on an api middleware shown in Part 1.

Still making sure to answer these questions:

  • What is the ease of development?
  • How fast can new team members understand what's going on?
  • How fast can you figure out where something is broken?

Sometimes a seemingly simple feature request comes through that looks like this:

If X happens do Y, but only if state looks like Z.

Since you want to maintain your code decoupled between react (view) and redux (data management), simple requests like this end up seeming more difficult than they should be.

A good example is client-side tracking.

At some point you'll want, nay, need to track when a user clicks on something and pass how many items are in display or other information about the current state.

The first pass at this was an analytics middleware.

Things were based on a property aptly named "analytics".

So the action looked like this:

return {
  types: [ MARK_READ, MARK_READ_SUCCESS, MARK_READ_ERROR ],

  callAPI: () => Api.updateTodos({
    ids,
    isRead: true 
  }),

  analytics: {
    [MARK_READ_SUCCESS] : {
      idsCount: ids.length,
      isSuccess: true
    },
    [MARK_READ_ERROR] : {
      idsCount: ids.length,
      isSuccess: false
    }
  }
}


So far... not horrible.

Depending on the event type (success/error), we can track different things.

But a new requirement comes in and... You won't believe what happened next.

If it's successful, we also need to redirect the user to a different section.

return {
  types: [ MARK_READ, MARK_READ_SUCCESS, MARK_READ_ERROR ],

  callAPI: () => Api.updateTodos({
    ids,
    isRead: true 
  }),

  analytics: {
    [MARK_READ_SUCCESS] : {
      idsCount: ids.length,
      isSuccess: true
    },
    [MARK_READ_ERROR] : {
      idsCount: ids.length,
      isSuccess: false
    }
  },

  redirect: ({ state }) {

  }

}


We made another middleware that looked for a specific property.

We sure did.

But that won't scale.

We need something more generic that can scale for different use cases.

A Super-Handy Generic Middleware

Scenario:

if I'm filtering a list of todos  
  If I mark all as read   
      clear the filter   
  If I mark only some of them as read 
     leave the filter on

OnError show an error notification


Rules:

  • This logic shouldn't live in the view.
  • This is based on the action of marking a todo.
    If I add a different version of this component, an A/B test for instance, state should be updated, and everything should just work.

component.react.js

handleMarkRead (ids) {
    dispatch(markReadTodos(ids));
}


actions.js

import { clearTodosFilter } from '../filterActions';
import {
    MARK_READ,
    MARK_READ_SUCCESS,
    MARK_READ_ERROR
} from '../constants'

export function updateItem (ids) {
  return {
    types: [ MARK_READ, MARK_READ_SUCCESS, MARK_READ_ERROR ],

    callAPI: () => Api.updateTodos({
        ids,
        isRead: true 
    }),

    effect ({ dispatch, state, type }) {
      if (type === MARK_READ_ERROR) {


        dispatch(showErrNotification(
         'There was an error updating your todos'
         ));  
      }

      if (type === MARK_READ_SUCCESS) {
        const { todosById } = this.state;
        let hasReadAll = true;
        for ( const id in todosById) {
          if (!todosById[id].isRead) {
            hasReadAll = false;
            break;
          }
        }

        if (hasReadAll) {
          dispatch(clearTodosFilter());
        }
      }
    },
  };
}


Scenario:

When the user updates an item, show an error notification if it fails.
If it succeeds, show a different notification, but only if the item is FOO or some other state changed.

Item.react.js

handleUpdateItem (item) {
  dispatch(updateItem({ 
    item
  });
}

ItemActions.js
  1. Update Item call will happen.
  2. App State will be updated.
  3. If there's an error:
    – We'll dispatch an error notification.
  4. If it was successul and the current changes count (based on current state) is not valid, we'll dispatch a different notification.
import { showErrNotifcation } from '../notificationActions';
import {
    UPDATE_ITEM,
    UPDATE_ITEM_SUCCESS,
    UPDATE_ITEM_ERROR,
    MAX_CHANGES
} from '../constants'

export function updateItem ({ item, prevItem }) {
  return {
    types: [ UPDATE_ITEM, UPDATE_ITEM_SUCCESS, UPDATE_ITEM_ERROR ],

    callAPI: () => Api.updateItem(item),

    effect ({ dispatch, state, type }) {
      if (type === UPDATE_ITEM_ERROR) {


        dispatch(showErrNotification('Error updating item'));  
      }

      if (type === UPDATE_ITEM_SUCCESS) {
        const { itemsById } = this.state;

        if (itemsById[item.id].changes === MAX_CHANGES) {
          dispatch(showNotification(
            `You can no longer alter this item`
           )); 
        }
      }
    },
  };
}


And the Middleware...

effectsMiddleware.js

export default function effectsMiddleware ({ dispatch, getState }) {
  return next => action => {
    const nextValue = next(action);




    if (!action.effect) {
      if (!isFunction(action.effect)) {
        throw new Error('Expected effect to be a function');
      }




      action.effect({
        dispatch : dispatch,
        state    : getState(),
        type     : action.type,
      });

      delete action.effect;
    }

    return nextValue;
  };
}
 
You'll want to make sure it is placed after your other custom middlewares, however. 
const middleware = process.env.NODE_ENV === 'production' ?
  [ thunk,  callAPIMiddleware, effectsMiddleware ] :
  [ thunk, callAPIMiddleware, effectsMiddleware, logger() ];


Where We Ended Up . . .

  • Logic for sub actions is with the actions code.
  • Since we're dispatching other sub actions, you can grep for action names, and they'll be displayed on your console for debugging.
  • You'll be able to follow a flow for actions happening in your code in the console.

Create data driven applications in Qlik’s free and easy to use coding environment, brought to you in partnership with Qlik.

Topics:
api middleware ,tracking ,analytics ,feature ,reactjs ,api ,client-side ,middleware

Published at DZone with permission of Cristiano Oliveira, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

The best of DZone straight to your inbox.

SEE AN EXAMPLE
Please provide a valid email address.

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.
Subscribe

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}