How to use override Button using Box component in Material-UI? - reactjs

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.

There are a few issues with the code you've shown in your question.
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">).
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>
</>
);
}

Related

React To Print not triggered when custom button is clicked

I have the following code to use React To print, a library to print a component:
<ReactToPrint
trigger={() =>
<SqIconButton action={(e)=>e} color={"success"}>
<AiIcons.AiFillPrinter style={{color: 'black',fontSize:'1rem'}}/>
</SqIconButton>
}
content={() => componentRef.current}
/>
And my custom Button SqIconButton reads as follows:
export default function SqIconButton({children,color="primary",fontColor="#000000",action,tip="",disableElevation=false,sxstyle={},classes}){
return(
<Tooltip title={tip}>
<Button
color={color}
variant="contained"
onClick={action}
disableRipple={disableElevation}
className={`TBButton ${classes}`}
sx={sxstyle}
>
{children}
</Button>
</Tooltip>
)
}
In the examples of React To Print code for its trigger nothing is referencing "onclick" property on buttons, examples mostly look like this: <button>print this</button> and if I use this element it actually works. But my custom button does not work as is, so I think I have to pass the onclick event over my action custom property, so I'm trying with action={(e)=>e}, but the button does nothing when I press it.
Documentation says about trigger:
A function that returns a React Component or Element. Note: under the hood, we inject a custom onClick prop into the returned Component/Element. As such, do not provide an onClick prop to the root node returned by trigger, as it will be overwritten
So I don't know if onclick will be overwritten how could I pass this event to a child button component.

Passing empty props changes the Button color in ButtonGroup

I'm using a customized IconButton element with ButtonGroup.
While passing an empty props, the color and hover shape of element is also changed. I want to know why did this happen? For me, an HOC shouldn't have different render effect with an empty prop passed.
I hosted a minimal environment with GitHub Pages. The question is shown on the second and third chapter.
Using a new customized component.
Misbehavior: buttons are white and not in group.
See the hover highlight of second and third "G"
The component is based on IconButton without passing {...props}.
Using a new customized component. All good.
The component is also based on IconButton but passing {...props}.
Although the props is not passed, hence should be empty(?).
Maybe useStyles changed color, but the hover background shape is changed too.
Also I made a related issue on official repo, and in my Learning note
It's the opposite, if you don't pass the props to IconButton:
function IconButtonWrapper(props /* unused props */) {
return (
<IconButton>
<DeleteIcon />
</IconButton>
);
}
Then no styles are applied and the IconButton appears in plain white, so nothing changes.
Although the props is not passed
The props are passed automatically from the ButtonGroup, even if you don't pass anything. The way it works is that ButtonGroup clones the child component and pass its own set of props to them, so when you don't pass anything yourself like this:
<ButtonGroup variant="contained">
<IconButtonWrapper />
<IconButtonWrapper />
<IconButtonWrapper />
</ButtonGroup>
The child component still has props provided by the parent, you can see it by logging the props:
function IconButtonWrapper(props) {
console.log(props);
return (
<IconButton {...props}>
<DeleteIcon />
</IconButton>
);
}

Using Stateful React classes in typescipt

