How to create a custom MUI-Datatables search box with outlined style - javascript

Default UI from MUI-Datatables v4.3.0 like this :
And i'am want to make this style :
For information i am using this package :
"#mui/material": "^5.11.4"
"mui-datatables": "^4.3.0"
How to make The TextField or Input with outlined style.

Based of example MUI-Databales from this link :
https://codesandbox.io/s/github/gregnb/mui-datatables
I have a answer and solution from my case.
My Component CustomSearchRender.js :
import React from "react";
import Grow from "#mui/material/Grow";
import TextField from "#mui/material/TextField";
import { withStyles } from "tss-react/mui";
import { SearchOutlined } from "#mui/icons-material";
import InputAdornment from "#mui/material/InputAdornment";
const defaultSearchStyles = (theme) => ({
searchText: {
flex: "0.8 0",
marginTop: 10,
},
searchIcon: {
"&:hover": {
color: theme.palette.error.main,
},
},
});
const CustomSearchRender = (props) => {
const { classes, onSearch, searchText } = props;
const handleTextChange = (event) => {
onSearch(event.target.value);
};
return (
<Grow appear in={true} timeout={300}>
<TextField
placeholder="Search"
size="medium"
className={classes.searchText}
value={searchText || ""}
onChange={handleTextChange}
fullWidth
variant="outlined"
InputProps={{
startAdornment: (
<InputAdornment position="start">
<SearchOutlined />
</InputAdornment>
),
}}
/>
</Grow>
);
};
export default withStyles(CustomSearchRender, defaultSearchStyles, {
name: "CustomSearchRender",
});
And put into options :
const options = {
selectableRows: "none",
filterType: "textField",
responsive: "simple",
confirmFilters: false,
jumpToPage: false,
download: false,
print: false,
enableNestedDataAccess: ".",
textLabels: {
body: {
noMatch: loading ? (
<TableSkeleton variant="h4" length={10} />
) : (
"Maaf, tidak ada data untuk ditampilkan"
),
},
},
searchOpen: true,
searchAlwaysOpen: true,
searchPlaceholder: "Search keyword",
customSearchRender: (searchText, handleSearch) => {
return (
<CustomSearchRender searchText={searchText} onSearch={handleSearch} />
);
},
};
And finnally the result like this :

Related

How can I insert a basic Search bar in Material UI Data Grid?

