Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cannot load images from placeholder service after adding react-hot-loader 3.0.0-beta.2 #423

Closed
vamsiampolu opened this issue Nov 22, 2016 · 9 comments

Comments

@vamsiampolu
Copy link

There is an error in my console that looks like this:

warning.js?8a56:36 Warning: setState(...): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op.

This is my Image component:

import React, { Component, PropTypes } from 'react';
import { style, keyframes, merge } from 'glamor';

const { string, number, bool, object } = PropTypes;

const defaultWrapperStyle = {
  width: 200,
  height: 200,
  backgroundColor: 'white',
  backgroundSize: 'contain',
  backgroundRepeat: 'none',
  boxSizing: 'border-box',
  position: 'relative',
};

const PENDING = 'PENDING';
const LOADING = 'LOADING';
const LOADED = 'LOADED';
const FAILED = 'FAILED';

export default class Image extends Component {
  constructor(props) {
    super(props);
    if (props.src != null && typeof props.src === 'string') {
      this.state = {
        status: LOADING,
      };
    } else {
      this.state = {
        status: PENDING,
      };
    }
    this.onLoad = this.onLoad.bind(this);
    this.onFail = this.onFail.bind(this);
  }

  componentWillReceiveProps(nextProps) {
    if (this.props.src !== nextProps.src) {
      this.setState({
        status: LOADING,
      });
    }
  }

  onLoad() {
    this.setState({
      status: LOADED,
    });
  }

  onFail() {
    this.setState({
      status: FAILED,
    });
  }
  render() {
    const {
      src,
      width,
      height,
      alt,
      loadingStyle,
      failureStyle,
      rounded,
      circle,
    } = this.props;
    const mainWrapperStyle = style({
      ...defaultWrapperStyle,
      width,
      height,
    });

    let wrapperStyle = {};
    if (rounded) {
      const roundedStyle = style({
        borderRadius: '10%',
        overflow: 'hidden',
      });

      wrapperStyle = merge(mainWrapperStyle, roundedStyle);
    } else if (circle) {
      const circularStyle = style({
        borderRadius: '50%',
        overflow: 'hidden',
      });

      wrapperStyle = merge(mainWrapperStyle, circularStyle);
    } else {
      wrapperStyle = mainWrapperStyle;
    }

    const defaultImageStyle = style({
      opacity: 0,
      transisition: 'opacity 150ms ease',
    });

    const loadedImageStyle = style({
      opacity: 1,
    });

    let imageStyle = defaultImageStyle;

    if (this.state.status === LOADED) {
      imageStyle = merge(defaultImageStyle, loadedImageStyle);
    } else {
      imageStyle = defaultImageStyle;
    }


    let image;
    if (alt != null) {
      image = (<img
        className={imageStyle}
        src={src}
        width={width}
        height={height}
        alt={alt}
        onLoad={this.onLoad}
        onError={this.onFail}
      />);
    } else {
      image = (<img
        className={imageStyle}
        src={src}
        width={width}
        height={height}
        role="presentation"
        onLoad={this.onLoad}
        onError={this.onFail}
      />);
    }

    let statusIndicator = null;
    if (this.state.status === LOADING) {
      statusIndicator = (<div className={loadingStyle} />);
    } else if (this.state.status === FAILED) {
      const crossArm = {
        background: '#000',
        width: 5,
        height: 50,
        position: 'absolute',
        top: '30%',
        left: '50%',
        transform: 'rotate(45deg)',
      };

      const otherCrossArm = style({
        ...crossArm,
        transform: 'rotate(-45deg)',
      });


      statusIndicator = [
        (<div className={style(crossArm)} />),
        (<div className={otherCrossArm} />),
      ];
    }

    return (<div className={wrapperStyle}>
      {statusIndicator}
      {image}
    </div>);
  }
}

Image.propTypes = {
  src: string.isRequired,
  width: number.isRequired,
  height: number.isRequired,
  alt: string,
  loadingStyle: object,
  failureStyle: object,
  circle: bool,
  rounded: bool,
};


const crossArm = {
  background: '#000',
  width: 5,
  height: 50,
  position: 'absolute',
  top: '30%',
  left: '50%',
};

const failureStyle = style({
  ...crossArm,
  transform: 'rotate(45deg)',
  ':before': {
    content: '',
    background: '#000',
    width: 5,
    height: 50,
    position: 'absolute',
    top: 0,
    left: 0,
    transform: 'rotate(-90deg)',
  },
});


// failureStyle = merge(failureStyle, otherCrossArm);

const ring = keyframes({
  '0%': {
    transform: 'rotate(0deg)',
  },
  '100%': {
    transform: 'rotate(360deg)',
  },
});

const loadingStyle = style({
  position: 'absolute',
  display: 'block',
  width: 40,
  height: 40,
  top: '37%',
  left: '37%',
  borderRadius: 80,
  boxShadow: '0 3px 0 0 #59ebff',
  animation: `${ring} 1s linear infinite`,
});

Image.defaultProps = {
  loadingStyle,
  failureStyle,
  circle: false,
  rounded: true,
};

