├── .babelrc ├── .editorconfig ├── .gitignore ├── .npmignore ├── .travis.yml ├── README.md ├── example ├── .babelrc ├── application.js ├── client.js ├── demo.gif ├── dist │ └── client.js ├── index.html ├── loading-50.gif ├── package.json ├── server.js └── webpack.config.js ├── lib └── index.js ├── package-lock.json ├── package.json ├── src └── index.js ├── styleguide.config.js └── webpack.config.dist.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "es2015", 4 | "react", 5 | "stage-0" 6 | ], 7 | "plugins": ["transform-class-properties", "transform-object-rest-spread"], 8 | "env": { 9 | "development": { 10 | "presets": ["react-hmre"] 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea/ 3 | node_modules/ 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | example/ 2 | .DS_Store/ 3 | .idea/ 4 | node_modules/ 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "5" 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | react-remote-image 2 | === 3 | Image component with declarative state control. Just like Relay. 4 | 5 | ![react-remote-image-view](https://github.com/StartupMakers/react-remote-image/raw/master/example/demo.gif) 6 | 7 | ## Installation 8 | 9 | ```bash 10 | npm install react-remote-image --save 11 | ``` 12 | 13 | ## Usage 14 | 15 | ```javascript 16 | import React from 'react' 17 | import ReactDOM from 'react-dom' 18 | import RemoteImage from 'react-remote-image' 19 | 20 | ReactDOM.render( 21 | ( 24 |
25 | Loading... 26 |
27 | )} 28 | alt="Meow meow cat" 29 | />, 30 | document.getElementById('app') 31 | ); 32 | ``` 33 | 34 | ## Advanced usage 35 | 36 | ```javascript 37 | import React from 'react' 38 | import ReactDOM from 'react-dom' 39 | import RemoteImage from 'react-remote-image' 40 | import Spinner from 'react-spinkit' 41 | 42 | ReactDOM.render( 43 | ( 46 | 47 | )} 48 | renderFetched={(src, image) => { 49 |
50 | 51 | 52 | {image.naturalWidth + 'x' + image.naturalHeight} 53 | 54 |
55 | }} 56 | renderFailure={(error, retry) => ( 57 | 58 | Failed to load image: {error.message}. 59 | 62 | 63 | )} 64 | alt="Meow meow cat" 65 | />, 66 | document.getElementById('app') 67 | ) 68 | ``` 69 | 70 | ## Fading 71 | 72 | ```css 73 | .image { 74 | opacity: 0; 75 | transition: 0.3s; 76 | } 77 | .image--loaded { 78 | opacity: 1; 79 | } 80 | ``` 81 | 82 | ```javascript 83 | import React, { Component } from 'react' 84 | import RemoteImage from 'react-remote-image' 85 | 86 | class CatView extends Component { 87 | 88 | constructor(props, context) { 89 | super(props, context) 90 | this.state = { 91 | isLoaded: false, 92 | } 93 | } 94 | 95 | render() { 96 | const { isLoaded } = this.state 97 | const imageClass = 98 | isLoaded ? 99 | 'image image-loaded' : 100 | 'image' 101 | return ( 102 | ( 105 |
106 | Loading... 107 |
108 | )} 109 | renderFetched={({ src }) => ( 110 | 111 | )} 112 | onLoad={() => this.setState({ 113 | isLoaded: true, 114 | })} 115 | alt="Meow meow cat" 116 | /> 117 | ) 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /example/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-0", "react"], 3 | "plugins": ["react-hot-loader/babel"] 4 | } 5 | -------------------------------------------------------------------------------- /example/application.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import ReactDOM from 'react-dom' 3 | import RemoteImage from '../src' 4 | 5 | const imageURLs = [ 6 | 'http://image.shutterstock.com/z/stock-photo-cat-and-her-kitten-48629962.jpg', 7 | 'http://image.shutterstock.com/z/stock-photo-father-and-teenager-daughter-with-head-in-his-shoulder-sharing-something-funny-in-a-mobile-phone-138701705.jpg', 8 | 'http://image.shutterstock.com/z/stock-photo-portrait-of-business-team-outside-office-141032806.jpg', 9 | 'http://image.shutterstock.com/z/stock-photo-chinese-new-year-s-food-258919031.jpg', 10 | ] 11 | const imageStyle = { 12 | display: 'inline-block', 13 | position: 'relative', 14 | width: 300, 15 | maxHeight: 250, 16 | padding: 15, 17 | } 18 | 19 | const loaderStyle = { 20 | display: 'inline-block', 21 | position: 'relative', 22 | width: 300, 23 | height: 220, 24 | padding: 15, 25 | } 26 | 27 | const loaderSpinnerStyle = { 28 | width: 148, 29 | height: 148, 30 | position: 'absolute', 31 | left: '50%', 32 | top: '50%', 33 | marginTop: -74, 34 | marginLeft: -74 35 | } 36 | 37 | const labelStyle = { 38 | position: 'absolute', 39 | left: 0, 40 | right: 0, 41 | top: 0, 42 | padding: 10, 43 | background: 'rgba(255,255,255,.7)', 44 | fontWeight: 'bold', 45 | } 46 | 47 | export default class Application extends Component { 48 | 49 | constructor(props, context) { 50 | super(props, context) 51 | this.state = { 52 | isLoaded: {}, 53 | isVisible: {}, 54 | } 55 | } 56 | 57 | render() { 58 | return ( 59 |
60 |

Simple

61 | {imageURLs.map(URL => ( 62 | ( 64 |
65 | 69 |
70 | )} 71 | src={URL} 72 | style={imageStyle} 73 | /> 74 | ))} 75 |