For example: refer this sample data grid.
Want to add a basic search bar like:here
How can I add in Data Grid MUI.
Please guide.
Just create a search bar using a label and text input. Aftermath you can update your grid according to the string in your search box by the search filtered data.
In datagrid there is prop components and Toolbar in that prop to pass whatever component you want.
components={{ Toolbar: QuickSearchToolbar }}
and pass props from
componentsProps={{
toolbar: {
value: searchText,
onChange: (event: React.ChangeEvent<HTMLInputElement>) => handleSearch
},
}}
This is a good example to check out:
import * as React from 'react';
import IconButton from '#mui/material/IconButton';
import TextField from '#mui/material/TextField';
import {
DataGrid,
GridToolbarDensitySelector,
GridToolbarFilterButton,
} from '#mui/x-data-grid';
import { useDemoData } from '#mui/x-data-grid-generator';
import ClearIcon from '#mui/icons-material/Clear';
import SearchIcon from '#mui/icons-material/Search';
import { createTheme } from '#mui/material/styles';
import { createStyles, makeStyles } from '#mui/styles';
function escapeRegExp(value: string): string {
return value.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
}
const defaultTheme = createTheme();
const useStyles = makeStyles(
(theme) =>
createStyles({
root: {
padding: theme.spacing(0.5, 0.5, 0),
justifyContent: 'space-between',
display: 'flex',
alignItems: 'flex-start',
flexWrap: 'wrap',
},
textField: {
[theme.breakpoints.down('xs')]: {
width: '100%',
},
margin: theme.spacing(1, 0.5, 1.5),
'& .MuiSvgIcon-root': {
marginRight: theme.spacing(0.5),
},
'& .MuiInput-underline:before': {
borderBottom: `1px solid ${theme.palette.divider}`,
},
},
}),
{ defaultTheme },
);
interface QuickSearchToolbarProps {
clearSearch: () => void;
onChange: () => void;
value: string;
}
function QuickSearchToolbar(props: QuickSearchToolbarProps) {
const classes = useStyles();
return (
<div className={classes.root}>
<div>
<GridToolbarFilterButton />
<GridToolbarDensitySelector />
</div>
<TextField
variant="standard"
value={props.value}
onChange={props.onChange}
placeholder="Search…"
className={classes.textField}
InputProps={{
startAdornment: <SearchIcon fontSize="small" />,
endAdornment: (
<IconButton
title="Clear"
aria-label="Clear"
size="small"
style={{ visibility: props.value ? 'visible' : 'hidden' }}
onClick={props.clearSearch}
>
<ClearIcon fontSize="small" />
</IconButton>
),
}}
/>
</div>
);
}
export default function QuickFilteringGrid() {
const { data } = useDemoData({
dataSet: 'Commodity',
rowLength: 100,
maxColumns: 6,
});
const [searchText, setSearchText] = React.useState('');
const [rows, setRows] = React.useState<any[]>(data.rows);
const requestSearch = (searchValue: string) => {
setSearchText(searchValue);
const searchRegex = new RegExp(escapeRegExp(searchValue), 'i');
const filteredRows = data.rows.filter((row: any) => {
return Object.keys(row).some((field: any) => {
return searchRegex.test(row[field].toString());
});
});
setRows(filteredRows);
};
React.useEffect(() => {
setRows(data.rows);
}, [data.rows]);
return (
<div style={{ height: 400, width: '100%' }}>
<DataGrid
components={{ Toolbar: QuickSearchToolbar }}
rows={rows}
columns={data.columns}
componentsProps={{
toolbar: {
value: searchText,
onChange: (event: React.ChangeEvent<HTMLInputElement>) =>
requestSearch(event.target.value),
clearSearch: () => requestSearch(''),
},
}}
/>
</div>
);
}

Material UI Autocomplete dropdown positioning

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

CK Editor Validation using React Hook Form

