Cant Edit dynamic Textfield form graphql data in reactjs - javascript

I'm trying to create a dynamic textfield that takes data from gql like this
const { data } = useQuery(DATA_LIST, {
variables: {
param: {
limit: 10,
offset: 0,
sortBy: 'order'
}
}
});
const [state, setState] = useState<any>([]);
useEffect(() => {
if (data) {
setState(data?.dataList?.data);
}}, [data]);
then create a textField like this :
<TextField
name="name"
required
fullWidth
// label="Status Name"
onChange={(event) => handleChange(event, index)}
value={item?.name}
sx={{ marginRight: 5 }}
/>
<TextField
name="category"
required
fullWidth
select
// label="Category"
onChange={(event) => handleChange(event, index)}
value={item?.category}
>
{Category.map((option, index) => (
<MenuItem key={index} value={option.value}>
{option.name}
</MenuItem>
))}
</TextField>
handleChange :
const handleChangeInput = (
event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
index: number
) => {
const values = [...state];
values[index][event.target.name] = event.target.value;
console.log(values[index], 'ini values');
setState(values);
};
and call the inputRow component like this (im using drag and drop for textField list) :
{state.map((item: any, index: any) => {
// console.log(statusName[index]);
return (
<Draggable key={item.id} draggableId={String(item.id)} index={index}>
{(provided, snapshot): JSX.Element => (
<div
key={index}
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
style={getItemStyle(snapshot.isDragging, provided.draggableProps.style)}
>
<Box marginRight={2}>
<TypographyComponent text={index + 1} type={'subBody'} />
</Box>
<InputRow index={index} item={item} handleChange={handleChangeInput} />
</div>
)}
</Draggable>
);
})}
but when i try to type the textfield, an error appears that Cannot assign to read only property
error message
This is weird because if I input dummy data, the textfield can be modified, but if I use data from the API the data cannot be modified.

Related

How to clear data on Select multiple when typing on another textfield using reactjs