Advanced

76 | {imageURLs.map(URL => ( 77 | ( 79 |
80 | 84 |
85 | )} 86 | renderFetched={({ src, image }) => ( 87 |
88 | 89 | 90 | {image.naturalWidth + 'x' + image.naturalHeight} 91 | 92 |
93 | )} 94 | src={URL} 95 | style={imageStyle} 96 | /> 97 | ))} 98 |
99 | ) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /example/client.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import ReactDOM from 'react-dom' 3 | import Application from './application' 4 | 5 | ReactDOM.render( 6 | , 7 | document.getElementById('root') 8 | ) 9 | -------------------------------------------------------------------------------- /example/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axept/react-remote-image/87f1fd8149383e25d8bb0c9815d82def5f7da322/example/demo.gif -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | react-remote-image example 4 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /example/loading-50.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axept/react-remote-image/87f1fd8149383e25d8bb0c9815d82def5f7da322/example/loading-50.gif -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "0.0.0", 4 | "description": "Example for react-remote-image", 5 | "main": "server.js", 6 | "scripts": { 7 | "clean": "rimraf dist", 8 | "build:server": "webpack", 9 | "build": "npm run clean && npm run build:server", 10 | "prepublish": "npm run build", 11 | "styleguide-server": "styleguidist server", 12 | "styleguide-build": "styleguidist build" 13 | }, 14 | "author": "", 15 | "license": "MIT", 16 | "repository": { 17 | "type": "git", 18 | "url": "https://github.com/StartupMakers/react-remote-image.git" 19 | }, 20 | "bugs": "https://github.com/StartupMakers/react-remote-image/issues", 21 | "dependencies": { 22 | "react": "^15.0.2" 23 | }, 24 | "devDependencies": { 25 | "babel-cli": "^6.3.17", 26 | "babel-core": "^6.3.17", 27 | "babel-loader": "^6.2.0", 28 | "babel-plugin-transform-class-properties": "^6.10.2", 29 | "babel-plugin-transform-object-rest-spread": "^6.8.0", 30 | "babel-preset-es2015": "^6.5.0", 31 | "babel-preset-es2015-loose": "^6.1.3", 32 | "babel-preset-react": "^6.5.0", 33 | "babel-preset-react-hmre": "^1.1.1", 34 | "babel-preset-stage-0": "^6.3.13", 35 | "cross-env": "^1.0.7", 36 | "file-loader": "^0.9.0", 37 | "node-libs-browser": "^0.5.2", 38 | "react-dom": "^15.0.2", 39 | "react-hot-loader": "^3.0.0-beta.1", 40 | "react-spinkit": "^1.1.7", 41 | "react-styleguidist": "^2.3.1", 42 | "rimraf": "^2.3.4", 43 | "webpack": "^1.9.11", 44 | "webpack-dev-server": "^1.9.0" 45 | }, 46 | "peerDependencies": { 47 | "react": "^0.14.0 || ^15.0.0-0" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /example/server.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var WebpackDevServer = require('webpack-dev-server'); 3 | var config = require('./webpack.config'); 4 | 5 | new WebpackDevServer(webpack(config), { 6 | publicPath: config.output.publicPath, 7 | hot: true, 8 | historyApiFallback: true, 9 | stats: { 10 | colors: true 11 | } 12 | }).listen(3000, 'localhost', function (err) { 13 | if (err) { 14 | console.log(err); 15 | } 16 | console.log('Listening at localhost:3000...'); 17 | }); 18 | -------------------------------------------------------------------------------- /example/webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var webpack = require('webpack'); 3 | 4 | module.exports = { 5 | devtool: 'cheap-eval-source-map', 6 | entry: [ 7 | 'webpack-dev-server/client?http://localhost:3000', 8 | 'webpack/hot/only-dev-server', 9 | 'react-hot-loader/patch', 10 | './client.js' 11 | ], 12 | output: { 13 | path: path.join(__dirname, 'dist'), 14 | filename: 'client.js', 15 | publicPath: '/assets' 16 | }, 17 | plugins: [ 18 | new webpack.HotModuleReplacementPlugin() 19 | ], 20 | module: { 21 | loaders: [{ 22 | test: /\.js$/, 23 | exclude: /node_modules/, 24 | loader: 'babel-loader' 25 | }, { 26 | test: /\.gif$/, 27 | loader: 'file-loader' 28 | }] 29 | }, 30 | }; 31 | 32 | if (process.env['NODE_ENV'] === 'production') { 33 | module.exports.plugins.push( 34 | new webpack.optimize.UglifyJsPlugin({ 35 | compressor: { 36 | warnings: false 37 | } 38 | }) 39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /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 _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; } 22 | 23 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 24 | 25 | 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; } 26 | 27 | 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; } 28 | 29 | function getURLWithSalt(URL) { 30 | if (typeof URL === 'string' && URL !== '' && URL.indexOf('data:') !== 0) { 31 | var salt = Math.floor((Date.now() + Math.random()) * 100); 32 | if (URL.indexOf('?') >= 0) { 33 | return URL + '&_=' + salt; 34 | } 35 | return URL + '?_=' + salt; 36 | } 37 | return URL; 38 | } 39 | 40 | var RemoteImage = function (_Component) { 41 | _inherits(RemoteImage, _Component); 42 | 43 | function RemoteImage(props, context) { 44 | _classCallCheck(this, RemoteImage); 45 | 46 | var _this = _possibleConstructorReturn(this, Object.getPrototypeOf(RemoteImage).call(this, props, context)); 47 | 48 | var src = props.forceFetch ? getURLWithSalt(props.src) : props.src; 49 | var isEmpty = typeof src !== 'string'; 50 | // Warning if source is empty, only for develop 51 | if (process.env['NODE_ENV'] !== 'production') { 52 | if (isEmpty) { 53 | console.warn('Image src is not a string', src); 54 | } 55 | } 56 | _this.state = _extends({ 57 | src: src, 58 | isLoading: true, 59 | isFailed: false, 60 | isEmpty: typeof src !== 'string' 61 | }, props); 62 | _this._handleRetry = _this.fetchImage.bind(_this); 63 | _this.onLoad = _this.onLoad.bind(_this); 64 | _this.onError = _this.onError.bind(_this); 65 | return _this; 66 | } 67 | 68 | _createClass(RemoteImage, [{ 69 | key: 'componentDidMount', 70 | value: function componentDidMount() { 71 | this.fetchImage(); 72 | } 73 | }, { 74 | key: 'componentWillUnmount', 75 | value: function componentWillUnmount() { 76 | this.clearEvents(); 77 | } 78 | }, { 79 | key: 'componentWillReceiveProps', 80 | value: function componentWillReceiveProps(_ref) { 81 | var _this2 = this; 82 | 83 | var src = _ref.src; 84 | var forceFetch = _ref.forceFetch; 85 | 86 | var finalSrc = forceFetch ? getURLWithSalt(src) : src; 87 | if (finalSrc !== this.state.src) { 88 | this.clearEvents(); 89 | this.setState({ 90 | src: src, 91 | isLoading: true, 92 | isFailed: false, 93 | isEmpty: typeof src !== 'string' 94 | }, function () { 95 | return _this2.fetchImage(); 96 | }); 97 | } 98 | } 99 | }, { 100 | key: 'onLoad', 101 | value: function onLoad() { 102 | this.setState({ 103 | isLoading: false, 104 | isFailed: false 105 | }, this.props.onLoad); 106 | } 107 | }, { 108 | key: 'onError', 109 | value: function onError() { 110 | this.clearEvents(); 111 | this.setState({ 112 | isLoading: false, 113 | isFailed: true 114 | }, this.props.onError); 115 | } 116 | }, { 117 | key: 'clearEvents', 118 | value: function clearEvents() { 119 | if (this.image) { 120 | this.image.removeEventListener('load', this.onLoad); 121 | this.image.removeEventListener('error', this.onError); 122 | } 123 | } 124 | }, { 125 | key: 'fetchImage', 126 | value: function fetchImage() { 127 | var _state = this.state; 128 | var src = _state.src; 129 | var isEmpty = _state.isEmpty; 130 | 131 | if (!isEmpty) { 132 | this.image = new Image(); 133 | this.image.addEventListener('load', this.onLoad); 134 | this.image.addEventListener('error', this.onError); 135 | this.image.src = src; 136 | } 137 | } 138 | }, { 139 | key: 'render', 140 | value: function render() { 141 | var image = this.image; 142 | var state = this.state; 143 | var props = this.props; 144 | var isLoading = state.isLoading; 145 | var isFailed = state.isFailed; 146 | var isEmpty = state.isEmpty; 147 | var src = state.src; 148 | var renderLoading = props.renderLoading; 149 | var renderFetched = props.renderFetched; 150 | var renderFailure = props.renderFailure; 151 | 152 | var renderProps = _objectWithoutProperties(props, ['renderLoading', 'renderFetched', 'renderFailure']); 153 | 154 | var error = new Error('Unable to load image'); 155 | if (isLoading && !isEmpty) { 156 | return renderLoading({ src: src, image: image }); 157 | } else if (isFailed || isEmpty) { 158 | return renderFailure(error, this._handleRetry); 159 | } else if (renderFetched) { 160 | return renderFetched({ src: src, image: image }); 161 | } 162 | return _react2.default.createElement('img', _extends({}, renderProps, { src: src })); 163 | } 164 | }]); 165 | 166 | return RemoteImage; 167 | }(_react.Component); 168 | 169 | RemoteImage.propTypes = { 170 | src: _propTypes2.default.string, 171 | forceFetch: _propTypes2.default.bool, 172 | renderLoading: _propTypes2.default.func.isRequired, 173 | renderFetched: _propTypes2.default.func.isRequired, 174 | renderFailure: _propTypes2.default.func.isRequired, 175 | onLoad: _propTypes2.default.func, 176 | onError: _propTypes2.default.func 177 | }; 178 | exports.default = RemoteImage; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-remote-image", 3 | "version": "0.4.0", 4 | "description": "Image component with declarative state control. Just like Relay.", 5 | "main": "lib/index.js", 6 | "scripts": { 7 | "clean": "rimraf lib dist", 8 | "build:lib": "NODE_ENV=production babel src --out-dir lib", 9 | "build:umd": "cross-env NODE_ENV=development webpack src/index.js dist/remote-image.js", 10 | "build:umd:min": "cross-env NODE_ENV=production webpack src/index.js dist/remote-image.min.js", 11 | "build": "npm run clean && npm run build:lib", 12 | "prepublish": "npm run build", 13 | "styleguide-server": "styleguidist server", 14 | "styleguide-build": "styleguidist build" 15 | }, 16 | "author": "Axept ", 17 | "license": "MIT", 18 | "repository": { 19 | "type": "git", 20 | "url": "https://github.com/axept/react-remote-image.git" 21 | }, 22 | "bugs": "https://github.com/axept/react-remote-image/issues", 23 | "peerDependencies": { 24 | "react": "^15.0.0 || ^16.0.0" 25 | }, 26 | "devDependencies": { 27 | "babel-core": "^6.7.4", 28 | "babel-loader": "^6.2.4", 29 | "babel-plugin-transform-class-properties": "^6.10.2", 30 | "babel-plugin-transform-object-rest-spread": "^6.8.0", 31 | "babel-preset-es2015": "^6.5.0", 32 | "babel-preset-react": "^6.5.0", 33 | "babel-preset-react-hmre": "^1.1.1", 34 | "cross-env": "^1.0.7", 35 | "react-dom": "^0.14.8", 36 | "react-hot-loader": "^1.3.0", 37 | "react-styleguidist": "^2.3.1", 38 | "rimraf": "^2.3.4", 39 | "webpack": "^1.12.14", 40 | "webpack-dev-server": "^1.14.1" 41 | }, 42 | "dependencies": { 43 | "prop-types": "^15.6.0" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | 4 | function getURLWithSalt(URL) { 5 | if (typeof URL === 'string' && URL !=='' && URL.indexOf('data:') !== 0) { 6 | const salt = Math.floor((Date.now() + Math.random()) * 100) 7 | if (URL.indexOf('?') >= 0) { 8 | return URL + '&_=' + salt 9 | } 10 | return URL + '?_=' + salt 11 | } 12 | return URL 13 | } 14 | 15 | export default class RemoteImage extends Component { 16 | 17 | static propTypes = { 18 | src: PropTypes.string, 19 | forceFetch: PropTypes.bool, 20 | renderLoading: PropTypes.func.isRequired, 21 | renderFetched: PropTypes.func.isRequired, 22 | renderFailure: PropTypes.func.isRequired, 23 | onLoad: PropTypes.func, 24 | onError: PropTypes.func, 25 | } 26 | 27 | constructor(props, context) { 28 | super(props, context) 29 | const src = props.forceFetch ? getURLWithSalt(props.src) : props.src 30 | const isEmpty = typeof src !== 'string' 31 | // Warning if source is empty, only for develop 32 | if (process.env['NODE_ENV'] !== 'production') { 33 | if (isEmpty) { 34 | console.warn('Image src is not a string', src) 35 | } 36 | } 37 | this.state = { 38 | src, 39 | isLoading: true, 40 | isFailed: false, 41 | isEmpty: typeof src !== 'string', 42 | ...props, 43 | } 44 | this._handleRetry = this.fetchImage.bind(this) 45 | this.onLoad = this.onLoad.bind(this) 46 | this.onError = this.onError.bind(this) 47 | } 48 | 49 | componentDidMount() { 50 | this.fetchImage() 51 | } 52 | 53 | componentWillUnmount() { 54 | this.clearEvents() 55 | } 56 | 57 | componentWillReceiveProps({ src, forceFetch }) { 58 | const finalSrc = forceFetch ? getURLWithSalt(src) : src 59 | if (finalSrc !== this.state.src) { 60 | this.clearEvents() 61 | this.setState({ 62 | src, 63 | isLoading: true, 64 | isFailed: false, 65 | isEmpty: typeof src !== 'string', 66 | }, () => this.fetchImage()) 67 | } 68 | } 69 | 70 | onLoad() { 71 | this.setState({ 72 | isLoading: false, 73 | isFailed: false, 74 | }, this.props.onLoad) 75 | } 76 | 77 | onError() { 78 | this.clearEvents() 79 | this.setState({ 80 | isLoading: false, 81 | isFailed: true, 82 | }, this.props.onError) 83 | } 84 | 85 | clearEvents() { 86 | if (this.image) { 87 | this.image.removeEventListener('load', this.onLoad) 88 | this.image.removeEventListener('error', this.onError) 89 | } 90 | } 91 | 92 | fetchImage() { 93 | const { src, isEmpty } = this.state 94 | if (!isEmpty) { 95 | this.image = new Image() 96 | this.image.addEventListener('load', this.onLoad) 97 | this.image.addEventListener('error', this.onError) 98 | this.image.src = src 99 | } 100 | } 101 | 102 | render() { 103 | const { image, state, props } = this 104 | const { 105 | isLoading, 106 | isFailed, 107 | isEmpty, 108 | src, 109 | } = state 110 | const { 111 | renderLoading, 112 | renderFetched, 113 | renderFailure, 114 | ...renderProps, 115 | } = props 116 | const error = new Error('Unable to load image') 117 | if (isLoading && !isEmpty) { 118 | return renderLoading({ src, image }) 119 | } else if (isFailed || isEmpty) { 120 | return renderFailure(error, this._handleRetry) 121 | } else if (renderFetched) { 122 | return renderFetched({ src, image }) 123 | } 124 | return ( 125 | 126 | ) 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /styleguide.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | title: 'My Great Style Guide', 3 | components: './src/**/*.js', 4 | }; 5 | -------------------------------------------------------------------------------- /webpack.config.dist.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var webpack = require('webpack'); 4 | 5 | var config = { 6 | output: { 7 | library: 'remote-image', 8 | libraryTarget: 'umd' 9 | }, 10 | externals: { 11 | root: 'React', 12 | commonjs2: 'react', 13 | commonjs: 'react', 14 | amd: 'react' 15 | }, 16 | module: { 17 | loaders: [ 18 | { test: /\.jsx?$/, loader: 'babel-loader', exclude: /node_modules/ } 19 | ] 20 | }, 21 | resolve: { 22 | extensions: ['', '.js', '.jsx'] 23 | } 24 | }; 25 | 26 | if (process.env['NODE_ENV'] === 'production') { 27 | config.plugins = [] 28 | config.plugins.push( 29 | new webpack.optimize.UglifyJsPlugin({ 30 | compressor: { 31 | pure_getters: true, 32 | unsafe: true, 33 | unsafe_comps: true, 34 | screw_ie8: true, 35 | warnings: false 36 | } 37 | }) 38 | ); 39 | } 40 | 41 | module.exports = config 42 | --------------------------------------------------------------------------------