How to apply MathJax/KaTex to render a React component - reactjs

I am making a web editor using React & SlateJS. There are LaTex code in the editor content and I want the user to see the rendered LaTex equations. MathJax and KaTex have auto-rendering feature by loading them as CDNs. Once they are loaded, the content on html body is rendered. But they are not live-rendering when I modify the content.
So I have made a button that opens a modal which renders the un-editable edior content in a smaller window, and I want the LaTex codes to be rendered in the modal.
The APP component:
import {Editor} from 'slate-react';
import ReactModel from 'react-modal';
import RenderedEditorDialog from "./RenderedEditorDialog";
class APP extends React.component {
...
render() {
return (
<div className={"editorContainer"}>
<div className={"editor"}>
<Editor
autoFocus
ref={this.ref}
value={this.state.value}
onChange={this.onChange}
onKeyDown={this.onKeyDown}
renderMark={this.renderMarks}
renderBlock={this.renderBlock}
/>
</div>
<ReactModal
isOpen={this.state.showMathDialog}
contentLabel="Rendered content"
onRequestClose={this.handleCloseMathDialog}
>
<button onClick={this.handleCloseMathDialog}>Close Dialog</button>
<RenderedEditorDialog value={this.state.value}/>
</ReactModal>
</div>
)
}
}
RenderedEditorDialog (modal) component:
import {Editor} from 'slate-react';
class RenderedEditorDialog extends React.Component {
// eslint-disable-next-line no-useless-constructor
constructor(props) {
super(props);
}
render() {
return (
<div>
<Editor
value={this.props.value}
renderMark={this.renderMarks}
renderBlock={this.renderBlock}/>
</div>
)
}
}
My question is how I can apply MathJax/KaTex to render the content in RenderedEditorDialog component?
Thanks in advance!

KaTeX can be applied to individual DOM elements on demand, instead of all at once, by calling renderMathInElement when desired. Calling this from componentDidUpdate should do the trick:
import {Editor} from 'slate-react';
class RenderedEditorDialog extends React.Component {
constructor(props) {
super(props);
this.ref = React.createRef();
}
render() {
return (
<div ref={this.ref}>
<Editor
value={this.props.value}
renderMark={this.renderMarks}
renderBlock={this.renderBlock}/>
</div>
)
}
componentDidUpdate() {
renderMathInElement(this.ref.current, katexOptions);
}
}
I'm more comfortable with hook-based components instead of classes, which would look like this:
function RenderedEditorDialog(props) {
const ref = useRef();
useEffect(() => {
renderMathInElement(ref.current, katexOptions);
});
return (
<div ref={ref}>
<Editor
value={props.value}
renderMark={props.renderMarks}
renderBlock={props.renderBlock}/>
</div>
)
};
I'm not sure whether you want this on RenderedEditorDialog or another more specific component, but this should give you the idea. For speed, you want to apply renderMathInElement to the smallest container that contains the updated math.

Related

Why I can't use an imported component inside a functional component in React?

I am new to React. For the code readability, instead of in-line styled button, I want to write it as a separate class component. I created a customed button 'addImageButton'and imported it to another .js file. It doesn't render the customer button when I try to use it within a functional component. How can I make the functional component be able to use the imported button? Thanks!
//addImageButton.js
import React, { Component } from "react";
class addImageButton extends Component {
render() {
return (
<div>
<button
style={{
borderStyle: "dotted",
borderRadius: 1,
}}
>
<span>Add Image</span>
<span>Optional</span>
</button>
</div>
);
}
}
export default addImageButton;
//AddNewTaskButton.js
import React, { Component } from "react";
import Modal from "react-modal";
**import addImageButton from "../addImageButton";**
class AddNewTaskButton extends Component {
constructor(props) {
super(props);
this.state = {
show: false,
};
this.setShow = this.setShow.bind(this);
this.closeShow = this.closeShow.bind(this);
this.addTaskModal = this.addTaskModal.bind(this);
}
setShow() {
this.setState({
show: true,
});
}
closeShow() {
this.setState({
show: false,
});
}
addTaskModal = () => {
return (
<div>
<Modal
isOpen={this.state.show}
onRequestClose={() => this.closeShow()}
>
**<addImageButton />**
</Modal>
</div>
);
};
render() {
return (
<div>
<button onClick={() => this.setShow()}>
<img src={addIcon} alt={text}></img>;
<span>text</span>
</button>
<this.addTaskModal className="modal" />
</div>
);
}
}
export default AddNewTaskButton;
Easier way would be to just use functional components. Also, react components should be upper case, like so:
export default function AddImageButton() {
return (
<div>...</div>
)
}
create a different component for Modal
import Modal from './Modal'
import AddImageButton from './AddImageButton'
function AddTaskModal() {
return (
<div>
<Modal> <AddImageButton/> </Modal>
</div>
)
}
then
import AddTaskModal from './AddTaskModal'
function AddNewTaskButton() {
return (
<div>
<AddTaskModal/>
</div>
)
}
I don't know your file directories, so I just put randomly.
as for your question, try to make the AddImageButton as a class and see if it renders then. If it doesn't it might be due to something else. Do you get errors? Also maybe create the AddTaskModal class separately and render it out as a component. Maybe that'll help

