Guerreri88
Guerreri88

Reputation: 63

This.props undefined when component re-renders

I've been having a problem in this component with props. At first props were undefined when mounted but after refreshing the page it worked.

I read that it was because the data was fetched after mounting, so I fixed it with a conditional in the render. However, now I have the opposite error and I cannot find any solution that it works for me. Props work ok when mounting but when refreshing I get undefined.

Anyone has any idea why this is happening?

Here is my code: Profile

import React from "react";
import axios from "axios";
import { Card, CardBody, CardTitle, CardText} from "reactstrap";
import Withuser from "./Withuser"

class Profile extends React.Component {
  constructor(props) {
    super(props)
    console.log(props)
    this.state = {
      thoughts: [],
    }
  }

  componentDidMount = () => {
    this.getShares()
  }

  getShares = () => {
    console.log(this.props.user[0].id)
    const user_id = this.props.user[0].id

    axios(`http://localhost:7001/api/profile/shares/${user_id}`)

    .then(res => {
      console.log(res.data)
      this.setState(state => ({
        thoughts: res.data,
        loggedIn: !state.loggedIn
      }))
    })
    .catch(error => {
      this.setState({ error: true })
    })
  }

  render() {
    const { thoughts } = this.state
    if (!thoughts.length === 0) {
      return <div />
    }
    return(
    <div>
      <h1>Your posts</h1>
      <ul>
        {thoughts.map((thought, index) => {
          return (
            <Card className='thoughts' key={index}>
              <CardBody>
                <CardTitle>{thought.user_name} posted at {thought.createdAt}</CardTitle>
                <CardText>{thought.body}</CardText>
              </CardBody>
            </Card>
          )
        })}
      </ul>
    </div>
    ) 
  }
}


export default Withuser(Profile);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

Whithuser

import React, { useState, useEffect } from 'react'
import axios from 'axios'

const withUser = (Component, options = { renderNull: true }) => props => {
  const [userData, setUserData] = useState(null)
  const [userId, setUserId] = useState(null)
  const [error, setError] = useState(false)
  //const [loading, setLoading] = useState(false)

  useEffect(() => {
    const token = localStorage.getItem('token')
    if (!token) return
    //setLoading(true)
    axios('http://localhost:7001/api/profile', {
      headers: {
        'x-access-token': token,
      },
    })
      .then(response => {
        const id = response.data.id
        setUserId(id)
      })
      .catch(error => {
        setError(true)
        console.log(error)
      })
      {/*}.finally(() => {
        setLoading(false)
      })*/}
  }, [])

  useEffect(() => {
    //setLoading(true)
    axios(`http://localhost:7001/api/users/${userId}`)
      .then(response => {
      
      setUserData(response.data)
    })
    {/*}.finally(() => {
      setLoading(false)
    })*/}
  }, [userId])
  
  //if(loading) return null;
  if (!userData && options.renderNull) return null
  return <Component {...props} user={userData} />
}

export default withUser
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

Upvotes: 0

Views: 1305

Answers (1)

Benjamin
Benjamin

Reputation: 3656

Here is how I would refactor this code.

First, inside your withUser HOC, instead of two useEffect hooks, I would combine the work into a single useEffect that is why you are getting an initial render with props.user.id is undefined.

I would also clean up the axios requests into async functions for readability.

withUser

import React, { useState, useEffect } from 'react';
import axios from 'axios';

const fetchProfile = async () => {
  const token = localStorage.getItem('token');

  if (!token) {
    throw new Error('Missing Token');
  }

  const response = await axios('http://localhost:7001/api/profile', {
    headers: {
      'x-access-token': token,
    },
  });

  const profile = response.data;

  return profile;
};

const fetchUsers = async (userId) => {
  const response = await axios(`http://localhost:7001/api/users/${userId}`);
  const users = response.data;
  return users;
};

const withUser = (Component, options = { renderNull: true }) => (props) => {
  const [userData, setUserData] = useState();

  useEffect(() => {
    async function loadUser() {
      try {
        const profile = await fetchProfile();
        const users = await fetchUsers(profile.id);
        setUserData(users);
      } catch (error) {
        console.error(error);
      }
    }
    loadUser();
  }, []);

  if (userData === undefined && options.renderNull === true) {
    return null;
  }

  return <Component {...props} user={userData} />;
};

export default withUser;

Then in the Profile component, I wouldn't change much, other than refactoring getShares() into an async function. And then a little cleanup here and there.

Profile

import React from 'react';
import axios from 'axios';
import { Card, CardBody, CardTitle, CardText } from 'reactstrap';
import withUser from './Withuser';

class Profile extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      error: false,
      loggedIn: undefined,
      thoughts: [],
    };
  }

  componentDidMount = () => {
    this.getShares();
  };

  getShares = async () => {
    const userId = this.props.user[0].id;

    try {
      const response = await axios(`http://localhost:7001/api/profile/shares/${userId}`);
      this.setState((state) => ({
        thoughts: response.data,
        loggedIn: !state.loggedIn,
      }));
    } catch (error) {
      this.setState({ error: true });
    }
  };

  render() {
    const { thoughts } = this.state;

    if (!(thoughts.length > 0)) {
      return null;
    }

    return (
      <div>
        <h1>Your posts</h1>
        <ul>
          {thoughts.map((thought, index) => {
            return (
              <Card className="thoughts" key={index}>
                <CardBody>
                  <CardTitle>
                    {thought.user_name} posted at {thought.createdAt}
                  </CardTitle>
                  <CardText>{thought.body}</CardText>
                </CardBody>
              </Card>
            );
          })}
        </ul>
      </div>
    );
  }
}

export default withUser(Profile);

Upvotes: 1

Related Questions