Skip to content

BlackWaspTech/redux-wasp

Repository files navigation

redux-wasp

npm Build Status npm bundle size (minified) code style: prettier

contributions welcome All Contributors License: MIT

Utilize cutting-edge GraphQL APIs within an existing Redux codebase.

Perform GraphQL queries exactly like you would a fetch request.

Takes a url and an init object as input. Returns a Promise containing the results of the request.

// fetch
fetch('/graphql', { body: JSON.stringify({ query: '{ foo { bar baz } }' }) });

// query
import { query } from 'redux-wasp';
query('/graphql', { fields: '{ foo { bar baz } }' });

// Logging the results
fetch(url, init)
  .then(res => res.json())
  .then(json => console.log(json));
query(url, init)
  .then(res => res.json())
  .then(json => console.log(json));

Also automates dispatching the results of the above query to the Redux Store (OPTIONAL).

// Apply the reducer
// reducers.js
import { waspGraphqlReducer } from 'redux-wasp';
const reducers = combineReducers({ graphql: waspGraphqlReducer });

// Configure the middleware
import { createWaspMiddleware } from 'redux-wasp';
const waspMiddleware = createWaspMiddleware();
const store = createStore(
  reducers,
  preloadedState,
  applyMiddleware(waspMiddleware)
);

New properties will now be added to your redux store. Every query and mutation executed will update this properties.

// Contents of Redux Store
{
  "foo": { /* ... */ },
  "bar": { /* ... */ },
  "graphql": {
    "isFetching": false,
    "didError": null,
    "status": null,
    "lastUpdated": null,
    "data": null,
    "error": null
  },
}

For the base query and mutation methods without Redux, check out wasp-graphql.

For additional interoperability with Apollo, check out redux-wasp-apollo.

For a live, full-stack application showcasing redux-wasp in action, visit The Buzz.

Installation

Install via npm:

npm install --save redux-wasp

Install via yarn:

yarn add redux-wasp

redux-wasp is a micro-library. Only the base methods included in wasp-graphql will be added as a dependency.

Requires fetch to be in scope.

Requires fetch to be in scope.

Use

// ES6 (with Destructuring)
import { query } from 'redux-wasp';

// ES6
import Wasp from 'redux-wasp';
const query = Wasp.query;

// ES5
var Wasp = require('redux-wasp');
var query = Wasp.query;

How It Works

GraphQL methods

How to query a GraphQL server.

Write a string to request data ("fields") from a GraphQL endpoint.

Given an example string:

var myFields = `{
  hero {
    name
    friends {
      name
    }
  }
}`;

Pass the query string alone as the second argument...

import { query } from 'redux-wasp';
query('/my/url/endpoint', myFields);

Or as a property called fields for the second argument...

import { query } from 'redux-wasp';

query('/my/url/endpoint', { fields: myFields });
// Any `fetch` init property can be included as well
query('/my/url/endpoint', { fields: myFields, mode: 'no-cors' });

Or as part of a fully customized body property (ADVANCED).

import { query } from 'redux-wasp';

// Remember that `body` must be a JSON parsable string. Also, many GQL
//    servers will expect fields to be sent under a `body.query` property.
const init = {
  body: JSON.stringify({
    query: myFields
  }),
  credentials: 'include',
  mode: 'same-origin'
};
query('/my/url/endpoint', init);

Then, you can unpack the results of query with .json():

import { query } from 'wasp-graphql';

query('/my/url/endpoint', init)
  .then(response => {
    console.log(response.json()); // my data
  })
  .catch(error => {
    console.log(error); // my error
  });

As a thin wrapper over the Fetch API, anything that applies to fetch will also apply to query as well.

Variables

About dynamic arguments

GraphQL variables can be passed on as a separate property named variables.

import { query } from 'redux-wasp';

query(url, { fields: myFields, variables: myVariables });

A longer example:

import { query } from 'redux-wasp';

const url = '/api/starwars';
const fields = `
  query HeroNameAndFriends($episode: Episode) {
    hero(episode: $episode) {
      name
      friends {
        name
      }
    }
  }
`;
const variables = {
  episode: 'JEDI'
};

query(url, { fields, variables })
  .then(res => res.json())
  .then(json => {
    console.log(json);
  });

// A custom body property can be used as well
query(url, { body: JSON.stringify({ fields, variables }) }).then(/* ... */);

Redux functionality

redux-wasp provides custom middleware to automate sending the results of query to the Redux store. This is completely optional.

redux-wasp exposes additional methods so that you can utilize your own approach to automation. See the API for more details.

Step 1: Apply the reducer

// reducers.js
import { waspGraphqlReducer } from 'redux-wasp';
// Alias: import { graphqlReducer } from 'redux-wasp';

const reducers = combineReducers({
  graphql: waspGraphqlReducer // or graphqlReducer
});

Step 2: Configure the middleware

// store.js
import { createWaspMiddleware } from 'redux-wasp';

// `createWaspMiddleware` must be invoked before applying it to the store
const waspMiddleware = createWaspMiddleware();
const store = createStore(r, p, applyMiddleware(waspMiddleware));
import { createWaspMiddleware } from 'redux-wasp'

