Search code examples
javascriptwordpressreactjsreact-hookswp-api

Why won't useEffect run my API fetch anymore and save results to state?


GOALS:

Note that examples used are not the exact context of my project, but are very similar, and therefore simplified for the sake of this post

I am trying to use WP-API to use Wordpress categories, post titles, and post content to generate a dynamic React app.

Each piece of information plays a part in the generation of the React app:

  • The Wordpress categories are used to populate a sidebar navigation component in the React App. Each Wordpress category will act as an item in this navigation. Think of this component as the table of contents.

  • The post titles will be submenu items to each category. So, using recipes as an example, if one of the category items on the menu was "Mexican Food", the post titles, or submenu items, could be "Tacos", "Enchiladas", "Tamales".

  • Sticking to the Mexican food example, the post content will be displayed in a main content area depending on which submenu item you click on. So, if you were to click on "Tacos" in the sidebar menu component, the main content would populate with the recipe for "Tacos". This main content area is its own individual component.

PROBLEM:

The main problem is that I am able to get the items to populate on the sidebar navigation component without much issue using React hooks, useEffect, and fetch. However, once I click on the submenu item, the recipe will not populate on the main content area.

RESEARCH FINDINGS:

I was able to establish that useEffect is somehow not setting my state. I declare two variables which process the JSON data I get by fetching the WP-API. I then want to set the results into my component state using a setPost function. However, this setPost function doesn't set the state. Below I'll post code examples for clarity.

CODE:

This is my current Main Content component, which receives props. Specifically, it receives the 'match' prop from React Router, which contains the URL slug for the specific WordPress post. I use this

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

function MainContent(props) {
    console.log(props)
    
    useEffect(() => {
        fetchPost()
    }, [])

    const [post, setPost] = useState([])

    const fetchPost = async () => {
        console.log('Fetched Main Content')
        const fetchedPost = await fetch(`http://coolfoodrecipes.xyz/wp-json/wp/v2/posts?slug=${props.match.params.wpslug}`)
        const postContent = await fetchedPost.json()
        console.log(postContent[0])

        setPost(postContent[0])
        console.log(post)
    }

    return (
        <div>
            {/* <h1 dangerouslySetInnerHTML={{__html: post.title.rendered}}></h1> */}
        </div>
    )
}

export default MainContent

Here are the results of the 1st console log, which contains the contents of my match prop:

{
isExact: true,
params: {wpslug: "tacos"},
path: "/:wpslug",
url: "/tacos"
}

The result of my 3rd console log, console.log(postContent[0]) , results in an object where it returns every single piece of detail for that specific post correctly.

After that, I use setPost(postContent[0]) to save this information to my post state.

To confirm this, I run console.log(post), to which it returns a simple [], an empty array.

RESULTS & EXPECTATIONS:

What I expected is that the content in setPost(postContent[0]) would be properly saved in the post state so that I could use that content to render content on the page.

However, the actual result is that nothing gets saved to the state, and any time I click on other categories, such as "Tamales", or "Enchiladas", the URL DOES update, but it never fetches information again after the first fetch.

I know this is a long winded question, but anyone who could help this poor newbie would be an absolute savior! Thanks in advance!


Solution

  • First let's take a look at your second problem/expectation: It never fetches information after the first fetch.

    The useEffect function may have two parameters. The callback function and an array of dependencies. The effect function will only be called when one of its dependencies changed. If the second parameter is omitted the function will run on every re-render. Your array is empty; that means the effect will run when the component is first mounted, after that it will not re-run.

    To resolve that problem you need to add your dependencies correctly. In your case you want to re-fetch if props.match.params.wpslug changes.

    useEffect(() => {
            fetchPost()
    }, [props.match.params.wpslug])
    

    Now for the problem of seemingly not setting the state correctly. There seems to be nothing wrong with setting/updating state in your example. The problem is the 'post' variable is for the current render-cycle already set to [] and will not change until the next cycle (you should and did mark it as const so it cannot change its value).

    I will try to explain 2 cycles here.

    /* First run, should be the mounting of the component */
    function MainContent(props) {
        const slug = props.match.params.wpslug;
        const [post, setPost] = useState([]) // 1. post is currently []
    
        useEffect(() => { // 3. the useEffect callback will be called, i.e. fetching posts
            fetchPost()
        }, [slug])
    
        const fetchPost = async () => {
            const fetchedPost = await fetch(`http://coolfoodrecipes.xyz/wp-json/wp/v2/posts?slug=${slug}`)
            const postContent = await fetchedPost.json()
    
            // 4. you change the state of this component, 
            // React will run the next render-cycle of this component
            setPost(postContent[0]) 
        }
    
        return (
            <div>
                <h1>{post.title}</h1> {/* 2. React will tell the browser to render this */}
            </div>
        )
    }
    
    /* Second run, initiated by setting the post state */
    function MainContent(props) {
        const slug = props.match.params.wpslug;
        const [post, setPost] = useState([]) // 1. post is now [{title: 'TestPost'}]
    
        useEffect(() => { // on this cycle this useEffect callback will not run because 'slug' did not change
            fetchPost()
        }, [slug])
    
        const fetchPost = async () => {
            const fetchedPost = await fetch(`http://coolfoodrecipes.xyz/wp-json/wp/v2/posts?slug=${slug}`)
            const postContent = await fetchedPost.json()
    
            setPost(postContent[0]) 
        }
    
        return (
            <div>
                <h1>{post[0].title}</h1> {/* 2. React will tell the browser to render this */}
            </div>
        )
    }
    

    Here (https://overreacted.io/a-complete-guide-to-useeffect/) is a very interesting article about the new useEffect Hook and some of the problems you encountered are addressed.