It is used by the LoremPixel to load a placeholder image from lorempixel:

import React, { PropTypes } from 'react';
import { style } from 'glamor';
import Image from './Image';

const centerImage = style({
  display: 'block',
  margin: 'auto',
  paddingLeft: 8,
  paddingRight: 8,
});

export default function LoremPixel({ url = 'http://lorempixel.com', width = 200, height = 400, alt = 'Placeholder image' }) {
  const src = `${url}/${width}/${height}?t=${Date.now()}`;
  return (<Image
    rounded={false}
    width={width}
    height={height}
    className={centerImage}
    src={src}
  />);
}

const { string, number, oneOf } = PropTypes;

LoremPixel.propTypes = {
  url: string,
  height: oneOf(string, number),
  width: oneOf(string, number),
  alt: string,
};

This is my react-hot-loader boilerplate:

const root = document.getElementById('root');
const renderApp = appRoutes => render(
  <AppContainer>
    <Router routes={appRoutes} history={hashHistory} />
  </AppContainer>,
  root,
);

renderApp(routes);

if (module.hot) {
  module.hot.accept('./routes', () => {
    const nextRoutes = require('./routes').default;
    renderApp(nextRoutes);
  });
}

I am using the webpack-dev-server with react-hot-loader/babel added to the .babelrc and the react-hot-loader/patch in the entry of the webpack config.

@calesce
Copy link
Collaborator

calesce commented Nov 22, 2016

Please update to beta.6. It looks like there's an error being thrown in one of your components, and there was a bug in beta.2 that didn't catch the error and display the red box correctly.

@vamsiampolu
Copy link
Author

warning.js?8a56:36 Warning: setState(...): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op. Please check the code for the Image component still occurs after I installed beta.6

@calesce
Copy link
Collaborator

calesce commented Nov 22, 2016

It's compatible with Node 6. I'm not sure what's going on there, I've been working with beta.6 for weeks.

@calesce
Copy link
Collaborator

calesce commented Nov 22, 2016

Also when I just run npm install react-hot-loader, it installs 1.3.0 which is not what I was expecting.

That's because beta/pre-release package versions aren't installed by default.

@vamsiampolu
Copy link
Author

I have put my react-hot-loader config and Image component code into a gist here and do a hard reset to remove react-hot-loader.

I think I should wait before reintroducing the project into my build. Thanks.

@calesce
Copy link
Collaborator

calesce commented Nov 22, 2016

OK, actually this issue looks like it could also be an instance of #313. To fix you'll need to add babel-preset-es2015.

@vamsiampolu
Copy link
Author

I have babel-preset-env in order to transpile my code, I do not want to transpile all ES2015 features when I am using node v7 and Chrome 54. Can I use this preset with react-hot-loader or does it have to be ES2015.

@calesce
Copy link
Collaborator

calesce commented Nov 22, 2016

Can I use this preset with react-hot-loader or does it have to be ES2015.

At least ES2015 classes, see the issue I linked to above.

@vamsiampolu
Copy link
Author

@calesce
I gave it another shot, I enabled babel-plugin-transform-es2015-classes as you suggested above:

.babelrc

{
  "presets":[
    "react",
    [
      "env",
      {
        "targets":{
          "chrome":52,
          "node":true
        }
      }
    ]
  ],
  "env": {
    "development": {
      "plugins":[
        "transform-es2015-classes",
        "react-hot-loader/babel"
      ]
    },
    "production": {
      "presets": ["babili"]
    }
  },
  "plugins":[
    "transform-class-properties",
    ["transform-object-rest-spread", {
        "useBuiltIns":true
      }
    ]
  ]
}

Webpack hmr config::

const hmr = {
  entry: [
    'react-hot-loader/patch',
    `${APP_PATH}/index.js`,
  ],
  devServer: {
    inline: true,
    hot: true,
    host: process.env.HOST || '0.0.0.0',
    port: process.env.PORT || 3000,
    stats: 'errors-only',
    historyApiFallback: true,
    contentBase: BUILD_PATH,
    watchOptions: {
      aggregateTimeout: 300,
      poll: 1000,
    },
  },
  plugins: [
    new DefinePlugin({
      NODE_ENV: JSON.stringify('development'),
    }),
    new HotModuleReplacementPlugin(),
    new NpmInstallPlugin({
      dev(module) {
        return (/(^babel-?.*|.*-plugin$|.*-loader)/).test(module);
      },
    }),
  ],
};

Main component:

import React from 'react';
import { Router, hashHistory } from 'react-router';
import { render } from 'react-dom';
import { AppContainer } from 'react-hot-loader';
import injectTapEventPlugin from 'react-tap-event-plugin';
import routes from './routes';

injectTapEventPlugin();

const root = document.getElementById('root');
function renderRoutes(nextRoutes) {
  render(<AppContainer>
    <Router routes={nextRoutes} history={hashHistory} />
  </AppContainer>, root);
}

renderRoutes(routes);

if (module.hot) {
  module.hot.accept('./routes', () => {
    const nextRoutes = require('./routes').default;
    renderRoutes(nextRoutes);
  });
}

This is working correctly for me.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants