Material UI - why InputBase ref is undefined - reactjs

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;
}
}}

Related

Styled components: cannot find name 'ref' when trying to pass a ref

I'm trying to pass a ref to be read inside of emotion component (similar to styled-components)
But receving the error Cannot find name 'ref'
What should I do in order to be able to access the ref inside the emotion component?
const Label = styled('p')<{ ref: React.MutableRefObject<null> }>({
fontsize: ref.current...,
});
export const NodeDisplayer = ({ data }) => {
const size = useRef(null);
return (
<>
<Label ref={size} id="title">
</Label>
</>
What are you trying to do? Why would a component need to access its own DOM node to determine its styling? I doubt it will work properly.
But to answer your question, the ref prop is special in React so it isn't accessible in the props, but nothing prevents you from also adding it as a different prop:
<Label ref={size} myRef={size} id="title">
Then you should be able to access myRef.
You cannot get a reference to a component function, because it does not exist in the DOM. You need to reference the HTML element, which you might as well do in the child. And since the ref will not be populated until after the component has rendered, you need to use useEffect to get the referenced node.
function Label() {
const ref = useRef<HTMLParagraphElement>(null);
const [style, setStyle] = useState<CSSProperties>({});
useEffect(() => setStyle({ fontSize: ref.current.clientWidth + 'px' }), []);
return (
<p ref={ref} style={style}>
Hi
</p>
);
}
export const NodeDisplayer = () => <Label></Label>

Assigning ref to HTMLInputElement giving error in typescript

In useRef variable cannot set to input element
const inputRef = React.useRef<HTMLInputElement>(null);
<Input name="answer" ref={inputRef}/>
On the <Input ref=""> I'm Getting the following error
Any Idea How to Fix this. Thanks In Advance
My Full Code :
Your ref in not assigned to HTMLInputElement but an Input component. You need to define the type of it accordingly
Since Input is a functional component, you can't specify a ref on it directly, you need to forward the ref and for that you need to use React.forwardRef
const inputRef = React.useRef<HTMLInputElement>(null);
..
<Input name="answer" ref={inputRef}/>
...
const Input = React.forwardRef((props, ref: React.Ref<HTMLInputeElement>) => (
<input ref={ref} {...props} />
));
Since you are using semantic-ui-react, in order to pass on ref to the components, you need to make use of Ref component from the library
import {Ref } from 'semantic-ui-react';
...
const inputRef = React.useRef<HTMLInputElement>(null);
..
<Ref innerRef={inputRef}><Input name="answer" ref={inputRef}/></Ref>
Check a bit.dev for more details

React Material UI Tooltips Disable Animation

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".

Material-ui's Switch component onChange handler is not firing

I've put some Switches in an app and they work fine. Then I put the Switches in another app, but they don't work when clicked.
Both apps are using the same component. Here it is working in one app:
And here's the other app, not working:
In the second app, the onChange handler doesn't seem to ever fire.
The code in both apps looks like the following:
<Switch
checked={(console.log('checked:', status === 'visible'), status === 'visible')}
onChange={(e, c) => console.log('event:', e, c)}
/>
In the first app I see the output of those console.logs, while in the second app I only see the initial console.log of the checked prop, but I never see any of the onChange prop.
I checked if any ancestor elements have click handlers, and I didn't find any that are returning false, calling stopPropagation, or calling preventDefault.
Notice in the gif that when I click, the ripple effect still works, so click handling is obviously still working.
Any ideas why onChange may not be firing?
UPDATE! I replaced the switches with regular <input type="checkbox"> elements, and it works great! See:
Looks to me like something is wrong with material-ui's <Switch> component. I have a hunch that I will investigate when I get a chance: there might be more than one React singleton in the application. I'll be back to post an update.
I think, this is a weird fix and it is working smoothly for me. So, instead of handleChange I am using handleClick. I am not using event here, instead I am passing a string which is obviously the name of the state or id in case of arrays.
<Switch
checked={this.state.active}
onClick={() => this.handleToggle('active')}
value="active"
inputProps={{ 'aria-label': 'secondary checkbox' }}
/>
handleToggle = (name: string) => {
this.setState({ active: !this.state.active });
};
I tried handleChange, but the problem still persists. I hope this will get fixed soon.
I've had the same issue with Checkbox and Switch in the WordPress admin area.
Turns out, there was global CSS rule like:
input[type="checkbox"] {
height: 1rem;
width: 1rem;
}
Clicking the upper left corner of the element works, though.
As a solution, I reset some styles in my app root.
EDIT: Nevermind, I just put my whole app into shadow DOM. There are a few gotchas, I'll list them here:
You have to provide a custom insertion point for Material-UI style elements inside the shadow DOM. In general, you have to make nothing gets put outside of you shadow DOM.
You have to load/link the font inside the shadow DOM and outside the shadow DOM (the light DOM).
Use ScopedCssBaseline instead of the global reset.
Dialogs have to have their container prop specified.
This is how I've set things up with Material-UI:
// configure-shadow-dom.js
import { create } from 'jss';
import { jssPreset } from '#material-ui/core/styles';
const shadowHostId = 'my-app-root-id'
export const appRoot = document.createElement('div')
appRoot.setAttribute('id', 'app-root')
const styleInsertionPoint = document.createComment('jss-insertion-point')
export const jss = create({
...jssPreset(),
insertionPoint: styleInsertionPoint,
})
const robotoFontLink = document.createElement('link')
robotoFontLink.setAttribute('rel', 'stylesheet')
robotoFontLink.setAttribute('href', 'https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap')
const shadowHost = document.getElementById(shadowHostId)
shadowHost.attachShadow({ mode: 'open' })
const shadowRoot = shadowHost.shadowRoot
shadowRoot.appendChild(robotoFontLink)
shadowRoot.appendChild(styleInsertionPoint)
shadowRoot.appendChild(appRoot)
// index.jsx
import React from 'react';
import ReactDOM from 'react-dom';
import ScopedCssBaseline from '#material-ui/core/ScopedCssBaseline';
import { StylesProvider } from '#material-ui/core/styles';
import App from './App';
import { jss, appRoot } from './configure-shadow-dom';
ReactDOM.render(
<React.StrictMode>
<StylesProvider jss={jss}>
<ScopedCssBaseline>
<App />
</ScopedCssBaseline>
</StylesProvider>
</React.StrictMode>,
appRoot
);
It turns out that in my case, there was a CSS in the page, something like
.some-component { pointer-events: none; }
.some-component * { pointer-events: auto; }
where .some-component was containing my material-ui buttons and switches. I had to manually set pointer-events to all (or some value, I don't remember at at the moment) for the elements inside the switches.
So, that's one thing to look out for: check what pointer-events style is doing.
in Switch component, changing onChange to onClick worked for me:
<Switch
checked={this.state.active}
onClick={() => this.handleToggle('active')}
value="active"
inputProps={{ 'aria-label': 'secondary checkbox' }}
/>
still running into similar issues getting an event from the switch. here's a quick fix using internal state (you should be using hooks now and functional components, if not you are missing out, but you can use setState to do what i'm showing here in a class component.)
const [switchChecked, setSwitchChecked] = useState(false);
.......
<Switch checked={switchChecked} onChange={() => {setSwitchChecked(!switchChecked);}}/>
you'll need to have a value for switchChecked in the components local state.
Try this one checked or unchecked passed as a second argument.
<Switch
checked={this.state.active}
onClick={(e,flag) => this.handleToggle('active', flag)}
value="active"
inputProps={{ 'aria-label': 'secondary checkbox' }}
/>```
The issue is that you have to sniff the checked attribute from the event: event.target.checked
Full solution:
import { Switch } from '#material-ui/core';
import { useState } from 'react';
function App() {
const [checked, setChecked] = useState(false);
const switchHandler = (event) => {
//THIS IS THE SOLUTION - use event.target.checked to get value of switch
setChecked(event.target.checked);
};
return (
<div>
<Switch checked={checked} onChange={switchHandler} />
</div>
);
}
export default App;

Selecting an element in React in a stateless function component in React?

<TextField
onChange={props.onChangeTextField}
ref="questionInput"
style={styles.textField}
value={props.existingValue}
fullWidth={true}
/>
I was trying to give an input field in a stateless function component to be able to focus it when the component loads like this:
componentWillMount = () => {
this.refs.questionInput.focus();
console.log('test')
}
}
But I got the error:
Stateless function components cannot have refs.
So is there a way to focus an input field in React without a ref?
You should wrap your input component with forwardRef function. Something like this:
import * as React from "react";
const TextInput = React.forwardRef(
(props, ref) => <input ref={ref} {...props} />
);
export default TextInput;
Note that it will add a second argument to your functional component, which you should pass to the DOM element as ref prop.
Yes. However your method of using ref is really outdated. You should update to the latest version of React (currently 16.3.2) and follow the official documentation
function CustomTextInput(props) {
// textInput must be declared here so the ref can refer to it
let textInput = React.createRef();
function handleClick() {
textInput.current.focus();
}
return (
<div>
<input
type="text"
ref={textInput} />
<input
type="button"
value="Focus the text input"
onClick={handleClick}
/>
</div>
);
}
No, you need to change the functional component into a class.
You may not use the ref attribute on functional components because they don’t have instances
You should also use the newer callback API to set the ref:
ref={ref => { this.questionInput = ref }}
Or createRef for v16.3.
Adding the autoFocus prop to the input component might do the trick if you just want it to be focused on mount:
<TextField autoFocus ..restOfProps />

Resources