Git Product home page Git Product logo

klis87 / redux-requests Goto Github PK

View Code? Open in Web Editor NEW
367.0 11.0 51.0 21.83 MB

Declarative AJAX requests and automatic network state management for single-page applications

Home Page: https://redux-requests.klisiczynski.com/

License: MIT License

JavaScript 96.44% TypeScript 3.05% CSS 0.49% HTML 0.02%
redux axios fetch ajax http-client monorepo higher-order-reducers ajax-request optimistic-updates optimistic-ui

redux-requests's Introduction

Redux-Requests

npm version gzip size dependencies dev dependencies peer dependencies Build Status Coverage Status Known Vulnerabilities lerna code style: prettier

Declarative AJAX requests and automatic network state management for single-page applications

Redux-Requests showcase

Note

If you use other feching libraries, like react-query, note, that one of the biggest advantage of redux-requests is an automatic normalisation and data updates. If you mostly care about this feature, you might also try to look at normy - a library to add this feature to any data fetching library!

Table of content

Motivation ⬆️

With redux-requests, assuming you use axios (you could use it with anything else too!) you could refactor a code in the following way:

  import axios from 'axios';
- import thunk from 'redux-thunk';
+ import { handleRequests } from '@redux-requests/core';
+ import { createDriver } from '@redux-requests/axios'; // or another driver

  const FETCH_BOOKS = 'FETCH_BOOKS';
- const FETCH_BOOKS_SUCCESS = 'FETCH_BOOKS_SUCCESS';
- const FETCH_BOOKS_ERROR = 'FETCH_BOOKS_ERROR';
-
- const fetchBooksRequest = () => ({ type: FETCH_BOOKS });
- const fetchBooksSuccess = data => ({ type: FETCH_BOOKS_SUCCESS, data });
- const fetchBooksError = error => ({ type: FETCH_BOOKS_ERROR, error });

- const fetchBooks = () => dispatch => {
-   dispatch(fetchBooksRequest());
-
-   return axios.get('/books').then(response => {
-     dispatch(fetchBooksSuccess(response.data));
-     return response;
-   }).catch(error => {
-     dispatch(fetchBooksError(error));
-     throw error;
-   });
- }

+ const fetchBooks = () => ({
+   type: FETCH_BOOKS,
+   request: {
+     url: '/books',
+     // you can put here other Axios config attributes, like method, data, headers etc.
+   },
+ });

- const defaultState = {
-   data: null,
-   pending: 0, // number of pending FETCH_BOOKS requests
-   error: null,
- };
-
- const booksReducer = (state = defaultState, action) => {
-   switch (action.type) {
-     case FETCH_BOOKS:
-       return { ...defaultState, pending: state.pending + 1 };
-     case FETCH_BOOKS_SUCCESS:
-       return { ...defaultState, data: action.data, pending: state.pending - 1 };
-     case FETCH_BOOKS_ERROR:
-       return { ...defaultState, error: action.error, pending: state.pending - 1 };
-     default:
-       return state;
-   }
- };

  const configureStore = () => {
+   const { requestsReducer, requestsMiddleware } = handleRequests({
+     driver: createDriver(axios),
+   });
+
    const reducers = combineReducers({
-     books: booksReducer,
+     requests: requestsReducer,
    });

    const store = createStore(
      reducers,
-     applyMiddleware(thunk),
+     applyMiddleware(...requestsMiddleware),
    );

    return store;
  };

Features ⬆️

Just actions

Just dispatch actions and enjoy automatic AJAX requests and network state management

First class aborts support

Automatic and configurable requests aborts, which increases performance and prevents race condition bugs before they even happen

Drivers driven

Compatible with anything for server communication. Axios, Fetch API, GraphQL, promise libraries, mocking? No problem! You can also integrate it with other ways by writing a custom driver!

Batch requests

Define multiple requests in single action

Optimistic updates

Update remote data before receiving server response to improve perceived performance

Cache

Cache server response forever or for a defined time period to decrease amount of network calls

Data normalisation

Use automatic data normalisation in GraphQL Apollo fashion, but for anything, including REST!

Server side rendering

Configure SSR totally on Redux level and write truly universal code between client and server

React bindings

Use react bindings to decrease code amount with React even more

Typescript friendly

It has many utilities to make Typescript experience even greater, for example all data generics are inferred in selectors and dispatch results automatically.

Installation ⬆️

To install the package, just run:

$ npm install @redux-requests/core

or you can just use CDN: https://unpkg.com/@redux-requests/core.

Also, you need to install a driver:

  • if you use Axios, install axios and @redux-requests/axios:

    $ npm install axios @redux-requests/axios
    

    or CDN: https://unpkg.com/@redux-requests/axios.

  • if you use Fetch API, install isomorphic-fetch (or a different Fetch polyfill) and @redux-requests/fetch:

    $ npm install isomorphic-fetch redux-requests/fetch
    

    or CDN: https://unpkg.com/@redux-requests/fetch.

Also, you have to install reselect, which probably you use anyway.

Usage ⬆️

For usage, see documentation

Examples ⬆️

I highly recommend to try examples how this package could be used in real applications. You could play with those demos and see what actions are being sent with redux-devtools.

There are following examples currently:

Companion libraries ⬆️

  • redux-smart-actions - Redux addon to create actions and thunks with minimum boilerplate, you can use it to create requests actions faster and in a less verbose way, without constants, useful especially to create thunks without constants, so you have access to Redux state in request actions without any need to pass them with action arguments

Licence ⬆️

MIT

redux-requests's People

Contributors

amanfrinati avatar fmmoret avatar jaulz avatar klis87 avatar medfreeman avatar megawac avatar mihaisavezi avatar nemosupremo avatar qizmaster avatar renovate-bot avatar renovate[bot] avatar strdr4605 avatar

