react-preloadr v2.0.2
react-preloadr
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);
4 years ago
4 years ago
5 years ago
5 years ago
5 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago