1

I'm trying to load data from Airtable with the javascript API. But when I try to setState of data inside of a promise block, the promise doesn't 'end'.

Airtable API stuff:

export const listRecords = function() {
  const Airtable = require('airtable');
  const base = new Airtable({ apiKey: apiKey }).base(baseId);

  const list = base(tableName)
  .select({ view: 'Grid view' }).all().then(records => {
    return records;
  }).catch(err => {
    if (err) {
        console.error(err);
        return null;
    }
  });

  return list;
};

Setting state stuff:

const [state, setState] = useState({
    records: []
});

Promise.all([listRecords()]).then((v) => {
    setState((s) => ({ ...s, records: v }));
});

Then when I put console.log before, inside, or after the promise statement, they get called in an unending loop. So my question is, how do I make it so the promise is only called/done once?

Maxim Mazurok
  • 3,856
  • 2
  • 22
  • 37
  • Can you try returning `base(tableName).select({ view: 'Grid view' }).all()` in `listRecords()`? – Maxim Mazurok Jun 23 '20 at 03:48
  • I've tried that (I think lol), I think the Airtable code is honestly a bit misleading cause I'm thinking the main problem is how I setState inside the promise. – Sir Poohbher Jun 23 '20 at 13:31

2 Answers2

0

In your example, it looks like you are calling promise without using useEffect. Any API calls and state updates should be called inside useEffect. Otherwise, there is going to be infinite number of updates, because setState rerender component and call promise again, over and over again.

Maxim Mazurok
  • 3,856
  • 2
  • 22
  • 37
  • I added the useEffect but I'm still having the same issue: `useEffect(() => { Promise.all([listRecords()]).then((v) => { setState((s) => ({ ...s, records: v })); }); });` – Sir Poohbher Jun 23 '20 at 13:18
  • Sorry about that comment's formatting, essentially i just wrapped the promise in this: `useEffect(() => {});` – Sir Poohbher Jun 23 '20 at 15:08
  • You need to pass an array as your last argument to `useEffect` to specify which values when different should cause a re-render. If you don't pass any array, then it'll run the effect on every render, causing another render/setState loop. – Jacob Jun 24 '20 at 01:05
0

If you only want your Promise to run on mount, place this in an effect with an empty dependencies Array:

function YourComponent(props) {
  const [state, setState] = useState({
    records: []
  });

  useEffect(() => {
    Promise.all([listRecords()]).then((v) => {
      setState((s) => ({ ...s, records: v }));
    });
  }, []);
}

The [] means that the effect will only run once, on initial mount. If you want listRecords() to be called when other values change, place those values inside of the Array:

function YourComponent({ id }) {
  const [state, setState] = useState({
    records: []
  });

  useEffect(() => {
    Promise.all([listRecords(id)]).then((v) => {
      setState((s) => ({ ...s, records: v }));
    });
  }, [id]);
}
Jacob
  • 77,566
  • 24
  • 149
  • 228