Search code examples
javascriptreactjswebpackreact-hot-loaderrecompose

Error creating HoC with React and recompose


Error when using recompose to create a HoC withState and lifecycle:

warning.js?8a56:36 Warning: React.createElement: type should not be null, undefined, boolean, or number. It should be a string (for DOM elements) or a ReactClass (for composite components). Check the render method of `AppContainer`.

My component tree looks like this:

export function SimpleImage(props) {
  const {
    src,
    width = 200,
    height = 200,
    rounded,
    circle,
    status,
    onLoad,
    onFail,
  } = props;

  const mainWrapperStyle = style({
    backgroundColor: 'white',
    backgroundSize: 'contain',
    backgroundRepeat: 'none',
    boxSizing: 'border-box',
    position: 'relative',
    width,
    height,
  });

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

  const circularStyle = style({
    borderRadius: '50%',
    overflow: 'hidden',
  });

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

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

  let imageStyle = defaultImageStyle;

  let wrapperStyle = mainWrapperStyle;
  if (rounded) {
    wrapperStyle = merge(mainWrapperStyle, roundedStyle);
  } else if (circle) {
    wrapperStyle = merge(mainWrapperStyle, circularStyle);
  }

  if (status === LOADED) {
    imageStyle = merge(defaultImageStyle, loadedImageStyle);
  }

  const image = (<img
      {...imageStyle}
      src={src}
      width={width}
      height={height}
      role="presentation"
      onLoad={this.onLoad}
      onError={this.onFail}
      />);

  let statusIndicator = null;
  if (this.state.status === LOADING) {
    statusIndicator = (<LoadingIndicator />);
  } else if (this.state.status === FAILED) {
    statusIndicator = (<ErrorIndicator />);
  }

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

const Image = compose(
  withState(
    'status',
    'setStatus',
    ({ src })=> src? LOADING: PENDING
  ),
  withProps(
    ({ setStatus }) => ({
    onLoad() {
      setStatus(LOADED);
    },
    onFail() {
      setStatus(FAILED);
    },
    reset() {
      setStatus(PENDING)
    },
    resetToLoading() {
      setStatus(LOADING)
    },
  })),
  lifecycle({
    componentWillReceiveProps(nextProps) {
      if(nextProps.src == null){
        this.props.reset();
      } else if(nextProps.src !== this.props.src) {
        this.props.resetToLoading();
      }
    }
  })
)(SimpleImage);

Solution

  • The SimpleImage component still has references to this and state despite being a stateless functional component. Removing these references and replacing this.onLoad with onLoad, this.onFail with onFail and changing this.state.status with status works correctly.

    The updated SimpleImage component looks like this:

    export function SimpleImage(props) {
      const {
        src,
        width = 200,
        height = 200,
        rounded,
        circle,
        status,
        onLoad,
        onFail,
      } = props;
    
      const mainWrapperStyle = style({
        backgroundColor: 'white',
        backgroundSize: 'contain',
        backgroundRepeat: 'none',
        boxSizing: 'border-box',
        position: 'relative',
        width,
        height,
      });
    
      const roundedStyle = style({
        borderRadius: '10%',
        overflow: 'hidden',
      });
    
      const circularStyle = style({
        borderRadius: '50%',
        overflow: 'hidden',
      });
    
      const defaultImageStyle = style({
        opacity: 0,
        transisition: 'opacity 150ms ease',
      });
    
      const loadedImageStyle = style({
        opacity: 1,
      });
    
      let imageStyle = defaultImageStyle;
    
      let wrapperStyle = mainWrapperStyle;
      if (rounded) {
        wrapperStyle = merge(mainWrapperStyle, roundedStyle);
      } else if (circle) {
        wrapperStyle = merge(mainWrapperStyle, circularStyle);
      }
    
      if (status === LOADED) {
        imageStyle = merge(defaultImageStyle, loadedImageStyle);
      }
    
      debugger
      const image = (<img
          {...imageStyle}
          src={src}
          width={width}
          height={height}
          role="presentation"
          onLoad={onLoad}
          onError={onFail}
          />);
    
      let statusIndicator = null;
      if (status === LOADING) {
        statusIndicator = (<LoadingIndicator />);
      } else if (status === FAILED) {
        statusIndicator = (<ErrorIndicator />);
      }
    
      return (<div {...wrapperStyle}>
        {statusIndicator}
        {image}
      </div>);
    }