简体   繁体   中英

useSWR hook behaving erratically or possible stale closure

I have a React functional component that renders a Lesson Video Player in a very similar way to the Instagram Stories. Some of those videos ("clips") in a lesson have "interactions" that pop up a card where the user answers a multiple-choice quiz. The code is simplified below.

On random lessons some interactions are not showing. In the log interactions 1 they show up as usual (a bunch of undefined values are logged and then after a few rerenders the array is logged) but when the onClipEnding function is called interactions 2 is undefined again.

Any clues on what might be happening? My best guess is a stale closure but I can't find a way to solve it.

export function getInteractions(lesson_id: string, profile_id?: string) {
  const { data, error } = useSWR<ManyResponse<Interaction>>(
    `${apiRoutes.interactions}/${lesson_id}?per_page=50${
      profile_id ? `&profile_id=${profile_id}` : ''
    }`,
    request,
  )

  return {
    interactions: data && data.data,
    isLoading: !data && !error,
    error,
  }
}

export default function LessonPlayer({ videoIds }) {
  const setVideos = useStoreActions((actions: Actions<Store>) => actions.setVideos)
  const { interactions } = getInteractions(lessonId, currentProfileId)
  console.log('interactions 1', interactions)

  useEffect(() => {
    if (!videoIds && videos) {
      setVideos(videos)
    }
  }, [videoIds, setVideos])

  return (
    <>
      <div>
        {(videoIds || []).map((videoId) => (
          <Video key={videoId} videoId={videoId} onEnd={onClipEnding} />
        ))}
      </div>
      {interactions && (
        <div className="absolute bottom-0 w-full">
          <InteractionCard interaction={interaction} handleInteraction={handleInteraction} />
        </div>
      )}
    </>
  )

  function onClipEnding(videoId: string) {
    const clipInteraction = interactions && interactions.find((item) => item.clip_id == videoId)
    console.log('interactions 2', interactions)
    if (clipInteraction) {
      setInteraction(clipInteraction)
    } else {
      nextClip({ profile_id: currentProfileId, status: 'completed' })
    }
  }

It's the stale closure of onClipEnding , that's created at initial render, which captures the interactions variable of value undefined , and then passed as a callback down to <Video /> via its onEnd prop. Down there it's kept as the stale version at initial render and never updated until called.

Since you're aware of stale closure problem, I believe above info should be adequate for you to debug. I'll leave the rest to you.

Bonus: I share with you my secret weapon, a panacea for stale closure problems. Entering the useFn custom hook:

function useFn(fn) {
  const ref = useRef(fn);
  ref.current = fn;

  function wrapper() {
    return ref.current.apply(this, arguments)
  }

  return useRef(wrapper).current
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM