How to apply code highlights within markdown-to-jsx package in react? - reactjs

I'm using the markdown-to-jsx package in order to render documentation content inside my react project.
This package provides a Markdown component, which accepts an options prop to override HTML elements's default style, and more.
const markdownOptions = {
wrapper: DocsContentWrapper,
forceWrapper: true,
overrides: {
h1: LeadTitle,
h2: SecondaryTitle,
h3: ThirdTitle,
p: Paragraph,
pre: CodeWrapper,
ol: ListWrapper,
li: ListItem,
},
};
<Markdown
options={MarkdownOptions}
>
{MockDoc}
</Markdown>
Now, the Markdown component accept a markdown, so I pass it a string which is formatted accoring to markdown rules.
It contains some code blocks, like the following, which I want to add colors to:
I have created a component using 'react-syntax-highlighter' package, and it looks like the following:
import React from 'react';
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"
import { tomorrow } from "react-syntax-highlighter/dist/esm/styles/prism"
const SyntaxHighligher = ({ language, markdown }) => {
return (
<SyntaxHighlighter
language={language}
style={tomorrow}
>
{markdown}
</SyntaxHighlighter>
);
};
export default SyntaxHighligher;
And here comes the question - how can I integrate the two?
I was thinking that it would have made sense if the options object would accept such configuration, but looking at 'markdown-to-jsx' docs via their GitHub page, shows no option.
I have seen a package called 'react-markdown' that is able to accept my SyntaxHighligher component and to the task, but I want to apply the same functionality with 'markdown-to-jsx' package.

markdown-to-jsx generates code blocks as <pre><code>...</code></pre>, but we can't simply override code tag since inline code uses it as well. The README from markdown-to-jsx suggests that we can override pre > code somehow:
Some element mappings are a bit different from other libraries, in particular:
span: Used for inline text.
code: Used for inline code.
pre > code: Code blocks are a code element with a pre as its direct ancestor.
But based on my experiments and reading of the source code, I think the only way is to override pre and check for code in its children. For example:
import {Prism as SyntaxHighlighter} from 'react-syntax-highlighter';
import {materialDark as CodeStyle} from 'react-syntax-highlighter/dist/esm/styles/prism';
const CodeBlock = ({className, children}) => {
let lang = 'text'; // default monospaced text
if (className && className.startsWith('lang-')) {
lang = className.replace('lang-', '');
}
return (
<SyntaxHighlighter language={lang} style={CodeStyle}>
{children}
</SyntaxHighlighter>
);
}
// markdown-to-jsx uses <pre><code/></pre> for code blocks.
const PreBlock = ({children, ...rest}) => {
if ('type' in children && children ['type'] === 'code') {
return CodeBlock(children['props']);
}
return <pre {...rest}>{children}</pre>;
};
const YourComponent = () => {
return (
<Markdown
options={{
overrides: {
pre: PreBlock,
},
}}
>
{yourMarkdown}
</Markdown>
);
};
Unfortunately, I don't have a good solution if you want to override BOTH inline code and code blocks. When overriding both pre and code tags, the code tag generated by SyntaxHighlighter also gets overridden, so the inline code and code blocks are rendered identically.

Related

React Element not re-rendering on setState when state is passed

