I'm using React Material UI's Tooltip Component in my React application.
import Tooltip from "#material-ui/core/Tooltip";
...
...
<Tooltip title="Add" arrow>
<Button>Arrow</Button>
</Tooltip>
...
...
I want to disable the entry and exit animations. How can I achieve this in the latest version
You can use the TransitionComponent and the TransitionProps to solve this.
Use the Fade Transition component with timeout: 0 as the properties for the transition component:
import Tooltip from "#material-ui/core/Tooltip";
import Fade from "#material-ui/core/Fade";
...
<Tooltip
title="Add"
arrow
TransitionComponent={Fade}
TransitionProps={{ timeout: 0 }}
>
<Button>Arrow</Button>
</Tooltip>
Just disable/mock the transition component.
ie: render automatically the children like this:
const FakeTransitionComponent = ({ children }) => children;
<Tooltip
title="tooltip title"
TransitionComponent={FakeTransitionComponent}
// or TransitionComponent={({ children}) => children}
>
<h1>Hello CodeSandbox</h1>
</Tooltip>
Here is a codesandbox demo
I've used Incepter's solution, that is clean. If anyone is looking for a TypeScript solution here it is.
const FakeTransitionComponent = React.forwardRef<
HTMLDivElement,
TransitionProps & { children?: React.ReactElement<any, any> }
>(
(
{
appear,
onEnter,
onEntered,
onEntering,
onExit,
onExited,
onExiting,
...props
},
ref
) => {
props.in = undefined;
return <div {...props} ref={ref}></div>;
}
);
TransitionProps are not passed to the wrapper element, because they would all cause React warnings.
You can just pass React.Fragment as TransitionComponent
<Tooltip
title="tooltip title"
TransitionComponent={React.Fragment}
>
<h1>Hello CodeSandbox</h1>
</Tooltip>
Best option is to create a simple NoTransition component:
export const NoTransition = React.forwardRef<
React.ReactFragment,
TransitionProps
// eslint-disable-next-line #typescript-eslint/no-unused-vars
>(({ children }, ref) => {
return <>{ children }</>;
});
And do TransitionComponent={ NoTransition }.
Also, do not omit the ref param or you will get a warning too:
Warning: forwardRef render functions accept exactly two parameters: props and ref. Did you forget to use the ref parameter?
Just adding TransitionProps={{ timeout: 0 }}, without setting TransitionComponent, will also work, but there's no need to render the default TransitionComponent in that case.
Some of the options proposed in other answers will throw errors or warnings:
Doing TransitionComponent={ Fragment } will result in the following warning:
Warning: Invalid prop appear supplied to React.Fragment. React.Fragment can only have key and children props.
Doing TransitionComponent={ ({ children }) => children } will result in this other warnings (might change a bit depending if you are using a Tooltip, Modal or other component):
Warning: Failed prop type: Invalid prop children supplied to ForwardRef(Modal). Expected an element that can hold a ref. Did you accidentally use a plain function component for an element instead? For more information see https://mui.com/r/caveat-with-refs-guide
Warning: Failed prop type: Invalid prop children supplied to ForwardRef(ModalUnstyled). Expected an element that can hold a ref. Did you accidentally use a plain function component for an element instead? For more information see https://mui.com/r/caveat-with-refs-guide
Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()? Check the render method of Unstable_TrapFocus.
And potentially also this error:
Uncaught Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
I'm using "#mui/material": "^5.5.2".
Related
I'm using Material-UI Select component, with Material-UI InputBase to show the values.
I need access for the ref of the ROOT of the Input component, but it's not working and the current of the ref is undefined.
I tried using the prop way: ref={inputRef}
I also tried the callback ref: ref={(ref) => inputRef.current = ref}
nothing works.
to make it clear i need the ref of the root element, not the inner inputRef.
Select component
import Input from '#material-ui/core/InputBase';
const MySelect = (props) => {
const inputRef = useRef<any>();
return (<Select
input={
<Input
ref={inputRef}
onClick={() => console.log(inputRef.current)} // output: undefined
/>
}
>
{props.menuItems}
</Select>}
}
EDIT 1 - FROM THE DOCS
Again, i need the ROOT element ref. NOT the inputRef. i need
the div wrapping the native input element.
https://material-ui.com/api/input-base/
EDIT 2 - code sandbox
here is a sandbox link to view the issue live: https://codesandbox.io/s/material-demo-forked-olbo2?file=/demo.tsx
Edit 3
it is confirmed by MUI that it is a bug -
https://github.com/mui-org/material-ui/issues/27792
ref is only available on native HTML element
For InputBase you will need to access native input ref via inputRef instead of ref.
Reference: here
for me it worked by not calling the ref prop "ref" but something like refObj and doing this:
ref={(element) => {
if (refObj) {
// eslint-disable-next-line no-param-reassign
refObj.current = element;
}
}}
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.
I'd like to change the transition of snackbar to Slide instead of Grow (the default behaviour), but I can't do that since I'm using snackbar with Alert.
This is the original demo from Material-UI:
https://codesandbox.io/s/e1dks
If I import this:
import Slide from '#material-ui/core/Slide';
import { TransitionProps } from '#material-ui/core/transitions';
Create this function:
function SlideTransition(props: TransitionProps) {
return <Slide {...props} direction="up" />;
}
And insert this attribute on Snackbar tag:
TransitionComponent={SlideTransition}
I have the error:
Cannot read property 'getBoundingClientRect' of null
Take a look the error when I try to use Snackbar with Alert and Slide at the same time
https://codesandbox.io/s/material-demo-ysub3
At https://material-ui.com/api/slide/ there is a warning that can help, but I didn't understand this:
A single child content element. ⚠️ Needs to be able to hold a ref.
I'm using React with Typescript.
Looking at your example, there is an error in the console:
Warning: Failed prop type: Invalid prop `children` supplied to `ForwardRef(Slide)`. Expected an element that can hold a ref. Did you accidentally use a plain function component for an element instead? For more information see https://material-ui.com/r/caveat-with-refs-guide
Following the "more information" link, it advises that you'll need to wrap your "plain function component" in React.forwardRef.
This results in the changing the Alert function from:
function Alert(props: AlertProps) {
return <MuiAlert elevation={6} variant="filled" {...props} />;
}
to
const Alert = React.forwardRef((props, ref) => <MuiAlert elevation={6} variant="filled" {...props} ref={ref} />);
Once this change has been made, the code works as expected - with the alert sliding in from the bottom rather than popping into view.
I've built my own custom react-bootstrap Popover component:
export default class MyPopover extends Component {
// ...
render() {
return (
<Popover {...this.props} >
// ....
</Popover>
);
}
}
The component is rendered like so:
// ... my different class ...
render() {
const popoverExample = (
<MyPopover id="my-first-popover" title="My Title">
my text
</MyPopover >
);
return (
<OverlayTrigger trigger="click" placement="top" overlay={popoverExample}>
<Button>Click Me</Button>
</OverlayTrigger>
);
}
Now, I want to add custom props to MyPopover component like that:
my text
And to use the new props to set some things in the popover
for example -
<Popover {...this.props} className={this.getClassName()}>
{this.showTheRightText(this.props)}
</Popover>
but then I get this warning in the browser:
Warning: Unknown props popoverType on tag. Remove these props from the element.
Now, I guess that I can just remove the {...this.props} part and insert all the original props one by one without the custom props, but In this way I lose the "fade" effect and also it's an ugly way to handle this problem. Is there an easier way to do it?
Updated answer (React v16 and older):
As of React v16, you can pass custom DOM attributes to a React Component. The problem/warning generated is no longer relevant. More info.
Original answer (React v15):
The easiest solution here is to simply remove the extra prop before sending it to the Popover component, and there's a convenient solution for doing that.
export default class MyPopover extends Component {
// ...
render() {
let newProps = Object.assign({}, this.props); //shallow copy the props
delete newProps.popoverType; //remove the "illegal" prop from our copy.
return (
<Popover {...newProps} >
// ....
</Popover>
);
}
}
Obviously you can (and probably should) create that variable outside your render() function as well.
Basically you can send any props you want to your own component, but you'd have to "clean" it before passing it through. All react-bootstrap components are cleansed from "illegal" props before being passed as attributes to the DOM, however it doesn't handle any custom props that you may have provided, hence why you have to do your own bit of housekeeping.
React started throwing this warning as of version 15.2.0. Here's what the documentation says about this:
The unknown-prop warning will fire if you attempt to render a DOM element with a prop that is not recognized by React as a legal DOM attribute/property. You should ensure that your DOM elements do not have spurious props floating around.
[...]
To fix this, composite components should "consume" any prop that is intended for the composite component and not intended for the child component.
For further reading, check this page from the official react site.
I've built my own custom react-bootstrap Popover component:
export default class MyPopover extends Component {
// ...
render() {
return (
<Popover {...this.props} >
// ....
</Popover>
);
}
}
The component is rendered like so:
// ... my different class ...
render() {
const popoverExample = (
<MyPopover id="my-first-popover" title="My Title">
my text
</MyPopover >
);
return (
<OverlayTrigger trigger="click" placement="top" overlay={popoverExample}>
<Button>Click Me</Button>
</OverlayTrigger>
);
}
Now, I want to add custom props to MyPopover component like that:
my text
And to use the new props to set some things in the popover
for example -
<Popover {...this.props} className={this.getClassName()}>
{this.showTheRightText(this.props)}
</Popover>
but then I get this warning in the browser:
Warning: Unknown props popoverType on tag. Remove these props from the element.
Now, I guess that I can just remove the {...this.props} part and insert all the original props one by one without the custom props, but In this way I lose the "fade" effect and also it's an ugly way to handle this problem. Is there an easier way to do it?
Updated answer (React v16 and older):
As of React v16, you can pass custom DOM attributes to a React Component. The problem/warning generated is no longer relevant. More info.
Original answer (React v15):
The easiest solution here is to simply remove the extra prop before sending it to the Popover component, and there's a convenient solution for doing that.
export default class MyPopover extends Component {
// ...
render() {
let newProps = Object.assign({}, this.props); //shallow copy the props
delete newProps.popoverType; //remove the "illegal" prop from our copy.
return (
<Popover {...newProps} >
// ....
</Popover>
);
}
}
Obviously you can (and probably should) create that variable outside your render() function as well.
Basically you can send any props you want to your own component, but you'd have to "clean" it before passing it through. All react-bootstrap components are cleansed from "illegal" props before being passed as attributes to the DOM, however it doesn't handle any custom props that you may have provided, hence why you have to do your own bit of housekeeping.
React started throwing this warning as of version 15.2.0. Here's what the documentation says about this:
The unknown-prop warning will fire if you attempt to render a DOM element with a prop that is not recognized by React as a legal DOM attribute/property. You should ensure that your DOM elements do not have spurious props floating around.
[...]
To fix this, composite components should "consume" any prop that is intended for the composite component and not intended for the child component.
For further reading, check this page from the official react site.