jackabe
jackabe

Reputation: 365

React - Proper way to update content in render

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

Answers (1)

Jake Luby
Jake Luby

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

Related Questions