Why is my functional react component's state throwing TypeError: state is undefined?

1k views Asked by At

I'm working on my first website in React, which is based off of a FaunaDB database. I'm using Netlify and Netlify functions to access my server-side code. When trying to to get all of the data from the database using react useEffect, useState, and useRef, I get TypeError: state is undefined. The results are set in state as an array of objects, so my initial value of state is an empty array. I tried converting to a class component, but I get the same response. I'm wondering if I shouldn't use array.map, but I already tried a for loop, and it doesn't throw an error but it also doesn't render anything.

Here is my code in the Component:

import React, { useState, useEffect, useRef } from "react";
import { getAll } from "../../fauna";

const AllResults = () => {
    const [state, setState] = useState([]);
    const isRendered = useRef(false);

    useEffect(() => {
        const getResults = async () => {
            try {
                if (!isRendered.current) {
                    const result = await getAll();
                    setState(result);
                    isRendered.current = true;
                }
            } catch (e) {
                console.log(e);
            }
        }

        getResults();
    }, [isRendered, setState]);

    console.log(state);

    const allResults = state.map((l) => {
        return <p key={ l.key } { ...l }></p>
    });
    
    return allResults;
}

export default AllResults;

Here is my getAll function:

export const getAll = () => {
    return fetch(
        "/.netlify/functions/read-all"
    )
    .then(
        (response) => {
            return response.json();
        }
    )
    .catch(
        (error) => {
            console.error(error);
        }
    );
}

and the function it is fetching:

const faunadb = require("faunadb");
const dotenv = require("dotenv");
const path = require("path");

dotenv.config({ path: path.resolve("../../../.env") });
const q = faunadb.query;
const db = new faunadb.Client({ secret: process.env.FAUNA_SERVER_KEY });

exports.handler = async (_event, _context, callback) => {
    console.log("Reading database...");
    try {
        const res = await db.query(
            q.Paginate(
                q.Match(
                    q.Index("all")
                )
            )
        );
        const all = res.data;
        console.log("Success! ${all.length} items found");

        const getAll = all.map(
            (ref) => {
                return q.Get(ref);
            }
        );
        const ret = await db.query(getAll);
        return callback(
            null,
            {
                statusCode: 200,
                body: JSON.stringify(ret.map((refs) => refs.data))
            }
        );
    }
    catch (err) {
        console.log("Error: ", err);
        return callback(
            null,
            {
                statusCode: 400,
                body: JSON.stringify(err)
            }
        );
    }
}

This is my first time using React hooks, so I'm still learning. Please help!

2

There are 2 answers

0
ztcollazo On BEST ANSWER

I figured out that all I needed to do was to make the getAll function asynchronous, so that the await would actually do something, and I knew it would work because fetch is a promise. It ended up looking like this:

export const getAll = async () => {
    try {
      const response = await fetch(
        "/.netlify/functions/read-all"
      )
      
      const json = response.json();

      console.log(json);
      return json;
    } catch (error) {
      console.error(error);
    }
}
0
Jeremy Dahms On

I think the problem is that your getAll method is not asynchronous, so the await doesn't do anything. I would make that function async and see if that fixes your problem.

Also, second parameter of useEffect is an array of values that useEffect watches to see if it should run again, every time one of the values in that array changes the useEffect hook runs again. Since you are doing an API call, I assume it should run once on the first render and then not again on subsequent renders. In that case, I would leave the array blank so that it only runs once. This is how the componentDidMount lifecycle method is replicated with hooks in a functional component. I would reduce this hook slightly to look something more like this:

useEffect(() => {      
    getAll()
        .then(result => setState(result))
        .catch(e => console.log(e);
    }
}, []);