从 sanity cms 获取数据时,反应状态未定义

React state is undefined when fetching data from sanity cms

我最近学习了一个关于将 cms 集成到您的网站中的教程。本教程使用了 sanity cms,使过程非常直观。完成本教程后,我就可以在自己的项目中使用它了。

然而,当我尝试使用 useEffect 挂钩获取数据时,出现错误:无法读取未定义的属性。我知道这是因为获取数据是异步完成的。但我无法理解的是,我是按照与教程完全相同的方式来做的。他没有使用任何状态进行加载或 isFetched。所以我的问题是我做了什么与教程不同的地方,我应该如何解决它?

我真的不想使用加载状态,因为它看起来不太好...

这是我从 api 收到的 JSON 对象:

[{…}]
0:
buttonlabel: "Start learning"
description: "Ranging from beginner to pro level tricks. Wanna know the best way to learn a trick? You can search for it down below and find a tutorial from one of our trainers as well as a detailed explanation. Still stuck? Come ask us at a Westsite training moment."
intro: "Welcome to the Westsite trick progression guide. Here you can find a collection of all the wakeboarding tricks you can think of.  "
_createdAt: "2022-05-24T16:26:13Z"
_id: "a4f8cf02-4b86-44d5-a63d-c95a3a7d3293"
_rev: "QYLgvM20Eo53w3noOOj0MB"
_type: "hero"
_updatedAt: "2022-05-24T17:29:10Z"

这是教程组件:

import React, { useState, useEffect } from "react";
import { motion } from "framer-motion";
import { urlFor, client } from "../../client";
import { AppWrap, MotionWrap } from "../../wrapper";

import "./About.scss";

const About = () => {
  const [abouts, setAbouts] = useState([]);

  useEffect(() => {
    const query = '*[_type == "abouts"]';

    client.fetch(query).then((data) => setAbouts(data));
  }, []);

  return (
    <div>
      <h2 className="head-text">
        I know that
        <span> Good Design </span>
        <br />
        means
        <span> Good Business</span>
      </h2>

      <div className="app__profiles">
        {abouts.map((about, index) => {
          return (
            <motion.div
              whileInView={{ opacity: 1 }}
              whileHover={{ scale: 1.1 }}
              transition={{ duration: 0.5, type: "tween" }}
              className="app__profile-item"
              key={about.title + index}
            >
              <img src={urlFor(about.imgUrl)} alt={about.title} />
              <h2 className="bold-text" style={{ marginTop: 20 }}>
                {about.title}
              </h2>
              <p className="p-text" style={{ marginTop: 10 }}>
                {about.description}
              </p>
            </motion.div>
          );
        })}
      </div>
    </div>
  );
};

export default AppWrap(
  MotionWrap(About, "app__about"),
  "about",
  "app__whitebg"
);

这是我的:

import React, { useState, useEffect } from "react";
import { motion } from "framer-motion";
import { BiRightArrowAlt } from "react-icons/bi";

import { client } from "../../client";
import "./Hero.scss";

const Hero = () => {
  const [heroContent, setHeroContent] = useState([]);

  useEffect(() => {
    const query = '*[_type == "hero"]';

    client.fetch(query).then((data) => setHeroContent(data));
  }, []);

  const content = heroContent[0];

  return (
    <div className="app__hero">
      <motion.div
        className="app__hero-content-container"
        whileInView={{ opacity: [0, 1], x: [500, 0] }}
        transition={{ duration: 1, ease: "easeOut" }}
      >
        <div className="app__hero-content">
          <h2 className="heading-text">
            Learn
            <span className="highlighted"> wakeboarding </span>
            the right way
          </h2>
          <p className="p-text">{content.intro}</p>
          <p className="p-text">{content.description}</p>
          <button className="primary-btn p-text app__flex">
            {content.buttonlabel}
            <BiRightArrowAlt />
          </button>
        </div>
      </motion.div>
    </div>
  );
};

export default Hero;

此行会在数据异步应用到状态之前引起问题

const content = heroContent[0];

在初始渲染中,heroContent 是一个空数组,因此在加载数据之前,content 将是未定义的。几个选项 - 1 - 呈现某种加载状态,直到 heroContent 被填充 -

if (!heroContent.length) return <LoadingSpinner />

2 - 将试图使用 content 的部分用保护子句包裹起来

{content && (
  <p className="p-text">{content.intro}</p>
  <p className="p-text">{content.description}</p>
  <button className="primary-btn p-text app__flex">
    {content.buttonlabel}
    <BiRightArrowAlt />
  </button>
)}

当您尝试从未定义的 content 访问属性时会出现问题。如果您不想显示任何加载指示器,我会在未定义内容时显示一些回退。例如

而不是:

<p className="p-text">{content.intro}</p>

你可以选择:

<p className="p-text">{content?.intro ?? '-'}</p>

或类似的东西。

问题是您将状态分解为不同级别,这会导致状态更改出现问题。所以,你必须这样做 要么将状态称为映射函数,要么使用特定索引 0 保存状态。

    import React, { useState, useEffect } from "react";
    import { motion } from "framer-motion";
    import { BiRightArrowAlt } from "react-icons/bi";
    
    import { client } from "../../client";
    import "./Hero.scss";
    
    const Hero = () => {
      const [heroContent, setHeroContent] = useState([]);
    
      useEffect(() => {
        const query = '*[_type == "hero"]';
    // this is giving response as array
        client.fetch(query).then((data) => setHeroContent(data));
      }, []);
    
      return (
        <div className="app__hero">
          {heroContent.map((content,index)=>
          <motion.div
            className="app__hero-content-container"
            whileInView={{ opacity: [0, 1], x: [500, 0] }}
            transition={{ duration: 1, ease: "easeOut" }}
            key={index}
          >

            <div className="app__hero-content">
              <h2 className="heading-text">
                Learn
                <span className="highlighted"> wakeboarding </span>
                the right way
              </h2>
              <p className="p-text">{content.intro}</p>
              <p className="p-text">{content.description}</p>
              <button className="primary-btn p-text app__flex">
                {content.buttonlabel}
                <BiRightArrowAlt />
              </button>
            </div>

          </motion.div>}
        </div>
      );
    
    };
    
    export default Hero;