I am trying to create a Stateful class in which you can call methods such as createHeaderButton() where after calling it would update the state and re-render with these new updates in the component.
Im using Material-UI and so most of their styling utilizes Reacts hook API which of course classes cant use. Ive tried to get around this by using;
export default withStyles(useStyles)(HeaderBar)
Which exports the class separately with the Styles(withStyles(useStyles) useStyles as the defined styles) And the class(HeaderBar). Now the only issue is that i need to access the styles in my class. Ive found a JS example online that wont work for me because of the strong typed syntax of TS. Additionally When initializing my Class component in other places i try to get the ref=(ref:any)=>{} And with that call the create button methods when i get a response from my server, Which doesnt work because of this new way of exporting the class component!
Thanks for the help, Heres my component class: https://pastebin.pl/view/944070c7
And where i try to call it: https://pastebin.com/PVxhKFHJ
My personal opinion is that you should convert HeaderBar to a function component. The reason that it needs to be a class right now is so you can use a ref to call a class method to modify the buttons. But this is not a good design to begin with. Refs should be avoided in cases where you can use props instead. In this case, you can pass down the buttons as a prop. I think the cleanest way to pass them down is by using the special children prop.
Let's create a BarButton component to externalize the rendering of each button. This is basically your this.state.barButtons.forEach callback, but we are moving it outside of the HeaderBar component to keep our code flexible since the button doesn't depend on the HeaderBar (the header bar depends on the buttons).
What is a bar button and what does it need? It needs to have a label text and a callback function which we will call on click. I also allowed it to pass through any valid props of the material-ui Button component. Note that we could have used children instead of label and that's just down to personal preference.
You defined your ButtonState as a callback which takes the HTMLButtonElement as a prop, but none of the buttons shown here use this prop at all. But I did leave this be to keep your options open so that you have the possibility of using the button in the callback if you need it. Using e.currentTarget instead of e.target gets the right type for the element.
import Button, {ButtonProps as MaterialButtonProps} from "#material-ui/core/Button";
type ButtonState = (button: HTMLButtonElement) => void;
type BarButtonProps = {
label: string;
callback: ButtonState;
} & Omit<MaterialButtonProps, 'onClick'>
const BarButton = ({ label, callback, ...props }: BarButtonProps) => {
return (
<Button
color="inherit" // place first so it can be overwritten by props
onClick={(e) => callback(e.currentTarget)}
{...props}
>
{label}
</Button>
);
};
Our HeaderBar becomes a lot simpler. We need to render the home page button, and the rest of the buttons will come from props.childen. If we define the type of HeaderBar as FunctionComponent that includes children in the props (through a PropsWithChildren<T> type which you can also use directly).
Since it's now a function component, we can get the CSS classes from a material-ui hook.
const useStyles = makeStyles({
root: {
flexGrow: 1
},
menuButton: {
marginRight: 0
},
title: {
flexGrow: 1
}
});
const HeaderBar: FunctionComponent = ({ children }) => {
const classes = useStyles();
return (
<div className={classes.root}>
<AppBar position="static">
<Toolbar>
<HeaderMenu classes={classes} />
<Typography variant="h6" className={classes.title}>
<BarButton
callback={() => renderModule(<HomePage />)}
style={{ color: "white" }}
label="Sundt Memes"
/>
</Typography>
{children}
</Toolbar>
</AppBar>
</div>
);
};
Nothing up to this point has used state at all, BarButton and HeaderBar are purely for rendering. But we do need to determine whether to display "Log In" or "Log Out" based on the current login state.
I had said in my comment that the buttons would need to be stateful in the Layout component, but in fact we can just use state to store an isLoggedIn boolean flag which we get from the response of AuthVerifier (this could be made into its own hook). We decide which buttons to show based on this isLoggedIn state.
I don't know what this handle prop is all about, so I haven't optimized this at all. If this is tied to renderModule, we could use a state in Layout to store the contents, and pass down a setContents method to be called by the buttons instead of renderModule.
interface LayoutProp {
handle: ReactElement<any, any>;
}
export default function Layout(props: LayoutProp) {
// use a state to respond to an asynchronous response from AuthVerifier
// could start with a third state of null or undefined when we haven't gotten a response yet
const [isLoggedIn, setIsLoggedIn] = useState(false);
// You might want to put this inside a useEffect but I'm not sure when this
// needs to be re-run. On every re-render or just once?
AuthVerifier.verifySession((res) => setIsLoggedIn(res._isAuthenticated));
return (
<div>
<HeaderBar>
{isLoggedIn ? (
<BarButton
label="Log Out"
callback={() => new CookieManager("session").setCookie("")}
/>
) : (
<>
<BarButton
label="Log In"
callback={() => renderModule(<LogInPage />)}
/>
<BarButton
label="Sign Up"
callback={() => renderModule(<SignUpPage />)}
/>
</>
)}
</HeaderBar>
{props.handle}
</div>
);
}
I believe that this rewrite will allow you to use the material-ui styles that you want as well as improving code style, but I haven't actually been able to test it since it relies on so many other pieces of your app. So let me know if you have issues.

React useRef to target specific div?

So I have this modal that is wrapped with an entire background div. My issue is that I added a close function on the background, so no matter where I click, it will close the modal.
I would like to have the useRef only target the background div and not work if I click any of the children inside of it
Here is the code
const modalRef = useRef();
const closeModal = e => {
if (modalRef.current.contains(e.target)) {
setShowModal(false);
}
}
return (
<>
{showModal ? (
<Background onClick={closeModal} ref={modalRef}>
<animated.div style={animation}>
<ModalWrapper showModal={showModal}>
<div>hi</div>
<CloseModalButton
aria-label='Close modal'
// onClick={() => setShowModal(!showModal)}
/>
</ModalWrapper>
</animated.div>
</Background>
) : null}
</>
);
};
So right now the ref is attached to the background, but if I console.log(modalRef.current) it will show me the entire jsx with all my children divs inside, but I only want to target the outside div aka the background
So whenever I click outside of my modal it will close
Note: when I console.log(modalRef.current) this is what shows up in the console aka my entire JSX
I'm also using styled-components, so I only want to target the top div which I called Background but shows sc-bdnylx iEsAwc so I have no idea how to target it since it doesn't have any ids or classNames
I tried to add this console.log(modalRef.current.children[0]) but when I implemented it into my function, it didn't work properly. This console.log does only show the divs below my Background div, but I haven't found anything about targeting just the Background only
You don't actually need to use refs here. Just add onClick={e => e.stopPropagation()} to one of the divs inside Background, like on ModalWrapper. It'll stop the click from passing through.

Pass Accessibility Props to Material UI Button

I want to add accessibility features to the Material UI Button.
I expect to use this custom button as follows:
import Button from '#material-ui/core/Button';
function AccessibleButton(props) {
const { accessKey, ariaLabel, isDisabled, label, onClick, tabIndex, variant, size} = props;
return (
<Button
accesskey={accessKey}
aria-label={ariaLabel}
disabled={isDisabled}
className={componentCls}
onClick={onClick}
tabindex={tabIndex}
variant={variant}
size={size}
>
{label}
</Button>
);
};
Aria labels are available for inputs, but don't seem to be for buttons. How do I pass the additional props (accessKey, ariaLabel) into the Material UI Button. How do I do this?
This should work since most of our components forward their excess props. On the corresponding api pages (here https://material-ui.com/api/button/) you will find a table with the apparent props. Below that is a note that tells you what happens with excess props.
It's a bit iffy to navigate (we're working on it) but in the end you'll see that excess props are forwarded to the native element. So <Button aria-label="ariaLabel" /> will render a <button aria-label="ariaLabel" />.
Your code should work.
I have created sandbox where you can inspect the button and see the button will have aria-label and accesskey attribute.
<Button
aria-label="This is aria label"
accessKey="Key"
variant="contained"
color="primary"
>
I'm a button
</Button>
Try inspecting button in below sandbox.

Resources