-1

I started having fun with custom hooks recently. I am mostly using them to fetch from api. The thing is that since I cannot really put useFetchLink inside of functions or useEffect i dont know how to prevent it from fetching after website first render. I could put some ifs in the hook but isn't there any other way?

***component*** 

export default function LinkShortener({ setLinkArr }) {
  const [nextLink, setNextLink] = useState();
  const inputRef = useRef(null);

  const handleClick = () => {
    setNextLink(inputRef.current.value);
  };

  const { shortLink, loading, error } = useFetchLink(nextLink);

  useEffect(() => {
    setLinkArr((prev) => [
      ...prev,
      {
        id: prev.length === 0 ? 1 : prev[prev.length - 1].id + 1,
        long: nextLink,
        short: shortLink,
      },
    ]);
    inputRef.current.value = "";
  }, [shortLink, error]);

  return (
    <LinkShortenerContainer>
      <InputContainer>
        <LinkInput ref={inputRef} type="text" />
      </InputContainer>
      <Button
        size={buttonSize.medium}
        text={
          loading ? (
            <Loader />
          ) : (
            <FormattedMessage
              id="linkShortener.shortenItBtn"
              defaultMessage="Shorten It !"
            />
          )
        }
        onClick={handleClick}
      ></Button>
    </LinkShortenerContainer>
  );
}
***hook***
const useFetchLink = (linkToShorten) => {
  const [shortLink, setShortLink] = useState("");
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState("");

  const fetchLink = async () => {
    setLoading(true);
    try {
      const response = await fetch(
        `https://api.shrtco.de/v2/shorten?url=${linkToShorten}`
      );
      if (response.ok) {
        const data = await response.json();
        setShortLink(data.result.short_link);
      } else {
        throw response.status;
      }
    } catch (err) {
      setError(err);
    } finally {
      setLoading(false);
    }
  };

  useEffect(() => {
    fetchLink(linkToShorten);
  }, [linkToShorten]);

  const value = { shortLink, loading, error };
  return value;
};```
  • 1
    An `if` in the hook is exactly what you'd want. – AKX Sep 11 '22 at 19:06
  • if `linkToShorten` doesn't change the effect won't fire – Giorgi Moniava Sep 11 '22 at 19:09
  • But linkToShorten is provided with nextLink state and its being set at first render to "" –  Sep 11 '22 at 19:11
  • What @GiorgiMoniava said, but it would be helpful to know the entire flow of your example by seeing the JSX code as well. You could create it in CodeSandbox, StackBlitz or something similar. – silvenon Sep 11 '22 at 19:14
  • @AglecikNaplecik what do you mean by "first render"? I bet the problem is hidden somewhere in the rest of the code. – silvenon Sep 11 '22 at 19:23
  • Also, just to be clear, by "stop invoking custom hook" you're referring only to calling the API, i.e. the hook's `useEffect`, correct? Because you shouldn't prevent the entire hook from being called, so I'm just checking. – silvenon Sep 11 '22 at 19:26
  • I have put whole component code in post. You are right, I was referring to calling the API. I want to call an API only when state nextLink is being set. Then I want to take api response and update linkArr with it. LinkArr is rendering components via map() in different component but right now linkArr is being updated after every site refresh. –  Sep 11 '22 at 20:49

2 Answers2

1

Generally speaking - the standard way to avoid useEffect from running of 1st render is to use a boolean ref initialized with false, and toggled to true after first render - see this answer.

However, in your case, you don't want to call the function if linkToShorten is empty, even if it's not the 1st render, so use an if inside useEffect.

const useFetchLink = (linkToShorten) => {
  const [shortLink, setShortLink] = useState("");
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState("");

  const fetchLink = useCallback(async (linkToShorten) => {
    setLoading(true);
    try {
      const response = await fetch(
        `https://api.shrtco.de/v2/shorten?url=${linkToShorten}`
      );
      if (response.ok) {
        const data = await response.json();
        setShortLink(data.result.short_link);
      } else {
        throw response.status;
      }
    } catch (err) {
      setError(err);
    } finally {
      setLoading(false);
    }
  }, []);

  useEffect(() => {
    if(linkToShorten) fetchLink(linkToShorten);
  }, [fetchLink, linkToShorten]);

  const value = { shortLink, loading, error };
  return value;
};
Ori Drori
  • 183,571
  • 29
  • 224
  • 209
0

Why not using directly fetchLink function and calling it whenever you need inside the component? I would change the hook in this way without useEffect inside

const useFetchLink = (linkToShorten) => {
  const [shortLink, setShortLink] = useState("");
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState("");

  const fetchLink = async () => {
    setLoading(true);
    try {
      const response = await fetch(
        `https://api.shrtco.de/v2/shorten?url=${linkToShorten}`
      );
      if (response.ok) {
        const data = await response.json();
        setShortLink(data.result.short_link);
      } else {
        throw response.status;
      }
    } catch (err) {
      setError(err);
    } finally {
      setLoading(false);
    }
  };

  const value = { shortLink, loading, error, fetchLink };
  return value;
};
Evren
  • 4,147
  • 1
  • 9
  • 16