I'm trying to implement a simple dark/light theme toggle to my website. In my base App.tsx I've implemented the state I use:
const [colorScheme, setColorScheme] = useState("light");
I pass that "colorScheme" variable as a prop to every other element. The theme toggle is contained in a header element, so I also pass the "setColorScheme" function to header as a prop. Within Header.tsx, the code triggered when the toggle is clicked is:
setColorScheme(s => s === "dark" ? "light" : "dark");
Within every specific element, I set the color scheme like so:
<ElementName className={"element_name element_name_"+colorScheme}/>
I have all the css for styling the component contained in the class "element_name", and then all relevant color data is contained in "element_name_light" or "element_name_dark".
When the toggle in the header is clicked, a re-render is triggered for the main body of the app, and for the header. But all of the other elements do not re-render. If I navigate to another element, the re-render happens and the color scheme appears as intended.
Attached is a gif of this happening.
I'm still learning React, so I'm sure it's something obvious I'm missing. I would appreciate any tips anyone can provide! Thanks
One note: I am using react functionally, rather than implementing classes for each component.
It's impossible to tell exactly what mistake you made since you haven't shared your code. But I can tell you the root mistake is not using React's context API. This will allow you to hold the color scheme and the toggle function as a global state and import them into every component via the useContext hook.
Here's an example on stackblitz: https://stackblitz.com/edit/react-ts-lhwstv?file=color-scheme-ctx.tsx
Here's the docs: https://reactjs.org/docs/context.html
Note: I'm using typescript, if you're using plain javascript just remove the type declarations and the generic typings <Type>.
You start by creating the context and giving a default value:
type ColorScheme = 'light' | 'dark';
type Props = { colorScheme: ColorScheme; toggleColorScheme: () => void };
export const ColorSchemeCtx = createContext<Props>({
colorScheme: 'light',
toggleColorScheme: () => {},
});
I like to then create a provider component for organization.
export const ColorSchemeCtxProvider: FC<PropsWithChildren<{}>> = ({
children,
}) => {
const [colorScheme, setColorScheme] = useState<ColorScheme>('light');
function toggleColorScheme() {
setColorScheme((s) => (s === 'dark' ? 'light' : 'dark'));
}
return (
<ColorSchemeCtx.Provider value={{ colorScheme, toggleColorScheme }}>
{children}
</ColorSchemeCtx.Provider>
);
};
Then wrap all components that need the context - probably just put it at the highest level possible.
root.render(
<StrictMode>
<ColorSchemeCtxProvider>
<App />
</ColorSchemeCtxProvider>
</StrictMode>
);
Now any component can get both the color scheme and / or the toggle function with useContext
export default function App() {
const { colorScheme, toggleColorScheme } = useContext(ColorSchemeCtx);
return (
<div>
<p>The color scheme is: {colorScheme}</p>
<button onClick={toggleColorScheme}>TOGGLE</button>
<CompOne />
<CompTwo />
<CompThree />
</div>
);
}
export default function CompOne() {
const { colorScheme } = useContext(ColorSchemeCtx);
return <div className={'comp-one ' + colorScheme}></div>;
}

How to export a component to a PDF file, that is not visible on UI, but has to be in PDF document (html-to-image, jsPDF, React)

Like the title says, I want to export a component to a PDF file, that I want to be invisible in the app or should I say on UI, but I want it to be inside a PDF document.
To make this PDF exporting functionality I have used the combination of html-to-image library, jsPDF library and everything is made using React.
This is my code:
function App() {
const [exporting, setExporting] = useState(false);
async function createPdf({ doc, element }) {
const imgData = await toPng(element);
const imgProps = doc.getImageProperties(imgData);
const pdfWidth = doc.internal.pageSize.getWidth();
const pdfHeight = (imgProps.height * pdfWidth) / imgProps.width;
doc.addImage(imgData, "PNG", 10, 0, pdfWidth, pdfHeight, "", "FAST");
}
async function handleDownloadPdf() {
const element = document.getElementsByClassName("container")[0];
const doc = new jsPDF(
"p",
"px",
[element.clientWidth, element.clientHeight],
true
);
setExporting(true);
await createPdf({ doc, element });
doc.save(`charts.pdf`);
}
return (
<pdfContext.Provider value={{ exporting, setExporting }}>
<div className="App">
<button onClick={handleDownloadPdf}>Test</button>
<div className="container">
<Hidden />
<Foo />
</div>
</div>
</pdfContext.Provider>
);
}
export default App;
The component that I want to be hidden is <Hidden />, this is a simple component but let me show the code anyways:
const Hidden = () => {
const { exporting, setExporting } = useContext(pdfContext);
return (
<div
className="elementOne"
style={{ visibility: exporting ? "visible" : "hidden" }}
>
</div>
);
};
export default Hidden;
As you can see I want to use the context called pdfContext that sets the visibility of a component to hidden when the component is not being exported, and to visible when it's being exported, but this way is not really a good solution, as the component gets visible for a split second before exporting and in my opinion it's not a good design.
So if anyone has any solution or a workaround on how to export a component to a PDF using these libraries, but without showing it on a UI, that would be great.
I know that the way these components are being exported to a PDF is by converting the container to an image, and probably the way I am asking to do this is maybe impossible but then again it does not hurt to ask.

