Search code examples
reactjsreduxreact-redux

TypeError: Cannot read property 'image' of null


I have connected my ProfileComponent.js with a redux store and then using that state to render user image but when I refresh the page I get this TypeError and when I did a console check it seems that my component is using user state before it is available as props(it's basically null in Profile props) but that shouldn't happen right?

error

error

And when I omitted the <img /> element it seems that my Profile is being rendered twice for the first time my user is null and for second my user is updated in props. Can anyone explain to me what's happening?

console

console

ProfileComponent.js

import React, { Component } from 'react';
import {  Container, Row, Col, Card, CardHeader, CardBody, CardFooter, Input, Form, Button } from 'reactstrap';
import {connect} from 'react-redux';
import { uploadImage, fetchUser } from '../redux/ActionCreators';
import { baseUrl } from '../config';
import { Switch, Route, withRouter, Redirect } from 'react-router-dom';

const mapStateToProps = (state) => {
    return {
        user: state.auth.user,

    }
}

const mapDispatchToProps = (dispatch) => ({
    uploadImage: (userId, imageData) => {dispatch(uploadImage(userId, imageData))},
    fetchUser: (userId) => {dispatch(fetchUser(userId))}
})

class Profile extends Component {
    constructor(props){
        super(props);
        this.state = {
            selectedFile: null
        }
        this.handleUpload = this.handleUpload.bind(this);
        this.onChangeHandler = this.onChangeHandler.bind(this);
    }

    handleUpload(event){
        const data = new FormData() 
        data.append('image', this.state.selectedFile)
        this.props.uploadImage(this.props.match.params.userId, data);
        event.preventDefault();
    }
    onChangeHandler=event=>{
        this.setState({selectedFile: event.target.files[0]});
    }
    componentDidMount() {
        console.log('user',this.props.user);
    }

    render() {
        //
        return(
            <Container className="form-container" >
                <Row>
                    <Col md={{size:6}}>
                        <Col md={{size:6,offset:3}}>
                            <div  style={{marginBottom:30}}> 
                                <img className="profile-image" src= {`${baseUrl}${this.props.user.image}`}/>
                                <Form >
                                    <Input type="file" name="image"  id="file" onChange={this.onChangeHandler} />
                                    <Button type="submit" color="primary" className="form-control" onClick = {this.handleUpload}>Upload</Button>
                                </Form>
                            </div>
                        </Col>
                    </Col>
                    <Col md={6}>
                        <div className="shadow-container" style={{marginTop:0}}>
                        <Card>
                            <CardHeader>
                                <h2>Profile details</h2>
                            </CardHeader>
                            <CardBody>
                                <h4 style={{color:'grey'}}>{this.props.match.params.userId}</h4>
                                <h5 style={{color:'grey'}}></h5>
                                <small><i></i></small>
                            </CardBody>
                            <CardFooter>
                                <Row>
                                    <Col>
                                        <a href="/editprofile"><span className="fa fa-pencil fa-2x ml-auto" 
                                            style={{color:'blue', display:'flex',
                                             flexDirection:'row',justifyContent:'flex-end'}}></span></a>
                                    </Col>
                                    <Col>
                                        <a href="/deleteprofile"><span className="fa fa-trash fa-2x" style={{color:'red'}}></span></a>
                                    </Col>
                                </Row>
                            </CardFooter>
                        </Card>
                        </div>
                    </Col>
                </Row>
            </Container>
        );
    }
}

export default withRouter(connect(mapStateToProps,mapDispatchToProps)(Profile));

MainComponent.js

import React, {Component} from 'react';
import  Menu  from './MenuComponent';
import Home from './HomeComponent';
import Users from './UsersComponent';
import Register from './RegisterComponent';
import Signin from './SigninComponent';
import Profile from './ProfileComponent';
import Edit from './EditComponent';
import { signin, signout, fetchUser } from '../redux/ActionCreators';
import {connect} from 'react-redux';
import { Switch, Route, withRouter, Redirect } from 'react-router-dom';
import { CSSTransition, TransitionGroup } from 'react-transition-group'

const mapStateToProps = (state) => {
    return {
        auth: state.auth
    }
}

const mapDispatchToProps = (dispatch) => ({
    signin: (credentials) => {dispatch(signin(credentials))},
    signout: () => {dispatch(signout())},
    fetchUser: (userId) => {dispatch(fetchUser(userId))}
})



class Main extends Component {

    componentDidMount() {
        this.props.fetchUser(this.props.auth.userId);
        
        console.log('main',this.props.auth);
    }

    render() {

        const PrivateRoute = ({ component: Component, ...rest }) => (
            <Route {...rest} render={(props) => (
              this.props.auth.authenticated
                ? <Component {...props} />
                : <Redirect to={{
                    pathname: '/signin',
                    state: { from: props.location }
                  }} authenticated={this.props.auth.authenticated} />
            )} />
          )
        return (
            <div>
                <Menu authenticated={this.props.auth.authenticated} signout={this.props.signout} userId={this.props.auth.userId}/>
                <TransitionGroup>
                    <CSSTransition key={this.props.location.key} classNames="page" timeout={300}>
                <Switch>
                    <Route path="/home" component={Home} />
                    <PrivateRoute path="/users" component={Users} />
                    <Route path="/signup" component={Register} />
                    <Route path="/signin" component={() => <Signin signin={this.props.signin} authenticated={this.props.auth.authenticated}/>} />} />
                    <PrivateRoute path="/profile/:userId" component={() => <Profile />} /> 
                    <Route path="/editprofile" component={Edit} />  
                    <Redirect to="/home" />  
                </Switch>
                </CSSTransition>
                </TransitionGroup>
            </div>
        );
    }
}
 
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Main));

Solution

  • First, check if the value is available or not,

    You can do something like this to solve the error :

    {this.props.user && <img className="profile-image" src= {`${baseUrl}${this.props.user.image}`}/> }
    

    Issue :

    user props might be initialized from some API or after some process, so it's not available for the first render, but as soon as props changes ( your user initialized ), it forces to re-render, so you get the data in the second time.