1

I am trying to fetch a single object, and it fetches, I get correct data in response.data, but i can't set it to state. It just remains as a null object. What am I doing wrong? Post is a json object with several fields like: post_id, post_title, post_content and etc.

const [post, setPost] = useState({})

let id = match.params.id

useEffect(() => {
     axios.get(`${BASE_URL}/post?id=${id}`)
     .then(response => {
        console.log(response.data)
        setPost(response.data)
     })
     .then(() => {
            console.log("post: ", post)
        }
     )
a b
  • 947
  • 1
  • 6
  • 7

3 Answers3

1

setAction like your setPost are asynchronous, as stated in the official REACT documentation (https://reactjs.org/docs/hooks-reference.html#usestate); this means that, once the setAction is executed, you don't know when it will be actually executed and terminated: you will know because the component will re-render.

In your case, if you'd like to perform action AFTER post has got the new value, you would need the useEffect hook (https://reactjs.org/docs/hooks-reference.html#useeffect):

React.useEffect(() => {
  console.log(post);
}, [post]);

By the way, I think you would want the data inside the response, so you would probably save the JSON retrieved from the body of the HTTP Response, that you can get using response.json().

EDIT: As stated in the comment from Siradji Awoual, what I wrote about response and response.json() is not valid for Axios (but it still is for fetch API).

Jolly
  • 1,678
  • 2
  • 18
  • 37
  • Actually, you don't need to do that when using axios. That will be true if you use something like fetch. – Suraj Auwal May 10 '20 at 15:24
  • Oh sorry, while writing the answer I lost that particular (using Axios). I'm gonna edit the asnwer. – Jolly May 10 '20 at 16:14
0

Setting a state is asynchronous. That means you don't know exactly when that action will finish executing.

If I were you, I would use something like useEffect to check if the state is being set.


React.useEffect(() => console.log(post), [post])

Suraj Auwal
  • 322
  • 3
  • 9
0

Using axios.get is low-level and requires that you hook up a bunch of extra stuff to get things working correctly. Instead, try writing custom hooks to abstract this logic away -

const identity = x => x

const useAsync = (runAsync = identity, deps = []) => {
  const [loading, setLoading] = useState(true)
  const [error, setError] = useState(null)
  const [result, setResult] = useState(null)

  useEffect(_ => { 
    Promise.resolve(runAsync(...deps))
      .then(setResult, setError)
      .finally(_ => setLoading(false))
  }, deps)

  return { loading, error, result }
}

Using useAsync looks like this -

const MyApp = () => {
  const { loading, error, result } =
    useAsync(_ => axios.get("./foo.json").then(res => res.json()))

  if (loading)
    return <p>loading...</p>

  if (error)
    return <p>error: {error.message}</p>

  return <pre>result: {result}</pre>
}

But you will probably have many components that fetch JSON, right? We can make an even higher level custom hook, useJSON that is a specialization of useAsync -

const fetchJson = (url = "") =>
  axios.get(url).then(r => r.json()) // <-- stop repeating yourself

const useJson = (url = "") =>
  useAsync(fetchJson, [url]) // <-- useAsync

const MyApp = () => {
  const { loading, error, result } =
    useJson("./foo.json")  // <-- dead simple

  if (loading)
    return <p>loading...</p>

  if (error)
    return <p>error: {error.message}</p>

  return <pre>result: {result}</pre>
}

See the custom hooks in action in this functioning code snippet -

const { useState, useEffect } =
  React

// fake fetch slows response down so we can see loading
const _fetch = (url = "") =>
  fetch(url).then(x =>
    new Promise(r => setTimeout(r, 2000, x)))

const identity = x => x

const useAsync = (runAsync = identity, deps = []) => {
  const [loading, setLoading] = useState(true)
  const [error, setError] = useState(null)
  const [result, setResult] = useState(null)

  useEffect(_ => { 
    Promise.resolve(runAsync(...deps))
      .then(setResult, setError)
      .finally(_ => setLoading(false))
  }, deps)

  return { loading, error, result }
}

const fetchJson = (url = "") =>
  _fetch(url).then(r => r.json())

const useJson = (url = "") =>
  useAsync(fetchJson, [url])

const MyComponent = ({ url = "" }) => {
  const { loading, error, result } =
    useJson(url)

  if (loading)
    return <pre>loading...</pre>

  if (error)
    return <pre style={{color: "tomato"}}>error: {error.message}</pre>

  return <pre>result: {JSON.stringify(result, null, 2)}</pre>
}

const MyApp = () =>
  <main>
    ex 1 (success):
    <MyComponent url="https://httpbin.org/get?foo=bar" />

    ex 2 (error):
    <MyComponent url="https://httpbin.org/status/500" />
  </main>

ReactDOM.render(<MyApp />, document.body)
pre {
  background: ghostwhite;
  padding: 1rem;
  white-space: pre-wrap;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
Mulan
  • 129,518
  • 31
  • 228
  • 259