0

I have a React app, where I have this method

const fetchPons = () => {
  fetch('http://localhost:8080/pons/')
    .then(r => r.json())
    .then(resp => {
      if (resp.ok === true) {
        setPons(resp.pons);
      } else {
        setErrors(resp.errors);
      }
    })
    .catch(e => console.log(e));
};

And am trying to use it in useEffect like this:

useEffect(() => {
  fetchPons().catch(e => console.log(e));
}, []);

but get

TypeError: fetchPons(...) is undefined

Here's the full code, since I have no idea what can be useful

import React, {useState, useEffect} from 'react';
import './App.css';
import {PonCard} from "./components/PonCard";
import {EnterEditModeButton, ExitEditModeButton} from "./components/buttons/EditButtons";
import {SaveTranslationsButton} from "./components/buttons/SaveTranslationsButton";
import {CancelButton} from "./components/buttons/CancelButton";
import {TranslationsTable} from "./components/TranslationsTable";
import {InputField} from "./components/InputField";
import {TranslateButton} from "./components/buttons/TranslateButton";

function App() {
  const [pons, setPons] = useState();
  const [translations, setTranslations] = useState();
  const [isInEditMode, setIsInEditMode] = useState(false);
  const [inputValue, setInputValue] = useState('');
  const [errors, setErrors] = useState([]);
  const [translationsToSave, setTranslationsToSave] = useState([]);
  const [ponSelectedForEdit, setPonSelectedForEdit] = useState(null);
  const [isInTranslationMode, setIsInTranslationMode] = useState(false);

  const generateFetchParams = (method, body) => ({
    method,
    body,
    mode: 'cors',
    headers: {
      'Content-Type': 'application/json',
    }
  });

  const handleInputChange = (e) => setInputValue(e.target.value);

  const fetchTranslations = async (e, value = inputValue) => {
    const resp = await fetch(`http://localhost:8080/pons/findTranslation/${value}`)
      .then(r => r.json())
      .catch(e => console.log(e));
    if (resp.ok === true) {
      setTranslations(resp.resp[0].hits);
      setErrors([]);
    } else {
      setErrors(resp.errors ? resp.errors : ['Something went wrong. check the input']);
    }
  };

  const handleSaveTranslations = async () => {
    if (isInEditMode) {
      const resp = await fetch(`http://localhost:8080/pons/${ponSelectedForEdit._id}`,
        generateFetchParams('PUT', JSON.stringify({translations: translationsToSave}))).then(r => r.json())
        .catch(e => {
          console.log(e);
          return {ok: false};
        });
      if (resp.errors) {
        setErrors(resp.errors);
      }
    } else {
      const resp = await fetch('http://localhost:8080/pons/',
        generateFetchParams('PUT', JSON.stringify({
          original: inputValue,
          translations: translationsToSave
        }))).then(r => r.json())
        .then(r => r.json())
        .catch(e => {
          console.log(e);
          return {ok: false};
        });
      if (resp.errors) {
        setErrors(resp.errors);
      }
    }
    setInputValue('');
    setTranslations(null);
    setIsInEditMode(false);
    await fetchPons();
  };

  const fetchPons = () => {
    fetch('http://localhost:8080/pons/')
      .then(r => r.json())
      .then(resp => {
        if (resp.ok === true) {
          setPons(resp.pons);
        } else {
          setErrors(resp.errors);
        }
      })
      .catch(e => console.log(e));
  };

  const handleDeletingPon = async (id) => {
    const resp = await fetch(`http://localhost:8080/pons/${id}`, generateFetchParams('DELETE'))
      .then(r => r.json())
      .catch(e => {
        console.log(e);
        return {ok: false};
      });
    if (resp.ok) {
      setPons((prev) => {
        const index = prev.findIndex(elem => elem._id === id);
        return [...prev.slice(0, index), ...prev.slice(index + 1)]
      })
    }
    if (resp.errors) {
      setErrors(resp.errors);
    }
  };

  useEffect(() => {
    fetchPons().catch(e => console.log(e));
  }, []);

  useEffect(() => {
    if (ponSelectedForEdit !== null) {
      const {original, translations} = ponSelectedForEdit;
      setInputValue(original);
      fetchTranslations(null, original).then(() => {
        translations.forEach(translation => setTranslationsToSave(prev => [...new Set([...prev, translation])]));
      }).catch(e => console.log(e));
    }
  }, [ponSelectedForEdit]);

  useEffect(() => {
    setTranslationsToSave([]);
  }, [inputValue]);

  return (
    <div className="App">
      <InputField {...{handleInputChange, inputValue, isInEditMode, setInputValue,}}/>

      <div className="mb-3">
        <TranslateButton {...{inputValue, errors, isInEditMode, fetchTranslations, setIsInTranslationMode}}/>
        {
          !isInEditMode ? (
            <EnterEditModeButton {...{setIsInTranslationMode, setTranslations, setIsInEditMode}}/>
          ) : (
            <ExitEditModeButton {...{
              isInTranslationMode,
              setIsInEditMode,
              setPonSelectedForEdit,
              setTranslations,
              setInputValue,
            }} />
          )
        }

        <SaveTranslationsButton {...{translationsToSave, handleSaveTranslations, setIsInTranslationMode,}}/>

        {
          isInTranslationMode ? (
            <CancelButton {...{setIsInTranslationMode, setTranslations, setInputValue,}}/>
          ) : null
        }
      </div>
      {errors.length > 0 ? errors.map(e => <div key={e}>{e}</div>) : null}
      {
        pons && !translations && inputValue === '' ? pons.map(pon => (
          <PonCard key={Math.random()} {...{pon, isInEditMode, setPonSelectedForEdit, handleDeletingPon}}/>
        )) : null
      }
      {
        translations ?
          <TranslationsTable {...{translations, translationsToSave, setTranslationsToSave}}/>
          : null
      }
    </div>
  );
}