Stargazers

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

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

redux-requests's Issues

Cache based on params

In my use case I am fetching many resources based on some parameter, mapId. I'd like to cache the responses of the actions based on the url rather than the action type.

For example, I have an action getFeatures that fetches a url with a 2 hour cache. Despite the url changing based on a passed in parameter which will alter the response the existing CacheMiddleware will not respect this as a different request.

const getFeatures = createAction(`get features`, ({ mapId }) => {
  return {
    request: {
      url: `/v2/maps/features/?map=${mapId}`
    },
    meta: {
      cache: 7200, // 2 hours
    }
  };
});

createDriver undefined in redux-saga-requests-axios

saw a commit yesterday, maybe something got broken in exports?

createDriver doesn't seem to be exported from redux-saga-requests-axios. i'm developing a project with react-redux-boilerplate as a basis. so to reproduce for my env (using node 8.9.0):

git clone [email protected]:flexdinesh/react-redux-boilerplate.git
npm install axios && npm install redux-saga-requests && npm install redux-saga-requests-axios

in src/app.js, add

import { createRequestInstance, watchRequests } from 'redux-saga-requests';
import { createDriver } from 'redux-saga-requests-axios';
console.log('createDriver', createDriver); 
// createDriver undefined

and the warning in chrome i see is:

./app/app.js 40:28-40"export 'createDriver' was not found in 'redux-saga-requests-axios' @ multi eventsource-polyfill webpack-hot-middleware/client?reload=true ./app/app.js

Could we also support promises?

I really enjoy using this library and I feel like we could also use it for promises?

(FSA) Payload would look something like:

{
  payload: {
    promise: new Promise(...),
  }
}

Remove API for adjusting naming convention

I mean things like dataKey, errorKey, pendingKey in requestsReducer or adjusting suffixes in success, error and abort action. I feel that it unnecessarily makes API bigger and more complex just for flexible naming convention.

I will probably remove those in the near future to make things simpler, but if you disagree with this decision, please let me know in this issue.

Uncaught (in promise) for aborted requests in requestsPromiseMiddleware

When using requestsPromiseMiddleware Uncaught (in promise) warnings can be seen in console for aborted requests. This is because requestsPromiseMiddleware rejects promise not only for error responses, but also aborted ones, and those usually won't be catched anywhere - hence this warning.

One possible solution would be to resolve promise for aborts with abort action value, but this is also not ideal, not to say confusing.

Do you have any ideas?

Status 204 throws json error

https://github.com/klis87/redux-saga-requests/blob/db0a9bdc6c2edf5676df564dacfdfcdc4ab5899b/packages/redux-saga-requests-fetch/src/fetch-api-driver.js#L5

In this line if the response.status is 204, the code assumes a response.json() and Throws a Error because there is no json to parse.

With this snippet works:

const statusEmptyContent = [204, 205];

const prepareSuccessPayload = (response, { responseType = 'json' }) => {
  if (responseTypes.indexOf(responseType) === -1) {
    throw new Error(
      "responseType must be one of the following: 'arraybuffer', 'blob', 'formData', 'json', 'text'",
    );
  }

  if (statusEmptyContent.indexOf(response.status) >= 0) return

  return response[responseType]();
};

Use API Facade instead of request url

In my project, I use API Facades to fetch data and saga looks like

export const handleGetItem: Saga = function*({
  payload,
}: PayloadAction<typeof ActionEnum.API_GET_REQUEST, Payload>): IterableIterator<any> {
  const { id }: Payload = payload;
  const facade: Facade = yield getContext(SagaContextEnum.facade);

  try {
    const storeItem: IItem = yield call(() => facade.getItem(id));
    yield put(actions.successGetItem(storeItem));
  } catch (err) {
    yield put(actions.errorGetItem(err));
  }
};

How can I pass facade.getItem(id) to the redux-saga-request action creator??
Is it possible to implement this feature in this library??

Is there a way to chain requests?

Say I have two axios requests. The first one runs and gets user information. The second one only needs to run if the first one returns with status: true.

Trying to figure out how to do this with redux-saga-requests so any tips would be appreciated!

Thoughts on passing meta property from action to requestInstance?

I'm writing a driver that allows you to attach json schema definitions to the request payload and it returns mock data as a means to create a mock api layer using redux-saga-requests. I have a pretty raw working example, but I'm currently putting the schema definition in the request payload and I feel like it should be in the meta property and should not be part of the request. Based on this line:

https://github.com/klis87/redux-saga-requests/blob/master/packages/redux-saga-requests/src/sagas.js#L114

It looks like the meta property is not being passed in, but it seems like it could be... thoughts? Additionally it could also be done in a more flexible way where you could define what keys of an action payload you want passed. Implementation is a but fuzzier for me on that approach though.

Also is there any disadvantage do you think to passing around a schema definition with the payload? I was also considering registering schemas during initialization of the instance and looking them up with a key from the action payload instead. Anyways, awesome job on the lib. I was basically writing something similar at work to deal with the boilerplate from sagas, but your solution is more refined and further along.

Add support for a react container which supports multiple requests

I find myself often needing to react to multiple requests operating in tandem. In particular I need to show a loading screen/error screen when any of these requests are in flight. Ideally I'd be able to use the RequestContainer however this only supports 1 request and it's not nice to nest them. In my particular case I need to react to 4 requests.

I see 3 options for this case: 1) nest RequestContainers, 2) Using custom code in my mapStateToProps and component to handle the states, 3) I decided to go along the path of creating a MultipleRequestContainer. The basic difference between the one major difference is it takes an array of requests to observer.

My implementation of this was pretty much coppied from the one you provided. Not sure if this is a common use case, I've reached for this hammer 4 times so far.

import React from 'react';
import PropTypes from 'prop-types';

import _ from 'lodash';