Props is not showing something on the screen

I am trying to show the number 0 on the screen using props. However nothing shows on the screen and I am not sure why. This is the code:
import React,{Component} from 'react';
import Toolbar from './Toolbar.js';
class Counter extends Component {
constructor(props) {
super(props);
this.state ={
counter:0
}
};
render() {
return(
<div>
{this.state.counter.map(count=>(
<Toolbar count={count}/>
))}
</div>
)
}
};
export default Counter;
And this is where I called it
<div className="toolbar__cart">
<span>{props.count}</span>
<img src="Images/basket.png" alt="Basket" width="40"/></div>
I don't think the map function is applicable here since the value of counter is an integer and not an array. See here for more info on the map function.
If you just want your Toolbar component to display the value of this.state.counter, you could use this:
return(
<Toolbar count={this.state.counter}></Toolbar>
)
And then your Toolbar component would use that counter value like this:
function Toolbar(props) {
return (
<div>
<span>{props.count}</span>
<img src="Images/basket.png" alt="Basket" width="40"/>
</div>
)
}
You are using map function incorrectly.
.map is a function used along with an array
Check out : https://www.w3schools.com/jsref/jsref_map.asp
Correct Code :
render() {
return (
<div>
<Toolbar count={this.state.counter} />
</div>
);
}
You can checkout full code here : https://codesandbox.io/s/naughty-lederberg-hb4wp?file=/src/App.js:203-309
counter.map map function for Arrays - sample
import React,{Component} from 'react';
import Toolbar from './Toolbar.js';
class Counter extends Component {
constructor(props) {
super(props);
this.state ={
counter:[0,1,2]
}
};
render() {
return(
<div>
{this.state.counter.map(count=>(
<Toolbar count={count}/>
))}
</div>
)
}
};

Display element based on event fired and props passed in

I am trying, to manipulate another element, by, passing props directly to it, and then have it display itself. If I pass true/false.
Live running code:
https://codesandbox.io/s/keen-dan-rt0kj
I don't know if it's possible to have a system of objects, and based on an event, tell a parent to display a child.
App.js
import React from "react";
import "./styles.css";
import Content from "./components/Content";
export default class App extends React.Component {
state = {
display: false
};
render() {
return (
<div className="App">
<button onClick={() => this.setState({ display: !this.state.display })}>
Display div
</button>
<Content display={this.state.display} />
</div>
);
}
}
./components/Content.js:
import React from "react";
export default class Content extends React.Component {
constructor(props) {
super();
this.state = {
display: props.display
};
}
render() {
const { display } = this.state;
return (
<div
id="mydiv"
className="mydiv"
style={{ display: display ? "block" : "none" }}
>
<h3>A simple div</h3>
</div>
);
}
}
Goal:
I want to based on a state, and based on fired event, display an element that already in store of root.
EDIT: I am aware that, this exists and can be used: import PropTypes from 'prop-types', however, I am not sure this is good practice, since it requires some parent or some other component to implement the props.
JUST Tried:
App:
<Content display={this.state.display} content={"Hello World"} />
Content:
<h3>{this.state.content}</h3>
It seems the passed in text, stored in Content state = {content: props.content} does get displayed, wheres, the boolean value does not work directly. Is there something wrong with sending in a bool ?
try this in your Content Component
export default class Content extends React.Component {
constructor(props) {
super();
this.state = {
};
}
render() {
return (
<>
{this.props.display?(
<div
id="mydiv"
className="mydiv"
>
<h3>A simple div</h3>
</div>
):null}
</>
);
}
}
The reason this may not be working is because you are initiating the state in a way that does not connect the display props after the component is initialized. This means that after the Content component is "constructed", the state of the Content and it's parent are not linked. This is because the constructor() function is only run once to initialize the state.
The best option you have is to not use the internal state of the Content component. Rather than initializing state with the display prop, just use the display prop in your render function.
Trying something like this might work
import React from "react";
export default class Content extends React.Component {
constructor(props) {
super(props);
}
render() {
const { display } = this.props;
return (
<div
id="mydiv"
className="mydiv"
style={{ display: display ? "block" : "none" }}
>
<h3>A simple div</h3>
</div>
);
}
}
Also I would reccommend using state in the root:
import React from "react";
import "./styles.css";
import Content from "./components/Content";
export default class App extends React.Component {
constructor(props) {
super();
state = {
display: false
};
}
render() {
return (
<div className="App">
<button onClick={() => this.setState({ display: !this.state.display })}>
Display div
</button>
<Content display={this.state.display} />
</div>
);
}
}

How to switch between Components in React.js

