Reputation: 63
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
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.
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.
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