// Without a temporary variable...
const store = createStore(r, p, applyMiddleware(createWaspMiddleware())

redux-wasp's dispatch automation can be deactivated by passing in { automate: false } as the first argument.

// store.js
import { createWaspMiddleware } from 'redux-wasp';

const waspMiddleware = createWaspMiddleware({ automate: false });

const store = createStore(r, ps, applyMiddleware(waspMiddleware));

Now, you may use query to your heart's content!

Here is the initial state provided by redux-wasp:

{
  isFetching: false,
  didError: false,
  status: null,
  lastUpdated: null,
  data: null,
  error: null
}

API

Quick Reference

import {
  // For interacting with a GQL server
  query,
  mutation,

  // For configuration Redux automation
  createWaspMiddleware,
  waspGraphqlReducer,
  graphqlReducer, // An alias for waspGraphqlReducer

  // For interacting with or overwriting
  //    specific parts of redux-wasp
  constants, // Action constants
  initialState, // Gets passed into graphqlReducer
  requestGraphqlData, // Action creator
  requestGraphqlData, // Action creator
  receiveGraphqlError, // Action creator
  clearGraphqlData // Action creator
} from 'redux-wasp';

createWaspMiddleware([config: Object])

/**
 * Generates a custom middleware function.  This middleware saves the dispatch function to the
 * current variable environment so that query/mutatate can auto-dispatch actions.
 *
 * SYNTAX: createWaspMiddleware(config)
 *
 * @param {Object} [config] - Optional settings
 * @param {boolean} [config.automate] - Deactivates the ability to auto-dispatch
 *
 * @returns {function} - Returns Redux Middleware
 */

import { createWaspMiddleware } from 'redux-wasp';

query(url: string, init: string | Object[, transform: function])

/**
 * Provides a thin, GQL-compliant wrapper over the Fetch API.
 *
 * SYNTAX: query(url, init, transform)

 * @param {string} url - The url for the intended resource
 * @param {(string|Object)} init - Can be a string of fields or a configuration object
 * @param {function} transform - The user can choose to provide a callback that transform
 *    the response's data before it reaches the Redux store
 * @param {string} [init.fields] - GQL fields: Will be added to the body of the request
 * @param {string} [init.variables] - GQL variables: Will be added to the body of the request
 * // For additional valid arguments, see the Fetch API:
 * // https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch
 *
 * Default init properties
 * @param {string} [init.method='POST']
 * @param {Object} [init.headers={ 'Content-Type': 'application/json', 'Accept': 'application/json' }]
 *
 * @returns {Promise}
 */

import { query } from 'redux-wasp';

mutation(url: string, init: string | Object[, transform: function])

Alias for query.

constants: Object

/**
 * Action Types
 *
 * var constants = {
 *    WASP_IDENTIFIER: '__WASP__',
 *    REQUEST_GRAPHQL_DATA: 'REQUEST_GRAPHQL_DATA',
 *    RECEIVE_GRAPHQL_DATA: 'RECEIVE_GRAPHQL_DATA',
 *    RECEIVE_GRAPHQL_ERROR: 'RECEIVE_GRAPHQL_ERROR',
 *    CLEAR_GRAPHQL_DATA: 'CLEAR_GRAPHQL_DATA'
 * }
 */

import { constants } from 'redux-wasp';

initialState: Object

/**
 * var initialState = {
 *    isFetching: false,
 *    didError: false,
 *    status: null,
 *    lastUpdated: null,
 *    data: null,
 *    error: null
 * }
 */

import { initialState } from 'redux-wasp';

waspGraphqlReducer(state: Object, action: Object)

Alias: graphqlReducer(state: Object, action: Object)

/**
 * Updates the Redux Store.
 *
 * @param {Object} state - Previous Redux Store
 * @param {Object} action - Description of the changes to be made
 *
 * @returns {Object} - The new state
 */

import { waspGraphqlReducer } from 'redux-wasp';
// Alias: import { graphqlReducer } from 'redux-wasp';

requestGraphqlData()

/**
 * Runs prior to executing a query.
 *
 * SYNTAX: requestGraphqlData()
 *
 * @returns {Object} - Action
 */

import { requestGraphqlData } from 'redux-wasp';

receiveGraphqlData(payload: any, status: number[, lastUpdated: number])

/**
 * Runs if a query is successful.
 *
 * SYNTAX: receiveGraphqlData(payload, status, lastUpdated)
 *
 * @param {any} payload - The data to be sent to the Redux Store
 * @param {number} status - The response object's status code
 * @param {number} [lastUpdated] - Date when the dispatch is executed;
 *    can be optionally passed in for testing purposes, otherwise it is
 *    set to the return value of Date.now() by default
 *
 * @returns {Object} - Action
 */

import { receiveGraphqlData } from 'redux-wasp';

receiveGraphqlError(error: string, status: number[, lastUpdated: number])

/**
 * Runs if a query returns an error.
 *
 * SYNTAX: receiveGraphqlError(error, status, lastUpdated)
 *
 * @param {string} error - The response object's error message
 * @param {number} status - Currently returns 0
 * @param {number} [lastUpdated] - Date when the dispatch is executed;
 *    can be optionally passed in for testing purposes, otherwise it is
 *    set to the return value of Date.now() by default
 *
 * @returns {Object} - Action
 */

import { receiveGraphqlError } from 'redux-wasp';

clearGraphqlData()

/**
 * Re-initializes state.
 *
 * SYNTAX: clearGraphqlData()
 *
 * @returns {Object} - Action
 */

import { clearGraphqlData } from 'redux-wasp';

Changelog

View it here

Contributing

Read more

Contributors


Denny Temple


Reynolds A Colon


kamo31


marceca

This project follows the all-contributors specification. Contributions of any kind welcome!

Code of Conduct

Read our Code of Conduct here.

License

Free and Open Source under the MIT License.