Giter VIP home page Giter VIP logo

iguazu-rpc's Introduction

NOTICE: Iguazu is no longer maintained. If you're looking for a data fetching solution for your React application, consider Fetchye. It is more performant and has a more intuitive API. If you know fetch, you know Fetchye.

Iguazu RPC - One Amex

npm Health Check

Iguazu RPC is a plugin for the Iguazu ecosystem that allows for configurable async calls and caching strategies. We use "RPC" loosely as you may use any form of communication strategy available in the browser to talk to a server API (e.g. REST, GraphQL, or even an unstructured endpoint returning JSON or XML).

👩‍💻 Hiring 👨‍💻

Want to get paid for your contributions to iguazu-rpc?

Send your resume to [email protected]

📖 Table of Contents

✨ Features

  • Plugs into Iguazu
  • Bring your own async request strategy to talk to any API
    • (e.g. REST, GraphQL, or even an unstructured endpoint returning JSON or XML)
  • Customizable Caching Strategies per request
  • Seamless integration in Redux

How it works

We can create a configuration object with a key for the request name and a value that contains lifecycle hooks for creating a request and specifying a caching strategy.

import { setProcedures } from 'iguazu-rpc';

setProcedures({
  readBlogPosts: {
    getResultFromCache: ({ args, cache }) => { /* ... */ },
    call: ({ fetchClient, getState, args }) => fetchClient('url').then((r) => r.text()),
    buildUpdatedCache: ({ cache, args, result }) => { /* ... */ },
  },
});

See it in action

We may then use a Redux action creator inside mapDispatchToProps to dispatch readBlogPosts.

import { queryProcedureResult } from 'iguazu-rpc';
import { connect } from 'react-redux';
// ...
const mapDispatchToProps = (dispatch) => ({
  readBlogPosts: (args) => dispatch(queryProcedureResult({ procedureName: 'readBlogPosts', args })),
});
// ...
connect(null, mapDispatchToProps)(SomeComponent);
// See Iguazu library for details on receiving data using connectAsync

🤹‍ Usage

Installation

npm install --save iguazu-rpc

Setup

Set up the reducer in your store:

import { proceduresReducer } from 'iguazu-rpc';
import { combineReducers } from 'redux-immutable';

import { createStore } from 'redux';

const reducer = combineReducers({
  procedures: proceduresReducer,
  // other reducers
});

const store = createStore(reducer);

Configure iguazu-rpc with a selector to the proceduresReducer.

import { configureIguazuRPC } from 'iguazu-rpc';

configureIguazuRPC({
  getToState: (state) => state.procedures,
});

Configure your procedure calls (allows you to register procedure calls and configure caching behavior):

import { setProcedures } from 'iguazu-rpc';

setProcedures({
  // procedure names with lifecycle methods
  readData: {
    getResultFromCache: ({ args, cache }) => {
      if (!cache.has(args.id)) {
        throw new Error('make the call');
      }
      return cache.get(args.id);
    },
    // Call method that supplies a fetchClient, getState, and args to use for making server calls
    // This method returns a Promise
    call: ({ fetchClient, getState, args }) => fetchClient('url').then((r) => r.text()),
    buildUpdatedCache: ({ cache, args, result }) => cache.set(args.id, result),
    // allows other procedures to invalidate/edit this procedure's cache while not relinquishing
    // management: iguazu-rpc wraps the returned function so the other procedures don't ever handle
    // this procedure's modified cache
    cacheModifier: ({ cache }) => ({ action = 'delete', id, value }) => {
      switch (action) {
        case 'delete':
          return cache.delete(id);
        case 'update':
          return cache.set(id, buildEntry(value));
        default:
          return cache;
      }
    },
  },
  updateData: {
    getResultFromCache: () => { throw new Error('Always go to the server'); },
    call: ({ getState, args }) => result,
    buildUpdatedCache: ({ cache, args, result }) => cache,
    modifyOtherCaches: ({ cache /* own cache */, args, result }) => ({
      // modifyCache is a wrapped form of the other procedure's `cacheModifier` result
      readData: (modifyCache) => modifyCache({ action: 'delete', id: args.id }),
    }),
  },
});

