Skip to content

Server rendering/setting initial markdown state #16

Closed
@mattywong

Description

@mattywong

Subject of the feature

Server rendering react-remark components by passing through an initial state value to useRemark, or using the component's children.

Describe your issue here.

Problem

The useRemark hook's reactContent initial state is null. It looks like the only way to update this state is by using the exposed setMarkdownSource method.

The component sets the state of reactContent in a useEffect calling setMarkdownSource(children) which does not get executed on the server (by calling react-dom/server renderToString).

Alternatives

What are the alternative solutions? Please describe what else you have considered?

Have tried using the first example in README.md, which creates an infinite loop.

Looking through the source code, potential solutions:

setMarkdownSource uses unified.process which is async. We could create a synchronous function which uses unified.processSync to set the initial state based off a new prop in the useRemark hook e.g initialContent, though as I understand, if a provided remark plugin is async, this will throw an error.

I haven't tested this in a server rendered environment, however running this projects storybook seems to work ok with the following.

export const useRemark = ({
  ...,
  initialContent,
}: UseRemarkOptions = {}): [ReactElement | null, (source: string) => void] => {
  const initialParser = useCallback((source: string) => {
    return unified()
      .use(remarkParse, remarkParseOptions)
      .use(remarkPlugins)
      .use(remarkToRehype, remarkToRehypeOptions)
      .use(rehypePlugins)
      .use(rehypeReact, { createElement, Fragment, ...rehypeReactOptions })
      .processSync(source).result as ReactElement;
  }, []);

  const [reactContent, setReactContent] = useState<ReactElement | null>(
    () => initialContent ? initialParser(initialContent) : null
  );

  const setMarkdownSource = useCallback((source: string) => {
    unified()
      .use(remarkParse, remarkParseOptions)
      .use(remarkPlugins)
      .use(remarkToRehype, remarkToRehypeOptions)
      .use(rehypePlugins)
      .use(rehypeReact, { createElement, Fragment, ...rehypeReactOptions })
      .process(source)
      .then((vfile) => setReactContent(vfile.result as ReactElement))
      .catch(onError);
  }, []);

  return [reactContent, setMarkdownSource];
};



export const Remark: FunctionComponent<RemarkProps> = ({
  children,
  ...useRemarkOptions
}: RemarkProps) => {
  const [reactContent, setMarkdownSource] = useRemark({
    ...useRemarkOptions,
    initialContent: children,
  });

  useEffect(() => {
    setMarkdownSource(children);
  }, [children, setMarkdownSource]);

  return reactContent;
};

For now, using react-markdown works fine (which seems to be synchronous) for our use case, however this project seems to align closer to our preference for parsing architecture).

Being able to server render initial content would be a useful feature.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions