I've got a big React app, which is now using Material UI 4.3.0.
I was trying to remove the margin-top style of label + .MuiInput-formControl. (It is a select mui component)
I used the 'overrides' tag in my App.js, as I have before, doing
const theme = createMuiTheme({
overrides: {
MuiInput: {
formControl: {
"label + &": {
marginTop: "0",
}
}
}
},
...
}
And it worked just fine... but it broke every other time I used the same component, as expected.
In my current, working file where I want to change margin, I'm having a hard time overriding the default styles. What is the correct way to override it?
I've tried overriding with classes, unsuccessfully, tried to do const styles = theme => ({ overrides.... etc as i wrote on the createmuitheme above, and tried with inline style.
I know there is a correct way to do it, but i'm not experienced enough to find it. An incorrect, but working way to do it, was to wrap the component in a div and using negative margins on it, but i'm looking to correct it the right way, as it is going to be useful later on too.
Thanks!
Edit: Component that i'm trying to style
renderFormats(){
const { formats } = this.state;
var formatsDOM = undefined;
if(formats !== undefined && this.state.selectedFormat !== undefined){
formatsDOM =
<Select
value={this.state.selectedFormat}
onChange={this.onExportChange.bind(this)}
disabled={!this.state.customFormatIsSelected}
inputProps={{
name: 'Format',
id: 'FormatInput',
}}
>
{formats.map( format => this.renderFormatEntry(format))}
</Select>
}
return formatsDOM;
}
If you are using TextField, then you need to specify the formControl class via InputProps. If you are using the lower-level components (FormControl, InputLabel, and Select) explicitly, then you need a custom Input component (called InputNoMargin in my example) with the formControl class specified and then specify that component using the input property of Select.
Below is an example which applies this styling to a text input TextField, a select TextField, and a "composed" Select using the lower-level components.
import React from "react";
import { makeStyles } from "#material-ui/core/styles";
import TextField from "#material-ui/core/TextField";
import MenuItem from "#material-ui/core/MenuItem";
import FormControl from "#material-ui/core/FormControl";
import InputLabel from "#material-ui/core/InputLabel";
import Input from "#material-ui/core/Input";
import Select from "#material-ui/core/Select";
const useStyles = makeStyles({
formControl: {
"label + &": {
marginTop: 0
}
}
});
const currencies = [
{
value: "USD",
label: "$"
},
{
value: "EUR",
label: "€"
},
{
value: "BTC",
label: "฿"
},
{
value: "JPY",
label: "¥"
}
];
const InputNoMargin = props => {
const inputClasses = useStyles(props);
return <Input {...props} classes={inputClasses} />;
};
export default function TextFields() {
const inputClasses = useStyles();
const [values, setValues] = React.useState({
name: "Cat in the Hat",
currency: "EUR"
});
const handleChange = name => event => {
setValues({ ...values, [name]: event.target.value });
};
return (
<form>
<TextField
id="standard-name"
label="Name"
value={values.name}
InputProps={{ classes: inputClasses }}
onChange={handleChange("name")}
margin="normal"
/>
<br />
<TextField
id="standard-select-currency"
select
label="Select"
value={values.currency}
onChange={handleChange("currency")}
InputProps={{ classes: inputClasses }}
helperText="Please select your currency"
margin="normal"
>
{currencies.map(option => (
<MenuItem key={option.value} value={option.value}>
{option.label}
</MenuItem>
))}
</TextField>
<br />
<FormControl>
<InputLabel htmlFor="composed-select">Composed Select</InputLabel>
<Select
value={values.currency}
onChange={handleChange("currency")}
inputProps={{ id: "composed-select", name: "composed-select" }}
input={<InputNoMargin />}
>
{currencies.map(option => (
<MenuItem key={option.value} value={option.value}>
{option.label}
</MenuItem>
))}
</Select>
</FormControl>
</form>
);
}
Related
I made a reusable formik form, but I want to use ant select instead of formik select. the Error message is not working with ant design and I dont know how to configure that.
I need to show the error message when it's not validated. in console.log there is no problem it wworks perfect as i change the ant select. But the errortext does not shows
import React from "react";
import { Formik } from "formik";
import {Form} from 'antd'
import * as Yup from "yup";
import FormikController from "../components/Forms/FormikController";
const city = [
{ key: "option 1", value: "option1" },
{ key: "option 2", value: "option2" },
{ key: "option 3", value: "option3" },
{ key: "option 4", value: "option4" },
];
const Contact = () => {
const initialValues = {
whoYouAre: "",
email: "",
message: "",
};
const validationSchema = Yup.object({
whoYouAre :Yup.string().required(),
email :Yup.string().required()
});
return (
<Container className="contact">
<h5 className="mb-4">Contact</h5>
<Formik
initialValues={initialValues}
validationSchema={validationSchema}
// onSubmit={handleAddData}
validateOnMount
>
{(formik) => (
<Form className="w-100">
<FormikController
control="selectsearch"
options={city}
formik={formik}
name="whoYouAre"
label="Please Tell Us Who You Are"
/>
<FormikController
control="input"
type="text"
name="email"
label="Email"
/>
<FormikController
control="textarea"
name="message"
rows="8"
label="Message"
/>
<div>
<Button variant="primary" type="submit">
Send
</Button>
</div>
</Form>
)}
</Formik>
</Container>
);
};
export default Contact;
and here the resusable select that i used from ant design
import React from "react";
import { Form,Select } from "antd";
import { ErrorMessage } from "formik";
import TextError from "./TextError";
const { Option } = Select;
const SearchSel = (props) => {
const { label, name, options ,formik, ...rest } = props;
console.log(formik);
return (
<Form.Item className="mb-3">
<Select
showSearch
size="large"
optionFilterProp="children"
placeholder={label}
name={name}
onChange={(value) => formik.setFieldValue("whoYouAre", value)}
>
{options.map((option) => {
return (
<Option key={option.value} value={option.value}>
{option.key}
</Option>
);
})}
</Select>
<ErrorMessage name={name} component={TextError} />
</Form.Item>
);
};
export default SearchSel;
Your default state is "" isn't right I believe. You probably want null.
Secondly, your Select is not correctly bound to the form state. You need to add value.
Moreover, you also need to add defaultActiveFirstOption={false} as that may be immediately selecting the first item on the initial state.
Finally, and probably most importantly, probably need to bind the fields blur such that it sets the field as touched. ErrorMessage won't show the error unless it thinks the user touched the field:
<Select
showSearch
size="large"
optionFilterProp="children"
placeholder={label}
name={name}
value={formik.values.whoYouAre}
defaultActiveFirstOption={false}
onChange={(value) => formik.setFieldValue("whoYouAre", value)}
onBlur={() => formik.setFieldTouched("whoYouAre", true)}
>
I am using MUI v5. I have made a form to choose fighters but I am stuck trying to override the label color of the TimePicker component. All my other form components have the normal color I want but I can't figure out how to change the color of the TimePicker.
The TimePicker component is not focused in the image. When I select the other form components the color changes to red, like it should but TimePicker stays red the entire time focused or not.
I have tried changing my theme colors, I have used
inputProps={{ className: classes.input }}
within the component and successfully changed the text color and background of the TimePicker but can't find the proper CSS to change the label color.
In my code you can see I have tried to override the .root and .floatingLabelFocusStyle but to no avail.
import { makeStyles, TextField } from "#material-ui/core";
import { LocalizationProvider, TimePicker } from "#mui/lab";
import AdapterDateFns from "#mui/lab/AdapterDateFns";
import { useState } from "react";
const useStyles = makeStyles((theme) => ({
input: {
color: 'blue'
},
floatingLabelFocusStyle: {
},
root: {
color: 'blue'
}
}));
const GameTimePicker = ({ setFormGameTime, currentTime = '' }) => {
const classes = useStyles();
const [gameTime, setGameTime] = useState(currentTime);
const handleChange = (e, v) => {
// console.log(v);
setGameTime(v);
setFormGameTime(v);
}
return (
<LocalizationProvider dateAdapter={AdapterDateFns}>
<TimePicker
ampm={false}
views={['minutes', 'seconds']}
inputProps={{ className: classes.input }}
color='success'
inputFormat="mm:ss"
mask="__:__"
label="Time of fight/event"
value={gameTime}
onChange={handleChange}
renderInput={(params) => <TextField {...params} />}
/>
</LocalizationProvider>
)
}
export default GameTimePicker;
Any ideas how I can override this color?
Is there any easy way to customize Material UI Autocomplete so it Popper dropdown can be positioned relative to the text cursor (Similar to the VS Code Intellisense dropdown)? I have a multiline Textfield component as an input field.
Code looks something like this:
import React from "react";
import { createStyles, makeStyles, Theme } from '#material-ui/core/styles';
import TextField from "#material-ui/core/TextField";
import Autocomplete from "#material-ui/lab/Autocomplete";
import Chip from '#material-ui/core/Chip';
import { Popper } from "#material-ui/core";
const targetingOptions = [
{ label: "(", type: "operator" },
{ label: ")", type: "operator" },
{ label: "OR", type: "operator" },
{ label: "AND", type: "operator" },
{ label: "Test Option 1", type: "option" },
{ label: "Test Option 2", type: "option" },
];
const useStyles = makeStyles((theme: Theme) =>
createStyles({
root: {
'& .MuiAutocomplete-inputRoot': {
alignItems: 'start'
}
},
}),
);
export default () => {
const classes = useStyles();
const CustomPopper = function (props) {
return <Popper {...props} style={{ width: 250, position: 'relative' }} />;
};
return (
<div>
<Autocomplete
className={classes.root}
multiple
id="tags-filled"
options={targetingOptions.map((option) => option.label)}
freeSolo
disableClearable
PopperComponent={CustomPopper}
renderTags={(value: string[], getTagProps) =>
value.map((option: string, index: number) => (
<Chip variant="outlined" label={option} {...getTagProps({ index })} />
))
}
renderInput={(params) => (
<TextField {...params} variant="outlined" multiline={true} rows={20} />
)}
/>
</div>
);
};
Autocomplete from material ui has a PopperComponent property which you can use to create a custom popper that has placement property you want.
check this : https://github.com/mui-org/material-ui/issues/19376
I encountered an annoying problem with Textfield using MateriaUI framework. I have a form with many inputs and it seems to be a bit laggy when typing or deleting values inside the fields. In other components when there are like 2 or 3 inputs there's no lag at all.
EDIT: The problem seems to be with my onChange handler.
Any help is much appreciated. Thanks in advance.
This is my custom input code:
import React, { useReducer, useEffect } from 'react';
import { validate } from '../utils/validators';
import TextField from '#material-ui/core/TextField';
import { ThemeProvider, makeStyles, createMuiTheme } from '#material-ui/core/styles';
import { green } from '#material-ui/core/colors';
const useStyles = makeStyles((theme) => ({
root: {
color: 'white'
},
input: {
margin: '10px',
'&& .MuiInput-underline:before': {
borderBottomColor: 'white'
},
},
label: {
color: 'white'
}
}));
const theme = createMuiTheme({
palette: {
primary: green,
},
});
const inputReducer = (state, action) => {
switch (action.type) {
case 'CHANGE':
return {
...state,
value: action.val,
isValid: validate(action.val, action.validators)
};
case 'TOUCH': {
return {
...state,
isTouched: true
}
}
default:
return state;
}
};
const Input = props => {
const [inputState, dispatch] = useReducer(inputReducer, {
value: props.initialValue || '',
isTouched: false,
isValid: props.initialValid || false
});
const { id, onInput } = props;
const { value, isValid } = inputState;
useEffect(() => {
onInput(id, value, isValid)
}, [id, value, isValid, onInput]);
const changeHandler = event => {
dispatch({
type: 'CHANGE',
val: event.target.value,
validators: props.validators
});
};
const touchHandler = () => {
dispatch({
type: 'TOUCH'
});
};
const classes = useStyles();
return (
<ThemeProvider theme={theme}>
<TextField
className={classes.input}
InputProps={{
className: classes.root
}}
InputLabelProps={{
className: classes.label
}}
id={props.id}
type={props.type}
label={props.label}
onChange={changeHandler}
onBlur={touchHandler}
value={inputState.value}
title={props.title}
error={!inputState.isValid && inputState.isTouched}
helperText={!inputState.isValid && inputState.isTouched && props.errorText}
/>
</ThemeProvider>
);
};
export default Input;
In addition to #jony89 's answer. You can try 1 more workaround as following.
On each keypress (onChange) update the local state.
On blur event call the parent's change handler
const Child = ({ parentInputValue, changeValue }) => {
const [localValue, setLocalValue] = React.useState(parentInputValue);
return <TextInputField
value={localValue}
onChange={(e) => setLocalValue(e.target.value)}
onBlur={() => changeValue(localValue)} />;
}
const Parent = () => {
const [valMap, setValMap] = React.useState({
child1: '',
child2: ''
});
return (<>
<Child parentInputValue={valMap.child1} changeValue={(val) => setValMap({...valMap, child1: val})}
<Child parentInputValue={valMap.child2} changeValue={(val) => setValMap({...valMap, child2: val})}
</>
);
}
This will solve your problems if you do not want to refactor the existing code.
But the actual fix would be splitting the state so that update in the state of child1 doesn't affect (change reference or mutate) state of child2.
Make sure to extract all constant values outside of the render scope.
For example, each render you are providing new object to InputLabelProps and InputProps which forces re-render of child components.
So every new object that is not must be created within the functional component, you should extract outside,
That includes :
const touchHandler = () => {
dispatch({
type: 'TOUCH'
});
};
const useStyles = makeStyles((theme) => ({
root: {
display: 'flex',
flexWrap: 'wrap',
color: 'white'
},
input: {
margin: '10px',
'&& .MuiInput-underline:before': {
borderBottomColor: 'white'
},
},
label: {
color: 'white'
}
}));
const theme = createMuiTheme({
palette: {
primary: green,
},
});
Also you can use react memo for function component optimization, seems fit to your case.
I managed to get rid of this lagging effect by replacing normal <TextField /> by <Controller /> from react-hook-form.
Old code with Typing Lag
<Grid item xs={12}>
<TextField
error={descriptionError.length > 0}
helperText={descriptionError}
id="outlined-textarea"
onChange={onDescriptionChange}
required
placeholder="Nice placeholder"
value={description}
rows={4}
fullWidth
multiline
/>
</Grid>
Updated Code with react-hook-form
import { FormProvider, useForm } from 'react-hook-form';
import { yupResolver } from '#hookform/resolvers/yup';
const methods = useForm({
resolver: yupResolver(validationSchema)
});
const { handleSubmit, errors, reset } = methods;
const onSubmit = async (entry) => {
console.log(`This is the value entered in TextField ${entry.name}`);
};
<form onSubmit={handleSubmit(onSubmit)}>
<Grid item xs={12}>
<FormProvider fullWidth {...methods}>
<DescriptionFormInput
fullWidth
name="name"
placeholder="Nice placeholder here"
size={matchesXS ? 'small' : 'medium'}
bug={errors}
/>
</FormProvider>
</Grid>
<Button
type="submit"
variant="contained"
className={classes.btnSecondary}
startIcon={<LayersTwoToneIcon />}
color="secondary"
size={'small'}
sx={{ mt: 0.5 }}
>
ADD
</Button>
</form>
import React from 'react';
import PropTypes from 'prop-types';
import { Controller, useFormContext } from 'react-hook-form';
import { FormHelperText, Grid, TextField } from '#material-ui/core';
const DescriptionFormInput = ({ bug, label, name, required, ...others }) => {
const { control } = useFormContext();
let isError = false;
let errorMessage = '';
if (bug && Object.prototype.hasOwnProperty.call(bug, name)) {
isError = true;
errorMessage = bug[name].message;
}
return (
<>
<Controller
as={TextField}
name={name}
control={control}
defaultValue=""
label={label}
fullWidth
InputLabelProps={{
className: required ? 'required-label' : '',
required: required || false
}}
error={isError}
{...others}
/>
{errorMessage && (
<Grid item xs={12}>
<FormHelperText error>{errorMessage}</FormHelperText>
</Grid>
)}
</>
);
};
With this use of react-hook-form I could get the TextField more responsive than earlier.
I have a material ui react select in a component that may be on a page multiple times.
In the examples all labelled selects use InputLabel with htmlFor that must be the id of the select.
I cannot give the select an id because id has to be unique for a page and this is a component that doesn't need to know all the id's on the page (nor anywhere in my code do I want to know about all the id's in the page).
According to the InputLabel documentation it doesn't even have a documented htmlFor propery.
Is it possible to label a MUI select without giving it an id?
So long as you don't run into any styling difficulties with your nested solution, then that is perfectly fine, but here's an example using a generated id that would allow you to avoid nesting the Select within the InputLabel:
import React from "react";
import { makeStyles } from "#material-ui/core/styles";
import InputLabel from "#material-ui/core/InputLabel";
import MenuItem from "#material-ui/core/MenuItem";
import FormControl from "#material-ui/core/FormControl";
import Select from "#material-ui/core/Select";
let nextIdSuffix = 1;
const useStyles = makeStyles(theme => ({
root: {
display: "flex",
flexWrap: "wrap"
},
formControl: {
margin: theme.spacing(1),
minWidth: 120
},
selectEmpty: {
marginTop: theme.spacing(2)
}
}));
const CustomSelect = () => {
const idRef = React.useRef();
const classes = useStyles();
const [value, setValue] = React.useState("");
if (!idRef.current) {
idRef.current = `CustomSelect${nextIdSuffix}`;
nextIdSuffix++;
}
return (
<FormControl className={classes.formControl}>
<InputLabel htmlFor={idRef.current}>Age</InputLabel>
<Select
value={value}
onChange={e => setValue(e.target.value)}
inputProps={{
name: idRef.current,
id: idRef.current
}}
>
<MenuItem value={10}>Ten</MenuItem>
<MenuItem value={20}>Twenty</MenuItem>
<MenuItem value={30}>Thirty</MenuItem>
</Select>
</FormControl>
);
};
export default CustomSelect;
Same as the html label tag; I did the nested the Select in the InputLabel:
<InputLabel className="paging">
Page:
<Select
value={current}
onChange={e => changePage(e.target.value)}
inputProps={{
name: 'page',
id: 'page',
}}
>
{options.map(option => (
<MenuItem key={option} value={option}>
{option}
</MenuItem>
))}
</Select>
</InputLabel>