export default App;

Why does this happen and how do I fix this?

Full error:

TypeError: fetchPons(...) is undefined
App/<
C:/Users/aironsid/Documents/Capgemini/NodeJsCourseTask/server/views/src/App.js:108

  105 | };
  106 | 
  107 | useEffect(() => {
> 108 |   fetchPons().catch(e => console.log(e));
  109 | }, []);
  110 | 
  111 | useEffect(() => {

I fixed it by changing fetchPons to

const fetchPons = () => fetch('http://localhost:8080/pons/')
  .then(r => r.json())
  .then(resp => {
    if (resp.ok === true) {
      setPons(resp.pons);
    } else {
      setErrors(resp.errors);
    }
  })
  .catch(e => console.log(e));

But why? Why does it have to return the fetch function?

Alex Ironside
  • 4,658
  • 11
  • 59
  • 119
  • 2
    `fetchPons()` doesn't have any return value, so implicitly the return value is `undefined`. You then try to call `.catch()` on that. – VLAZ May 08 '20 at 11:08
  • Right. Thanks! Brain freeze – Alex Ironside May 08 '20 at 11:09
  • @VLAZ since you were first would you like to add an answer and it'll be selected? – Alex Ironside May 08 '20 at 11:10
  • 1
    Not really sure it's useful information. I'll try to find relevant duplicates. It's a common problem, yes, but just because people keep trying to use the results of a function without a return value. In each case, there is a different thing to be returned. We basically have dupes for any pair of "needs `return`" + "you need X". Many pairs, really. – VLAZ May 08 '20 at 11:12
  • 1
    Off Topic: You could change `const fetchPons = () => fetch('http://localhost:8080/pons/').then(r => r.json()).then(resp => (resp.ok === true) ? resp.pons : Promise.reject(resp.errors));` and use it like `useEffect(() => { fetchPons().then(setPons, setErrors); }, []);` as `fetchPons` is now independant of the Component where it is used. – Thomas May 08 '20 at 11:13

2 Answers2

3

It is because of the how return works in arrow functions.

const fetchPons = () => fetch('http://localhost:8080/pons/')

This returns a Promise.

const fetchPons = () => {
  fetch('http://localhost:8080/pons/')
}

This returns undefined.

Therefore, when you were doing .catch you were getting undefined.

More on arrow function syntax here

Utsav Patel
  • 2,789
  • 1
  • 16
  • 26
1

Your original fetchPons() didn't return anything:

const fetchPons = () => {
    fetch('http://localhost:8080/pons/')
      .then(r => r.json())
      .then(resp => {
        if (resp.ok === true) {
          setPons(resp.pons);
        } else {
          setErrors(resp.errors);
        }
      })
      .catch(e => console.log(e));
  // nothing returned here
};

So fetchPons().catch(e => console.log(e)); must fail because you're calling catch() on undefined.

Quick fix:

const fetchPons = () => {
    return fetch('http://localhost:8080/pons/')
      .then(r => r.json())
      .then(resp => {
        if (resp.ok === true) {
          setPons(resp.pons);
        } else {
          setErrors(resp.errors);
        }
      })
      .catch(e => console.log(e));
  // nothing returned here
};

// or just
const fetchPons = () => fetch('http://localhost:8080/pons/')
    .then(r => r.json())
    .then(resp => {
      if (resp.ok === true) {
        setPons(resp.pons);
      } else {
        setErrors(resp.errors);
      }
    })
    .catch(e => console.log(e))

Or arguably more readable (note that there's no value returned at the end):

const fetchPons = fetch('http://localhost:8080/pons/')
    .then(r => r.json())
    .then(resp => {resp.ok === true ? setPons(resp.pons) : setErrors(resp.errors)})
    .catch(e => console.log(e))
m02ph3u5
  • 3,022
  • 7
  • 38
  • 51