chanind / hanzi-writer

Chinese character stroke order animations and practice quizzes
https://hanziwriter.org
MIT License
3.55k stars 555 forks source link

Integrating with React components #183

Closed ryougi1 closed 4 years ago

ryougi1 commented 4 years ago

I'm having some issues with using HanziWriter inside my React component. It seems that the div element passed to the .create method is not what it expects. Simplified source code and error message:

... imports

... styled components 

const Loader = ({ finishLoading }) => {
  const ct1 = <div></div>;

  const writer = HanziWriter.create(ct1, '我', {
    width: 250,
    height: 250,
    padding: 5,
  });

  const [isMounted, setIsMounted] = useState(false);

  useEffect(() => {
    const timeout = setTimeout(() => setIsMounted(true), 10);
    writer.animeCharacter();
    return () => clearTimeout(timeout);
  }, []);

  return (
    <StyledContainer className="loader">
      <Helmet bodyAttributes={{ class: `hidden` }} />
      <StyledLogo isMounted={isMounted}>{ct1}</StyledLogo>
    </StyledContainer>
  );
};

export default Loader;

The error:

TypeError: Cannot read property 'toUpperCase' of undefined
var nodeType = elm.nodeName.toUpperCase();
if (nodeType === 'SVG' || nodeType === 'G') {
  svg = elm;
} else {
  svg = createElm('svg');
  elm.appendChild(svg);
 }

I have also tried passing a styled component as argument to .create, which did not work.

Am I going about this the wrong way? Thanks in advance.

chanind commented 4 years ago

I think the issue is that React components aren't actual DOM nodes. To do that, you'll need to use a ref (https://reactjs.org/docs/refs-and-the-dom.html), which should give you a reference to the actual dom element so HanziWriter can render into it.

ryougi1 commented 4 years ago

@chanind Thanks David, your suggestion was spot on. For anyone else wondering, the setup with useRef ended up looking like this:

const Loader = ({ finishLoading }) => {
  const divRefGu = useRef();

  useEffect(() => {
    ...
    if (mounted) {
      const gu = HanziWriter.create(divRefGu.current, '古', {
       ...
      });

      gu.animateCharacter({
        onComplete: () => {
           setTimeout(() => {
             finishLoading();
           }, 200);
        },
      });

      return () => (mounted = false);
    }
  }, []);

  return (
    <StyledContainer className="loader">
      <Helmet bodyAttributes={{ class: `hidden` }} />
       ...
      <div ref={divRefGu}></div>
      ...
    </StyledContainer>
  );
};

export default Loader;
chanind commented 4 years ago

Thanks for sharing the solution @ryougi1! Glad to hear it works!