Advanced Setup

You may also supply a custom fetch client to iguazu-rpc using Redux Thunk. (See Thunk withExtraArgument docs)

import { combineReducers } from 'redux-immutable';
import { createStore } from 'redux';
import { proceduresReducer, setProcedures } from 'iguazu-rpc';
import thunk from 'redux-thunk';

configureIguazuRPC({
  getToState: (state) => state.procedures,
});

setProcedures({
  // Set your procedures as specified above
});

const reducer = combineReducers({
  procedures: proceduresReducer,
  // other reducers
});

/* Contrived custom fetch client */
const customFetchClient = (...args) => fetch(...args);

const store = createStore(
  combineReducers({
    resources: resourcesReducer,
  }),
  applyMiddleware(thunk.withExtraArgument({
    fetchClient: customFetchClient,
  }))
);

Dispatching Procedures

With the configuration set you can now make calls in your module and expect conformance to the Iguazu pattern:

/* MyContainer.jsx */
import React from 'react';
import { connectAsync } from 'iguazu';
import { queryProcedureResult } from 'iguazu-rpc';

function MyContainer({ isLoading, loadedWithErrors, myData }) {
  if (isLoading()) {
    return <div>Loading...</div>;
  }

  if (loadedWithErrors()) {
    return <div>Oh no! Something went wrong</div>;
  }

  return (
    <div>
myData =
      {myData}
    </div>
  );
}

function loadDataAsProps({ store, ownProps }) {
  const { dispatch } = store;
  const procedureName = 'readData';
  const args = { id: '123' };
  return {
    myData: () => dispatch(queryProcedureResult({ procedureName, args })),
    // To force fetch the data
    // Note: forceFetch shouldn't be hardcoded to true
    // in loadDataAsProps as this will result in a loop of fetches
    forceFetchMyData: () => dispatch(queryProcedureResult({
      procedureName,
      args,
      forceFetch: true,
    })),
  };
}

🎛️ API

Detailed procedure configuration

Procedure configurations allow the use of the following keys:

getResultFromCache (required)

A function with signature ({ args, cache }) that returns the cached data or throws if there is no data in the cache. If the procedure should not ever cache its results then always throwing is acceptable.

This function is used internally when a query of the procedure result is made in order to decide whether a remote call is needed.

Example:

configureIguazuRPC({
  procedures: {
    readData: {
      getResultFromCache: ({ args, cache }) => {
        if (!cache.has(args.id)) {
          throw new Error('make the call');
        }
        return cache.get(args.id);
      },
    },
    // ...
  },
  // ...
});

call (required)

A function with signature ({ fetchClient, getState, args }) that returns a Promise.

Note It is recommended to use fetchClient argument for running fetch calls rather than using global fetch.

This approach allows for the client and the server to specify different fetch implementations. For example, the server needs to support cookies inside a server-side fetch versus the client-side which works with cookies by default. Also, enforcing timeouts for fetch requests is needed to keep requests performant.

Example:

configureIguazuRPC({
  procedures: {
    readData: {
      // Note we use the fetchClient argument rather than global fetch
      call: ({ fetchClient, getState, args }) => fetchClient(
        `${process.env.HOST_URL}/readData`,
        { credentials: 'include' }
      ).then((response) => response.json()),
    },
    // ...
  },
  // ...
});

buildUpdatedCache (optional)

A function with signature ({ cache, args, result }) that returns the new cache.

This function is called internally after a call to the procedure is made. The returned value will be set as the new cache for the procedure.

Example:

configureIguazuRPC({
  procedures: {
    readData: {
      buildUpdatedCache: ({ cache, args, result }) => cache.set(args.id, result),
    },
    // ...
  },
  // ...
});

