2.0.2 • Published 4 years ago

react-preloadr v2.0.2

Weekly downloads
4
License
MIT
Repository
github
Last release
4 years ago

react-preloadr

Build Status Coverage Status semantic-release

A React preloader component, for displaying an indicator while performing asynchronous tasks. It includes the <Preloadr /> component, a simple redux reducer and a simple action creator for the redux reducer.

Installation

react-preloadr can be installed via npm:

$ npm i -S react-preloadr

Usage

To use react-preloadr you need to pass three rendered components and a status to it and it will render the applicable component for you. For convenience a redux reducer and action creator is included.

Action creators

We are using redux-thunk in this example for our asynchronous loading:

/reducers/products/actions.js:

import fetchProducts from 'api/products';

export const PRODUCTS_FAILED = 'PRODUCTS_FAILED';
export const PRODUCTS_REQUESTED = 'PRODUCTS_REQUESTED';
export const PRODUCTS_SUCCEEDED = 'PRODUCTS_SUCCEEDED';

export const productsFailed = payload => ({
  payload,
  type: PRODUCTS_FAILED,
});
export const productsRequested = () => ({
  type: PRODUCTS_REQUESTED,
});
export const productsSucceeded = payload => ({
  payload,
  type: PRODUCTS_SUCCEEDED,
});

export const productsRequestedAsync = () => (dispatch) => {
  dispatch(productsRequested());
  fetchProducts()
    .then(payload => dispatch(productsSucceeded(payload)))
    .catch(error => dispatch(productsFailed(error)));
};

Reducer

To build a status driven reducer, you can either roll your own (by using the three statuses exposed by react-preloadr, namely PRELOAD_STATUS_FAILED, PRELOAD_STATUS_REQUESTED and PRELOAD_STATUS_SUCCEEDED) or you can use the convenient reducer factory provided:

/reducers/products/reducer.js

import { reducer, PRELOAD_STATUS_REQUESTED } from 'react-preloadr';

import { PRODUCTS_FAILED, PRODUCTS_REQUESTED, PRODUCTS_SUCCEEDED } from './actions'

const optionalInitialState = {
  payload: [],
  status: PRELOAD_STATUS_REQUESTED,
};

export default reducer(
  PRODUCTS_FAILED,
  PRODUCTS_REQUESTED,
  PRODUCTS_SUCCEEDED,
  optionalInitialState,
);

And then combine this reducer into your application reducer:

/reducers/index.js

import { combineReducers } from 'redux';
import products from './products/reducer';

export default combineReducers({ products });

Component

After your reducer has been set up you can connect your component to the redux store to pull out the status and payload and pass it to <Preloadr />:

import PropTypes from 'prop-types';
import React from 'react';
import Preloadr, { preloadDefaultProp, preloadPropTypes } from 'react-preloadr';

import { productsRequestedAsync } from './thunks';

const Failed = ({ error }) => <p>{error.message}</p>;
const Requested = () => <p>Loading products</p>;
const ProductsList = ({ payload }) => (
  <ul>{payload.map(product => <li key={product.id}>{product.name}</li>)}</ul>
);

class Products extends React.Component {
  componentDidMount() {
    this.props.fetchProducts();
  }
  render() {
    const { error, payload, status } = this.props;
    return (
      <Preloadr
        failed={() => <Failed error={error} />}
        requested={() => <Requested />}
        status={status}
      >
        {() => <ProductsList payload={payload} />}
      </Preloadr>
    );
  }
}

Products.propTypes = {
  error: PropTypes.shape({ message: PropTypes.string }),
  payload: PropTypes.arrayOf(PropTypes.shape()),
  status: preloadDefaultProps,
};

Products.defaultProps = {
  error: {},
  payload: [],
  status: preloadDefaultProp,
};

const mapStateToProps = state => state.products;
const mapDispatchToProps = dispatch => ({ fetchProducts: dispatch(productsRequestedAsync()) });

export default connect(mapStateToProps, mapDispatchToProps)(Products);

Usage without redux

This component can also be used without Redux, to do this you will have to manually set the status as you are doing your asynchronous call.

