Define a functional component inside storybook preview

Viewed 2138

I have a custom modal component as functional component and in typescript. This modal component exposes api's through context providers and to access them, I'm using useContext hook.

const { openModal, closeModal } = useContext(ModalContext);

Example code on how I use this api's:

const TestComponent = () => {
    const { openModal, closeModal } = useContext(ModalContext);
    const modalProps = {}; //define some props
    const open = () => {
        openModal({...modalProps});
    }
    return (
       <div>
          <Button onClick={open}>Open Modal</Button>
       </div>
    )
}

And I wrap the component inside my ModalManager

<ModalManager>
    <TestComponent />
</ModalManager>

This example works absolutely fine in my Modal.stories.tsx

Problem: But this doesn't work inside my Modal.mdx. It says I cannot access react hooks outside functional component. So, I need to define a TestComponent like component to access my modal api's from context. How to define it and where to define it so that below code for preview works?

import {
 Props, Preview, Meta
} from '@storybook/addon-docs/blocks';
<Meta title='Modal' />
<Preview
  isExpanded
  mdxSource={`
    /* source of the component like in stories.tsx */
  `}
>
  <ModalManager><TestComponent /></ModalManager>
</Preview>
2 Answers

I'm not sure if this is a hack or the only way. I created the TestComponent in different tsx file and then imported it in mdx. It worked.

You may have a utility HOC to render it inside a MDX file as below

HOCComp.tsx in some Utils folder

import React, { FunctionComponent, PropsWithChildren } from 'react';

export interface HOCCompProps {
  render(): React.ReactElement;
}

const HOCComp: FunctionComponent<HOCCompProps> = (props: PropsWithChildren<HOCCompProps>) => {
  const { render } = props;
  return render();
};

export default HOCComp;

Inside MDX File

import HOCComp from './HOC';

<HOCComp render={()=>  {
    function HOCImpl(){
      const [count,setCount] = React.useState(180);
      React.useEffect(() => {
        const intId = setInterval(() => {
          const newCount = count+1;
          setCount(newCount);
        },1000)
        return () => {
          clearInterval(intId);
        }
      })
      return <Text>{count}</Text>
    }
    return <HOCImpl />
}}
/>
Related