从刚刚完成的请求中读取数据

Read data from request which just finished

如果用户在输入中输入 id,我想通过用户输入的特定 ID 获取 post。如果没有,我想获取整个 post 数组,然后从获取的数据中获取一些 id。我知道这没有意义,但它只是为了测试。

它不起作用,因为 useState 异步工作。我尝试了几个解决方案,但所有的解决方案看起来都很丑。

LIVE DEMO

我收到一个错误:

Cannot read properties of undefined (reading 'id')

原因setPosts还没有设置。

处理这种情况的最好、最明确的方法是什么?

import { useState } from "react";

export default function App() {
  const [id, setId] = useState("");
  const [post, setPost] = useState(null);
  const [posts, setPosts] = useState([]);

  const fetchPost = async (id) => {
    const res = await axios.get(
      `https://jsonplaceholder.typicode.com/posts/${id}`
    );
    setPost(res.data);
  };

  const fetchPosts = async () => {
    const res = await axios.get(`https://jsonplaceholder.typicode.com/posts`);
    setPosts(res.data);
  };

  const onSubmit = async (e) => {
    e.preventDefault();

    if (id) {
      await fetchPost(id);
    } else {
      await fetchPosts();
      await fetchPost(posts[0].id);
    }
  };
  return (
    <div className="App">
      <form onSubmit={onSubmit}>
        <input type="text" onChange={(e) => setId(e.target.value)} />
        <button type="submit">submit</button>
      </form>
    </div>
  );
}

就像你说的 useState 工作 asynchronously ,如果你想在改变它之后做一些事情,你将不得不使用 useEffect 并将 posts 设置为它的参数,现在每当 posts 发生突变时,你的函数将是 运行 并且数组的第一个索引将被发送到 fetchPost(id) ,

import axios from "axios";
import "./styles.css";

import { useEffect, useState } from "react";

export default function App() {
  const [id, setId] = useState("");
  const [post, setPost] = useState(null);
  const [posts, setPosts] = useState([]);
  useEffect(() => {
    if (posts.length) {
      fetchPost(posts[0].id);
    }
  }, [posts]);
  const fetchPost = async (id) => {
    console.log(`fetching ${id}`);
    const res = await axios.get(
      `https://jsonplaceholder.typicode.com/posts/${id}`
    );
    console.log(res.data);
    setPost(res.data);
  };

  const fetchPosts = async () => {
    console.log(`fetching all posts`);
    const res = await axios.get(`https://jsonplaceholder.typicode.com/posts`);
    setPosts(res.data);
  };

  const onSubmit = async (e) => {
    e.preventDefault();
    if (id) {
      await fetchPost(id);
    } else {
      await fetchPosts();
      // res = await fetchPost(posts[0].id); we dont need it here it defined in useffect function
    }
  };
  const setDefaultId = (e) => {
    setId(e.target.value);
  };
  return (
    <div className="App">
      <form onSubmit={onSubmit}>
        <input type="text" onChange={(e) => setDefaultId(e)} />
        <button type="submit">submit</button>
      </form>
    </div>
  );
}

同时考虑永远不要在你的return函数中直接更新状态,这会导致性能问题

问题出在“fetchPost”方法中。在这个方法中,你有两个同名的变量。来自状态挂钩的“id”,以及来自函数参数的“id”。 您可以解决更改这些变量名称之一的问题。

还有一件事,如果“id”没有价值,你获得第一个 post 的方法将不起作用,因为 await 方法不会等待状态的改变。

我修改了一些代码来解决这两个问题。

import { useState } from 'react';
import axios from 'axios';

export default function App() {
  const [id, setId] = useState('');
  const [post, setPost] = useState(null);

  const fetchPost = async (idProp) => {
    const res = await axios.get(
      `https://jsonplaceholder.typicode.com/posts/${idProp}`,
    );
    setPost(res.data);
  };

  const fetchPosts = async () => {
    const res = await axios.get('https://jsonplaceholder.typicode.com/posts');
    await fetchPost(res.data[0].id);
  };

  const onSubmit = async (e) => {
    e.preventDefault();
    if (id) {
      await fetchPost(id);
    } else {
      await fetchPosts();
    }
  };

  return (
    <div className="App">
      <form onSubmit={onSubmit}>
        <input type="text" onChange={(e) => {
          setId(e.target.value);
        }} />
        <button type="submit">submit</button>
      </form>
    </div>
  );
}

希望对你有所帮助。

您可以将 fetchPosts 视为 side-effect 并将 fetchPost(posts[0].id) 包装在依赖于 postsuseEffect 中。

或者直接在 onSubmit() 中使用结果(假设您不需要 posts 做其他事情)。

  const fetchPosts = async () => {
    const res = await axios.get(`https://jsonplaceholder.typicode.com/posts`);
    // setPosts(res.data);  // this state is transitory and not used directly by the render
    return res.data;           
  };

  const onSubmit = async (e) => {
    e.preventDefault();

    if (id) {
      await fetchPost(id);
    } else {
      const posts = await fetchPosts();  // Only used as part of submit event?
      await fetchPost(posts[0].id);
    }
  };

  return (
    <div className="App">
      <form onSubmit={onSubmit}>
        <input type="text" onChange={(e) => setId(e.target.value)} />
        <button type="submit">submit</button>
      </form>
      <div>{(post && post.title) || "No post yet"}</div>
    </div>
  );