export const MultipleRequestContainer = ({
  requests,
  children,
  component: Component,
  showLoaderDuringRefetch,
  noDataMessage,
  errorComponent: ErrorComponent,
  errorComponentProps,
  loadingComponent: LoadingComponent,
  loadingComponentProps,
  ...extraProps
}) => {
  if (showLoaderDuringRefetch && _.some(requests, (request) => request.pending > 0)) {
    return LoadingComponent ? (
      <LoadingComponent {...loadingComponentProps} />
    ) : null;
  }

  const error = _.find(requests, 'error');

  if (error) {
    return ErrorComponent ? (
      <ErrorComponent error={error} {...errorComponentProps} />
    ) : null;
  }

  const dataMap = _.mapValues(requests, 'data');

  return <Component {...dataMap} {...extraProps} />;
};

MultipleRequestContainer.defaultProps = {
  showLoaderDuringRefetch: true,
  noDataMessage: null,
  errorComponent: null,
  errorComponentProps: {},
  loadingComponent: null,
  loadingComponentProps: {}
};

MultipleRequestContainer.propTypes = {
  component: PropTypes.elementType.isRequired,
  requests: PropTypes.objectOf(
    PropTypes.shape({
      data: PropTypes.any,
      error: PropTypes.any,
      pending: PropTypes.number.isRequired,
    })
  ).isRequired,

  showLoaderDuringRefetch: PropTypes.bool,
  noDataMessage: PropTypes.node,
  errorComponent: PropTypes.elementType,
  errorComponentProps: PropTypes.objectOf(PropTypes.any),
  loadingComponent: PropTypes.elementType,
  loadingComponentProps: PropTypes.objectOf(PropTypes.any),
};

And the corresponding ConnectedMultipleRequestContainer:

import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import _ from 'lodash';

import { MultipleRequestContainer } from '.';

export const ConnectedMultipleRequestContainer = connect(
  (state, props) => ({ requests: _.pick(state, props.requests) })
)(MultipleRequestContainer);

const { requests, ...commonRequestContainerProps } = MultipleRequestContainer.propTypes;

ConnectedMultipleRequestContainer.propTypes = {
  ...commonRequestContainerProps,
  requests: PropTypes.arrayOf(PropTypes.string).isRequired,
};

Best Practice for Normalizr Implementation?

I am using this package for an app that relies on Normalizr and am curious if there is a way to run a normalization after a GET request on the response, prior to the success action being dispatched so that I may be able to easily hydrate data in my reducers?

edit

I think I figured out a way to do this using the onSuccess interceptor.

function * onSuccessSaga (response, action) {
  switch (action.type) {
    case objectA.types.GET:
    case objectA.types.POST:
      response = normalize(response, schemas.objectA)
      break
    case objectB.types.GET:
    case objectB.types.POST:
    case objectB.types.PATCH:
      response = normalize(response, schemas.objectB)
      break
  }
  return response
}

regeneratorRuntime is not defined

When trying to use the library it crashes with the next error:

ReferenceError: regeneratorRuntime is not defined
./node_modules/redux-saga-requests/es/sagas.js
node_modules/redux-saga-requests/es/sagas.js:3
  1 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
  2 | 
> 3 | var _marked = /*#__PURE__*/regeneratorRuntime.mark(getRequestInstance),
  4 |     _marked2 = /*#__PURE__*/regeneratorRuntime.mark(sendRequest),
  5 |     _marked3 = /*#__PURE__*/regeneratorRuntime.mark(cancelSendRequestOnAction),
  6 |     _marked4 = /*#__PURE__*/regeneratorRuntime.mark(watchRequests);
View compiled
__webpack_require__
/opt/frontend/webpack/bootstrap 565f41d7711f2490b577:707

Abort request in `onRequest`

Hi Konrad,

Thank you for publishing redux-saga-requests!

I'm trying to abort requests in the onRequest hook and I'm either not sure what's the right way to do it, or maybe the redux-saga-requests onRequest API should be changed to support it:

Here's the scenario:

function* onRequestSaga(axiosInstance, request, action) {
  if (action.confirm) {
    const confirmed = yield call(confirmSaga, action.confirm);
    if (!confirmed) {
      // option 1 - yields a console exception
      throw new CancelRequest('Aborted by confirmation dialog');
     // option 2 - return error - request still tried to be dispatched and the exception is swollen - see attached screenshot below
      return { error: new CancelRequest('Aborted by confirmation dialog'))
      // dispatch abort - doesn't abort the request
      yield put({
          type: abort(action.type),
          meta: {
            requestAction: action,
          },
        })
    }
  }
  return request;
}

any ideas?

image

TypeError: Cannot read property 'driver' of undefined

Hi, thanks for your library, it is very nice and useful.

I create RequestInstance via effect createRequestInstance in my app root saga:

...

function* rootSaga() {
    yield createRequestInstance({
        driver: createDriver(axios)
    });

    yield call(watchRequests);
}

And sendRequest call in in another saga, which runs through sagaMiddleware.run after loading a chunk (code-splitting):

function* anotherSaga(): Generator<Effect, void, any> {
    yield takeLatest(types.FETCH, sendRequest);
}

sagaMiddleware.run(anotherSaga);

When takeLatest call sendRequest, the error will be throw:
image
because requestsConfig === undefined for this "saga instance"
image

I'll try describe my case. I request user names with params userIds: string[] and if for some userId the name have already been loaded, I don’t want to send it to sendRequest:

function* fetch(action) {
    const userNames = yield select(getUserNames);

    const { userIds } = action.meta;

    const necessaryUserIds = userIds.filter(userId => userTimezoneIds[userId] == null);

    if (!necessaryUserIds.length) {
        return;
    }

    yield call(sendRequest, actions.fetch({ userIds: necessaryUserIds }), {
        dispatchRequestAction: false
    });
}

