2

I've been trying to understand and write code on the Box component in material-UI. (https://material-ui.com/components/box/#box)

I've been trying to override a Button component the two ways it describes in the documentation, but I have no idea how. When I run the code segment using both methods, the button appears but no color change. Then when I try to add an extra Button underneath the clone element code segment I get an error saying 'Cannot read property 'className' of undefined'.

            <Box color="primary" clone>
                <Button>Click</Button>
                <Button>Click</Button>
            </Box>

When I add a Button component underneath in the second render props way, the first button just disappears from the DOM completely.


             <Box color="secondary">
                {props => <Button {...props} > Click </Button>}
                <Button color="secondary">Click</Button>
            </Box> 

Would appreciate an explanation of how overriding underlying DOM elements work.

robert theboss
  • 101
  • 4
  • 13
  • Does this answer your question? https://stackoverflow.com/a/60926017/11872246 – keikai Apr 01 '20 at 12:07
  • Unfortunately not. I do want to override styling, but only using either of the methods mentioned within the Box component documentation. Using clone element or render props. – robert theboss Apr 01 '20 at 12:16

1 Answers1

4

There are a few issues with the code you've shown in your question.

  1. primary and secondary are not valid colors within the palette. They are valid options for the color prop of Button, but here you are trying to reference colors within the theme's palette object. For this purpose, you need primary.main and secondary.main (which is what Button uses when you specify <Button color="primary">).

  2. Box only supports a single child when using the clone property and it only supports a single child when using the render props approach. In both of your examples you have two children.


Here is the Material-UI source code that deals with the clone option:

      if (clone) {
        return React.cloneElement(children, {
          className: clsx(children.props.className, className),
          ...spread,
        });
      }

This is creating a new child element that combines the className generated by Box with any existing class name on the child. It gets at this existing class name via children.props.className, but when there are multiple children then children will be an array of elements and will not have a props property so you get the error:

Cannot read property 'className' of undefined


Here is the Material-UI source code that deals with the render props approach:

      if (typeof children === 'function') {
        return children({ className, ...spread });
      }

When you have more than one child, then typeof children === 'function' will not be true and it won't use the render props approach. In this case, both children just get normal react rendering and trying to render a function doesn't render anything.


Below is a working example that fixes all of these problems by using a single Button child in the clone case and a single function child in the render props case (a function that then renders two Button elements).

import React from "react";
import Button from "@material-ui/core/Button";
import Box from "@material-ui/core/Box";

export default function App() {
  return (
    <>
      <Box color="primary.main" clone>
        <Button>Click</Button>
      </Box>
      <Box color="secondary.main">
        {props => (
          <>
            <Button {...props}> Click </Button>
            <Button color="secondary">Click</Button>
          </>
        )}
      </Box>
    </>
  );
}

Edit Box clone and render props

Ryan Cogswell
  • 75,046
  • 9
  • 218
  • 198
  • 1
    Amazing explanation! Thanks so much. – robert theboss Apr 01 '20 at 14:31
  • So I just changed my code to match yours in my React project and the color of the buttons are still not changing. I took a screenshot. https://imgur.com/a/4EY6upk – robert theboss Apr 01 '20 at 16:05
  • 1
    The order of the imports matters. You have Box before Button, but I have it after. The order of import impacts the order of the resulting styles in the `` and therefore which styles win when specificity is the same. Unfortunately, the order that matters is the order in which they are **first** imported, so even an import of `Box` in some other page that is used before `Button` is ever imported has the potential to mess this up. – Ryan Cogswell Apr 01 '20 at 17:12
  • 1
    You can find more explanation of this import order effect in my answer here: https://stackoverflow.com/questions/56929702/material-ui-v4-makestyles-exported-from-a-single-file-doesnt-retain-the-styles/56941531#56941531 – Ryan Cogswell Apr 01 '20 at 17:15
  • That fixed it. Thanks so much. – robert theboss Apr 01 '20 at 17:43