18

In class based Component:

componentDidMount() {
    axios.get('https://jsonplaceholder.typicode.com/posts').then((res) => {
        this.setState({
            posts: res.data.slice(0, 10)
        });
        console.log(posts);
    })
}

I tried this:

const [posts, setPosts] = useState([]);

useEffect(() => {
    axios.get('https://jsonplaceholder.typicode.com/posts').then((res) => {
        setPosts(res.data.slice(0, 10));
        console.log(posts);
    })
});

It creates an infinite loop. If I pass a []/{} as the second argument[1][2], then it blocks further call. But it also prevents the array from updating.

[1] Infinite loop in useEffect

[2] How to call loading function with React useEffect only once

Mahmudul Haque
  • 502
  • 2
  • 8
  • 18
  • What do you mean by "If I pass a `[]`/`{}` as the second argument, then it blocks further call". That is what you want to do to only run the function given to `useEffect` after the initial render. – Tholle Nov 05 '18 at 11:58
  • 1
    Yes. I want to run the function once as it's updating posts array again and again. After following the approach you mentioned in other post I got the expected "run useEffect once", but my posts array is still empty. Isn't it supposed to be [post1, post2, ...]. – Mahmudul Haque Nov 05 '18 at 12:35
  • 1
    [It should work to give an empty array as the second argument](https://codesandbox.io/s/0oq7z37v0w). – Tholle Nov 05 '18 at 13:00
  • @Tholle I have tried this already. `setPosts(res.data.slice(0, 10)); console.log(posts);` Output: `Array []`. – Mahmudul Haque Nov 05 '18 at 13:19
  • Yes, `console.log(posts);` will be an empty array because of how closures work in JavaScript, but if you use `posts` in your rendering, it will work as expected. – Tholle Nov 05 '18 at 13:22
  • 3
    Thanks. https://codesandbox.io/s/w0xx6zl5wk?module=%2Fsrc%2FHome.js – Mahmudul Haque Nov 05 '18 at 13:25
  • 1
    @Tholle I have accepted your answer. – Mahmudul Haque Nov 05 '18 at 16:39

3 Answers3

9

Giving an empty array as second argument to useEffect to indicate that you only want the effect to run once after the initial render is the way to go. The reason why console.log(posts); is showing you an empty array is because the posts variable is still referring to the initial array, and setPosts is also asynchronous, but it will still work as you want if used in the rendering.

Example

const { useState, useEffect } = React;

function App() {
  const [posts, setPosts] = useState([]);

  useEffect(() => {
    setTimeout(() => {
      setPosts([{ id: 0, content: "foo" }, { id: 1, content: "bar" }]);
      console.log(posts);
    }, 1000);
  }, []);

  return (
    <div>{posts.map(post => <div key={post.id}>{post.content}</div>)}</div>
  );
}

ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.production.min.js"></script>

<div id="root"></div>
Tholle
  • 108,070
  • 19
  • 198
  • 189
5

You can check how axios-hooks is implemented.

It's super simple and uses the config object (or url) you provide to decide when to make a request, and when not to, as explained in Tholle's answer.

In addition to allowing you to use the awesome axios in your stateless functional components, it also supports server side rendering, which - it turns out - hooks make very straightforward to implement.

Disclaimer: I'm the author of that package.

Simone
  • 3,607
  • 1
  • 31
  • 37
  • The package is awesome! although I wonder how to not send request when useAxios is called for the first time ? since you will never POST something before you submit the form. – Min May 13 '19 at 03:50
  • @Min it's not currently supported but it would be hopefully simple to implement. Would you create an issue on the project github page? PRs welcome too :) – Simone May 13 '19 at 09:50
2

I've written a Custom Hooks for Axios.js.

Here's an example:

import React, { useState } from 'react';

import useAxios from '@use-hooks/axios';

export default function App() {
  const [gender, setGender] = useState('');
  const {
    response,
    loading,
    error,
    query,
  } = useAxios({
    url: `https://randomuser.me/api/${gender === 'unknow' ? 'unknow' : ''}`,
    method: 'GET',
    options: {
      params: { gender },
    },
    trigger: gender,
    filter: () => !!gender,
  });

  const { data } = response || {};

  const options = [
    { gender: 'female', title: 'Female' },
    { gender: 'male', title: 'Male' },
    { gender: 'unknow', title: 'Unknow' },
  ];

  if (loading) return 'loading...';
  return (
    <div>
      <h2>DEMO of <span style={{ color: '#F44336' }}>@use-hooks/axios</span></h2>
      {options.map(item => (
        <div key={item.gender}>
          <input
            type="radio"
            id={item.gender}
            value={item.gender}
            checked={gender === item.gender}
            onChange={e => setGender(e.target.value)}
          />
          {item.title}
        </div>
      ))}
      <button type="button" onClick={query}>Refresh</button>
      <div>
        {error ? error.message || 'error' : (
          <textarea cols="100" rows="30" defaultValue={JSON.stringify(data || {}, '', 2)} />
        )}
      </div>
    </div>
  );
}

You can see the result online.

int64ago
  • 21
  • 2