Is this behavior requestsConfig normal?

I solved my problem by filtering the identifiers in the saga, which works earlier than sending the fetch action to redux-request-saga.

I want to get your opinion on this case. Thanks!

Remove 2nd argument (custom reducer) from requestsReducer

Initially passing custom reducer to requestsReducer to allow adding extra state which then is merged with request state from this library seemed like good idea to me. Now I find it unnecessary. Why not just use separate reducer, listening for proper requests actions, which then could be merged with combineReducers? And you you really want to merge some custom state with request state, you can easily use selectors.

So, I think I will probably remove the possibility to pass custom reducer to requestsReducer, which unnecessarily makes thinks complicated and not always obvious. This will be especially important quite soon, as I am going to create higher level reducer, which will act like automatic requestsReducer for all your requests actions, so you won't need to define requestsReducer per action type anymore.

However, if you think that removing this is a bad idea, pls comment here

Never rejecting promise in promise middleware

Just an idea, maybe promise could be always resolved, but with an object with one of the following params: success, error, abort

Why? Because otherwise there are issues with requests aborts which will raise unhandling promise rejection error. The same goes for error, sometimes you don't want to handle an error in a specific place because there aren't any predictable error and random network errors you wanna handle globally.

After this, we could give up meta.asPromise as well as auto config in promise middleware . We could promisify all requests simply because there would be no negative side of doing so, it would reduce the boilerplace.

Connected with #93 , #128 , #179 , @nemosupremo , @mowbell, @megawac , what do you think?

Would something like `getData: false` be useful?

I'm using requestsReducer to at the very minimum make my error reductions consistent. I'm finding that during authentication we don't reduce any data into the store so we do something silly to override this like getData: () => null.

Multiple createRequestInstance with different onError/onRequest interceptors

Hello there,

Is there any possible way to implement multiple sagas with createRequestInstance and watchRequests that will be independent from each other. I have multi-tenant application that have different levels: authentication, administrator, organization level. Each level should have a separate createRequestInstance with global interceptors. For example authentication level saga shouldn't attach any JWT tokens to the request, but administrator and organization level should, moreover organization level should attach current organization id to each request.

Examples do not work

I'm trying to run the examples but keep getting the error below:

suricactus@orange:~/work/tests/redux-saga-requests/examples/basic$ node --version
v10.3.0
suricactus@orange:~/work/tests/redux-saga-requests/examples$ yarn
yarn install v1.7.0
[1/4] Resolving packages...
[2/4] Fetching packages...
info [email protected]: The platform "linux" is incompatible with this module.
info "[email protected]" is an optional dependency and failed compatibility check. Excluding it from installation.
[3/4] Linking dependencies...
[4/4] Building fresh packages...

success Saved lockfile.
Done in 3.98s.
suricactus@orange:~/work/tests/redux-saga-requests/examples$ cd basic/
suricactus@orange:~/work/tests/redux-saga-requests/examples/basic$ npm start

> [email protected] start /home/suricactus/work/tests/redux-saga-requests/examples/basic
> webpack-dev-server

ℹ 「wds」: Project is running at http://localhost:3000/
ℹ 「wds」: webpack output is served from /
✖ 「wdm」: Hash: 0aa3479e85fb680c205f
Version: webpack 4.9.1
Time: 1765ms
Built at: 07/01/2018 2:41:31 AM
 1 asset