So I am building a React app and got I think a decent idea pf whay I am doing. But I am looking to find how I can switch between components. Each component is its own individual js file.
App.js file:
import React from 'react';
import './App.css';
import MainPage from './mainpage'
function App() {
return (
<div className="App">
<h1>Welcome to Comix Nation </h1>
<MainPage />
</div>
);
}
export default App;
mainpage.js file:
import React from 'react';
import './App.css';
import CreateAccount from './createaccount.js'
import LogIn from './login.js'
import MainMenu from './mainmenu.js'
class MainPage extends React.Component {
constructor(props){
super(props);
this.state = {
currentPage: 'login'
};
}
getPage(currentPage){
const page ={
mainmenu: <MainMenu />,
createaccount: <CreateAccount />,
login: <LogIn />
};
return page[currentPage]
}
switchPage(currentPage){
this.setState({currentPage});
};
render(){
return (
<div>
<div>
<MainMenu switchPages={this.switchPage}/>
</div>
</div>
);
}
}
export default MainPage;
mainmenu.js file:
import React from 'react';
import './App.css';
class MainMenu extends React.Component {
constructor(props){
super(props);
this.state = {page: 'none'}
}
handleSelection(pageSelection){
this.props.switchPage(pageSelection);
}
render(){
return (
<div>
<h2 onClick={()=> this.handleSelection('createaccount')}>Click to create new account</h2>
<h2>Click to log in</h2>
</div>
);
}
}
export default MainMenu;
The idea is that I can click on either the create or login and get the appropriate js file to render.
so, from reading your code it sounds like you want to do routing (judging from your naming convention at least). There are a number of routing libraries you can use to render different pages if you want to use that. If you just want to switch out components, you've almost got it
class MainPage extends React.Component {
constructor(props){
super(props);
this.state = {
currentPage: 'login'
};
}
switchPage(currentPage){
this.setState({currentPage});
};
render(){
return (
<div>
<div>
{
this.state.currentPage === 'login' &&
<Login/>
}
{
this.state.currentPage === 'MainMenu' &&
<MainMenu/>
}
{
this.state.currentPage === 'SignUp' &&
<SignUp/>
}
</div>
</div>
);
}
}
The way react reads this is true and render this component some people prefer to use a ternary and return null but this is cooler imho 😎
There are several ways to do this, if you are trying to avoid react-router-dom you can implement this system fairly easy.
this.state = {
currentComponent: "",
}
this will allow you to keep track of what component is suppose to show. Put this in your controllers state.
showComponent = (component) => {
this.setState({currentComponent: component})
}
Put this in your main controller file, where you import your components that you will use.
Then you set up your components to display depending what is sent in.
let checkCurrentComponent = this.state.currentComponent;
Make a variable to check for easy checking.
{checkCurrentComponent === "topicList" ? (
<TopicTitles
showComponent={this.showComponent}
/>
) : checkCurrentComponent === "author" ? (
<TopicData
showComponent={this.showComponent}
/>
) : checkCurrentComponent === "commentForm" ? (
<CommentForm }
showComponent={this.showComponent}
/>
): null}
Then in your components you can use that function to pass in the name. Here is how I like to do that.
const handleCommentForm = (e, component) => {
e.preventDefault();
props.showComponent(component);
}
This will be at the top of my stateless function.
will bring up my comment form.
Then the button..
<button
className="btn btn-outline-none"
onClick={e => handleCommentForm(e, "commentForm")}
>
Add Comment
</button>

Wrapping React components in other React components that mange behavior props.

I found this code online and because I'm new to React wanted to know how to use it without getting this error.
this.props.children is not a function
From what I gather its listing to the body scroll position and trying to pass it as props to any React children its wrapped around. Am I correct ?
If so why the above error when I use it like below.
import React, {Component} from 'react';
import Nav from './nav';
import styles from '../../styles/header.scss';
import bgCover from '../../images/homeslider.jpg';
import Scroll from '../utils/scroll';
export default class Header extends Component{
render(){
return(
<Scroll>
<div id='header'>
<div className="container">
<img src={bgCover} id='bg-cover' alt="background-image" />
<div id="temp-text">HEADER</div>
<Nav />
</div>
</div>
</Scroll>
)
}
}
This is the scroll.js file
import React, {Component} from 'react';
export default class Scroll extends Component {
constructor(props) {
super(props);
this.state = { scrollTop: 0,
scrollLeft: 0 };
window.addEventListener('scroll', event => {
this.setState({ scrollTop: document.body.scrollTop,
scrollLeft: document.body.scrollLeft});
});
}
render() {
return this.props.children(this.state.scrollTop, this.state.scrollLeft)
}
}
As Andrew mentions, this.props.children is not a function. In your render function, if you wanted to render the children components, then your render would be written something like this.
render() {
return (
<div>
{this.props.children}
</div>
)
}
In your example, the code above would place this JSX block
<div id='header'>
<div className="container">
<img src={bgCover} id='bg-cover' alt="background-image" />
<div id="temp-text">HEADER</div>
<Nav />
</div>
</div>
into your Scroll component, because they are the children (nested) components.
Now, it looks like you want to pass props to your children components. You can do this by adding accessing React.Children.
An nice example of passing a function as a prop to all children components can be found here :
doSomething: function(value) {
console.log('doSomething called by child with value:', value);
}
const childrenWithProps = React.Children.map(this.props.children,
(child) => React.cloneElement(child, {
doSomething: this.doSomething
})
);
return <div>{childrenWithProps}</div>

Resources