I have a nextJs project in which i use a CKeditor react component and I use React hook form as my validation library
my code for the main page is as shown below
import React from "react";
import { Paper } from "#material-ui/core";
import { makeStyles, withStyles } from "#material-ui/core/styles";
import { useForm } from "react-hook-form";
import dynamic from "next/dynamic";
const Editor = dynamic(() => import("../../components/Form/Editor"), {
ssr: false,
});
const useStyles = makeStyles((theme) => ({
root: {
flexGrow: 1,
"& > *": {
margin: theme.spacing(1),
},
},
paper: {
padding: theme.spacing(2),
textAlign: "center",
color: theme.palette.text.secondary,
},
}));
function create2() {
const classes = useStyles();
const { errors, control, handleSubmit } = useForm();
const onSubmit = (data) => {
console.log(data);
};
return (
<Paper className={classes.paper}>
<form onSubmit={handleSubmit(onSubmit)}>
<Editor
control={control}
onReady={(editor) => {
// You can store the "editor" and use when it is needed.
// console.log("Editor is ready to use!", editor);
editor.editing.view.change((writer) => {
writer.setStyle(
"height",
"200px",
editor.editing.view.document.getRoot()
);
});
}}
defaultValue=""
name="question"
value={formValues.question}
rules={{ required: "Question Field is required" }}
errors={errors}
/>
<input type="submit" />
</form>
</Paper>
);
}
export default create2;
and My Editor component is as shown below
import React, { useEffect, useState } from "react";
import { CKEditor } from "#ckeditor/ckeditor5-react";
import { makeStyles, useTheme } from "#material-ui/core/styles";
import ClassicEditor from "#ckeditor/ckeditor5-build-classic";
import { Controller } from "react-hook-form";
//import ClassicEditor from 'ckeditor5-classic-with-mathtype';
const useStyles = makeStyles((theme) => ({
formControl: {
// margin: theme.spacing(1),
minWidth: 120,
fullWidth: true,
},
errordiv: {
width: "100%",
border: "1px solid red",
},
errorText: {
width: "100%",
color: "red",
display: "flex",
padding: "2px 10px",
},
}));
const Editor = (props) => {
const classes = useStyles();
const [isLayoutReady, setIsLayoutReady] = useState(true);
const [error, setError] = useState({ error: false, helperText: "" });
const errorObj = props.errors || "";
useEffect(() => {
const errorVal = errorObj ? errorObj[props.name] : "";
if (errorVal) {
setError({ ...error, error: true, helperText: errorVal.message });
} else {
setError({ error: false, helperText: "" });
}
}, [errorObj[props.name]]);
useEffect(() => {
setIsLayoutReady(true);
}, []);
return (
<div>
<div className={error.error ? classes.errordiv : ""}>
{isLayoutReady ? (
<Controller
as={CKEditor}
{...props}
config={{
ckfinder: {
// Upload the images to the server using the CKFinder QuickUpload command.
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Credentials": "true",
uploadUrl:
"https://run.mocky.io/v3/5125188a-47c1-4138-8871-8837d5ab8764",
},
toolbar: {
items: [
"heading",
"|",
"bold",
"italic",
"link",
"bulletedList",
"numberedList",
"|",
"indent",
"outdent",
"|",
"imageUpload",
"blockQuote",
"insertTable",
"mediaEmbed",
"undo",
"redo",
"fontColor",
"fontSize",
"fontFamily",
"MathType",
"ChemType",
],
},
language: "en",
}}
editor={ClassicEditor}
/>
) : null}
</div>
<div className={classes.errorText}>
{error.error ? error.helperText : ""}
</div>
</div>
);
};
export default Editor;
I am getting the validation alert at the first time i click the submit button with required rule but the validation is not returning if i clear the ck editor value and also the out put data I am getting on submit is some object
question: tsname: "change:data"off: ƒ t()path: [qg]source: qg {model: Fp, version: 19, history: Vg, selection: Cf, roots: vs, …}stop: ƒ t()proto: Object__proto__: Object
Can Anybody help me with this error.
Try this...
<CKEditor
data={ textValue }
onChange={(event, editor) => {
const data = editor.getData();
setTextValue(data);
}}
/>
Cheer me !!

MaterialUI TextField lag issue. How to improve performance when the form has many inputs?

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.

React Beginner Question: Textfield Losing Focus On Update

