TheLearner
TheLearner

Reputation: 2863

Using react-markdown with Material-UI table

I'm trying to parse a markdown table using react-markdown and rendering the resulting tags using the Material-UI table component. Here's my renderer:

import withStyles from '@material-ui/core/styles/withStyles';
import PropTypes from 'prop-types';
import TableCell from '@material-ui/core/TableCell';

const styles = (theme) => ({
  root: {
    marginBottom: theme.spacing(2),
  },
});

const RenderedTCell = ({rendererProps, classes}) => {
  if(rendererProps.children.length !== 0) {
    console.log('rendererProps.children', rendererProps.children);
  }
  return (
    <TableCell className={classes.td} {...rendererProps} />
  );
};

RenderedTCell.propTypes = {
  classes: PropTypes.shape({
    root: PropTypes.string,
  }).isRequired,
};

export default withStyles(styles)(RenderedTCell);

The markdown renderer for all table tags is defined as follows:

const renderers = {
    tableCell: (props) => (
      <RenderedTCell
        rendererProps={props}
      />
    ),
    tableRow: (props) => (
      <RenderedTRow
        rendererProps={props}
      />
    ),
    tableBody: (props) => (
      <RenderedTBody
        rendererProps={props}
      />
    ),
    tableHead: (props) => (
      <RenderedTHead
        rendererProps={props}
      />
    ),
    table: (props) => (
      <RenderedTable
        rendererProps={props}
      />
    ),
  };

Here, all other elements (table, tableHead, tableBody, tableRow) render fine, but the tableCell element keeps throwing an error:

Error while running `getDataFromTree` TypeError: Cannot read property 'charAt' of null
at capitalize (/home/ubuntu/proost/web/node_modules/@material-ui/core/utils/capitalize.js:19:17)
at Object.TableCell [as render] (/home/ubuntu/proost/web/node_modules/@material-ui/core/TableCell/TableCell.js:183:148)
at a.b.render (/home/ubuntu/proost/web/node_modules/react-dom/cjs/react-dom-server.node.production.min.js:46:105)
at a.b.read (/home/ubuntu/proost/web/node_modules/react-dom/cjs/react-dom-server.node.production.min.js:44:18)
at renderToStaticMarkup (/home/ubuntu/proost/web/node_modules/react-dom/cjs/react-dom-server.node.production.min.js:54:462)
at process (/home/ubuntu/proost/web/node_modules/@apollo/react-ssr/lib/react-ssr.cjs.js:38:16)
at process._tickCallback (internal/process/next_tick.js:68:7)

Any idea what might be breaking the code?

Upvotes: 5

Views: 5934

Answers (2)

Nicholas Barrow
Nicholas Barrow

Reputation: 736

Here's a bit more updated solution that uses Material UI 5 and React Markdown 8. You can override the tags as necessary (such as how I override the strong tag below) when ReactMarkdown adds them. This has extensive support for unordered lists, ordered lists, and tables.

Edit 1: thanks @Greg Michalec for the suggestion to add span to del, em, strong, and b.

import React from "react";
import { default as ReactMarkdown } from "react-markdown";
import {
  Link,
  List,
  ListItem,
  ListItemText,
  Paper,
  Table,
  TableBody,
  TableCell,
  TableContainer,
  TableRow,
  Typography,
  useTheme,
} from "@mui/material";
import remarkGfm from "remark-gfm";
import { default as SyntaxHighlighter } from "react-syntax-highlighter";
import {
  tomorrow as lightHighlightStyle,
  tomorrowNight as darkHighlightStyle,
} from "react-syntax-highlighter/dist/cjs/styles/hljs";
import GlobalStyles from "@mui/material/GlobalStyles";

export default function Markdown({ markdown }: { markdown: string; }) {

  const theme = useTheme();

  return (
    <ReactMarkdown
      remarkPlugins={[remarkGfm]}
      components={{
        // *********
        // * Links *
        // *********
        a: ({ href, title, children }) => (<Link href={href} underline={"always"}>{children}</Link>),

        // ********
        // * Text *
        // ********
        p: ({ children }) => (<Typography sx={{ mt: 1 }}>{children}</Typography>),
        del: ({ children }) => (<Typography component="span" sx={{ mt: 1, textDecoration: "line-through" }}>{children}</Typography>),
        em: ({ children }) => (<Typography component="span" sx={{ mt: 1, fontStyle: "italic" }}>{children}</Typography>),
        strong: ({ children }) => (<Typography component="span" sx={{ mt: 1, fontWeight: "bold" }}>{children}</Typography>),
        b: ({ children }) => (<Typography component="span" sx={{ mt: 1, fontWeight: "bold" }}>{children}</Typography>),
        h1: ({ children }) => (<Typography gutterBottom sx={{ mt: 2 }} variant={"h1"}>{children}</Typography>),
        h2: ({ children }) => (<Typography gutterBottom sx={{ mt: 2 }} variant={"h2"}>{children}</Typography>),
        h3: ({ children }) => (<Typography gutterBottom sx={{ mt: 2 }} variant={"h3"}>{children}</Typography>),
        h4: ({ children }) => (<Typography gutterBottom sx={{ mt: 2 }} variant={"h4"}>{children}</Typography>),
        h5: ({ children }) => (<Typography gutterBottom sx={{ mt: 2 }} variant={"h5"}>{children}</Typography>),
        h6: ({ children }) => (<Typography gutterBottom sx={{ mt: 2 }} variant={"h6"}>{children}</Typography>),

        // **********
        // * Tables *
        // **********
        table: ({ children }) => (<TableContainer component={Paper} sx={{ mt: 2 }}>
          <Table size="small">{children}</Table>
        </TableContainer>),
        tbody: ({ children }) => (<TableBody>{children}</TableBody>),
        // th: ({ children }) => (<TableHead>{children}</TableHead>),
        tr: ({ children }) => (<TableRow>{children}</TableRow>),
        td: ({ children }) => (<TableCell><Typography>{children}</Typography></TableCell>),

        // *********
        // * Lists *
        // *********
        ol: ({ children }) => (<List sx={{
          listStyleType: "decimal",
          mt: 2,
          pl: 2,
          "& .MuiListItem-root": {
            display: "list-item",
          },
        }}>{children}</List>),
        ul: ({ children }) => (<List sx={{
          listStyleType: "disc",
          mt: 2,
          pl: 2,
          "& .MuiListItem-root": {
            display: "list-item",
          },
        }}>{children}</List>),
        li: ({ children, ...props }) => (
          <ListItem sx={{ m: 0, p: 0, ml: 2 }} disableGutters><ListItemText
            sx={{ pl: 0.25 }}>{children}</ListItemText></ListItem>),

        // ********
        // * Code *
        // ********
        code: ({ node, inline, className, children, ...props }) => {
          const match = /language-(\w+)/.exec(className || "");
          return (
            <>
              <GlobalStyles styles={{ code: { color: "inherit", background: "transparent" } }} />
              <SyntaxHighlighter
                style={theme.palette.mode === "light" ? lightHighlightStyle : darkHighlightStyle}
                language={match ? match[1] : undefined}
                PreTag="div"

              >
                {String(children).replace(/\n$/, "")}
              </SyntaxHighlighter>
            </>
          );
        },
      }}
    >
      {markdown}
    </ReactMarkdown>
  );
}

Upvotes: 5

Bogdan Negru
Bogdan Negru

Reputation: 86

I had the same issue. I solved it by not passing all the props further down to material components.

You can see what I used to theme the markdown with material ui in the component in this gist: https://gist.github.com/boganegru/a4da0b0da0b1233d30b10063b10efa8a

Upvotes: 7

Related Questions