Entrypoint main = main.js
[../node_modules/babel-polyfill/lib/index.js] 833 bytes {main} [built]
[../node_modules/loglevel/lib/loglevel.js] 7.68 KiB {main} [built]
[../node_modules/webpack/hot sync ^\.\/log$] ../node_modules/webpack/hot sync nonrecursive ^\.\/log$ 170 bytes {main} [built]
[0] multi ../node_modules/webpack-dev-server/client?http://localhost:3000 webpack/hot/dev-server ./src 52 bytes {main} [built]
[../node_modules/strip-ansi/index.js] 161 bytes {main} [built]
[../node_modules/url/url.js] 22.8 KiB {main} [built]
[../node_modules/webpack-dev-server/client/index.js?http://localhost:3000] ../node_modules/webpack-dev-server/client?http://localhost:3000 7.75 KiB {main} [built]
[../node_modules/webpack-dev-server/client/overlay.js] 3.58 KiB {main} [built]
[../node_modules/webpack/hot/dev-server.js] 1.66 KiB {main} [built]
[../node_modules/webpack/hot/emitter.js] 77 bytes {main} [built]
[../node_modules/webpack/hot/log-apply-result.js] 1.31 KiB {main} [built]
[../node_modules/webpack/hot/log.js] 1.03 KiB {main} [built]
[./src/components/app.js] 3.57 KiB {main} [built]
[./src/index.js] 507 bytes {main} [built]
[./src/store/index.js] 1.89 KiB {main} [built]
    + 444 hidden modules

ERROR in ./src/store/index.js
Module not found: Error: Can't resolve 'redux-saga-requests' in '/home/suricactus/work/tests/redux-saga-requests/examples/basic/src/store'
 @ ./src/store/index.js 8:0-75 22:17-38 26:17-30
 @ ./src/index.js
 @ multi ../node_modules/webpack-dev-server/client?http://localhost:3000 webpack/hot/dev-server ./src

ERROR in ./src/store/reducers.js
Module not found: Error: Can't resolve 'redux-saga-requests' in '/home/suricactus/work/tests/redux-saga-requests/examples/basic/src/store'
 @ ./src/store/reducers.js 3:0-54 7:26-41 12:25-40
 @ ./src/store/index.js
 @ ./src/index.js
 @ multi ../node_modules/webpack-dev-server/client?http://localhost:3000 webpack/hot/dev-server ./src

ERROR in ./src/store/index.js
Module not found: Error: Can't resolve 'redux-saga-requests-axios' in '/home/suricactus/work/tests/redux-saga-requests/examples/basic/src/store'
 @ ./src/store/index.js 9:0-52 22:64-75
 @ ./src/index.js
 @ multi ../node_modules/webpack-dev-server/client?http://localhost:3000 webpack/hot/dev-server ./src
Child html-webpack-plugin for "index.html":
     1 asset
    Entrypoint undefined = index.html
    [../node_modules/html-webpack-plugin/lib/loader.js!./src/index.html] 500 bytes {0} [built]
    [../node_modules/lodash/lodash.js] 527 KiB {0} [built]
    [../node_modules/webpack/buildin/global.js] 509 bytes {0} [built]
    [../node_modules/webpack/buildin/module.js] 519 bytes {0} [built]
ℹ 「wdm」: Failed to compile.

I've also tried installing with npm in each example directory or switching to node 8, but I kept getting this error.

Override requests-promise-middleware when auto

We use requests-promise-middleware a lot, with auto, however one request in our code base does not need to be promisified, and also gets aborted a lot - which leads to a lot of "errors" in the console.

I'd like to be able to set isPromise explicitly to false to disable this behavior. I thought I would ask before making a PR.

https://github.com/klis87/redux-saga-requests/blob/bb40be382d72dcef76bda493d797f1cf59ea28dd/packages/redux-saga-requests/src/middleware.js#L8

would become something like

const shouldActionBePromisified = (action, auto) =>
  (auto && !(action.meta && action.meta.asPromise === false)) || (action.meta && action.meta.asPromise);

Side-loading data using success action results in negative `pending`

Sometimes I want to load the same data as with a requests action, from a difference source, in my case, EventSource.
So I tried to just put the FETCH_DATA_SUCCESS action with the same payload as usual, however that results in a negative pending.
I am not sure it's a very smart way to go about it, but my plan is to push actions directly from the server with SSE and avoid having to do additional processing on the client side... Not sure I could go about it some other way...

Why not just make sure that pending can never be <0?

Network history proposal

Hello !

First of all thank you for this package. Our application has many api requests calls and this is going to make our life much simpler.

After playing with your library we realized we could make something we called "network history". It is basically a root reducer which takes all the actions (success, error, request) and with that it fills a network tree in the state that later we use to populate our app with selectors.

Here is the code I have so far:

import {createRequestInstance, watchRequests} from "redux-saga-requests/src";
import {isRequestAction} from "redux-saga-requests/src/sagas";
import axiosConfig from './axiosConfig'

const initialState = {};

/**
 * All network response actions has meta.request object in the payload
 */
const isNetworkResponseAction = action => 
  action.payload
  && action.payload.meta
  && action.payload.meta.request

/**
 * A success network action always has data in the meta
 */
const isSuccessAction = action => isNetworkResponseAction(action) && action.payload.data;

/**
 * An error network action always has error in the meta
 */
const isErrorAction = action => isNetworkResponseAction(action) && action.payload.error;

/**
 * REDUCER
 */
export default function network(state = initialState, action) {

  if (isRequestAction(action)) {

    const {type} = action;
    return {
      ...state,
      [type]: {
        ...state[type],
        fetching: true,
      },
    }
  }
  if (isSuccessAction(action)) {

    return {
      ...state,
      [action.payload.meta.type]: {
        fetching: false,
        data: action.payload.data,
        error: '',
      },
    }
  }
  if (isErrorAction(action)) {
    return {
      ...state,
      [action.payload.meta.type]: {
        ...state[action.payload.meta.type],
        fetching: false,
        error: 'Error',
      },
    }
  }
  //We can also have _ABORT action but we still not need it

  return state;
}

/**
 * SELECTORS
 */

export const selectNetworkResource = (state, resource: string) => state.network[resource] && state.network[resource].data

/**
 * LISTENERS
 */
export function* networkListeners() {
  yield createRequestInstance(axiosConfig)
  yield watchRequests();
}

After adding this network reducer in the root reducer with combineReducers we have a tree like this when we have one success call:
webpack_app

So now we can use a selector in mapStateToProps to access the data:

const mapStateToProps = state => ({
  products: selectNetworkResource(state, PRODUCTS_REQUEST) || [],
});

We do this in orther to get rid of all the network related logic, in you example this:

- const booksReducer = (state = defaultState, action) => {
-     switch (action.type) {
-       case FETCH_BOOKS:
-         return { ...defaultState, fetching: true };
-      case FETCH_BOOKS_SUCCESS:
-      case success(FETCH_BOOKS):
-         return {
-           ...defaultState,
-           data: { ...action.payload.data },
-         };
-      case FETCH_BOOKS_ERROR:
-      case error(FETCH_BOOKS):
-         return { ...defaultState, error: true };
-       default:
-         return state;
-     }
-   };

As you see in the network reducer we don't have an easy way to know which actions are SUCCESS, ERROR, ABORT...

  • Is there an easy way to know that?
  • Are you interested to have this 'network history' like this? If so I will be glad to submit a PR :)

Adding a validation hook as a precondtion to submitting an operation

Hi,

I've hit a situation where I wanted to add validation logic as a precondtion to submitting a request through the library. Before submitting a request I want to run through a series of client side validations that must be met before the request is dispatched to the server.

I was what your thoughts are about validation in the library. My original plan was to dispatch a put(createErrorAction(myRequestAction, myException)) to have the store updated with the validation error. The issue with this approach is pending would go from 0 -> -1 as the request would of never initiated. I've included a summary of an early attempt at this below


I already have a little saga wrapper that intercepts my requests and then dispatches the requests with some additional context from my store which is reused by about 30 operations. I was hoping I could add a hook in here to add a validation step if provided. Below is a snippet that I've updated to remove some buisness logic

import { call, select, put } from 'redux-saga/effects';

import { createErrorAction } from 'redux-saga-requests/lib/actions';

/**
 * Creates a saga to intercept a request and pass the map to the action.
 * @param {action} requestAction: The request action to dispatch
 * @param {function} hasData({payload}) request to validate the the action was provided with data.
 *    Return false to avoid submitting the rpc call.
 */
export function createMapRequestSaga(requestAction, {
  validate = () => true,
}) {
  return function* updateMapSaga({ payload }) {
    const { loadedMap } = yield select((state) => state.maps);

    const request = requestAction({ ...payload, map: loadedMap });

    if (myMapIsValid()) {
      const exception = new InvalidMap(requestAction);
      yield put(createErrorAction(request, exception));
    } else if (!validate(payload)) {
      const exception = new MapValidationError(requestAction);
      yield put(createErrorAction(request, exception));
    }
    return yield call(sendRequest, request);
  };
}

This gets used as such

  const mapRequestConfig = {
    validate: ({ [type]: features }) => validateFeatureCollection(features),
  };

  const setFeaturesAction = createAction(`set ${type}`);
  const _setFeaturesAction = createAction(`set ${type} internal`, ({ map, [type]: features }) => {
    return {
      request: {
        url: '/v2/maps/operations/',
        method: 'post',
        data: formatRPCCall(`set${typeCapitalized}`, {
          map_id: map.id,
          features,
        })
      },
    };
  }, ({ map, [type]: features }) => ({ map, features }));
  const setFeaturesSaga = createMapRequestSaga(_setFeaturesAction, mapRequestConfig);

[Idea] byId Reducer For Direct Object Access

Currently it looks like the all reducer stores objects as an array. You could convert it with something like the following.

const arrayToObject = (array, key) => array.reduce((result, val, inx) => {
  result[val[key]] = val
  return result
}, {})

const myObjectsById = arrayToObject(myObjects, 'id')

However this adds overhead for every component that may directly access these values via connect or any other property sharing mechanism. Currently, I do not use the pre-built reducers included with redux-saga-requests, mostly in part because I can't find a way to directly access objects by id. Typically I run an all reducer right next to a byId reducer. While this results in twice the storage being used, I feel it adds more efficiency to my code base by allowing direct es5 based iteration over the all reducer, and direct access to objects with the byId reducer.

I was just curious, are there any plans to add this feature in the future? I've implemented this library in several projects so far and love everything it seems to offer, this is the only limitation I've found so far even though its very easy to work around and is not necessary.

Actions with parameters, multiple states

Hi,

I'm having a play to see to see if we can use this where I work.

How would you handle multiple calls using the same action? For example:

getPost(1);
getPost(2);

Redux override post 1 with post 2, whereas I need both. Is there any provision for that?

Driver not adding headers dynamicly React-native

I have a stage when I add authorization token to my axios instance and when I want to make any request with it, headers are missing
Here is my rootSaga
2019-01-29 18 37 11

My axios service:
2019-01-29 18 37 27

And place where I add my token.
2019-01-29 18 37 37

Console.logs . Request missing headers that I've added later as a common(global axios header)
2019-01-29 18 42 17

Any ideas? Suggestions?

Infinite Request Loop when request fails due to Network Error.

I'm using the Axios driver, and the example in the ReadMe. I noticed that when a request fails due to a request error (ex. WiFi not connected), the library goes into a loop, re-requesting the same url over and over again.

As a result you end up with with action types like these:

LOGIN_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR_ERROR

While the library retries requests over and over.

The problem is with isRequestAction:

const isRequestAction = action => {
  const actionPayload = getActionPayload(action);
  return !!actionPayload.request && !actionPayload.response;
};

It checks if the payload has a request object and doesn't have a response object. However, when axios throws an error, the payload is of instance type Error and looks like this:

{
  "config": {
    "transformRequest": {},
    "transformResponse": {},
    "timeout": 0,
    "xsrfCookieName": "XSRF-TOKEN",
    "xsrfHeaderName": "X-XSRF-TOKEN",
    "maxContentLength": -1,
    "headers": {
      "Accept": "application/json, text/plain, */*"
    },
    "baseURL": "http://localhost:4729/api",
    "method": "get",
    "cancelToken": {
      "promise": {}
    },
    "url": "http://localhost:4729/api"
  },
  "request": XMLHttpRequest{}
}

