H5P Instance is duplicated in reactjs

126 views Asked by At

I'm developing with h5p standalone plugin in react (nextjs), passing the path as prop to a Modal Component which render the h5p activity.

useEffect(() => {
    const initH5p = async (contentLocation) => {
    const { H5P: H5PStandalone } = require('h5p-standalone')
    const h5pPath = `https://cdn.thinkeyschool.com/h5p/${contentLocation}`

    const options = {
      id: 'THINKeyLesson',
      h5pJsonPath: h5pPath,
      frameJs: '/h5p/dist/frame.bundle.js',
      frameCss: '/h5p/dist/styles/h5p.css',
    }
    let element = document.getElementById('h5p_container')
    removeAllChildNodes(element)
    await new H5PStandalone(element, options)

    fireCompleteH5PTopic(H5P)
    setIsLoaderVisible(false)
    }
    initH5p(location)
  }, [location, session.data.user.id, course.slug, topic])

With that code, I get two h5p rendered in screen. So I'm using removeAllChildren() to eliminate them from the render.

 function removeAllChildNodes(parent) {
    console.log(parent)
    while (parent.firstChild) {
      parent.removeChild(parent.firstChild)
    }
  }

That hack is working fine, but when I try to send the xAPI statement to my database, it fires twice

const fireCompleteH5PTopic = async (H5P) => {
    H5P.externalDispatcher.on("xAPI", (event) => {
      // console.log('event fired')
      if (event?.data?.statement?.result?.completion) {
        setCounter(counter + 1)
        completeH5PTopic(event, session.data.user.id, course.slug, topic)
        return true
      }
    })
  }

Any help regarding why it fires twice? I think it may be related to h5p rendering twice too.

Thanks in advance.

I tried using a state to render only once, but it is not working.

1

There are 1 answers

2
Diego González Cruz On

I ended up doing this:

import { useEffect, useMemo, useState } from 'react'
import { Loading } from '../../../Animations/Loading'
import { completeH5PTopic } from '../../../../lib/sinapsis/gamification'
import { useSession } from 'next-auth/react' // ES6
import ToastGamification from '../../../Notificaciones/ToastGamification'
import { toast } from 'react-hot-toast'
import { useRef } from 'react'

const POINTS_PER_TOPIC_COMPLETED = 5

const H5PContainer = ({ location, course, topic }) => {
  const H5PStandalone = useMemo(() => require('h5p-standalone').H5P, [])

  const [isLoaderVisible, setIsLoaderVisible] = useState(true)

  let firstTime = true
  const h5pPath = `https://mycdn.com/h5p-v1/${location}`
  const { data } = useSession()
  const user = data?.user
  // console.log(user, 'user')
  const name = ''
  const options = {
    h5pJsonPath: h5pPath,
    frameJs: '/h5p/dist/frame.bundle.js',
    frameCss: '/h5p/dist/styles/h5p.css',
  }
  const h5pRef = useRef(null)

  // console.log('H5PStandalone', H5PStandalone)
  const loadH5PComponent = async () => {
    if (h5pRef.current && window) {
      await new H5PStandalone(h5pRef.current, options)
      setIsLoaderVisible(false)

      window.H5P.externalDispatcher.on('xAPI', (event) => {
        //do something useful with the event
        // console.log(event, 'event')
        // console.log('results:', event.getScore(), '---->', name)
        if (
          event.getScore() > 1 ||
          event.data.statement.verb.display['en-US'] === 'completed'
        ) {
          fireCompleteH5PTopicFn(
            window.H5P,
            user?.id,
            course?.slug,
            topic?.slug,
          )
        }
      })
    }
  }
  useEffect(() => {
    if (firstTime) {
      const hasChildren = h5pRef.current.children
      if (hasChildren.length !== 0) {
        const parent = hasChildren[0].parentNode
        parent.removeChild(hasChildren[0])
      }

      loadH5PComponent()
      firstTime = false
      // setIsLoaderVisible(false)
    }
  }, [name])

  return (
    <div className="w-full h-full ">
      {isLoaderVisible && <Loading />}
      <div id={`h5p-container-${location}`} ref={h5pRef} />
    </div>
  )
}

export default H5PContainer

async function fireCompleteH5PTopicFn(userId, courseSlug, topic) {
  const res = await completeH5PTopic(userId, courseSlug, topic)
  toast.dismiss()
  if (res.status === 200) {
    toast.custom((t) => (
      <ToastGamification
        isVisible={t.visible}
        onDismiss={() => toast.dismiss(t.id)}
        points={POINTS_PER_TOPIC_COMPLETED}
      />
    ))
  }
  return true
}