├── .babelrc
├── .eslintrc
├── .gitignore
├── .npmignore
├── .watchmanconfig
├── LICENSE
├── README.md
├── example
├── dist.js
├── example.js
├── index.html
└── volunteer.png
├── index.d.ts
├── lib
└── index.js
├── package-lock.json
├── package.json
├── src
└── index.js
└── webpack.config.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "react",
4 | "es2015",
5 | "stage-0"
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "es6": true,
5 | "node": true
6 | },
7 |
8 | "parser": "babel-eslint",
9 | "plugins": [
10 | "react"
11 | ],
12 |
13 | "ecmaFeatures": {
14 | "modules": true,
15 | "jsx": true
16 | },
17 |
18 | "rules": {
19 | "strict": [2, "global"],
20 |
21 | "comma-dangle": [2, "always-multiline"],
22 | "no-floating-decimal": 2,
23 | "no-use-before-define": [2, "nofunc"],
24 |
25 | "indent": [2, 2, {"indentSwitchCase": true}],
26 | "brace-style": [2, "1tbs", {"allowSingleLine": true}],
27 | "comma-style": [2, "last"],
28 | "consistent-this": [2, "self"],
29 | "consistent-return": 0,
30 | "curly": [2, "multi-line"],
31 | "new-cap": [2, {"newIsCap": true, "capIsNew": false}],
32 | "object-curly-spacing": [2, "never"],
33 | "quote-props": [2, "as-needed"],
34 | "quotes": [2, "single", "avoid-escape"],
35 | "space-after-function-name": [2, "never"],
36 | "space-after-keywords": [2, "always"],
37 | "space-before-blocks": [2, "always"],
38 | "space-in-brackets": [2, "never"],
39 | "space-in-parens": [2, "never"],
40 | "spaced-line-comment": [2, "always"],
41 | "yoda": [2, "never"],
42 |
43 | "no-var": 2,
44 | "generator-star-spacing": [2, "before"],
45 |
46 | "valid-jsdoc": [2, {
47 | "prefer": {
48 | "return": "returns"
49 | }
50 | }],
51 |
52 | "react/jsx-boolean-value": [2, "never"],
53 | "react/jsx-no-undef": 2,
54 | "react/jsx-quotes": [2, "double", "avoid-escape"],
55 | "react/jsx-uses-react": 1,
56 | "react/jsx-uses-vars": 1,
57 | "react/no-did-mount-set-state": 1,
58 | "react/no-did-update-set-state": 1,
59 | "react/no-unknown-property": 1,
60 | "react/prop-types": 2,
61 | "react/self-closing-comp": 2,
62 | "react/sort-comp": 2,
63 | "react/wrap-multilines": 2
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | node_modules/
3 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | .eslintrc
3 | .babelrc
4 | src/
5 | example/
6 |
--------------------------------------------------------------------------------
/.watchmanconfig:
--------------------------------------------------------------------------------
1 | {
2 | "ignore_dirs": [
3 | ".git",
4 | "node_modules"
5 | ]
6 | }
7 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2017 DeedMob -- MIT License
2 |
3 | Copyright 2014 HZDG
4 | http://hzdg.com
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining
7 | a copy of this software and associated documentation files (the
8 | "Software"), to deal in the Software without restriction, including
9 | without limitation the rights to use, copy, modify, merge, publish,
10 | distribute, sublicense, and/or sell copies of the Software, and to
11 | permit persons to whom the Software is furnished to do so, subject to
12 | the following conditions:
13 |
14 | The above copyright notice and this permission notice shall be
15 | included in all copies or substantial portions of the Software.
16 |
17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | react-load-image
2 | =================
3 | 
4 | 
5 | 
6 |
7 | This is a fork of https://github.com/hzdg/react-imageloader, however many design changes were made and deprecations fixed so it warranted its own repo/package.
8 |
9 | This React component allows you to display
10 | content while waiting for the image to load, as well as by showing alternate
11 | content if the image fails to load.
12 |
13 | Installing
14 | -----
15 | `npm i react-load-image`
16 |
17 |
18 | Usage
19 | -----
20 |
21 | ```js
22 | import React from 'react';
23 | import ImageLoader from 'react-load-image';
24 |
25 | function Preloader(props) {
26 | return
;
27 | }
28 |
29 | React.render((
30 |
33 |
34 | Error!
35 |
36 |
37 | ), document.body);
38 |
39 | ```
40 |
41 |
42 | Props
43 | -----
44 |
45 | Name | Type | Description
46 | ------------|----------|------------
47 | `onError` | function | An optional handler for the [error] event.
48 | `onLoad` | function | An optional handler for the [load] event.
49 | `src` | string | The URL of the image to be loaded, will be passed as the src prop to your first child provided. If you want to use it as a background image, make your first child a react component like Name = (props) =>
and do
50 | `srcSet` | string | An optional value for the srcset attribute of the img
51 |
52 |
53 | Children
54 | --------
55 | The first child of `ImageLoader` will be rendered when the image is successfully loaded. The `src` prop will be passed to it.
56 |
57 | The second child of `ImageLoader` will be rendered when the image load fails.
58 |
59 | The third child of `ImageLoader` will be rendered when the image is in the process of loading
60 |
61 |
62 | Avoiding duplication Example
63 | -------
64 | ```js
65 | import React from 'react';
66 | import ImageLoader from 'react-load-image';
67 | import ImageError from './ImageError';
68 | import ImageLoading from './ImageLoading';
69 |
70 | const Image = (props) =>
71 |
72 | {this.props.children[0]}
73 |
74 |
75 |
76 |
77 | export default Image;
78 | ```
79 | -----
80 | ```js
81 | import Image from './Image';
82 |
83 | ...
84 |
85 |
86 |
87 | ...
88 |
89 | ```
90 |
91 |
92 | Using it as a backgroundImage
93 | -----
94 | ```js
95 | import React from 'react';
96 |
97 | const BackgroundImage = ({src, style = {}, ...props} = {}) =>
98 |
99 |
100 | export default BackgroundImage;
101 | ```
102 |
103 | ```js
104 |
105 |
106 |
107 | ```
108 |
--------------------------------------------------------------------------------
/example/example.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from 'react-dom';
3 | import ImageLoader from '../lib';
4 |
5 | const Preloader = () => SPINNNNER
6 |
7 | render(
8 |
9 |
10 | Error!
11 |
12 | ,
13 | document.getElementById('app')
14 | );
15 |
--------------------------------------------------------------------------------
/example/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | react-load-image demo
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/example/volunteer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DeedMob/react-load-image/0e70a6f6489369a4126210671a2f0541f93411db/example/volunteer.png
--------------------------------------------------------------------------------
/index.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | export = ImageLoader;
4 |
5 | interface ImageLoaderProps {
6 | /**
7 | * The URL of the image to be loaded, will be passed as the src prop to your
8 | * first child provided. If you want to use it as a background image, make
9 | * your first child a react component like
10 | * `(props) => `
11 | */
12 | src: string;
13 | /**
14 | * The first child of `ImageLoader` will be rendered when the image is successfully loaded. The `src` prop will be passed to it.
15 | * The second child of `ImageLoader` will be rendered when the image load fails.
16 | * The third child of `ImageLoader` will be rendered when the image is in the process of loading
17 | */
18 | children: React.ReactNode;
19 | /** An optional value for the srcset attribute of the img */
20 | srcSet?: string;
21 | /** An optional handler for the [load] event. */
22 | onLoad?: (this: GlobalEventHandlers, ev: Event) => any;
23 | /** An optional handler for the [error] event. */
24 | onError?: OnErrorEventHandler;
25 | /** Wrapper container class name */
26 | className?: string;
27 | /** Optional attributes of wrapper container */
28 | wrapperProps?: Record;
29 | }
30 |
31 | declare var ImageLoader: React.ComponentType;
32 |
--------------------------------------------------------------------------------
/lib/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 |
7 | 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; };
8 |
9 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
10 |
11 | var _react = require('react');
12 |
13 | var _react2 = _interopRequireDefault(_react);
14 |
15 | var _propTypes = require('prop-types');
16 |
17 | var _propTypes2 = _interopRequireDefault(_propTypes);
18 |
19 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
20 |
21 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
22 |
23 | function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
24 |
25 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
26 |
27 | var Status = {
28 | PENDING: 'pending',
29 | LOADING: 'loading',
30 | LOADED: 'loaded',
31 | FAILED: 'failed'
32 | };
33 |
34 | var ImageLoader = function (_React$Component) {
35 | _inherits(ImageLoader, _React$Component);
36 |
37 | function ImageLoader(props) {
38 | _classCallCheck(this, ImageLoader);
39 |
40 | var _this = _possibleConstructorReturn(this, (ImageLoader.__proto__ || Object.getPrototypeOf(ImageLoader)).call(this, props));
41 |
42 | _this.state = { status: props.src ? Status.LOADING : Status.PENDING };
43 | if (_react2.default.Children.count(props.children) !== 3) console.error('wrong # of children provided to ImageLoader');
44 | return _this;
45 | }
46 |
47 | _createClass(ImageLoader, [{
48 | key: 'componentDidMount',
49 | value: function componentDidMount() {
50 | if (this.state.status === Status.LOADING) {
51 | this.createLoader();
52 | }
53 | }
54 | }, {
55 | key: 'componentWillReceiveProps',
56 | value: function componentWillReceiveProps(nextProps) {
57 | if (this.props.src !== nextProps.src) {
58 | this.setState({
59 | status: nextProps.src ? Status.LOADING : Status.PENDING
60 | });
61 | }
62 | }
63 | }, {
64 | key: 'componentDidUpdate',
65 | value: function componentDidUpdate() {
66 | if (this.state.status === Status.LOADING && !this.img) {
67 | this.createLoader();
68 | }
69 | }
70 | }, {
71 | key: 'componentWillUnmount',
72 | value: function componentWillUnmount() {
73 | this.destroyLoader();
74 | }
75 | }, {
76 | key: 'createLoader',
77 | value: function createLoader() {
78 | this.destroyLoader(); // We can only have one loader at a time.
79 |
80 | var img = new Image();
81 | img.onload = this.handleLoad.bind(this);
82 | img.onerror = this.handleError.bind(this);
83 | img.src = this.props.src;
84 |
85 | // if srcSet is not passed in then use src for srcset
86 | // Setting srcset to a non-string is a bad idea. E.g. img.srcset = undefined actually sets srcset to the string "undefined", causing a load failure)
87 | img.srcset = this.props.srcSet || this.props.src;
88 | this.img = img;
89 | }
90 | }, {
91 | key: 'destroyLoader',
92 | value: function destroyLoader() {
93 | if (this.img) {
94 | this.img.onload = null;
95 | this.img.onerror = null;
96 | this.img = null;
97 | }
98 | }
99 | }, {
100 | key: 'handleLoad',
101 | value: function handleLoad(event) {
102 | this.destroyLoader();
103 | this.setState({ status: Status.LOADED });
104 |
105 | if (this.props.onLoad) this.props.onLoad(event);
106 | }
107 | }, {
108 | key: 'handleError',
109 | value: function handleError(error) {
110 | this.destroyLoader();
111 | this.setState({ status: Status.FAILED });
112 |
113 | if (this.props.onError) this.props.onError(error);
114 | }
115 | }, {
116 | key: 'getClassName',
117 | value: function getClassName() {
118 | var className = 'imageloader imageloader-' + this.state.status;
119 | if (this.props.className) className = className + ' ' + this.props.className;
120 | return className;
121 | }
122 | }, {
123 | key: 'render',
124 | value: function render() {
125 | var _props = this.props,
126 | src = _props.src,
127 | srcSet = _props.srcSet,
128 | onLoad = _props.onLoad,
129 | onError = _props.onError,
130 | wrapperProps = _props.wrapperProps,
131 | children = _props.children;
132 |
133 | var childrenArray = _react2.default.Children.toArray(children);
134 |
135 | return _react2.default.createElement(
136 | 'div',
137 | _extends({}, wrapperProps, { className: this.getClassName() }),
138 | this.state.status === Status.LOADED && _react2.default.cloneElement(childrenArray[0], { src: src, srcSet: srcSet }),
139 | this.state.status === Status.FAILED && childrenArray[1],
140 | (this.state.status === Status.LOADING || this.state.status === Status.PENDING) && childrenArray[2]
141 | );
142 | }
143 | }]);
144 |
145 | return ImageLoader;
146 | }(_react2.default.Component);
147 |
148 | ImageLoader.propTypes = {
149 | src: _propTypes2.default.string.isRequired,
150 | srcSet: _propTypes2.default.string,
151 | onLoad: _propTypes2.default.func,
152 | onError: _propTypes2.default.func,
153 | children: _propTypes2.default.arrayOf(_propTypes2.default.node)
154 | // Allow any extras
155 | };
156 | exports.default = ImageLoader;
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-load-image",
3 | "version": "0.1.7",
4 | "description": "A React component for showing placeholder content during image loading",
5 | "main": "./lib/index.js",
6 | "scripts": {
7 | "build:example": "webpack example/example.js example/dist.js",
8 | "build:commonjs": "babel src --out-dir lib",
9 | "build": "npm run build:commonjs",
10 | "prepublish": "npm run build",
11 | "watch": "onchange 'src/**/*.js' -- npm run build"
12 | },
13 | "pre-commit": [
14 | "build"
15 | ],
16 | "repository": {
17 | "type": "git",
18 | "url": "git://github.com/DeedMob/react-load-image.git"
19 | },
20 | "keywords": [
21 | "react-component",
22 | "react",
23 | "loader",
24 | "component"
25 | ],
26 | "author": "David Furlong ",
27 | "license": "MIT",
28 | "bugs": {
29 | "url": "https://github.com/DeedMob/react-load-image/issues"
30 | },
31 | "homepage": "https://github.com/DeedMob/react-load-image",
32 | "devDependencies": {
33 | "babel-cli": "^6.24.0",
34 | "babel-core": "^6.25.0",
35 | "babel-eslint": "^3.1.16",
36 | "babel-loader": "^7",
37 | "babel-preset-es2015": "^6.24.1",
38 | "babel-preset-react": "^6.24.1",
39 | "babel-preset-stage-0": "^6.24.1",
40 | "babel-runtime": "^5.5.8",
41 | "chai": "^3.0.0",
42 | "eslint": "^0.23.0",
43 | "eslint-plugin-react": "^2.5.2",
44 | "express": "^4.12.4",
45 | "git-release-notes": "0.0.2",
46 | "mocha": "^2.2.5",
47 | "mversion": "^1.10.0",
48 | "node-libs-browser": "^0.5.2",
49 | "onchange": "^3.2.1",
50 | "opn": "^2.0.1",
51 | "prop-types": "^15.6.0",
52 | "react": "^16.0.0",
53 | "react-dom": "^16.0.0",
54 | "webpack": "^3.3.0",
55 | "webpack-dev-middleware": "^1.0.11",
56 | "yargs": "^3.11.0"
57 | },
58 | "peerDependencies": {
59 | "prop-types": "^15.6.0",
60 | "react": "^16.0.0"
61 | },
62 | "dependencies": {}
63 | }
64 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | const Status = {
5 | PENDING: 'pending',
6 | LOADING: 'loading',
7 | LOADED: 'loaded',
8 | FAILED: 'failed',
9 | };
10 |
11 | export default class ImageLoader extends React.Component {
12 | static propTypes = {
13 | src: PropTypes.string.isRequired,
14 | srcSet: PropTypes.string,
15 | onLoad: PropTypes.func,
16 | onError: PropTypes.func,
17 | children: PropTypes.arrayOf(PropTypes.node),
18 | // Allow any extras
19 | };
20 |
21 | constructor(props) {
22 | super(props);
23 | this.state = {status: props.src ? Status.LOADING : Status.PENDING};
24 | if(React.Children.count(props.children) !== 3)
25 | console.error('wrong # of children provided to ImageLoader')
26 | }
27 |
28 | componentDidMount() {
29 | if (this.state.status === Status.LOADING) {
30 | this.createLoader();
31 | }
32 | }
33 |
34 | componentWillReceiveProps(nextProps) {
35 | if (this.props.src !== nextProps.src) {
36 | this.setState({
37 | status: nextProps.src ? Status.LOADING : Status.PENDING,
38 | });
39 | }
40 | }
41 |
42 | componentDidUpdate() {
43 | if (this.state.status === Status.LOADING && !this.img) {
44 | this.createLoader();
45 | }
46 | }
47 |
48 | componentWillUnmount() {
49 | this.destroyLoader();
50 | }
51 |
52 | createLoader() {
53 | this.destroyLoader(); // We can only have one loader at a time.
54 |
55 | const img = new Image();
56 | img.onload = ::this.handleLoad;
57 | img.onerror = ::this.handleError;
58 | img.src = this.props.src;
59 |
60 | // if srcSet is not passed in then use src for srcset
61 | // Setting srcset to a non-string is a bad idea. E.g. img.srcset = undefined actually sets srcset to the string "undefined", causing a load failure)
62 | img.srcset = this.props.srcSet || this.props.src;
63 | this.img = img;
64 | }
65 |
66 | destroyLoader() {
67 | if (this.img) {
68 | this.img.onload = null;
69 | this.img.onerror = null;
70 | this.img = null;
71 | }
72 | }
73 |
74 | handleLoad(event) {
75 | this.destroyLoader();
76 | this.setState({status: Status.LOADED});
77 |
78 | if (this.props.onLoad) this.props.onLoad(event);
79 | }
80 |
81 | handleError(error) {
82 | this.destroyLoader();
83 | this.setState({status: Status.FAILED});
84 |
85 | if (this.props.onError) this.props.onError(error);
86 | }
87 |
88 | getClassName() {
89 | let className = `imageloader imageloader-${this.state.status}`;
90 | if (this.props.className) className = `${className} ${this.props.className}`;
91 | return className;
92 | }
93 |
94 |
95 | render() {
96 | const { src, srcSet, onLoad, onError, wrapperProps, children } = this.props;
97 | const childrenArray = React.Children.toArray(children);
98 |
99 | return (
100 |
101 | {this.state.status === Status.LOADED &&
102 | React.cloneElement(childrenArray[0], { src, srcSet })
103 | }
104 | {this.state.status === Status.FAILED &&
105 | childrenArray[1]
106 | }
107 | {(this.state.status === Status.LOADING || this.state.status === Status.PENDING) &&
108 | childrenArray[2]
109 | }
110 |
111 | )
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const webpack = require('webpack');
3 |
4 | module.exports = {
5 | module: {
6 | rules: [
7 | {
8 | test: /\.js$/,
9 | exclude: /node_modules/,
10 | use: 'babel-loader'
11 | },
12 | ],
13 | },
14 | };
15 |
--------------------------------------------------------------------------------