This also satisfies isRequestAction, and the loop repeats again (and it doesn't do what you expect, since request is a XMLHttpRequest object, and not an object of params).

Remove Axios as the default driver

Now Axios driver is used as the default, but because of this it is always bundled even if not used. This adds an unnecessary weight to the package

How would I add a retry mechanism?

I currently have my own implementation of Redux Saga & axios (albeit a not very good one as I'm still learning how to use Redux Saga) when I came across your library. I'm exploring if it'll work for my project. The basic question is, how would I implement a retry mechanism? My code currently looks something like:

import { delay } from 'redux-saga';
import { call, cancelled } from 'redux-saga/effects';

const RETRIES = 5;
const RETRY_DELAY = 2000;

export function* callApi(url) {
  let error;
  let cancelToken = axios.CancelToken;
  let source = cancelToken.source();
  for (let i = 0; i <= RETRIES; i++) {
    try {
      return yield axios.get(url, {
        cancelToken: source.token
      });
    } catch (err) {
      error = err;
      if (
        err.response &&
        err.response.status >= 400 &&
        err.response.status <= 499
      ) {
        // Ignore 4xx errors.
        break;
      }
      if (i < RETRIES) {
        yield call(delay, RETRY_DELAY);
      }
    } finally {
      if (yield cancelled()) {
        source.cancel('Cancelling axios');
      }
    }
  }
}

So I want to retry failed requests, but only if they're not 4xx. Using your library, I'm not sure where this logic would go.

Error from API comes empty

I have implemented this project into my early work for a newly created SPA.

My successful responses come into the State of Redux as expected. But the error responses come in a empty error in the State of Redux, but the response from the API seems to look ok to me.

image

image

If you could enlightenment me about it, it good be awesome.

UPDATE: I'm using fetch driver, without a polyfill and using Google Chrome: 64.0.3282.140 (Official Build) (64-bit) in MacOS High Sierra

Thanks for this work, is pretty awesome. :-)

Negative `pending` when using `sendRequest`

When using the low-level API with sendRequest the pending state becomes negative
e.g:

// saga
function* loadSomething(): Saga {
  const {response, error} = yield call(sendRequest, actions.fetchSomething());
}
// reducer
....
    something: requestsReducer({
      actionType: FETCH_SOMETHING,
      getData: (state, action) => action.data,
    }),
...

Promise middleware force you to catch and then all requests

Can I mix promise middleware only for some requests and normal requests without handle these as promises?
When I use optional promise middleware, I must handle all requests (then and catch)
What if I need handle only ones as promises, and the rest as normal redux-saga-requests?

For fix it on my project, I override requestsPromiseMiddleware config as:

requestsPromiseMiddleware({
  getRequestAction: (action) => {
    return action.meta && action.meta.requestAction && action.meta.requestAction.isPromise ? action.meta.requestAction : null;
  },
})

Then I write actions creators that needs promise handling as:

export const sendContactMessage = ({ name, email, message }) => ({
  type: SEND_CONTACT_MESSAGE,
  isPromise:true,
  request: {
    url: '/contact',
    method: 'post',
    data: {
      name,
      email,
      message,
    },
  },
});

Maybe someone with the same problem can do it, but
My question, It is an issue or How can I do the same?

Best practice to transform responses?

What is the recommended way to perform action-specific transformations on response before it reaches reducers? I use axios and currently put my transformation logic in transformResponse property - the issue is that if any error occurs in my logic, axios interceptor process it and it makes it inconvenient for debugging.

Do you have any advice on what could be the better alternative?

PS: great job on this lib, really enjoying it so far!

Operation request which requires state information

I have run into a case quite frequently where I have an operation on a request which requires some state information before I can dispatch the request. So I run a select -> dispatching the request. However this causes me to have to dispatch 2 different actions: first we have to dispatch the action to run the saga, then we run the saga, then we call the request action.

The downside of this with redux-act is the user dispatches action1 but they have to watch for action2 on the store. Example:

// Call to run set feature saga
const setFeaturesAction = createAction(`set ${type}`);

const _setFeaturesAction = createAction(`set ${type} internal`, ({ map, [type]: features }) => {
  return {
    request: {
      url: '/v2/maps/operations/',
      method: 'post',
      data: formatRPCCall(`set${typeCapitalized}`, {
        map_id: map.id,
        features,
      })
    },
  };
}, ({ map, [type]: features }) => ({ map, features }));

// Get some stuff from state then run _setFeaturesAction.
const setFeaturesSaga = function* ({ payload }) {
  const { map } = yield select(state.maps);
  return yield put({ map, ...payload });
};

const reducer = requestsReducer({
  actionType: getFeaturesAction,
  // stuff and things...


  operations: {
    [_setFeaturesAction]: {},
  }
}, baseReducer);


// User dispatches
function mapStateToProps(state) {
  return {
    data: state.data,
    operation: state.operations._setFeaturesAction
  };
}

function mapDispatchToProps(dispatch) {
  return {
    runOp: () => {
      dispatch(setFeaturesAction)
    }
  }
}

From a user perspective its a bit awkward to know the difference between _setFeaturesAction and setFeaturesAction to use the store. Do you have any suggestions to improve this pattern?

onRequest is not triggered when calling action via sendRequest

I'm using sendRequest in a couple spots to load several resources in parallel and yield for their completion. Unfortunately I noticed when I try to use sendRequest the onRequest handler in the reducers fails to be triggered as the base action never gets triggered. This causes pending states to not get tracked correctly and I've been getting pending states in the negatives as I fetch other resources.

This is a simplified version of what I'm doing (

const getFeaturesAction = createAction(`get feature`, ({ mapId, type }) => {
  return {
    request: {
      url: `/v2/maps/${type}/?map=${mapId}`
    },
  };
});

function createFeatureFetchAction(type) {
  return createAction(`get feature of ${type}`, ({  }) => {
    return {
      request: {
        url: `/v2/maps/${type}/?map=${mapId}`
      },
    };
  });
}

const fetchFeature1 = createFeatureFetchAction('feature-set-1');
const fetchFeature2 = createFeatureFetchAction('feature-set-2');
const fetchFeature3 = createFeatureFetchAction('feature-set-3');

function* getFeaturesSaga({ mapId }) {
  yield all([
    call(sendRequest,fetchFeature1({ mapId })),
    call(sendRequest, fetchFeature2({ mapId })),
    call(sendRequest, fetchFeature3({ mapId })),
  ])

  // Do some stuff....
}

const reducer = requestsReducer({
  type: fetchFeature1
});

// reducer2, 3...

Perhaps there is another way you'd suggest achieving this workflow instead?

Removing createRequestsReducer

I am going to remove createRequestsReducer, because if you want to have a requestsReducer with different defaults, it is easy to do like below:

import { requestsReducer } from 'redux-saga-requests';

const myRequestsReducer = config => requestsReducer({ multiple: true ...config });

so there is not point to have createRequestsReducer in this library.

Please comment here if you disagree for some reason, so I might change my mind.

on GET requests params in request.data are not passed in the request

using axios, an action as defined below does not pass offset into the resulting network request. is the data payload intentionally stripped on GETs?

export function loadThing(offset) {
  return {
    type: LOAD_THING_REQUEST,
    request: {
      data: { offset },
      url: 'https://someapi.com'
    }
  };
}

successAction from action

Is it possible to get the successAction (or errorAction) from a defined action? I'd like to have access to them for testing reducers.

Example:

import { successAction } from 'redux-saga-requests';

import { FETCH_PHOTO } from './constants';

export const fetchPhoto = id => ({
  type: FETCH_PHOTO,
  request: { url: `/photos/${id}` },
});

export const fetchPhotoSucceeded = successAction(FETCH_PHOTO);

Does it make sense? I'd then use this action on my tests: fetchPhotoSucceeded(myData, myMeta)

pd: nice library btw :)

