1

What I need: In my component I need to show a loading spinner when a value from useContext changes. I found a solution but I do not really like it.

const MyComponent = () => {
   const { stateDB } = useDB // custom useContext hook
   // stateDB is an array of objects
   const [loading, setLoading] = useState(false)

   useEffect(() => {
       // I need to show loading each time the stateDB changes
       // but not each time the component will mount
       setLoading(true)
       setTimeout(() => {
           setLoading(false)
       }, 2000)
   }, [stateDB])

   return(
   <>
    { loading ? <LoadingComponent/> : {stateDB.someValue} }
   </>
)
}

If I do it like that then the useEffect is gonna be called each time when the component will be mounted even if the stateDB will not be changed. So my question is: is there a way how call this useEffect only then when the value from the useContext will change ? As the useContext hook each time returns new object so useEffect considers it as different from the previous one.

Inside useDB I have:

const DBContext = React.createContext();

export const useDB = () => {
    return useContext(DBContext);
}

export const DBProvider = ({children}) => {

// the state i need in my component
const [ state, setState ] = useState([]) // default empty array

const fetchSomeData = async () => {
   const response = await db...// fetchin data here
      .then( () => setState(fetchedData) )
   return () => response()
}

useEffect( () => {
    fetchSomeData() // fetch the data only once
}, [])


value = { state } // the state I need in my component

return <DBContext.Provider value={value}>
         {children}
    </DBContext.Provider> 
}

the solution I found out:

// in my component
let flag = true;
const MyComponent = () => {
   const { stateDB } = useDB 
   const [loading, setLoading] = useState(false)

   useEffect( () => {
      if (flag) {
       flag = false;
       setLoading(true)
       setTimeout(() => {
            setLoading(false)
        }, 1200)
       }
   }, [flag])

   return(
   <>
    { loading ? <LoadingComponent/> : {stateDB.someValue} }
   </>
)
}

Nichita
  • 11
  • 1
  • 2
  • you should memoize it inside `useDB` – Dupocas Nov 15 '20 at 18:29
  • The solution I found out , is adding a new variable outside component and set it to true, and in the useEffect check if this flag variable is true then show useTimeout and show loading and set the flag to false, in that manner when the component will be mounted again the useEffect will check if the flag is true but it will be false and the loading component will not be showed again – Nichita Nov 15 '20 at 18:29
  • Dupocas, how do I memoize a state inside my context ? – Nichita Nov 15 '20 at 18:53
  • Share the code for `useDB` – Dupocas Nov 15 '20 at 19:04
  • @Dupocas please take a look now ! – Nichita Nov 15 '20 at 19:20
  • This seems only tangentially related to `useContext`, when really what you're asking is how to prevent `useEffect` being fired on the initial render, which you've already discovered for yourself. Although it's better to use a `useRef` to keep everything React-friendly - see [this thread](https://stackoverflow.com/questions/53179075/with-useeffect-how-can-i-skip-applying-an-effect-upon-the-initial-render). Alternatively, why not just initialise `state` with some neutral value like `null` that you can check against for the first render? – lawrence-witt Nov 15 '20 at 20:04

0 Answers0