import PropTypes from 'prop-types';
import React from 'react';
import Preloadr, {
  PRELOAD_STATUS_FAILED,
  PRELOAD_STATUS_REQUESTED,
  PRELOAD_STATUS_SUCCEEDED,
} from 'react-preloadr';

import fetchProducts from 'api/products';

const Failed = ({ error }) => <p>{error.message}</p>;
const Requested = () => <p>Loading products</p>;
const ProductsList = ({ payload }) => (
  <ul>{payload.map(product => <li key={product.id}>{product.name}</li>)}</ul>
);

class Products extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      error: {},
      payload: [],
      status: PRELOAD_STATUS_REQUESTED,
    };
  }
  componentDidMount() {
    fetchProducts()
      .then(payload => this.setState({ payload, status: PRELOAD_STATUS_SUCCEEDED }))
      .catch(error => this.setState({ error: error, status: PRELOAD_STATUS_FAILED }));
  }
  render() {
    const { error, payload, status } = this.state;
    return (
      <Preloadr
        failed={() => <Failed error={error} />}
        requested={() => <Requested />}
        status={status}
      >
        {() => <ProductsList payload={payload} />}
      </Preloadr>
    );
  }
}

export default Products;

Multiple statii

It is also possible to send multiple statii to <Preloadr />. First, if any of the statii is PRELOAD_STATUS_REQUESTED the requested component will be loaded. Secondly, if any of the statii is PRELOAD_STATUS_FAILED the failed component will be loaded (after all PRELOAD_STATUS_REQUESTED are resolved). If all statii are PRELOAD_STATUS_SUCCEEDED then the children component will be loaded:

import PropTypes from 'prop-types';
import React from 'react';
import Preloadr, { preloadDefaultProp, preloadPropTypes } from 'react-preloadr';

import { productsRequestedAsync, usersRequestedAsync } from './thunks';

const Failed = ({ error }) => <p>{error.message}</p>;
const Requested = () => <p>Loading products</p>;
const List = ({ products, users }) => (
  <div><!-- Do something with products and users --></div>
);

class Products extends React.Component {
  componentDidMount() {
    const { fetchProducts, fetchUsers } = this.props;
  }
  render() {
    const {
      products: { error: productsError, payload: productsPayload, status: productsStatus },
      users: { error: usersError, payload: usersPayload, status: usersStatus },
    } = this.props;
    return (
      <Preloadr
        failed={() => <Failed error={productsError || usersError} />}
        requested={() => <Requested />}
        status={[productsStatus, usersStatus]}
      >
        {() => <List products={productsPayload} users={usersPayload} />}
      </Preloadr>
    );
  }
}

Products.propTypes = {
  products: PropTypes.shape({
    error: PropTypes.shape({ message: PropTypes.string }),
    payload: PropTypes.arrayOf(PropTypes.shape()),
    status: preloadDefaultProps,
  }),
  users: PropTypes.shape({
    error: PropTypes.shape({ message: PropTypes.string }),
    payload: PropTypes.arrayOf(PropTypes.shape()),
    status: preloadDefaultProps,
  }),
};

Products.defaultProps = {
  products: {
    error: undefined,
    payload: [],
    status: preloadDefaultProp,
  },
  users: {
    error: undefined,
    payload: [],
    status: preloadDefaultProp,
  },
};

const mapStateToProps = state => ({
  products: state.products,
  users: state.users,
});
const mapDispatchToProps = dispatch => ({
  fetchProducts: dispatch(productsRequestedAsync()),
  fetchUsers: dispatch(usersRequestedAsync()),
});

export default connect(mapStateToProps, mapDispatchToProps)(Products);
2.0.2

4 years ago

2.0.1

4 years ago

2.0.0

5 years ago

1.2.3

5 years ago

1.2.2

5 years ago

1.2.1

6 years ago

1.2.0

6 years ago

1.1.5

6 years ago

1.1.4

6 years ago

1.1.3

6 years ago

1.1.2

6 years ago

1.1.1

6 years ago

1.1.0

6 years ago

1.0.0

7 years ago

0.2.0

7 years ago

0.1.0

7 years ago

0.0.1

7 years ago

0.0.0

7 years ago

0.0.0-semver

7 years ago