Create "Copy Code" button for React-Markdown
FSMD Fahid Sarker
Senior Software Engineer · July 7, 2024
Markdown is awesome for creating blog sites like DevZone. It allows you to write content in a simple and readable format. However, when it comes to code snippets, it's often useful to provide a "Copy Code" button for users to easily copy the code to their clipboard.
Step 1: Get started with React-Markdown
Create a basic component to render the markdown content using the react-markdown
library.
Code.tsxexport async function MarkdownRenderer({ content }: { content: string }) { return ( <Markdown remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeRaw]}> {content} </Markdown> ); }
Step 2: Add Syntax Highlighting to Code Blocks
The next step is to add the code
function the components
prop. This function will render the code blocks with syntax highlighting using a library like react-syntax-highlighter
or anything else you like. For more info on this, check out the react-syntax-highlighter package.
Code.tsx... <Markdown remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeRaw]} components={{ code({ node, inline, className = "blog-code", children, ...props }: any) { const match = /language-(w+)/.exec(className || ""); return !inline && match ? ( <SyntaxHighlighter style={nightOwl} PreTag="div" language={match[1]} {...props} > {String(children).replace(/\n$/, "")} </SyntaxHighlighter> ) : ( <code className={cn(className, "bg-slate-200 px-1 rounded")} {...props} > {children} </code> ); }, }} > {content} </Markdown> ...
Step 3: Create a CopyAble
Component
We now need to create a component that can take another component as a parameter and add a "Copy" button to it. The principal of the component is very simple - it will take a children
prop and a toCopy
prop. The children
prop will be the component that we want to add the "Copy" button to, and the toCopy
prop will be the text that we want to copy to the clipboard.
Code.tsxconst CopyAble = ({ children, toCopy, }: { children: React.ReactNode; toCopy: string; }) => { const [copied, setCopied] = useState(false); const copyCode = () => { navigator.clipboard.writeText(toCopy); setCopied(true); }; return ( <div className="relative"> <div className="absolute right-1 top-1 text-white hover:text-black hover:bg-white rounded cursor-pointer" onClick={copyCode} > {copied ? ( <CheckIcon className="m-1 text-green-200 hover:text-green-600" size={18} /> ) : ( <CopyIcon className="m-1 " size={18} /> )} </div> {children} </div> ); };
Step 4: Wrap the SyntaxHighlighter
Component with CopyAble
Simply wrap the SyntaxHighlighter
component with the CopyAble
component in the code
function. Pass the code snippet as the toCopy
prop - String(children).replace(/\n$/, "")
. Note that this is the same code snippet that we are rendering in the SyntaxHighlighter
component.
Code.tsx... const match = /language-(w+)/.exec(className || ""); return !inline && match ? ( <CopyAble toCopy={String(children).replace(/\n$/, "")}> <SyntaxHighlighter style={nightOwl} PreTag="div" language={match[1]} {...props} > {String(children).replace(/\n$/, "")} </SyntaxHighlighter> </CopyAble> ) : ( <code className={cn(className, "bg-slate-200 px-1 rounded")} {...props}> {children} </code> ); ...
Final Code
After following all the steps, this is how the final code should look.
Code.tsxexport async function MarkdownRenderer({ content }: { content: string }) { return ( <Markdown remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeRaw]} components={{ code({ node, inline, className = "blog-code", children, ...props }: any) { const match = /language-(w+)/.exec(className || ""); return !inline && match ? ( <CopyAble toCopy={String(children).replace(/\n$/, "")}> <SyntaxHighlighter style={nightOwl} PreTag="div" language={match[1]} {...props} > {String(children).replace(/\n$/, "")} </SyntaxHighlighter> </CopyAble> ) : ( <code className={cn(className, "bg-slate-200 px-1 rounded")} {...props} > {children} </code> ); }, }} > {content} </Markdown> ); }