Markdown in Antd

I can confirm that this has neither been asked nor been addressed anywhere. I am currently working on a website using Gatsby, Strapi and Antd for the design. I am using the the rich text editor for one of the content types where I have put all my markdown content. However, when I try to display the actual content on the webpage, the styling is completly nuked. I figured this was because the content uses normal HTML elements like <h1> and <p> instead of the antd components like <Title> or <Text>.
So I did some researches and found that Antd has a markdown.less in their source, which I figure is used to style the markdown in their documentation. I haven't found the same after scouring the source code inside the node modules folder. Does this mean that Antd does not support styling for markdown or am I missing something here?
Btw I am using the react-markdown library to display the all the markdown. I have also posted all the relevant code below.
template.tsx
const ProductTemplate: React.FC<Props> = ({ data }: Props) => {
const {
...
} = data
const {
product: { strapiId: selectedKeyProp },
} = data
return (
<Layout>
<AntLayout>
<ProductSidebar
selectedKeyProp={selectedKeyProp}
productsInfo={productsInfo}
>
<ProductInfo product={product} />
</ProductSidebar>
</AntLayout>
</Layout>
)
}
export const query = graphql`
...
`
export default ProductTemplate
page-component.tsx
const ComponentName = ({ data }) => {
const {
...
} = data
console.log(data)
return (
<Layout>
<AntLayout>
<ProductSidebar productsInfo={productsInfo}>
<div style={{ display: "unset", padding: "15px 35px" }}>
<ReactMarkdown className="markdown" children={content} />
</div>
</ProductSidebar>
</AntLayout>
</Layout>
)
}
export const query = graphql`
...
`
export default ComponentName
There problem can be pointed out here I guess. The <ReactMarkdown> receives the markdown content in the children props. But once the content is displayed to the page, the styling, as I mentioned above, is nuked out.
I raised this issue on github here. According to the devs, there is currently no markdown support for antd. As I thought, the markdown.less file linked above was just for the markdown in their documentation.
One way to get around this solution is to include the tags exactly as specified by antd component elements. For example, instead of a # or <h1> for a header, we can use <h1 class="antd typography">, though this definitely is painful and prone to error.
This other solution would be to use MDX, which support jsx inside markdown.
About a month late, but I just ran into this: I needed a way to change the styling of html generated by the react-markdown React component. I am using react-markdown in a NextJS app, and using Antd as my component library. I am additionally using react-syntax-highlighter and react-syntax-highlighter-prismjs for handling lighting codeblocks
While there is no support in Antd for markdown, there is support for custom components in react-markdown! react-markdown allows you to override the rendering engine and replace the individual components with your own, so I went through and replaced a bunch of them with Antd components:
Fair warning: This was my first pass to make sure this worked, it doesn't for instance handle checkboxes inside list item inputs.
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import {coy} from "react-syntax-highlighter/dist/cjs/styles/prism/prism";
import gfm from 'remark-gfm';
import { Typography } from 'antd';
const { Title, Text } = Typography;
import { List } from 'antd';
const mymarkdowndata = '
# A heading
Some text
## A second heading
* List!
* Has
* Many
* Items!
'
const renderers = {
heading: function Heading(props) {
return <Title level={props.level}>{props.children}</Title>;
},
list: function MakeList(props) {
return <List bordered>{props.children}</List>
},
listItem: function MakeListItem(props) {
return <List.Item>{props.children}</List.Item>
},
inlineCode: function makeInlineCode(props) {
return <Text code>{props.children}</Text>
},
code: function makeCodeBlock(props) {
return <SyntaxHighlighter language={props.language} style={coy}>{props.value}</SyntaxHighlighter>
},
blockquote: function makeBlockQuote(props) {
return <Text type="secondary">{props.children}</Text>
}
};
Then inside your component rendering function:
render() {
return <ReactMarkdown renderers={renderers} plugins={[gfm]} children={mymarkdowndata} />
}

