Closed tanmayaBiswalOdiware closed 2 years ago
Hi there! Yeah, what is done in that piece of code in the second link should basically happen automatically in the first link so I'm not sure what's going on... I will check it during the weekend.. I'm positive there is a slick way to accomplish it :)
Ayo, thanks for the heads up. I will keep looking into it but I am not sure if I would find out much. Will be waiting to hear from you still. Thanks for the good work!
Ok I got a solution :) It's a little bit hacky but not too much :)
All of these packages are kind of building on old-style Javascript where you import global variables and use them. By that I mean that both MathJax, EasyMDE and SimpleMDE work in this old-fashioned way and it seems that we're quite limited as to how we can interact with EasyMDE. Nonetheless, it's possible via the previewRender
hook that you showed in one of your links.
Normally, better-react-mathjax
typesets automatically any content it finds. The current usecase is non-standard in many ways:
MathJax
components should lie WITHIN components which hold state. Here we are wrapping the SimpleMdeReact
component INSIDE a MathJax
component. The SimpleMdeReact
component is stateful so in a normal scenario, we couldn't even guarantee that the MathJax
component rerenders when the state changes (which is necessary for typesetting to take place). However, thanks to the onChange
callback, the SimpleEditor
component rerenders and with it the MathJax
component so this is not a problem from that perspective.SimpleMdeReact
component has one textarea
with its content as a property on the HTML textarea
element. Such a string will never be typeset by MathJax since this text is not per se text in an HTML element. On the other hand, there is a HTML element for the preview also inside SimpleMdeReact
which holds the content as text. This is actually what we see in the preview and this is possible to typeset of course. However, it seems that the content of this container is added by SimpleMdeReact
in an effect, thus it is added AFTER the MathJax
component typesets so no matter if typesetting takes place or not, the content will be replaced with the SimpleMdeReact
output from processing the markdown. This content is not typeset by MathJax and I suspect that the order of these effects come from the fast that the MathJax
component wraps the SimpleMdeReact
component so the effect of the MathJax
component is queued before the effect of the SimpleMdeReact
component and therefore any typesetting by MathJax is overwritten. This is the real problem in your usecase and the reason to why it doesn't work out of the box.We CAN hook into the process using the previewRender
like you show in your second link. However, there are quite a few things that differ given the use of React instead of raw Javascript. For once, we can't access the EasyMDE / SimpleMDE instance using this.parent
and the problem is that if we replace the previewRender
option with our own custom renderer, then the markdown will not be rendered as it should which we ALSO want, besides the MathJax typesetting. However, there is the option in SimpleMdeReact
to access the EasyMDE instance which we then can use. The result is a bit verbose because:
SimpleMdeReact
component doesn't rerender more than it should. We also need to store this instance in a ref because we need to pass something to the memoized options that keep referential integrity during the entire lifetime of the component (a React state is therefore not possible).better-react-mathjax
which is nonetheless documented and possible to use for very special scenarios like this one (https://github.com/fast-reflexes/better-react-mathjax#custom-use-of-mathjax-directly). Since the SimpleMdeReact
options can't handle promises which the aforementioned use of better-react-mathjax
will yield, we have to add the MathJax instance to a ref using an effect. Also here we use a ref because we need to be able to access the MathJax context from the previewRender
function via something with referential integrity which a React state does not have.
UPDATE: Due to that we can't use the promise returned by MathJaxBaseContext
, I instead extracted the MathJax object in the MathJaxContext
component using its onStartup
callback. This assigns the MathJax object to a ref which is then passed to the SimpleEditor
component.previewRender
function which looks like this:previewRender: (text) => {
// first process the markdown
const markdownHtml = easyMde.current.markdown(text);
// typeset with MathJax
const container = document.createElement('div');
container.innerHTML = markdownHtml;
props.mathJax.current.typesetClear([container]);
props.mathJax.current.typeset([container]);
return container.innerHTML;
}
Initially both props.mathJax.current
and easyMde.current
will be null
but by the time you hit preview, they will both be set. Otherwise you could add some conditional to handle if they, against all odds, should not be set.
Here is a sandbox that works: https://codesandbox.io/s/better-react-mathjax-35-v8fg5z
Note, I don't have the sandbox system (Stackblitz) that you use and I wasn't too keen to create a new account so I just anonymously used a forked sandbox from your example and then I ported it to CodeSandbox instead. However, there seems to be some problem there using react-simplemde-editor
version 5.2.0 so I had to downgrade it. Should work in any real scenario with the updated version though (and it worked in the anonymous sandbox I used at Stackblitz). Also, I added the MathJaxContext
component to the parent of SimpleEditor
since the MathJaxContext
component shouldn't rerender all the time, it should ideally just render once.
Let me know if this is ok and close the ticket if so.
First - I award you the MVP of the year. This is a lot of work you did. I appreciate the effort.
Second - No, I don't care which sandbox you use if you were willing to help. I actually thought you will prefer codesandbox, but like you mentioned, there is some problem with codesandbox in the latest react-simplemde-editor
so I switched to stackblitz.
And I dont find it hacky at all! I mean sure, these component don't use the React logic anymore, but there is only so much React that could go around hahaha. I really debated that I should just download the MathJax from the CDN but I guess I wanted the 'better' package! Hah, get it. lol anyway...
I don't know if you opened my sandbox again, because I did end up in the same boat as you - use the previewRender
property. But accessing previewRender meant I need to get that MathJax
instance. As you pointed out, there is a way to get the instance with MathJaxBaseContext
but sadly I couldn't understand how to initialize it in my context. Smh. But you also figured out why the wrapping did not work. Its because of useEffect. Makes sense. And you actually used the easymde's
previewer. Because I straight up cut off its default renderer to just import marked
package myself to try and render the markdown with my own config. but again, I did not have the Mathjax instance, so that's where I stopped.
So bonus points for not using more package than needed! 🎉 I will close the ticket. Let me just kick around the code a bit and try to see how it works.
Hope this thread also helps others with integrating this awesome package with other Editors.
Yep, it works with multiple instances of easymde too. Sweet. Now my next objective is to add Math-quill to the toolbar for quick/easy input of math expressions. But I wont be ruining your weekend anymore. Thanks again for the huge help! Happy holidays!
Super glad we got it to work and thanks for the kind words too!
I did use the MathJaxBaseContext
like this first:
const mathJax = useRef(null)
const mathJaxContext = useContext(MathJaxBaseContext)
useEffect(() => {
if(mathJaxContext)
mathJaxContext.promise.then(mathJaxObject => {
mathJax.current = mathJaxObject
})
}, [mathJaxContext])
and it worked too but it was more verbose than the solution using the onStartup
callback so preferred that one :)
Thanks for checking in and good luck with the project! Think this thread might come in handy for others too ;)
Yea, I missed the part where I had to wait for the promise to be fulfilled. Just tried to shove the context and get the result.
That would be a good pointer for anyone who follows the thread. I also did not know I should be using the typeset property. Probably because I don't understand the complete package too well.
Hey, I am back. I do not want to re-open the issue yet, but just wanted a clarification regarding this context approach we took.
<MathJaxContext
onStartup={(mathJax) => {
mj.current = mathJax;
console.log("entering");
}}
config={config}
>
As you can see, I tried to log when onStartup is getting triggered, and it seems like its not 'starting up' when I redirect from another component. It starts up fine when I directly go into the page, but if I use react-router to navigate to the page where MathJaxContext is wrapped, it doesn't start. Meaning there is no typeset property for the children to access -> hence app crash.
The useEffect technique did not work either. 🤷♂️
Should I just wrap the MathJaxContext at the base of the app, like the place where I wrap the BrowserRouter?
You can do it in two ways:
MathJaxContext
, and create a context of your own, let's call it MyMathJaxContext
. Put it somewhere in the same component and set its value to the value taken from the startup call:
// you can use state instead of ref under some circumstances, it all depends on how the rest renders
const [mj, setMj] = useState()
<MathJaxContext .... onStartup={ setMj }>
<MyMathJaxContext.Provider value={ mj }
<App>
</..>
</..>
Then use the context from all your components.
MathJaxBaseContext
. Here is a sandbox showing that method: https://codesandbox.io/s/better-react-mathjax-35-b-02qz7eNo matter how you do it, if you include the MathJaxContext
, then at SOME point, the onStartup
callback should fire, but it will never fire AGAIN once it has fired once so if you include your MathJaxContext
in some place without the onStartup
callback and then include it WITH the callback, then the callback won't fire because MathJax has already been downloaded (it only downloads once during the lifetime of the app). That's why you should really only keep one MathJaxContext
wrapping your entire app (like BrowserRouter
and many other similar components).
Given your usecase, I would use the MathJaxBaseContext
solution because it requires less changes outside the editor component even though it's slightly more verbose.
Then use the context from all your components.
OR.. use the other method with MathJaxBaseContext. Here is a sandbox showing that method: https://codesandbox.io/s/better-react-mathjax-35-b-02qz7e
That's the thing, for some reason MathJaxBaseContext inside useEffect did not work like I expected.
...callback should fire, but it will never fire AGAIN once it has fired once so if you include your MathJaxContext in some place without the onStartup callback and then include it WITH the callback, then the callback won't fire because MathJax has already been downloaded (it only downloads once during the lifetime of the app). That's why you should really only keep one MathJaxContext wrapping your entire app (like BrowserRouter and many other similar components).
As I suspected. So right now, I just drilled some props from the top of the App. This old outdated project is not helping my case either, with class components written all over so I can't even use React hooks (like useRef) so just going to do the job with states.
Yea I should be using useContext Hook more, but drilling the props is just so trivial lol. It seems to work okay now, but I may holler at you once again. Hopefully not though.
Link to the Stackblitz example
As you can see from the example, I have an EasyMDE instantiated. Following which I initialized better-react-mathjax package and wrapped it around the Editor.
Now that is obviously not the way to go, since I just want to render the Math in the preview mode of the Editor (you can see the 👁🗨 icon in the toolbar) but I just wanted to see what happens. Well nothing happened haha.
So any clues as to what I could do from here? Here is a link I found while kicking around on the internet - https://gist.github.com/chooco13/c280c1cc6584c97af85307028ecaebb1 It has SimpleMDE (an origin branch of EasyMDE) configured with MathJax package. But these are not in React.
Any ideas would be helpful here.