Home > Software engineering >  Read data from request which just finished
Read data from request which just finished

Time:02-25

If user type id in input, I'd like to fetch post by specific id typed by user. If not, I'd like to fetch whole array of posts, and then get some id from fetched data. I know it doesn't make sense, but it just only for tests.

It doesn't work, cause useState works asynchronously. I tried few solutions, but all of them looked very ugly.

LIVE DEMO

I received an error:

Cannot read properties of undefined (reading 'id')

Cause setPosts hasn't set yet.

What is the best, clear way to handle this case?

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>
  );
}

CodePudding user response:

You can treat fetchPosts as a side-effect and wrap fetchPost(posts[0].id) in a useEffect dependant on posts.

Or just use the result directly in onSubmit() (presuming you don't need posts for something else).

  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>
  );

CodePudding user response:

Just like you said useState works asynchronously , if you want to do something after mutating it you will have to use useEffect and set posts as its arguments , now whenever the posts get mutated your funcion will be run and the first index of array will be sent to the 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>
  );
}

Also consider never to update state directly in your return function it will cause performance issues

CodePudding user response:

The problem is in the method "fetchPost". Inside this method, you have two variables with the same name. "id" from the state hook, and "id" from the function parameter. You can solve the problem changing one of those variables names.

One more thing, if "id" doesn't have value, your way to get the first post won't work because the await method won't wait to the change of the state.

I have edit a bit the code to solve both problems.

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>
  );
}

I hop I've helped you.

  • Related