7

My component relies on local state (useState), but the initial value should come from an http response.

Can I pass an async function to set the initial state? How can I set the initial state from the response?

This is my code

  const fcads = () => {
    let good;
    Axios.get(`/admin/getallads`).then((res) => {
      good = res.data.map((item) => item._id);
    });
    return good;
  };

  const [allads, setAllads] = useState(() => fcads());

But when I try console.log(allads) I got result undefined.

kidroca
  • 3,480
  • 2
  • 27
  • 44
test tet
  • 93
  • 1
  • 3
  • Where did you do ```console.log(allads)```? – MacOS Dec 21 '20 at 09:36
  • I make a button and do onclick to console.log(allads) @MacOS – test tet Dec 21 '20 at 09:42
  • Use a [useEffect](https://reactjs.org/docs/hooks-effect.html) hook and make the server call inside that. – adiga Dec 21 '20 at 09:42
  • Also, you can't use `good = res.data` inside the callback and `return good` outside. `return` is called before the callback is run. [How do I return the response from an asynchronous call?](https://stackoverflow.com/questions/14220321) – adiga Dec 21 '20 at 09:46
  • I tried as you said but still got undefined @adiga – test tet Dec 21 '20 at 09:54

2 Answers2

4

If you use a function as an argument for useState it has to be synchronous.

The code your example shows is asynchronous - it uses a promise that sets the value only after the request is completed

You are trying to load data when a component is rendered for the first time - this is a very common use case and there are many libraries that handle it, like these popular choices: https://www.npmjs.com/package/react-async-hook and https://www.npmjs.com/package/@react-hook/async. They would not only set the data to display, but provide you a flag to use and show a loader or display an error if such has happened

This is basically how you would set initial state when you have to set it asynchronously

const [allads, setAllads] = useState([]);
const [loading, setLoading] = useState(false);

React.useEffect(() => {
  // Show a loading animation/message while loading
  setLoading(true);

  // Invoke async request
  Axios.get(`/admin/getallads`).then((res) => {
    const ads = res.data.map((item) => item._id);
    // Set some items after a successful response
    setAllAds(ads):
  })
  .catch(e => alert(`Getting data failed: ${e.message}`))
  .finally(() => setLoading(false))
// No variable dependencies means this would run only once after the first render
}, []);

Think of the initial value of useState as something raw that you can set immediately. You know you would be display handling a list (array) of items, then the initial value should be an empty array. useState only accept a function to cover a bit more expensive cases that would otherwise get evaluated on each render pass. Like reading from local/session storage

const [allads, setAllads] = useState(() => {
  const asText = localStorage.getItem('myStoredList');
  const ads = asText ? JSON.parse(asText) : [];
  return ads;
});
kidroca
  • 3,480
  • 2
  • 27
  • 44
-1

You can use the custom hook to include a callback function for useState with use-state-with-callback npm package.

npm install use-state-with-callback

For your case:

import React from "react";
import Axios from "axios";
import useStateWithCallback from "use-state-with-callback";

export default function App() {
  const [allads, setAllads] = useStateWithCallback([], (allads) => {
    let good;
    Axios.get("https://fakestoreapi.com/products").then((res) => {
      good = res.data.map((item) => item.id);
      console.log(good);
      setAllads(good);
    });
  });

  return (
    <div className="App">
      <h1> {allads} </h1>
    </div>
  );
}


Demo & Code: https://codesandbox.io/s/distracted-torvalds-s5c8c?file=/src/App.js

Shankar Ganesh Jayaraman
  • 1,401
  • 1
  • 16
  • 22