This is my current code, what I want here is after selecting on Select option (CHIP) and if the user type on the textfield I want to clear what the user selected on CHIP, What should I do to get what i want functionality?
const names = [
'Oliver Hansen',
'Van Henry',
'April Tucker',
];
function getStyles(name, personName, theme) {
return {
fontWeight:
personName.indexOf(name) === -1
? theme.typography.fontWeightRegular
: theme.typography.fontWeightMedium,
};
}
export default function MultipleSelectChip() {
const theme = useTheme();
const [personName, setPersonName] = React.useState([]);
const handleChange = (event) => {
const {
target: { value },
} = event;
setPersonName(
// On autofill we get a stringified value.
typeof value === 'string' ? value.split(',') : value
);
};
const handleChangeTextField = (event) => {
setPersonName(null);
};
return (
<div>
<FormControl sx={{ m: 1, width: 300 }}>
<InputLabel id="demo-multiple-chip-label">Chip</InputLabel>
<Select
labelId="demo-multiple-chip-label"
id="demo-multiple-chip"
multiple
value={personName}
onChange={handleChange}
input={<OutlinedInput id="select-multiple-chip" label="Chip" />}
renderValue={(selected) => (
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}>
{selected.map((value) => (
<Chip key={value} label={value} />
))}
</Box>
)}
MenuProps={MenuProps}
>
{names.map((name) => (
<MenuItem
key={name}
value={name}
style={getStyles(name, personName, theme)}
>
{name}
</MenuItem>
))}
</Select>
<TextField
variant="outlined"
label="Type anything to remove the value of Chip"
onChange={handleChangeTextField} />
</FormControl>
</div>
This is my current code, what I want here is after selecting on Select option (CHIP) and if the user type on the textfield I want to clear what the user selected on CHIP, What should I do to get what i want functionality?
I would set your textfield to be controlled (ie backed by a state variable) and add an effect hook to watch it.
When it receives a value, clear the selected names by setting personNames back to an empty array.
const [text, setText] = useState("");
useEffect(() => {
if (text) {
setPersonName([]);
}
}, [text]);
const handleChangeTextField = ({ target: { value } }) => {
setText(value);
};
<TextField
value={text}
variant="outlined"
label="Type anything to remove the value of Chip"
onChange={handleChangeTextField}
/>
You might also want to clear the text field when selecting names by adding this into handleChange...
setText("");

How to update a useState which has an array, inside this array I have objects this objects will update when the input value will change in react js

const [fvalues,setFvalues]=useState(data.map((ele,id)=>{
return(
{mobile:'',age:'',emailId:'',destinationAddress:'',destinationPin:''}
);
}));
I want to update these objects when there is a change in input tag values
let handleChange = (e)=>{
let {name,value}=e.target;
data.map((ele,id)=>{
// return setFvalues({ ...fvalues[id], [name]: value });
setFvalues(fvalues[id].name)
})
// setFvalues(fvalues[0].name=value)
console.log(name,value);
}
but this logic is not working
I have mapped forms and want to submit all the forms with one submit button, I want to update the input values which is entered by the users
{datas.map((ele, id) => {
let val = id + 1;
return (
<>
<Box key={id}>
{/* <HealthAndContactPass key={id} fun={handelSubmit} psName={ele?.psName} address={ele?.address} /> */}
{/* <HealthAndContactPassForm errors={errors} handleSubmit={handleSubmit} register={register} id={id} psName={ele?.psName} address={ele?.address} onSubmit={onSubmit}/> */}
<Typography className={styles.psName}>{ele.psName}</Typography>
<Box className={styles.white_box}>
<Box className={styles.form_flex}>
<Box className={styles.mobile}>
<Select className={classes.select} name='countryCode' defaultValue={'+91'} value={code} {...register("code")}>
<MenuItem className={styles.code_id} value={'+91'}>+91</MenuItem>
<MenuItem className={styles.code_id} value={'+25'}>+25</MenuItem>
<MenuItem className={styles.code_id} value={'+12'}>+12</MenuItem>
<MenuItem className={styles.code_id} value={'+13'}>+13</MenuItem>
</Select>
<TextField helperText={ferrors?.mobile} value={fvalues[id].mobile} name="mobile" classes={{ root: classes.textField }} InputProps={{ className: classes.textField }} label="Mobile Number" variant="outlined" onChange={handleChange} />
<TextField value={fvalues[id].emailId} name="emailId" classes={{ root: classes.textField }} InputProps={{ className: classes.textField }} label="Email Id" variant="outlined" onChange={handleChange} />
<TextField value={fvalues[id].age} name="age" classes={{ root: classes.textField }} InputProps={{ className: classes.textField }} label="age" variant="outlined" onChange={handleChange} />
</Box>
</Box>
<Box className={styles.form_flex2}>
<TextField value={fvalues[id].destinationAddress} name="destinationAddress" classes={{ root: classes.textField }} InputProps={{ className: classes.textField }} label="destinationAddress" variant="outlined" onChange={handleChange} />
<TextField value={fvalues[id].destinationPin} name="destinationPin" classes={{ root: classes.textField }} InputProps={{ className: classes.textField }} label="destinationPin" variant="outlined" onChange={handleChange} />
</Box>
<Box className={styles.hr}></Box>
{id===0?(
<Box className={styles.addres}>
<ThemeProvider theme={theme}>
<Checkbox className={classes.check} {...label} />
</ThemeProvider>
<Typography className={styles.selectAdd}>Select same address for all</Typography>
</Box>):null
}
</Box>
</Box>
</>
)
})}
In this case you don't "update" the state array per se, rather you create a clone of the state array then modify the values you want and set the state to be this cloned array. I wasn't quite sure what exactly you wanted to do to the array, but see the general example below:
const [state, setState] = useState([{mobile:'',age:'',emailId:'',destinationAddress:'',destinationPin:''}]);
let handleChange = e => {
const {name, value} = e.target;
const stateClone = state.map((item, i) => ({...state[i], [name]: value }))
// do what you want to this new array
setState(stateClone); // update the state array with the new values
}
If you want to update one column of a row, you can create a callback that will take an index (that you'll get when you will render the array of rows) and that will return a callback that will take the event (triggered when the event is dispatched by the browser) and that will update your value.
Then, you only need to trigger a new state change by cloning the old array (using Array.prototype.map) and mapping the new value of the row at any given index. If the index does not match, this means that the row that is mapped is not concerned by the change event so we return the row as-is.
import React, {useState, useCallback} from "react";
const App = () => {
const [rows, setRows] = useState([
{id: 1, value: ""},
{id: 2, value: ""},
{id: 3, value: ""}
]);
const handleRowValueChange = useCallback(index => ({currentTarget: {value}}) => {
setRows(rows.map((row, rowIndex) => {
if (rowIndex === index) {
return {
...row,
value
};
}
return row;
}));
}, [rows]);
return (
<table>
<tbody>
{rows.map((row, index) => (
<tr key={row.id}>
<td>
<input value={row.value} onChange={handleRowValueChange(index)} />
</td>
</tr>
))}
</tbody>
</table>
);
};
export default App;
Update your handleChange function definition to
let handleChange = (e)=>{
let {name,value}=e.target;
const newData = data.map((ele,id)=>{
return { ...fvalues[id], [name]: value };
// setFvalues(fvalues[id].name)
})
setFvalues(newData);
console.log(name,value);
}
Here, you will first create a new array (newData) using data.map and then assign the same as the new state using the setFvalues call.
Currently, you are calling setFvalues inside data.map because of which the state is being updated again and again with an individual array element (an object, in your case) on each iteration of the map method.

Is rendering the Autocomplete options list with column headers possible?

I would like to know if it is possible to customise the above example so that the list would have column headers such as Title and duration. I have tried to see if I could get it to work using a custom ListBox, but no such luck. Below is a snippet of my own code:
const PopperMy = function (props: PopperProps) {
return <Popper {...props} style={{ width: 500 }} placement='bottom-start' />;
};
return (
<Autocomplete
filterOptions={(x) => x}
getOptionLabel={(option: Record<string, unknown>) => `${option.order}, ${option.name}, ${option.email}, ${option.phone}, ${option.location}`}
renderOption={(props, option: any) => {
return (
<li {...props} key={option.ID} >
Order: {option.order}, Name: {option.name}, Email: {option.email}, Phone: {option.phone}, Location: {option.location}, Status: {option.status}
</li>
);
}}
options={results}
value={selectedValue}
clearOnBlur={false}
freeSolo
PopperComponent={PopperMy}
disableClearable={true}
includeInputInList
onChange={(ev, newValue) => {
setSelectedValue(newValue);
}}
onInputChange={(ev, newInputValue) => {
setInputValue(newInputValue);
}}
renderInput={(params) => (
<TextField {...params} />
)} /> )
this is achievable by customizing the popper component. In your case, something like `
const PopperMy = function (props) {
const { children, ...rest } = props;
return (
<Popper {...rest} placement="bottom-start">
<Box display="flex" justifyContent="space-between" px="16px">
<Typography variant="h6">Title</Typography>
<Typography variant="h6">Year</Typography>
........... rest of the titles
</Box>
{props.children}
</Popper>
);
};
`
would work. Here is a working example i have created - https://codesandbox.io/s/heuristic-golick-4sv24u?file=/src/App.js:252-614

React TextField with collapse

How to hide the lists of cities when the textfield is empty and when the user start typing the lists will show.
https://codesandbox.io/s/loving-platform-t2cyb?file=/src/LocationWidget.js
const openSearch = () => {
setViewLocationList(true);
startSearch();
};
const stopSearch = () => {
setSearchParameter('')
setViewLocationList(false);
};
<TextField
variant="outlined"
placeholder="Search Locations"
onFocus={openSearch}
onChange={filterResults}
value={searchParameter}
classes={{notchedOutline:classes.input}}
InputProps={{
endAdornment: (
<IconButton onClick={stopSearch} edge="end">
<ClearIcon />
</IconButton>
),
classes:{notchedOutline:classes.noBorder},
startAdornment: (
<InputAdornment position="start">
<SearchIcon />
</InputAdornment>
),
}}
/>
</Box>
<Collapse in={viewLocationList} sx={{ my: '2px' }}>
<Box className="rounded-scrollbar widget-result-container">
{filteredLocations.map((location, index) => (
<LocationWidgetItem
key={index}
location={location}
onClickLocation={setActiveLocation}
/>
))}
</Box>
</Collapse>
You have to take state variable in which you have to store whatever user is typing:
const [text,setText] = useState("");
and in your filterLocations you have to update its value
const filterLocations = (txt) => {
setText(txt.target.value);
let filteredLocations = locations.filter((e) =>
e.name.toLowerCase().includes(txt.target.value.toLowerCase())
);
setSearchParameter(filteredLocations);
};
And Finally in your render, render ul conditioanlly
{ !!text && <ul>
{searchParameter.map((location) => (
<li key={location.name}>{location.name}</li>
))}
</ul>}
https://codesandbox.io/s/dreamy-roentgen-0jnoi?file=/src/LocationWidget.js
please find below code.
export default function LocationWidget({ locations }) {
const [searchParameter, setSearchParameter] = useState(locations);
const [inputText, setInputText] = useState(null);
const filterLocations = (txt) => {
setInputText(txt.target.value);
let filteredLocations = locations.filter((e) =>
e.name.toLowerCase().includes(txt.target.value.toLowerCase())
);
setSearchParameter(filteredLocations);
};
return (
<>
<input type="text" onChange={filterLocations} />
{inputText && <ul>
{searchParameter.map((location) => (
<li key={location.name}>{location.name}</li>
))}
</ul>}
</>
);
}
You'd like to set an 'input' eventListener on the input field. And check if the inputField length > 0, then set the setViewLocationList(true); and if the inputField length === 0, then set the setViewLocationList(false).

Material-UI TextField loses focus on every onChange

I am creating the following component:
It will contain an array of objects, where each object is a prescription, with the medicine name from the select and a TextField for the Dosis.
My problem is that the TextField loses focus on every onChange() and is very frustrating because it cannot be edited on a single focus.
This is my component :
const MedicineSelect = ({ medications, setMedications, ...props }) => {
const { medicines } = useMedicines()
const classes = useStyles()
const handleChange = (index, target) => {
// setAge(event.target.value)
const newMedications = cloneDeep(medications)
newMedications[index][target.name] = target.value
setMedications(newMedications)
}
const handleAddMedicine = () => {
const newMedications = cloneDeep(medications)
newMedications.push({ medicine: '', dosis: '', time: '' })
setMedications(newMedications)
}
const handleDeleteMedicine = (index) => {
console.log('DELETE: ', index)
const newMedications = cloneDeep(medications)
newMedications.splice(index, 1)
setMedications(newMedications)
}
return (
<Paper style={{ padding: 5 }}>
<List>
{medications.map((medication, index) => (
<ListItem key={nanoid()} divider alignItems='center'>
<ListItemIcon>
<Tooltip title='Eliminar'>
<IconButton
className={classes.iconButton}
onClick={() => handleDeleteMedicine(index)}
>
<HighlightOffOutlinedIcon />
</IconButton>
</Tooltip>
</ListItemIcon>
<FormControl className={classes.formControl}>
<InputLabel
id={`${index}-select-${medication}-label`}
>
Medicamento
</InputLabel>
<Select
labelId={`${index}-select-${medication}-label`}
id={`${index}-select-${medication}`}
name='medicine'
value={medication.medicine}
onChange={(event) =>
handleChange(index, event.target)
}
>
{medicines.map((medicine) => (
<MenuItem
key={nanoid()}
value={medicine.name}
>
{medicine.name}
</MenuItem>
))}
</Select>
</FormControl>
<TextField
// fullWidth
id={`${index}-text-${medication}`}
label='Dosis'
name='dosis'
onChange={(event) =>
handleChange(index, event.target)
}
value={medication.dosis}
/>
</ListItem>
))}
<Button onClick={handleAddMedicine}>+ agregar</Button>
</List>
</Paper>
)
}
And here is where I set the component:
const [medications, setMedications] = useState([
{ medicine: '', dosis: '', time: '' },
])
...
<Grid item md={12} xs={12}>
<Accordion>
<AccordionSummary
expandIcon={<ExpandMoreIcon />}
aria-controls='panel1a-content'
id='panel1a-header'
>
<Typography variant='h4'>
Tratamiento:
</Typography>
</AccordionSummary>
<AccordionDetails>
<Container disableGutters>
<MedicineSelect
medications={medications}
setMedications={setMedications}
/>
</Container>
</AccordionDetails>
</Accordion>
</Grid>
...
Adding and removing objects from the array works perfect. selecting the medicine from the select, also works perfect. the only problem I have is when editing the Dosis TextField, with every character, the focus is lost and I have to click again on the TextField.
Please help me getting this fixed!!!
After searching a lot, finally I found the solution. Actually when using nanoid() to create unique keys, on every state update React re-renders all components and since the id of both the List and the TextField component are regenerated by nanoid on every render, React loses track of the original values, that is why Focus was lost.
What I did was keeping the keys unmuttable:
<ListItem key={`medication-${index}`} divider alignItems='center'>
and
<TextField
key={`dosis-${index}`}
fullWidth
// id={`${index}-dosis-${medication}`}
label='Dosis'
name='dosis'
onChange={(event) =>
handleChange(index, event.target)
}
value={medication.dosis}
/>

Categories

Resources