user0101
user0101

Reputation: 1517

Redundant piece of code using React hooks

There's a fixed number of settings that determine whether the component should be visible, i.e.:

const restrictions = {
  isLogged: true, // TRUE stands for: check if the condition is met
  hasMoney: true,
  didWinYesterday: false,
}

For each restrictions key, I've created a state with useState and initialized them all with false, like:

const [isUserLogged, setIsUserLogged] = useState(false)
const [hasUserMoney, setHasUserMoney] = useState(false)
const [didUserWinYday, setDidUserWinYday] = useState(false)

Next, I am checking against each condition with useEffect and updating the state accordingly:

useEffect(() => {
 const checkIfUserIsLogged = async () => {
   // calling an API to get boolean
   const isLogged = await API.call()

   setIsUserLogged(isLogged)
 }

  // If the restriction is set to false, ignore checking and set relevant state
  if (!restrictions.isLogged) {
    setIsUserLogged(true)
    return
  }

  checkIfUserIsLogged()
}, [restrictions.isLogged])

Finally, I am checking if I should render the actual component or should I break early like so:

if (!isUserLogged) return <p>User not logged in.</p>

The useEffect code and the check above is repeated 3 times in total. Each of the repetition is making different API call and is updating different state, but the overall structure stays the same. I wish I could do it more DRY but not sure how to get started. Any tips are welcome, thanks!

Upvotes: 0

Views: 194

Answers (2)

user0101
user0101

Reputation: 1517

Here's my take:

import { useState, useEffect } from 'react'

const useCheck = (condition: boolean, performCheck: () => boolean): boolean => {
  const [isConditionMet, setIsConditionMet] = useState<boolean>(false)

  useEffect(() => {
    const check = async () => {
      const isConditionMet = await performCheck()
      setIsConditionMet(isConditionMet)
    }

    if (!condition) {
      setIsConditionMet(true)
    }

    check()
  }, [condition, performCheck])

  return isConditionMet
}

export default useCheck

Usage:

const isUserLogged = useCheck(restrictions.isLogged, () => true) // 2nd parameter should be an API call in my case

Upvotes: 1

AKX
AKX

Reputation: 168957

I'd refactor a single atom into a custom useRestrictionState atom that internally deals with the effect:

const restrictions = {
  isLogged: true, // TRUE stands for: check if the condition is met
  hasMoney: true,
  didWinYesterday: false,
};

function useRestrictionState(restrictionFlag, apiFunc) {
  const [flag, setFlag] = React.useState(undefined);
  useEffect(() => {
    if (!restrictionFlag) {
      setFlag(true);
    } else {
      apiFunc().then((result) => setFlag(result));
    }
  }, [restrictionFlag]);
  return flag;
}

function Component() {
  const isUserLogged = useRestrictionState(restrictions.isLogged, getLoginState);
  const hasUserMoney = useRestrictionState(restrictions.hasMoney, getUserMoney);
  const didUserWinYday = useRestrictionState(restrictions.didWinYesterday, getUserDidWinYesterday);
}

If you always need all of them, you can naturally wrap this in another custom hook:

function useUserRestrictionState(restrictions) {
  const isUserLogged = useRestrictionState(restrictions.isLogged, getLoginState);
  const hasUserMoney = useRestrictionState(restrictions.hasMoney, getUserMoney);
  const didUserWinYday = useRestrictionState(restrictions.didWinYesterday, getUserDidWinYesterday);
  return { isUserLogged, hasUserMoney, didUserWinYday };
}

function Component() {
  const { isUserLogged, hasUserMoney, didUserWinYday } = useUserRestrictionState(restrictions);
}

Upvotes: 2

Related Questions