Closed mattywong closed 3 years ago
Server rendering react-remark components by passing through an initial state value to useRemark
What are you using for serverside rendering? A framework like Gatsby or Next? Something else?
Have tried using the first example in README.md, which creates an infinite loop
Could you share a runnable example of this? for example in https://codesandbox.io
setMarkdownSource uses unified.process which is async
Could you expand why this is an problem?
Next, for example, supports async processes in SSR via getStaticProps
https://nextjs.org/docs/basic-features/data-fetching#getstaticprops-static-generation
Another consideration here, remark
and rehype
plugins can be async, and async plugins can only be used with process
, I'd like to avoid the usage of processSync
if possible to avoid restrictions on what plugins work.
to set the initial state based off a new prop in the useRemark hook e.g initialContent
an initialContent
prop could be a good add.
For now, using react-markdown works fine (which seems to be synchronous) for our use case
FYI, react-markdown v6 will likely be built on top of react-remark.
Server rendering react-remark components by passing through an initial state value to useRemark
What are you using for serverside rendering? A framework like Gatsby or Next? Something else?
We are server rendering in .NET Core via ReactJS.NET
Have tried using the first example in README.md, which creates an infinite loop
Could you share a runnable example of this? for example in https://codesandbox.io
Below example that crashes:
https://codesandbox.io/s/objective-montalcini-cixpt?
setMarkdownSource uses unified.process which is async
Could you expand why this is an problem? Next, for example, supports async processes in SSR via
getStaticProps
https://nextjs.org/docs/basic-features/data-fetching#getstaticprops-static-generation Another consideration here,remark
andrehype
plugins can be async, and async plugins can only be used withprocess
, I'd like to avoid the usage ofprocessSync
if possible to avoid restrictions on what plugins work.
We are using base react in a polyfilled Chakra core runtime on the server. AFAIK there is no way in base react to set an initial state via async in useState constructor (calling setState in a useEffect is not an option as that does not get run in react-dom/server's renderToString method). Perhaps a solution is to have an option to use processSync with process as the default? e.g a synchronous
or async
boolean option passed to useRemark hook? However we would still need a way to set the initial content state so react-dom/server can render it's contents.
https://codesandbox.io/s/objective-montalcini-cixpt this is the equivalent of:
function InfiniteLoop () {
const [state, setState] = useState();
setState({});
return null;
}
each render setState
is called, setState
triggers a render, repeat ad infinitum.
a useEffect
can be used to call set just once https://codesandbox.io/s/stoic-snow-4846x
We are server rendering in .NET Core via ReactJS.NET in a polyfilled Chakra core runtime on the server
Oof, isomorphic react on a non-V8 runtime, with lightly supported language bindings, that doesn't sound fun, sorry.
Perhaps a solution is to have an option to use
processSync
withprocess
as the default? e.g a synchronous or async boolean option passed touseRemark
hook?
It is an option :thinking: And maybe one that could make sense.
Taking a step back from the problem, from a moment, it sounds like you're looking more for a pure static content generator? Would going from markdown to javascript, and saving/running an ahead of time generated component work in your use case? Something along the lines of: https://mdxjs.com/advanced/api (built on remark)
Taking a step back from the problem, from a moment, it sounds like you're looking more for a pure static content generator? Would going from markdown to javascript, and saving/running an ahead of time generated component work in your use case? Something along the lines of: https://mdxjs.com/advanced/api (built on remark)
Unfortunately not as the content is stored in a headless CMS. Currently it handles blog posts in markdown, but we are in the process of extending it out to handle custom pages as well (which may contain JSX and/or HTML), which will be handled by a content team and occasional developer for more complicated markup/layouts.
I'm also not sure if you'd be able to server render on nextjs either, though you're able to set initial component props in getStaticProps
, this still doesn't give a pathway to setting the initial useRemark inner reactContent state (which is null useState(null)
https://github.com/remarkjs/react-remark/blob/main/src/index.ts#L33). Though I haven't tested this in nextjs, my thinking is that it will still render null
from the server, then after client hydration, the useEffect will run replacing with the parsed result from unified. Though you could use unified directly in getStaticProps to get it server rendered, however this sort of defeats the purpose of using this package.
EDIT: I have got an example with nextjs as described above on codesandbox (open the result in a new browser window, disable JavaScript and the text will not be rendered): https://codesandbox.io/s/0-no-persistent-layout-elements-forked-yh5pz?file=/pages/index.js
Result window: https://yh5pz.sse.codesandbox.io/
I'm also not sure if you'd be able to server render on nextjs either
Not currently, I'm waiting for React Server Components and Serverside Suspense to shake out some more to allow for more flexible async components on the serverside.
Unfortunately not as the content is stored in a headless CMS. Currently it handles blog posts in markdown, but we are in the process of extending it out to handle custom pages as well (which may contain JSX and/or HTML), which will be handled by a content team and occasional developer for more complicated markup/layouts.
What would you think of
export interface UseRemarkOptions {
remarkParseOptions?: Partial<RemarkParseOptions>;
remarkToRehypeOptions?: RemarkRehypeOptions;
rehypeReactOptions?: Partial<RehypeReactOptions<typeof createElement>>;
remarkPlugins?: PluggableList;
rehypePlugins?: PluggableList;
}
export const useRemarkSync = (
source: string,
{
remarkParseOptions,
remarkToRehypeOptions,
rehypeReactOptions,
remarkPlugins = [],
rehypePlugins = [],
}: UseRemarkOptions = {}
): ReactElement =>
unified()
.use(remarkParse, remarkParseOptions)
.use(remarkPlugins)
.use(remarkToRehype, remarkToRehypeOptions)
.use(rehypePlugins)
.use(rehypeReact, { createElement, Fragment, ...rehypeReactOptions })
.processSync(source).result as ReactElement;
?
Looks good to me! Would it make sense to wrap it in a useMemo and have source as the dependency? Looks like it could potentially get expensive and block the main thread if the containing parent component gets re-rendered
Popping my head in just to say that this is a blocker for me. I'm using Next.
I just had same issue with SSG on Next.JS.
Using the <Remark>
component would result in texts creating a javascript code instead of HTML tags on source code, which is bad for SEO.
So I ended up creating my own <Markdown>
component with the code snippet from remark-react
:
unified().use(parse).use(remark2react).processSync(children).result}
added in https://github.com/remarkjs/react-remark/pull/18 documentation at https://github.com/remarkjs/react-remark#server-side-rendering
This change will be part of the next release
has this been released yet?
It is ready for release, but hasn't been yet. /cc @remarkjs/releasers
@ChristianMurphy your last comment said “This change will be part of the next release”. Sounds like there is more work to do?
Sounds like there is more work to do?
There is, cutting a minor release.
Released.
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.
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.