I wrote a component that is supposed to list out a bunch of checkboxes with corresponding textfields. When you click on the checkboxes, or type in the fields it's meant to update state.
The textbox is working ok, but when I type in the fields, it updates state ok, but I lose focus whenever I tap the keyboard.
I realized this is probably due to not having keys set, so I added keys to everything but it still is losing focus. At one point I tried adding in stopPropegation on my events because I thought maybe that was causing an issue?? I'm not sure.. still learning...didn't seem to work so I removed that part too.
Still can't seem to figure out what is causing it to lose focus... does anyone have any advice/solves for this issue?
I consolidated my code and cut out the unnecessary bits to make it easier to read. There are three relevant JS files.. please see below:
I'm still a beginner/learning so if you have useful advice related to any part of this code, feel free to offer. Thanks!
App.js
import React, { Component } from 'react';
import Form from './Form'
class App extends Component {
constructor() {
super();
this.state = {
mediaDeliverables: [
{label: 'badf', checked: false, quantity:''},
{label: 'adfadf', checked: false, quantity:''},
{label: 'adadf', checked: false, quantity:''},
{label: 'addadf', checked: false, quantity:''},
{label: 'adfdes', checked: false, quantity:''},
{label: 'hghdgs', checked: false, quantity:''},
{label: 'srtnf', checked: false, quantity:''},
{label: 'xfthd', checked: false, quantity:''},
{label: 'sbnhrr', checked: false, quantity:''},
{label: 'sfghhh', checked: false, quantity:''},
{label: 'sssddrr', checked: false, quantity:''}
]
}
}
setMediaDeliverable = (value, index) => {
let currentState = this.getStateCopy();
currentState.mediaDeliverables[index] = value;
this.setState(currentState);
}
getStateCopy = () => Object.assign({}, this.state);
render() {
return (
<div className="App">
<Form
key="mainForm"
mediaDeliverablesOptions={this.state.mediaDeliverables}
setMediaDeliverable={this.setMediaDeliverable}
/>
</div>
);
}
}
export default App;
Form.js
import React from 'react';
import { makeStyles, useTheme } from '#material-ui/core/styles';
import FormControl from '#material-ui/core/FormControl';
import FormLabel from '#material-ui/core/FormLabel';
import FormGroup from '#material-ui/core/FormGroup';
import FormControlLabel from '#material-ui/core/FormControlLabel';
import Checkbox from '#material-ui/core/Checkbox';
import MediaDeliverablesCheckBox from './MediaDeliverablesCheckBox';
const useStyles = makeStyles(theme => ({
container: {
display: 'inline-block',
flexWrap: 'wrap',
},
root: {
display: 'inline-block',
flexWrap: 'wrap',
maxWidth: 600,
textAlign: 'left',
},
extendedIcon: {
marginRight: theme.spacing(1),
},
formControl: {
margin: theme.spacing(1),
minWidth: 120,
maxWidth: 300,
},
textField: {
marginLeft: theme.spacing(1),
marginRight: theme.spacing(1),
width: 370,
},
dense: {
marginTop: 19,
},
chips: {
display: 'flex',
flexWrap: 'wrap',
},
chip: {
margin: 2,
},
noLabel: {
marginTop: theme.spacing(3),
},
}));
const ITEM_HEIGHT = 48;
const ITEM_PADDING_TOP = 8;
const MenuProps = {
PaperProps: {
style: {
maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP,
width: 250,
},
},
};
function getStyles(name, accountName, theme) {
// console.log('>> [form.js] (getStyles) ',accountName)
return {
fontWeight:
accountName.indexOf(name) === -1
? theme.typography.fontWeightRegular
: theme.typography.fontWeightMedium,
};
}
export default function Form(props) {
const mediaDeliverablesOptions = props.mediaDeliverablesOptions;
const classes = useStyles();
const theme = useTheme();
const CheckboxGroup = ({ values, label, onChange }) => (
<FormControl component="fieldset">
<FormLabel component="legend">{label}</FormLabel>
<FormGroup>
{values.map((value, index) => (
<FormControlLabel
key={index}
control={
<Checkbox
checked={value.checked}
onChange={onChange(index)}
/>
}
label={value.label}
/>
))}
</FormGroup>
</FormControl>
);
const MediaDeliverableCheckBoxList = ({values, label}) => (
<FormControl component="fieldset">
<FormLabel component="legend">{label}</FormLabel>
<FormGroup>
{values.map((value, index) => (
<MediaDeliverablesCheckBox
key={index}
mediaDeliverablesOptions={value}
onMediaDeliverableChange={onMediaDeliverableChange(index)}
/>
))}
</FormGroup>
</FormControl>
);
const onCheckBoxChange = index => ({ target: { checked } }) => {
const newValues = [...values];
const value = values[index];
newValues[index] = { ...value, checked };
props.setDesignOrDigital(newValues);
};
const onMediaDeliverableChange = index => (deliverableData, e) => {
props.setMediaDeliverable(deliverableData, index);
}
return (
<div className={classes.root}>
<MediaDeliverableCheckBoxList
label="Please Choose Deliverables:"
values={mediaDeliverablesOptions}
key="media-deliverable-checkbox-list"
/>
</div>
);
}
MediaDeliverablesCheckbox.js
import React from 'react';
import Checkbox from '#material-ui/core/Checkbox';
import { makeStyles, useTheme } from '#material-ui/core/styles';
import FormControl from '#material-ui/core/FormControl';
import FormLabel from '#material-ui/core/FormLabel';
import FormGroup from '#material-ui/core/FormGroup';
import FormControlLabel from '#material-ui/core/FormControlLabel';
import TextField from '#material-ui/core/TextField';
export default function MediaDeliverablesCheckBox(props) {
let deliverableData = Object.assign({}, props.mediaDeliverablesOptions);
const onCheckBoxChange = (e) => {
deliverableData.checked = e.target.checked;
props.onMediaDeliverableChange(deliverableData, e);
}
const onQuantityChange = (e) => {
deliverableData.quantity = e.target.value;
props.onMediaDeliverableChange(deliverableData, e);
}
const CheckboxGroup = ({ value, label }) => (
<FormControl component="fieldset">
<FormGroup>
<FormControlLabel
control={
<Checkbox
key={props.index}
checked={value.checked}
onChange={onCheckBoxChange}
/>
}
label={label}
/>
</FormGroup>
</FormControl>
);
return(
<div className="MediaDeliverablesCheckBox">
<CheckboxGroup
key={props.index}
label={props.mediaDeliverablesOptions.label}
value={props.mediaDeliverablesOptions}
/>
<TextField
key={'tf'+props.index}
id={'quantity-'+props.index}
label="Quantity"
placeholder="How many do you need?"
multiline
variant="outlined"
value={props.mediaDeliverablesOptions.quantity}
onChange={onQuantityChange}
fullWidth
/>
</div>
);
}
Updated Form.js based on recommended edits by Ryan C.
import React from 'react';
import { makeStyles, useTheme } from '#material-ui/core/styles';
import FormControl from '#material-ui/core/FormControl';
import FormLabel from '#material-ui/core/FormLabel';
import FormGroup from '#material-ui/core/FormGroup';
import FormControlLabel from '#material-ui/core/FormControlLabel';
import Checkbox from '#material-ui/core/Checkbox';
import MediaDeliverablesCheckBox from './MediaDeliverablesCheckBox';
const useStyles = makeStyles(theme => ({
container: {
display: 'inline-block',
flexWrap: 'wrap',
},
root: {
display: 'inline-block',
flexWrap: 'wrap',
maxWidth: 600,
textAlign: 'left',
},
extendedIcon: {
marginRight: theme.spacing(1),
},
formControl: {
margin: theme.spacing(1),
minWidth: 120,
maxWidth: 300,
},
textField: {
marginLeft: theme.spacing(1),
marginRight: theme.spacing(1),
width: 370,
},
dense: {
marginTop: 19,
},
chips: {
display: 'flex',
flexWrap: 'wrap',
},
chip: {
margin: 2,
},
noLabel: {
marginTop: theme.spacing(3),
},
}));
const ITEM_HEIGHT = 48;
const ITEM_PADDING_TOP = 8;
const MenuProps = {
PaperProps: {
style: {
maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP,
width: 250,
},
},
};
function getStyles(name, accountName, theme) {
return {
fontWeight:
accountName.indexOf(name) === -1
? theme.typography.fontWeightRegular
: theme.typography.fontWeightMedium,
};
}
// Failed to compile
// ./src/Form.js
// Line 86: Parsing error: Unexpected token, expected ","
// 84 |
// 85 | const MediaDeliverableCheckBoxList = ({values, label, onMediaDeliverableChange}) => (
// > 86 | {values.map((value, index) => (
// | ^
// 87 | <MediaDeliverablesCheckBox
// 88 | key={index}
// 89 | index={index}
// This error occurred during the build time and cannot be dismissed.
const MediaDeliverableCheckBoxList = ({values, label, onMediaDeliverableChange}) => (
{values.map((value, index) => (
<MediaDeliverablesCheckBox
key={index}
index={index}
mediaDeliverablesOptions={value}
onMediaDeliverableChange={onMediaDeliverableChange(index)}
/>
))}
);
export default function Form(props) {
const mediaDeliverablesOptions = props.mediaDeliverablesOptions;
const classes = useStyles();
const theme = useTheme();
const CheckboxGroup = ({ values, label, onChange }) => (
<FormControl component="fieldset">
<FormLabel component="legend">{label}</FormLabel>
<FormGroup>
{values.map((value, index) => (
<FormControlLabel
key={index}
control={
<Checkbox
checked={value.checked}
onChange={onChange(index)}
/>
}
label={value.label}
/>
))}
</FormGroup>
</FormControl>
);
const onCheckBoxChange = index => ({ target: { checked } }) => {
const newValues = [...values];
const value = values[index];
newValues[index] = { ...value, checked };
props.setDesignOrDigital(newValues);
};
const onMediaDeliverableChange = index => (deliverableData, e) => {
props.setMediaDeliverable(deliverableData, index);
}
return (
<div className={classes.root}>
<MediaDeliverableCheckBoxList
onMediaDeliverableChange={onMediaDeliverableChange}
/>
</div>
);
}
I see two main issues:
How you are defining your different components (nesting component types)
Not passing the index prop through to components that are expecting it
You have the following structure (leaving out details that are not directly related to my point):
export default function Form(props) {
const onMediaDeliverableChange = index => (deliverableData, e) => {
props.setMediaDeliverable(deliverableData, index);
}
const MediaDeliverableCheckBoxList = ({values, label}) => (
<FormGroup>
{values.map((value, index) => (
<MediaDeliverablesCheckBox key={index} onMediaDeliverableChange={onMediaDeliverableChange(index)}/>
))}
</FormGroup>
);
return (
<MediaDeliverableCheckBoxList/>
);
}
The function MediaDeliverableCheckBoxList represents the component type used to render the <MediaDeliverableCheckBoxList/> element. Whenever Form is re-rendered due to props or state changing, React will re-render its children. If the component type of a particular child is the same (plus some other criteria such as key being the same if specified), then it will update the existing DOM node(s). If the component type of a particular child is different, then the corresponding DOM nodes will be removed and new ones added to the DOM.
By defining the MediaDeliverableCheckBoxList component type within the Form function, you are causing that component type to be different on every render. This will cause all of the DOM nodes to be replaced rather than just updated and this will cause the focus to go away when the DOM node that previously had focus gets removed. It will also cause performance to be considerably worse.
You can fix this by moving this component type outside of the Form function and then adding any additional props that are needed (e.g. onMediaDeliverableChange) to convey the context known inside of Form. You also need to pass index as a prop to MediaDeliverablesCheckBox since it is using it.
const MediaDeliverableCheckBoxList = ({values, label, onMediaDeliverableChange}) => (
<FormGroup>
{values.map((value, index) => (
<MediaDeliverablesCheckBox key={index} index={index} onMediaDeliverableChange={onMediaDeliverableChange(index)}/>
))}
</FormGroup>
);
export default function Form(props) {
const onMediaDeliverableChange = index => (deliverableData, e) => {
props.setMediaDeliverable(deliverableData, index);
}
return (
<MediaDeliverableCheckBoxList onMediaDeliverableChange={onMediaDeliverableChange}/>
);
}
You have this same issue with CheckboxGroup and possibly other components as well.
This issue is solely because of your key in the TextField. You must ensure that the key remains the same on every update. Otherwise you would the face the current issue.

Categories

Resources