Material-Ui TextField not affected with the RTL direction - reactjs
I'm using Material-Ui in my React Project !
i followed the steps in the documentation to allow RTL in my project and all work fine !
except the TextField Component
LTR DIRECTION :
RTL DIRECTION
Like you see ! the problem is with the label still in left ( the input text work fine )
App.js file
import React, {useState} from 'react';
//i18n
import {withTranslation} from "react-i18next";
import './i18n';
//jss
import { create } from 'jss';
import rtl from 'jss-rtl';
import { StylesProvider, jssPreset } from '#material-ui/core/styles';
// Configure JSS
const jss = create({ plugins: [...jssPreset().plugins, rtl()] });
function App(props) {
// initialize Language
const { i18n } = props;
const [ prefLang, setPrefLang] = useState(i18n.language);
let theme =createMuiTheme({
palette : {
primary : {
main : '#ed5ac0',
},
},
typography : {
fontFamily : "lalezar, cursive",
h3 : {
fontSize : 1.4,
},
h4 : {
fontSize : 1.5
},
fontAwseomeSize : {
xs : "14px",
sm : "14px",
md : "16px"
},
responsiveFont : {
xs : "20px",
sm : "12.5px",
md : "14px"
},
highLight : {
md : "25px"
},
subHighLight : {
md : "18px"
}
},
}
);
return (
<BrowserRouter>
<LangContext.Provider
value ={{
prefLang,
setPrefLang
}}
>
<CssBaseline/>
<ThemeProvider theme={theme}>
<StylesProvider jss={jss}>
<Grid dir={(prefLang === "ar") ? "rtl" : "ltr"}>
{/*<AppHeader/>*/}
<ContentRouter/>
</Grid>
</StylesProvider>
</ThemeProvider>
</LangContext.Provider>
</BrowserRouter>
);
}
export default withTranslation()(App);
My Form Component
const LoginForm = () => {
return (
<>
<Backdrop style={{ zIndex : 999 , color : theme.palette.primary.main}} open={backdrop} >
<CircularProgress color="inherit" />
</Backdrop>
<form onSubmit={formik.handleSubmit} style={{width: "100%", marginTop: "20px"}}>
{ userNotFound ? <Alert style={{marginBottom : "20px"}} variant="outlined" severity="error">
This is an error alert — check it out!
</Alert> : null}
<TextField
id="identifier"
name="identifier"
onChange={formik.handleChange}
value={formik.values.identifier}
label={t('formIdentifier')}
fullWidth
/>
{formik.touched.identifier && formik.errors.identifier ?
(
<Alert style={{ marginTop :"10px"}} variant="outlined" severity="error">{formik.errors.identifier}</Alert>
) : null}
<TextField
style={{marginTop: "50px"}}
id="password"
name="password"
type="password"
onChange={formik.handleChange}
value={formik.values.password}
label={t('formPassword')}
fullWidth
/>
{formik.touched.password && formik.errors.password ?
(
<Alert style={{ marginTop :"10px"}} variant="outlined" severity="error">{formik.errors.password}</Alert>
) : null}
<Button type="submit" color="primary">{t('login')}</Button>
</form>
</>
);
};
My Theme.js File
import createMuiTheme from "#material-ui/core/styles/createMuiTheme";
let theme =createMuiTheme({
direction : 'rtl',
palette : {
primary : {
main : '#ed5ac0',
},
},
typography : {
fontFamily : "Merienda One, sans-serif",
h3 : {
fontSize : 1.4,
},
h4 : {
fontSize : 1.5
},
fontAwseomeSize : {
xs : "14px",
sm : "14px",
md : "16px"
},
responsiveFont : {
xs : "20px",
sm : "12.5px",
md : "14px"
},
highLight : {
md : "40px"
}
},
}
);
export default theme;
Any suggestion to make the label RTL ?
The documentation contains four steps for rtl support:
Set the dir attribute on the body element.
In my examples below, this is handled by the following:
React.useLayoutEffect(() => {
document.body.setAttribute("dir", isRtl ? "rtl" : "ltr");
}, [isRtl]);
Set the direction in the theme.
In my examples below, I am toggling between two themes:
const ltrTheme = createTheme({ direction: "ltr" });
const rtlTheme = createTheme({ direction: "rtl" });
...
<ThemeProvider theme={isRtl ? rtlTheme : ltrTheme}>
...
</ThemeProvider>
Install the rtl plugin.
For v4 (using JSS), this means installing jss-rtl.
For v5 (using Emotion), this means installing stylis-plugin-rtl.
Load the rtl plugin.
Below is a v4 example showing how to load the rtl plugin for JSS (v5 example further down).
For performance reasons it is important to avoid re-rendering StylesProvider, so this should not be in a component with state that can change and therefore trigger a re-render. In my own app, I have the StylesProvider element in my index.js file as the first element inside the call to react-dom render.
import rtl from "jss-rtl";
import {
StylesProvider,
jssPreset
} from "#material-ui/core/styles";
// Configure JSS
const jss = create({ plugins: [...jssPreset().plugins, rtl()] });
export default function App() {
return (
<StylesProvider jss={jss}>
<AppContent />
</StylesProvider>
);
}
The example below includes a TextField and you can see that the label's position toggles correctly.
import React from "react";
import { create } from "jss";
import rtl from "jss-rtl";
import {
StylesProvider,
jssPreset,
ThemeProvider,
createTheme
} from "#material-ui/core/styles";
import CssBaseline from "#material-ui/core/CssBaseline";
import TextField from "#material-ui/core/TextField";
import Button from "#material-ui/core/Button";
import Box from "#material-ui/core/Box";
// Configure JSS
const jss = create({ plugins: [...jssPreset().plugins, rtl()] });
const ltrTheme = createTheme({ direction: "ltr" });
const rtlTheme = createTheme({ direction: "rtl" });
function AppContent() {
const [isRtl, setIsRtl] = React.useState(false);
const [value, setValue] = React.useState("initial value");
React.useLayoutEffect(() => {
document.body.setAttribute("dir", isRtl ? "rtl" : "ltr");
}, [isRtl]);
return (
<ThemeProvider theme={isRtl ? rtlTheme : ltrTheme}>
<CssBaseline />
<Box m={2}>
<TextField
variant="outlined"
value={value}
onChange={(event) => setValue(event.target.value)}
label={isRtl ? "بريد الكتروني او هاتف" : "Email or Phone"}
/>
<br />
<br />
Current Direction: {isRtl ? "rtl" : "ltr"}
<br />
<Button onClick={() => setIsRtl(!isRtl)}>Toggle direction</Button>
</Box>
</ThemeProvider>
);
}
export default function App() {
return (
<StylesProvider jss={jss}>
<AppContent />
</StylesProvider>
);
}
Below is an equivalent example for v5 using Emotion.
import React from "react";
import rtlPlugin from "stylis-plugin-rtl";
import { CacheProvider } from "#emotion/react";
import createCache from "#emotion/cache";
import { ThemeProvider, createTheme } from "#mui/material/styles";
import CssBaseline from "#mui/material/CssBaseline";
import TextField from "#mui/material/TextField";
import Button from "#mui/material/Button";
import Box from "#mui/material/Box";
import { prefixer } from "stylis";
const cacheLtr = createCache({
key: "muiltr"
});
const cacheRtl = createCache({
key: "muirtl",
// prefixer is the only stylis plugin by default, so when
// overriding the plugins you need to include it explicitly
// if you want to retain the auto-prefixing behavior.
stylisPlugins: [prefixer, rtlPlugin]
});
const ltrTheme = createTheme({ direction: "ltr" });
const rtlTheme = createTheme({ direction: "rtl" });
export default function App() {
const [isRtl, setIsRtl] = React.useState(false);
const [value, setValue] = React.useState("initial value");
React.useLayoutEffect(() => {
document.body.setAttribute("dir", isRtl ? "rtl" : "ltr");
}, [isRtl]);
return (
<CacheProvider value={isRtl ? cacheRtl : cacheLtr}>
<ThemeProvider theme={isRtl ? rtlTheme : ltrTheme}>
<CssBaseline />
<Box m={2}>
<TextField
value={value}
onChange={(event) => setValue(event.target.value)}
label={isRtl ? "بريد الكتروني او هاتف" : "Email or Phone"}
/>
<br />
<br />
Current Direction: {isRtl ? "rtl" : "ltr"}
<br />
<Button onClick={() => setIsRtl(!isRtl)}>Toggle direction</Button>
</Box>
</ThemeProvider>
</CacheProvider>
);
}
In addition, I have a later answer that discusses flipping icons: material-ui icons won't flip when I change to RTL.
Another solution I've found to set this for specific component is to add it via native JS after component is rendered.
First I created a ref to the input element:
const inputRef = createRef()
<TextField inputRef={inputRef} />
Then added a useEffect hook to perform once on each render:
useEffect(() => {
if(inputRef)
inputRef.current.dir = 'auto'
}, [])
Not the most beautiful code, but it sure works 😉
RTL for specific components, and not the whole app:
MUI rtl documentation and the accepted answer pertain to the entire application.
If you want just a few components here and there, all you need to do is create a simple wrapper component, and children will have rtl. No need for a theme provider.
Just be sure to wrap the children with a <div dir="rtl">.
Wrapper Component:
import React from 'react';
import rtlPlugin from "stylis-plugin-rtl";
import { CacheProvider } from "#emotion/react";
import createCache from "#emotion/cache";
import { prefixer } from 'stylis'
const cacheRtl = createCache({
key: "muirtl",
stylisPlugins: [prefixer, rtlPlugin]
});
const RtlProvider = ({ children }) => {
return (
<CacheProvider value={cacheRtl}>
<div dir="rtl">
{children}
</div>
</CacheProvider>
);
};
export default RtlProvider;
Simple Implementation
return (
<RtlProvider>
<TextField label="my label"/>
</RtlProvider>
)
Making CacheProvider tag as a youngest parent for your content, such like below:
<ThemeProvider>
<ScrollToTop />
<BaseOptionChartStyle />
<CacheProvider value={cacheRtl}>
<Router />
</CacheProvider>
</ThemeProvider>
*note:
(Router) includes the various textFields
apart from theme and jss-rtl and stuff that Ryan Cogswell said, there's one tiny problem that i found no where after days of searching (just 3-4 days actualy :)).
and that is the import statement.
make sure youre import statement is like the following:
import TextField from "#material-ui/core/TextField"; //true
if youre import statement is like following, none of those theming and jss stuff works:
import TextField from "#mui/material/TextField"; //false
Related
material-ui icons won't flip when I change to RTL
I've used Ryan Cogswell's answer to have my project compatible to RTL. But for some reason, the Material-ui icon <Send/> didn't flip accordingly. Is it because it's not compatible to RTL? Or am I missing something? Here's an example showing that the Send icon doesn't flip: import React from "react"; import { create } from "jss"; import rtl from "jss-rtl"; import { StylesProvider, jssPreset, ThemeProvider, createMuiTheme } from "#material-ui/core/styles"; import CssBaseline from "#material-ui/core/CssBaseline"; import TextField from "#material-ui/core/TextField"; import Button from "#material-ui/core/Button"; import Box from "#material-ui/core/Box"; import SendIcon from "#material-ui/icons/Send"; // Configure JSS const jss = create({ plugins: [...jssPreset().plugins, rtl()] }); const ltrTheme = createMuiTheme({ direction: "ltr" }); const rtlTheme = createMuiTheme({ direction: "rtl" }); function AppContent() { const [isRtl, setIsRtl] = React.useState(false); React.useLayoutEffect(() => { document.body.setAttribute("dir", isRtl ? "rtl" : "ltr"); }, [isRtl]); return ( <ThemeProvider theme={isRtl ? rtlTheme : ltrTheme}> <CssBaseline /> <Box m={2}> <TextField label={isRtl ? "بريد الكتروني او هاتف" : "Email or Phone"} /> <br /> <SendIcon /> <br /> Current Direction: {isRtl ? "rtl" : "ltr"} <br /> <Button onClick={() => setIsRtl(!isRtl)}>Toggle direction</Button> </Box> </ThemeProvider> ); } export default function App() { return ( <StylesProvider jss={jss}> <AppContent /> </StylesProvider> ); } Thanks
Material-UI icons are not automatically flipped for rtl. This is discussed some here: https://github.com/mui-org/material-ui/issues/22726. Here is an example of one way to handle this for the Send icon (and this approach should be usable for other icons as well): const DirectionAwareSendIcon = withStyles((theme) => ({ root: { transform: theme.direction === "rtl" ? "scaleX(-1)" : undefined } }))(SendIcon); It is also possible to handle this globally using overrides in the theme: MuiSvgIcon: { root: { "body[dir=rtl] &": { transform: "scaleX(-1)" } } } There is some risk that this could conflict with styling in some Material-UI components which use transform in the default styles, but the examples that I have looked at so far (e.g. AccordionSummary), seem to still work fine. This global approach would currently cause issues for TablePaginationActions and PaginationItem which both swap which icons they use based on theme.direction. This global approach would then flip the already-flipped icon, so if you use either of these components you would need to take this into consideration. There are also some icons where flipping is not desirable, for instance icons with a recognizable symbol such as Help ("?") and AttachMoney ("$"), so my recommendation would be the first approach of just explicitly adding the flipping behavior to the icons that need it. Here's a full working example with the theme approach: import React from "react"; import { create } from "jss"; import rtl from "jss-rtl"; import { StylesProvider, jssPreset, ThemeProvider, createMuiTheme } from "#material-ui/core/styles"; import CssBaseline from "#material-ui/core/CssBaseline"; import TextField from "#material-ui/core/TextField"; import Button from "#material-ui/core/Button"; import Box from "#material-ui/core/Box"; import SendIcon from "#material-ui/icons/Send"; const overrides = { MuiSvgIcon: { root: { "body[dir=rtl] &": { transform: "scaleX(-1)" } } } }; // Configure JSS const jss = create({ plugins: [...jssPreset().plugins, rtl()] }); const ltrTheme = createMuiTheme({ direction: "ltr" }); const rtlTheme = createMuiTheme({ direction: "rtl", overrides }); function AppContent() { const [isRtl, setIsRtl] = React.useState(false); React.useLayoutEffect(() => { document.body.setAttribute("dir", isRtl ? "rtl" : "ltr"); }, [isRtl]); return ( <ThemeProvider theme={isRtl ? rtlTheme : ltrTheme}> <CssBaseline /> <Box m={2}> <TextField label={isRtl ? "بريد الكتروني او هاتف" : "Email or Phone"} /> <br /> <SendIcon /> <br /> Current Direction: {isRtl ? "rtl" : "ltr"} <br /> <Button onClick={() => setIsRtl(!isRtl)}>Toggle direction</Button> </Box> </ThemeProvider> ); } export default function App() { return ( <StylesProvider jss={jss}> <AppContent /> </StylesProvider> ); }
React Material-UI and color: warning
I am new to React and MUI and maybe I am just missing something. I am trying to make a button with color='warning' that is defined in my palette like this (the theme works and I can use primary and secondary colors): const theme = createMuiTheme({ palette: { primary: { main: '#70B657' }, secondary: { light: '#2face3', main: '#4199D8', contrastText: '#ffcc00' }, warning: { main: '#BC211D' } } }); I noticed in the documentation that the <Button> color prop only takes default|inherit|primary|secondary so it is not possible to use it like that. So what is the CORRECT or best practice to use warning colored button in Material-UI? I think this is a basic thing and should be pretty easy to achieve..?? Preferably a solution that does not involve making several different Themes and importing them when needed. Thanks!
Usage: const useStyles = makeStyles(theme => ({ root: { color: theme.palette.warning.main } })); Full code: import React from "react"; import "./styles.css"; import { Button } from "#material-ui/core"; import { createMuiTheme, ThemeProvider } from "#material-ui/core/styles"; import { makeStyles } from "#material-ui/core/styles"; const useStyles = makeStyles(theme => ({ root: { color: theme.palette.warning.main } })); function YourComponent() { const classes = useStyles(); return ( <div className="App"> <Button variant="contained" classes={{ root: classes.root }}> Secondary </Button> </div> ); } const theme = createMuiTheme({ palette: { warning: { main: "#FFFFFF" } } }); export default function App() { return ( <ThemeProvider theme={theme}> <YourComponent /> </ThemeProvider> ); } Update Pass props to makeStyles import React from "react"; import "./styles.css"; import { makeStyles } from "#material-ui/core/styles"; const useStyles = props => makeStyles(theme => ({ root: { color: props.value === "111" ? "red" : "blue" } })); const Comp = props => { const classes = useStyles(props)(); return <input defaultValue={props.value} className={classes.root} />; }; export default function App() { return ( <div className="App"> <div> <Comp value={"111"} /> </div> <div> <Comp value={"222"} /> </div> </div> ); }
yeah I don't understand why the first example would work and the second dont. app component const theme = createMuiTheme({ palette: { primary: { main: '#bed000', }, secondary: { main: '#110b36', }, error: { main: '#B33A3A', }, }, }) <MuiThemeProvider theme={theme}> <Route exact path="/" component={LoginView} /> </MuiThemeProvider> <LoginView> <TextField autoFocus label="Contraseña" name="Password" type="Password" value={values.Password} onChange={handleChange} onBlur={handleBlur} fullWidth color={touched.Password && errors.Password ? "primary" : "secondary"} /> <TextField autoFocus label="Contraseña" name="Password" type="Password" value={values.Password} onChange={handleChange} onBlur={handleBlur} fullWidth color={touched.Password && errors.Password ? "error" : "secondary"} /> </LoginView>
Material UI Palette not updating
I'm new to React and Material UI, i'm trying to add some custom styles to the MUI buttons by using the createMuiTheme. I've followed the docs and pretty much copied the example but it's having no effect and no errors are being thrown in the console. I've been banging my head against this for a while now and I can't see what the problem is, what am I missing? import React from 'react'; import { createMuiTheme } from '#material-ui/core/styles'; import { ThemeProvider } from '#material-ui/styles'; import Button from '#material-ui/core/Button'; const mytheme = createMuiTheme({ palette: { primary: { main: '#1565C0'}, secondary: { main: '#11cb5f' }, }, }); export const PrimaryButton = (props) => { return ( <ThemeProvider theme={mytheme}> <a href={props.buttonLink}> <Button style={{ ...props.styles}} onClick={props.handleClick} variant='contained' color='primary' size='large' > {props.buttonText} </Button> </a> </ThemeProvider> ); }; export const SecondaryButton = (props) => { return ( <ThemeProvider theme={mytheme}> <Button style={{...props.styles }} value={props.value || ''} onClick={props.handleClick} variant='outlined' color='secondary' size='large' > {props.buttonText} </Button> </ThemeProvider> ) }
It is working. Try to change this: variant='outlined' by variant='contained'
React input loses focus after each keystroke
I have a search bar on my nav component. After each keystroke, the input loses focus and you have to re-click on it to type the next key. Here is the input: <input type="text" name="search" placeholder="Search" value={search} onChange={handleInputChange} /> Here is the handleInputChange function: function handleInputChange(event) { event.preventDefault(); let value = event.target.value; setSearch(value); } Here is the hook for setting the search: const [search, setSearch] = useState(""); I've tried adding a key to the input, but that doesn't work. When I move the search input to a new component, that also doesn't work. Here is the complete code: import React, { useEffect, useState, useCallback } from "react"; import PropTypes from 'prop-types'; import { useSelector } from 'react-redux'; import AppBar from '#material-ui/core/AppBar'; import Toolbar from '#material-ui/core/Toolbar'; import { Row, Col } from '../Grid'; import IconButton from '#material-ui/core/IconButton'; import SearchIcon from '#material-ui/icons/Search'; import ShoppingCartOutlinedIcon from '#material-ui/icons/ShoppingCartOutlined'; import MenuIcon from '#material-ui/icons/Menu'; import Badge from '#material-ui/core/Badge'; import useScrollTrigger from '#material-ui/core/useScrollTrigger'; import Slide from '#material-ui/core/Slide'; import SideMenu from '../SideMenu'; import { MuiThemeProvider, createMuiTheme } from '#material-ui/core/styles'; import { toast } from 'react-toastify'; import 'react-toastify/dist/ReactToastify.css'; import { css } from 'glamor'; import "./style.css"; function Navbar(props) { const cart = useSelector(state => state.cart); const [cartTotal, setCartTotal] = useState(0); const [loggedIn, setLoggedIn] = useState(false); const [search, setSearch] = useState(""); const [isOpen, setIsOpen] = useState(false); const [renderMiddleCol, setMiddleCol] = useState(true); const [menuClass, setMenuClass] = useState("no-menu"); useEffect(() => { if (cart[0]) { setCartTotal(cart[0].line_items.length) } }, [cart[0]]); useEffect(() => { if (window.sessionStorage.id) { setLoggedIn(true); } else { setLoggedIn(false); } }, [loggedIn]); useEffect(() => { if (window.innerWidth < 450) { setMiddleCol(false); } else { setMiddleCol(true); } }, [window.innerWidth]); function HideOnScroll(props) { const { children, window } = props; const trigger = useScrollTrigger({ target: window ? window() : undefined }); return ( <Slide appear={false} direction="down" in={!trigger}> {children} </Slide> ); } HideOnScroll.propTypes = { children: PropTypes.element.isRequired, window: PropTypes.func, }; function CheckCart() { if (window.sessionStorage.id) { window.location.href = "/cart"; } else { toast("Please login to view your cart", { className: css({ background: '#3E0768', boxShadow: '2px 2px 20px 2px rgba(0,0,0,0.3)', borderRadius: '17px' }), bodyClassName: css({ fontSize: '20px', color: 'white' }), progressClassName: css({ background: "linear-gradient(90deg, rgba(0,0,0,1) 0%, rgba(62,7,104,1) 80%)" }) }); } } function Search() { if (search) { sessionStorage.setItem("search", search); window.location.href = "/search"; } else { toast("Search field cannot be empty", { className: css({ background: '#3E0768', boxShadow: '2px 2px 20px 2px rgba(0,0,0,0.3)', borderRadius: '17px' }), bodyClassName: css({ fontSize: '20px', color: 'white' }), progressClassName: css({ background: "linear-gradient(90deg, rgba(0,0,0,1) 0%, rgba(62,7,104,1) 80%)" }) }); } } function logOut(event) { event.preventDefault(); setIsOpen(false); sessionStorage.clear(); window.location.href = "/login"; } function handleInputChange(event) { event.preventDefault(); let value = event.target.value; setSearch(value); } function toggleMenu(event) { event.preventDefault(); setIsOpen(!isOpen); if (menuClass === "no-menu") { setMenuClass("menu-background"); } else { setMenuClass("no-menu"); } } const theme = createMuiTheme({ palette: { primary: { main: '#000000', contrastText: '#ffffff', }, secondary: { light: '#3E0768', main: '#3E0768', contrastText: '#ffffff', }, tertiary: { main: '#ffffff', } }, }); return ( <MuiThemeProvider theme={theme}> <HideOnScroll {...props}> <AppBar position="fixed" color="primary"> <Toolbar> <Col size="md-1"> <IconButton onClick={toggleMenu} aria-label="Menu" > <MenuIcon fontSize="large" className="white" /> </IconButton> </Col> <Col size="md-2"> <h6>Demo Company</h6> </Col> {renderMiddleCol ? ( <Col size="lg-6 md-5 sm-3" /> ) : (<div />)} <Col size="md-2 4"> <Row no-gutters> <div className="search-box"> <Col size="md-2 1"> <IconButton onClick={Search} aria-label="search" > <SearchIcon className="white" /> </IconButton> </Col> <Col size="md-8 9"> {/* <SearchForm value={search} onChange={handleInputChange} /> */} <input className="search-field white" type="text" name="search" placeholder="Search" value={search} onChange={handleInputChange} /> </Col> </div> </Row> </Col> <Col size="md-1"> <IconButton onClick={CheckCart} aria-label="Go to cart" > <MuiThemeProvider theme={theme}> <Badge badgeContent={cartTotal} color="secondary" > <ShoppingCartOutlinedIcon className="white" /> </Badge> </MuiThemeProvider> </IconButton> </Col> </Toolbar> </AppBar> </HideOnScroll> <SideMenu isOpen={isOpen} menuClass={menuClass} toggleMenu={toggleMenu} loggedIn={loggedIn} logOut={logOut} /> </MuiThemeProvider> ); } export default Navbar;
I ended up using a ref on the input and setting it focus on each re-render. Here is the code that fixed it. const [search, setSearch] = useState(""); const searchInput = React.useRef(null); useEffect(() => { searchInput.current.focus(); }, [search]); And here is the input: <input ref={searchInput} className="search-field white" type="text" name="search" placeholder="Search" value={search} onChange={handleInputChange} /> Credit for the solution: React: set focus on componentDidMount, how to do it with hooks?
Here's a detailed explanation that I've found helpful: https://reactkungfu.com/2015/09/react-js-loses-input-focus-on-typing/ To summarize: without an unchanging key, React is throwing away the previous instance of your controlled input upon state change and is creating a new one in its place. The input that had focus is removed immediately after its value changes. Make sure: Your controlled input has a key attribute The value of key isn't derived from the input's value in any way, because you don't want the key to change when the value changes
I don't know exactly why, but my focus problem was solved by changing this code: import { Route } from 'react-router-dom' <Route path='xxx' component={() => <TheComponent... />} where TheComponent contains the input element that loses focus while typing, to this code: <Route path='xxx'> <TheComponent... /> </Route> See my SO question, hopefully someone will soon shed some light on how this worked
What fixed this issue for me was to not use inner components. Such as const NewComponent = (props: {text: string}) => <div>{text}</div>; return <div><NewComponent text="Text" /><div>; When I declared input components this way it caused a re-render on every keystroke. The solution was to extract the components to another file or just put the whole component in the JSX without using the above method to clean up the code. It has something to do with React not being able to know when to rerender.
Making theme available in HOC component
I am trying to use a navbar from the Material UI collection however the component was written as a function and was using hooks. I'm trying to convert the component to a HOC (class) and I am having issues with accessing the theme in the component. Theme in the component in undefined const styles = theme => ({ root: { display: "flex" }, }); <IconButton onClick={this.handleDrawerClose}> {theme.direction === "ltr" ? ( <ChevronLeftIcon /> ) : ( <ChevronRightIcon /> )} </IconButton>
Try this: import React from 'react'; import PropTypes from 'prop-types'; import { withStyles } from '#material-ui/core'; import Paper from './Paper'; const styles = () => ({ root: { display: 'flex' } }); const Bubble = props => { const { classes } = props; return ( <IconButton className={classes.root} onClick={this.handleDrawerClose}></IconButton> ); }; export default withStyles(styles)(Bubble);