Search code examples
reactjsreduxpromiseredux-thunk

Async Image Load with React and Redux


I am trying to create a simple message wall with a <PostList /> container which displays a list of <Post /> components.

 {posts.map(function (post: any) {
                return <Post key={post.postid} post={post} />;
            })}

I pass in a single post to the Post component which has a <Avatar /> component that displays the users profile_pic inside it otherwise it displays a spinner.

My question is how would I allow the components to display on screen and once the image is loaded replace the spinner with the retrieved image?

I currently have the following Reducers and Actions:

User Reducer:

export default function(state = INITIAL_STATE, action : any){
    switch(action.type){
        case FETCH_USER_LOADING:
            return Object.assign({}, state, {isLoading: true});
        case FETCH_USER_DONE:
            return Object.assign({}, state, {users: state.users.concat(action.payload)});
}

    return state;
}

User Actions:

export function fetchUser(id: any) {
    return function (dispatch: any) {
        dispatch({ type: FETCH_USER_LOADING });
        return axios.get(`${ROOT_URL}/users/${id}`, {
            headers: { token: localStorage.getItem('token') }
        })
            .then(function (response) {
                dispatch({type: FETCH_USER_DONE, payload: response.data});
                return response.data
            })
    } 
}

Solution

  • Plenty of ways to do it.

    One of which is to write your own component, where, a newly loaded image prompts a redraw in componentDidMount. Here's the full source code of a lazy image that you can use in your own project:

    export default class LazyImage extends React.Component {
      constructor(props) {
        super(props);
        this.state = {
          loaded: false,
          error: false
        };
      }
    
      componentDidMount() {
        const img = new Image();
        img.onload = () => {
          this.setState({
            loaded: true
          });
        };
        img.onerror = () => {
          this.setState({
            error: true
          });
        };
        img.src = this.props.src;
      }
    
      render() {
        if (this.state.error) {
          return <img
            className={this.props.className}
            style={this.props.style}
            src={this.props.unloadedSrc}
            alt={this.props.alt} />
        } else if (!this.state.loaded) {
          return <img
            className={this.props.className}
            style={this.props.style}
            src={this.props.unloadedSrc}
            alt={this.props.alt} />
        }
        return <img
          className={this.props.className}
          style={this.props.style}
          src={this.props.src}
          alt={this.props.alt} />
      }
    }
    

    Which you would then use like so:

    <LazyImage unloadedSrc={unloadedSrc} src={src} />
    

    And then, you have the option of using a plethora of components that you can find just by googling the terms:

    • "image load react"
    • "react image lazy load"

    Or any similar search term variety, thereof. My favourite component is react-imageloader.

    I hope this helps.