Operations/Mutations should also update cached data

Currently cached data is static, until next refetch after timeout. It should be editable though as when you have following scenario:

  • you fetch STH
  • you cache it
  • you update data
  • if cache is not expired and you fetch STH again, then you will use cached data with state prior to mutation, leading to outdated value

With this task probably I will also need to store cached data in reducer, not in cacheMiddleware closure, which will also make thinks compatible with SSR

getLastActionKey doesn't seem to work?

Hi,

I end up firing a bunch of actions to fetch some resources using:

yield all([put(fetchResource('abc123')), put(fetchResource('abc567'))]);

If fetchResource is a standard redux-saga-requests action I'd think I could prevent it from aborting the abc567 request by formulating my request using the action creator below (I'm using FSA compatible actions) but it still seems to abort my requests. There doesn't seem to be a test written for request specific getLastActionKey so maybe this isn't working as expected?

export const fetchResource = resourceId => ({
    type: types.FETCH_RESOURCE,
    payload: {
        request: {
            url: `/resource/${resourceId}`,
        },
    },
    meta: {
        resourceId,
        getLastActionKey: action => action.type + action.meta.resourceId
    },
});

TypeError: requestActionPromise is not a function

I'm using auto promise actions in a next.js getInitialPorps method. It gives following error at the server sometimes.

TypeError: requestActionPromise is not a function
    at /home/project/node_modules/redux-saga-requests/lib/middleware.js:41:13
    at /home/project/node_modules/@redux-saga/core/dist/redux-saga-core.dev.cjs.js:1412:22
    at dispatch (/home/project/node_modules/redux/lib/redux.js:617:28)
    at /home/project/node_modules/@redux-saga/core/dist/chunk-774a4f38.js:126:12
    at /home/project/node_modules/@redux-saga/core/dist/redux-saga-core.dev.cjs.js:476:54
    at exec (/home/project/node_modules/@redux-saga/core/dist/redux-saga-core.dev.cjs.js:35:5)
    at flush (/home/project/node_modules/@redux-saga/core/dist/redux-saga-core.dev.cjs.js:91:5)
    at asap (/home/project/node_modules/@redux-saga/core/dist/redux-saga-core.dev.cjs.js:50:5)
    at runPutEffect (/home/project/node_modules/@redux-saga/core/dist/redux-saga-core.dev.cjs.js:472:3)
    at runEffect (/home/project/node_modules/@redux-saga/core/dist/redux-saga-core.dev.cjs.js:1202:7)
    at digestEffect (/home/project/node_modules/@redux-saga/core/dist/redux-saga-core.dev.cjs.js:1269:5)
    at next (/home/project/node_modules/@redux-saga/core/dist/redux-saga-core.dev.cjs.js:1159:9)
    at currCb (/home/project/node_modules/@redux-saga/core/dist/redux-saga-core.dev.cjs.js:1249:7)
    at runCallEffect (/home/project/node_modules/@redux-saga/core/dist/redux-saga-core.dev.cjs.js:543:5)
    at runEffect (/home/project/node_modules/@redux-saga/core/dist/redux-saga-core.dev.cjs.js:1202:7)
    at digestEffect (/home/project/node_modules/@redux-saga/core/dist/redux-saga-core.dev.cjs.js:1269:5)
The above error occurred in task sendRequest
    created by rootSaga
    created by rootSaga
Tasks cancelled due to error:
rootSaga
takeLatest(PASSWORD_LOGIN_SUCCESS, loginSuccessSaga)
takeLatest(AUTHENTICATE_USER, authSetCookie)

Add ability to specify default value of reducer

Currently we can specify the default to either null (multiple: false) or an empty array (multiple: true). However we may sometimes want the default to be something else. For my use case I am fetching a GeoJSON FeatureCollection, so I'd like my default to be {type: "FeatureCollection", features: []}.

I propose replacing the multiple option with a getDefault function which returns the default value. So, getDefault: () => {your default}

Headers Missing On POST Requests?

Greetings Again!

I'm trying to write a POST request to a login endpoint generated using Loopback. My action creator looks like this,

export const login = (username, password) => ({
  type: types.LOGIN,
  request: {
    url: utils.genApiUrl(models.USER_PROFILE) + '/login',
    method: 'POST',
    body: JSON.stringify({ username, password }),
    headers: {
      'Content-Type': 'application/json'
    }
  }
})

This fails with a 400 error stating that the username or email key must be supplied in the body. I have confirmed this works with Postman as well as the Loopback API explorer. However, even when trying a static JSON string as the body value in my action creator Loopback continued to give the same error. I started looking deeper into the request, and it seems the headers property, specifically 'content-type' is not being passed on my request.

POST /api/user-profiles/login HTTP/1.1
Host: localhost:3000
Connection: keep-alive
Content-Length: 0
Accept: application/json, text/plain, */*
Origin: http://localhost:3000
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36
Referer: http://localhost:3000/login/
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9

Content-Type is not listed above, which is likely why Loopback does not know how to parse the body of the request. Content-Length is also 0 which makes me think no content is actually being posted.

Also, here is a link to an image showing request/response headers for the above request from Chrome's Network tab.

EDIT

I'm using redux-saga-requests-axios.

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.