cacheModifier (optional)

Sometimes procedures can change the validity of the data that other procedures might have cached. To this end, cacheModifier is a way for a procedure to still retain control over its own cache while allowing other procedures to modify it.

The value for cacheModifier is a function of signature ({ cache }) that should return another function. The signature of the wrapped function is defined by the procedure, but should return the updated cache. The returned function is wrapped such that other procedures can call it but never see the resulting cache.

Example:

configureIguazuRPC({
  procedures: {
    readData: {
      cacheModifier: ({ cache }) => ({ action = 'delete', id, value }) => {
        switch (action) {
          case 'delete':
            return cache.delete(id);
          case 'update':
            return cache.set(id, buildEntry(result));
          default:
            return cache;
        }
      },
      // ...
    },
    // ...
  },
  // ...
});

modifyOtherCaches (optional)

Sometimes procedures can change the validity of the data that other procedures might have cached. To this end, modifyOtherCaches is a way to signal to iguazu-rpc what procedure caches should be edited and how.

modifyOtherCaches is a function with signature ({ cache, args, result }) that should return an object of other configured procedure names as keys, and a function accepting their wrapped cacheModifier function as a value.

configureIguazuRPC({
  procedures: {
    updateData: {
      modifyOtherCaches: ({ cache /* own cache */, args, result }) => ({
        // modifyCache is a wrapped form of the other procedure's `cacheModifier` result
        readData: (modifyCache) => modifyCache({ action: 'delete', id: args.id }),
      }),
      // ...
    },
    // ...
  },
  // ...
});

Clearing the data after you are done.

clearProcedureResult({ procedureName, args })

Usually you can clear your entire store when a user session ends but sometimes residual data can be a concern for long running sessions. In these cases the clearProcedureResult action can be dispatched to selectively clean up the residual data when it is no longer needed.

import { clearProcedureResult } from 'iguazu-rpc';
import { connect } from 'react-redux';
// ...
const mapDispatchToProps = (dispatch) => ({
  clearDataCache: (args) => dispatch(clearProcedureResult({ procedureName: 'readData', args })),
});
// ...
connect(null, mapDispatchToProps)(SomeComponent);

This function uses a procedure's buildUpdatedCache method to update a key's result and error values to undefined. To have the key removed from the cache, buildUpdatedCache can be implemented as in the following example:

configureIguazuRPC({
  procedures: {
    readData: {
      buildUpdatedCache: ({
        cache,
        args,
        result,
        error,
      }) => (
        typeof result === 'undefined' && typeof error === 'undefined'
          ? cache.remove(hash(args))
          : cache.set(hash(args), error || result)
      ),
    },
    // ...
  },
  // ...
});

Selectors

getProcedureResult

Retrieve the result of a procedure without making a request.

const procedureResult = getProcedureResult({ procedureName, args })(state);

🏆 Contributing

We welcome Your interest in the American Express Open Source Community on Github. Any Contributor to any Open Source Project managed by the American Express Open Source Community must accept and sign an Agreement indicating agreement to the terms below. Except for the rights granted in this Agreement to American Express and to recipients of software distributed by American Express, You reserve all right, title, and interest, if any, in and to Your Contributions. Please fill out the Agreement.

Please feel free to open pull requests and see CONTRIBUTING.md to learn how to get started contributing.

🗝️ License

Any contributions made under this project will be governed by the Apache License 2.0.

🗣️ Code of Conduct

This project adheres to the American Express Community Guidelines. By participating, you are expected to honor these guidelines.

iguazu-rpc's People

Contributors

10xlacroixdrinker avatar benmclees avatar brantmullinix avatar dependabot[bot] avatar dogpatch626 avatar heythisispaul avatar jimchapter avatar narmeennaveedahmed avatar nellyk avatar pixnbits avatar semantic-release-bot avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.