Using Styled Components in functional React components

(I hope and assume that there is a simple answer to this question, but I could not find it in the docs)
I've just converted a React project to use Styled Components, and love the DRYness and reusability. But, I've not yet figured out the syntax for using styled components in existing functional components that do other work too.
Here's one example:
const StyledSearchBarPane = styled(Pane)`
grid-area: search-bar;
`;
const SearchBarPane = () => {
const {query} = useContext(panelContext);
let [newQuery, setNewQuery] = useState(query);
return (
<StyledSearchBarPane>
<Bar>
<SearchInput newQuery={newQuery} setNewQuery={setNewQuery}/>
</Bar>
</StyledSearchBarPane>
);
};
How can I avoid naming StyledSearchBarPane? It's only used once -- in SearchBarPane -- and I'd rather it were simply part of the latter's definition.
Yes you can.
Basically what you need to do is use your component as you would normally do and if you want to use a local style on the component without creating one you can use a Wrapper like you did.
But the change is, instead of exporting your component you export the wrapper. Then your component will receive as props className and you need to put it where you want to apply thoses style, in the example since it's globally you put in the root element.
If you don't put the className props it won't style anything.
const SearchBarPane = ({className}) => {
const {query} = useContext(panelContext);
let [newQuery, setNewQuery] = useState(query);
return (
<div className={className}>
<Bar>
<SearchInput newQuery={newQuery} setNewQuery={setNewQuery}/>
</Bar>
</div>
);
};
const StyledSearchBarPane = styled(SearchBarPane)`
grid-area: search-bar;
`;
export default StyledSearchBarPane;
https://styled-components.com/docs/advanced

Getting component propTypes as string

I will like to make a wrapper component that will automatically display its children's proptypes as a string.
Example
For example, my component may be:
export class abc extends Component {
static propTypes = {
disabled: PropTypes.bool,
text: PropTypes.oneOf(['a','b'])
}
}
The wrapper will take in the component:
<Wrapper>
<abc />
</Wrapper>
And output HTML that looks like:
<pre>
disabled: PropTypes.bool
text: PropTypes.oneOf(['a', 'b'])
</pre>
What I have tried
I have tried extracting props directly from the actual children, and ended up with a wrapper component that looks like this:
const wrapper = props => (<pre>
{Object.entries(props.children.type.propTypes).map((entry) => (
<div key={entry[0]}>{entry[0]}: {getPropTypeFromFunction(entry[1])}<br/></div>
))}
</pre>)
where the getPropTypeFromFunction method is:
const getPropTypeFromFunction = func => {
for (const k in PropTypes) {
switch (func) {
case PropTypes[k]:
return k
case PropTypes[k].isRequired:
return `${k}.isRequired`
default:
break;
}
}
return "Unknown PropType"
}
This however does not work when we get non-primitive (?) proptypes (eg PropTypes.onOf(['a', 'b']). Also it feels kind of hacky to have to deal with something seemingly so simple this way.
Is there some kind of elegant solution to this (ideally without any external libraries?)
Ended up using react-docgen, which uses recast internally. This works by parsing the entire source file as a string into an Abstract Syntax Tree (AST) which is then used to generate the doc. There are some libraries using this to implement documentation, such as storybook.js and their info add-on, which I ended up using.

Resources