Reputation: 365
Normally I am quite confident with the principles of ReactJS and rendering dynamic content. I could really do with some help however.
The problem started with the fact that I have a page called Feed. This feed should display a list of images fetched from an API.
I need this API to be secured by a token passed as a header.
In App.js, here is the relevant portion of code which checks if a user is logged in and restricts access to feed unless they are. The token is saved to state and passed into Feed as a prop.
authenticate = () => {
const token = localStorage.getItem("token");
validateToken(token)
.then(() => {
this.setState({ authenticated: true, token: token });
})
.catch(() => {
this.setState({ authenticated: false, token: false });
})
}
render() {
let privateRoutes = null;
if (this.state.authenticated) {
privateRoutes =(
<div>
<Route exact path="/feed"
render={(props)=>{
return (
<Feed token={this.state.token}/>)}} />
<Route exact path="/profile/:username"
render={(props)=>{
return (
<Profile data={props}/>)}} />
</div>
);
}
In Feed, I then call the API to get the images passing in this token. Here is where the problem is, in Feed, when I console log 'this.props.token', it is null for a split second before it is what was sent from app.js.
This means I cannot call the API in componentDidMount as the token is initially null. Furthermore this means I cannot set the state of the posts as I am in render() at this point.
Here is the feed code:
render() {
const token = this.props.token;
let posts = []; // should be updated by below
if (token !== false) {
loadFeedForUser(token).then((response) => {
posts = response;
console.log(posts); // logs successfully
})
.catch(() => {
posts = [];
})
}
return (
<div className="App">
<div className="content">
<div className="feed">
{ Object.values(posts).map((post, i) => {
console.log(post);
return (
<Post/> // This does not run as posts still empty array
)
})}
In the HTML, the object for loop never runs as the posts array is empty as it has not registered as being updated by the API.
What is wrong with my architecture here? It does not feel good at all.
Thanks.
Can anybody suggest where I can improve this? I need to pass the token from app.js only when authenticated and then get all the images.
Upvotes: 0
Views: 183
Reputation: 1758
The component is mounting before the prop is ready. This is a very common scenario, which is a big reason why I prefer hooks. To fix it you should use the componentDidUpdate
lifecycle method, something like
componentDidUpdate(prevProps, prevState, snapshot) {
if (props.token && props.token !== prevProps.token) {
makeApiCall(props.token)
}
}
You'll also need to have similar logic in your componentDidMount
just in case it does have the token value during mount. Otherwise it won't make the initial call.
As a hook, this is all the code that would be needed:
useEffect(() => {
makeApiCall(props.token)
}, [props.token])
Upvotes: 2