I'm seeing a weird issue with Material UI's Autcomplete component. I have the following code at present:
const [isContactListInitialised, setContactListInitialised] = useState(false);
const toggleContactListInitialised = () => { setContactListInitialised(state => !state) }
const [contactList, setContactList] = useState([]);
...
const newJobStructure = {
name: "",
isJobStartLinked: true,
isJobFinishLinked: true,
outgoingDateTime: moment(),
jobStartDateTime: moment(),
returningDateTime: moment().add(1, "hours"),
jobFinishDateTime: moment().add(1, "hours"),
contactId: -1,
jobTypeId: -1,
deliveryLocationId: -1,
}
const [newJob, setNewJob] = useState(newJobStructure)
const handleNewJobChange = (event) => setNewJob({...newJob, [event.target.name]: event.target.value})
...
useEffect(() => {
if(!isContactListInitialised) { fetchAllContacts(); }
}, [])
...
return(
...
<Autocomplete
id="contacts-group"
options={contactList}
getOptionLabel={(contact) => contact.contactName || ''}
groupBy={(contact) => contact.firstLetter}
value={newJob.contactId}
onChange={(event, value) => setNewJob({...newJob, contactId: value.contactId})}
freeSolo
sx={{ width: "100%", minWidth: 400 }}
renderInput={(params => <TextField {...params} label="Contact Name" />)} />
...
)
Now, as the title explains, when the form is displayed initially, I see the TextField as expected, along with it being blank and having "Contact Name" in it. When I click it, it displays an ordered list, again, as expected.
When I then select an option, the contact.contactName value displays for a split second and then it disappears. However, it still looks populated as the label retreats to the top left corner of the box.
If I then tap the same option again, the contact.contactName value then displays as expected.
What on Earth could be going on here? Is it a bug with my code, or MUI's? how would I be able to get around this?
Thanks!
It seems your option list is in format { contactId: number, contactName: string } which is an object, and the autocomplete has been controlled with value property which is mapped with number type. To solve this issue, do the below changes in your code.
Add property contactName in newJobStructure
const newJobStructure = {
name: "",
isJobStartLinked: true,
isJobFinishLinked: true,
outgoingDateTime: moment(),
jobStartDateTime: moment(),
returningDateTime: moment().add(1, "hours"),
jobFinishDateTime: moment().add(1, "hours"),
contactId: -1,
contactName: '', // Add contactName Property
jobTypeId: -1,
deliveryLocationId: -1
};
Update controlled value and onChange event of autocomplete control as below
<Autocomplete
id="contacts-group"
options={contactList}
getOptionLabel={(contact) => contact.contactName || ''}
groupBy={(contact) => contact.firstLetter}
// value={newJob.contactId}
value={{ contactId: newJob.contactId, contactName: newJob.contactName }} // Update controlled value
// onChange={(event, value) => setNewJob({ ...newJob, contactId: value.contactId })}
onChange={(event, value) => setNewJob({ ...newJob, contactId: value?.contactId ?? -1, contactName: value?.contactName ?? '' })} // Update onChange event
isOptionEqualToValue={(option, value) => option.contactId === value.contactId }
freeSolo
sx={{ width: "100%", minWidth: 400 }}
renderInput={(params => <TextField {...params} label="Contact Name" />)}
/>
Related
I wanna asign the email value to the text field but it does not work but When i tried to put it on a text the value is there like for example on a span , the value of emailAddress should be the value of the textfield , any idea why does this not work guys ?
Thanks.
<span style={{paddingTop:5}}>
Full Name: {email.firstName} {email.lastName} {email.emailAddress}
</span>
#html
<TextField
type="text"
style={{ width: "95%" }}
onChange={($event) => emailOnChange($event, prop.id, mIndex)}
label="Email Address"
variant="filled"
name={email.emailAddress}
value={email.emailAddress}
defaultValue={email.emailAddress}
// InputProps={{
// endAdornment: fetching ? (
// <CircularProgress />
// ) : null,
// }}
/>
#ts snippet
const emailOnChange = debounce(function (event, id, index) {
setRoleId(id);
setEmailCurrentIndex(index);
const payload: IYardUserRequestPayload | InitialReqPaylod = {
accountId: 1,
searchString: event.target.value,
};
setFetching(true);
dispatch(getYardUser(payload));
}, 500);
I have a dynamic form where users can add multiple products. I wonder how I could save the selected products' id.
In the console.log(fields, "fields");, this is where I can see the saved product. So how can I save the selected product id as well?
Any help would be appreciated. Thank you.
Codesandbox: https://codesandbox.io/s/react-hook-form-wizard-form-from-reddit-with-data-ouy64e?file=/src/fieldArray.js:322-4143
const products = [
{
prodName: "Tumbler",
price: 1.5,
size: "500",
colorMap: { Black: 20, Pink: 10, Green: 5 },
id: "aRLMZkiSU7T0lcsPCSsV"
},
{
prodName: "Shirt",
price: 2.0,
size: "L",
colorMap: { Blue: 10, Black: 10 },
id: "uTHIR6OQFRuqP9Drft0e"
},
{
size: "200",
price: 2.0,
colorMap: { Green: 50, Red: 19, Black: 20 },
prodName: "Notebook",
id: "y9ECyZBKp2OBekmWym4M"
}
];
const options = products.map(
(object) =>
object.prodName +
" - " +
object.size +
`${object.cat === "CM" || object.cat === "ML" ? "- " + object.cat : ""}` +
" "
);
console.log(options, "options");
const FieldArray = ({ control, register, setValue, getValues }) => {
const { fields, append, remove, prepends } = useFieldArray({
control,
name: "order"
});
console.log(fields, "fields");
renderCount++;
return (
<div>
<ul>
{fields.map((item, index) => {
console.log(item);
return (
<li key={item.id}>
<Controller
control={control}
name={`order.${index}.product`}
render={({ field: { onChange, value = "", ...rest } }) => (
<Autocomplete
{...rest}
onInputChange={(e, newValue) => {
onChange(newValue);
console.log(newValue, "new value");
}}
inputValue={value}
options={products}
// isOptionEqualToValue={(option, value) =>
// option?.value === value?.value
// }
getOptionLabel={(option) =>
option.prodName + " " + option.size
}
// getOptionLabel={(option) => option?.label ?? ""}
renderInput={(params) => (
<TextField
{...params}
label="Product"
variant="outlined"
fullWidth
/>
)}
/>
)}
/>
);
})}
</div>
);
};
export default FieldArray;
Update
this is the submit button in step1.js
const onSubmit = (data) => {
// action(data);
console.log(data, "d");
const newOrder = [];
data.order.forEach(({ product, variation }) => {
const newVariantion = [];
variation.forEach(({ qty, color }) => {
newVariantion.push({ qty: parseInt(qty), color });
});
newOrder.push({ product, variation: newVariantion });
});
actions.updateAction(data);
console.log(newOrder, "new order");
navigate("/step2", newOrder);
};
Update:
How would I be able to push the product ID inside the newOrder where it matches the productID of the selected product?
Some development on answer from this question:
You can always add useState with a first product (save entire product, not just an id) and then manage everything through onChange:
import {useState} from 'react';
/*...something here...*/
const FieldArray = ({ control, register, setValue, getValues }) => {
const [prod, setProd] = useState({0: product[0]});
/*...something here...*/
{fields.map((item, index) => {
/*...something here...*/
<Autocomplete
onChange={(e, v)=>{console.log(v); setProd({...prod, [index]:v});}}
value={prod[index] || {}}
options={products}
/*...other stuff here...*/
Have a look at what is available in console.log(v) inside onChange.
Also check out difference between inputValue and value here.
Update
If you need multiple products to be saved - prod must be an object with key to represent "fields" item. For example, something like this {0: prod1, 1: prod3, 2: prod11}. Then for value use prod[index] and change setter appropriately. (I've edited code above for this case). There is multiple ways to do this - that's just one from top of my head.
Update 2:
I don't know what you want in onSubmit exactly, so here is an idea and you change it to what you want.
In Step1.onSubmit you can do something like that:
// forEach 2nd argument is an index (counter)
data.order.forEach(({ product, variation }, indx) => {
// some code
newOrder.push({ product, variation: newVariantion, prod: prod[indx] });
// more code
}
I want to check/uncheck the value of checkbox upon Edit(Modal) based on the return of addAdvisory(hooks) which is 'Y'-means true/checked.
Here's my simple code:
const [addAdvisory, setaddAdvisory] = useState({
SYSTEMID: '',
WITHOTHERSYSTEM: '',
DATEFROM: '',
DATETO: '',
DOWNTIMEREASON: '',
AFFECTEDSYSTEMS: '',
AFFECTEDMODULES: '',
DATEOFRELEASE: '',
TIME: '',
ADVISORYDESC: '',
ADVISORYMESSAGE: '',
ADVISORYID:''
})
const openCloseModalEdit = () =>{
setModalEdit(!modalEdit);
}
//function upon clicking Edit button
const selectedAdvisory=(ADVISORYID,action1) =>{
setaddAdvisory(ADVISORYID);
(action1==='Edit') ? openCloseModalEdit() : openCloseModalDelete();
console.log("For Editing: ", addAdvisory)
}
//components inside modal edit
const bodyEdit = (
<FormControl className={style.FormControl}>
<FormControlLabel
control={
<Checkbox
checked={<i want to return true/false here to check/uncheck the textbox}
onChange={checkboxSystemChange}
name='checkedSystem'
color = 'primary'
/>
}
label = 'is there other system/s affected?'
/>
</FormControl>
)
Please help me with this, newbie on reactjs
const [checkVal, setCheckVal] = useState('Y');
const bodyEdit = (
<FormControlLabel
control={
<Checkbox
checked={checkVal === 'Y' ? true : false}
onChange={()=>{setCheckVal(checkVal === 'Y' ? 'N' : 'Y')}}
name='checkedSystem'
color = 'primary'
/>
}
</FormControl>
)
Please check above code.
I need to edit array of objects and show new value to the view. I'm pretty new in ReactJS, so I tried do it like below, but after edit I lose everything except for the one I have edited. Can you give me info if my way to do this is correct? Any advices how to do this?
Parent component:
constructor(props) {
super(props);
this.state = { data: [], open: false, openEdit: false, openAlert:false, priority:'', nameTask:'', deadline:new Date() };
}
// Edit row from table
handleEdit = e => {
const index = e.currentTarget.getAttribute('index');
let foundObject = this.state.data[index];
let nameTaskNew = foundObject.nameTask;
let priorityNew = foundObject.priority;
let deadlineNew = foundObject.deadline;
this.setState({openEdit: true, nameTask: nameTaskNew, priority: priorityNew, deadline: deadlineNew });
}
handleSubmitEdit = (e) => {
const err = this.validate();
if (!err) {
this.setState({
// Set new data array
data: [this.state],
// Clear form
openEdit: false,
nameTask: "",
nameTaskError: "",
priority: "Low",
deadline: new Date()
});}}
render() {
const actions = [
<FlatButton label="Edit" primary={true} keyboardFocused={true} onClick={e => this.handleSubmitEdit(e)} />];
return (
{/* Edit form */}
<form>
<Dialog title="Edit your Task" open={this.state.openEdit} actions={actions}>
<TextField floatingLabelText="Task" value={this.state.nameTask} errorText={this.state.nameTaskError}
onChange={e => this.handleTextFieldChange(e)}
onKeyPress={this.handleKeyPress} />
<DatePicker floatingLabelText="Deadline" value={this.state.deadline} onChange={this.handleChangeDate} />
<SelectField floatingLabelText="Priority" value={this.state.priority} onChange={this.handleChangeSelectField}>
<MenuItem value="High" primaryText="High" />
<MenuItem value="Medium" primaryText="Medium" />
<MenuItem value="Low" primaryText="Low" />
</SelectField>
</Dialog>
</form>
);}}
export default Home;
I suggest you check this post. I believe it's what you're looking for:
https://medium.com/#thejasonfile/using-the-spread-operator-in-react-setstate-c8a14fc51be1
I think you are missing spreading the full state.
handleSubmitEdit = (e) => {
const err = this.validate();
if (!err) {
this.setState({
// Set new data array
...this.state,
// Clear form
openEdit: false,
nameTask: "",
nameTaskError: "",
priority: "Low",
deadline: new Date()
});}}
This may help
I have TextField and FlatButton inside the Dialog. I want to save complete task list in an array which I defined in a state.
this.state = {
open: false,
todos: [{ id: -1, text: "", completed: false }],
notetext: ""
};
I am able to get text of TextField from the state. I want to save task in an array on clicking of FlatButton. I have handleCreateNote function which is attached on tap on FlatButton.
I don't know what is the way to add task in an array. Can anyone help me what is the way in the react ?
const AppBarTest = () =>
<AppBar
title={strings.app_name}
iconClassNameRight="muidocs-icon-navigation-expand-more"
style={{ backgroundColor: colors.blue_color }}
/>;
class App extends Component {
constructor(props) {
injectTapEventPlugin();
super(props);
this.state = {
open: false,
todos: [{ id: -1, text: "", completed: false }],
notetext: ""
};
this.handleChange = this.handleChange.bind(this);
}
handleOpen = () => {
this.setState({ open: true });
};
handleClose = () => {
this.setState({ open: false });
};
handleCreateNote = () => {
console.log(this.state.notetext);
};
handleChange(event) {
this.setState({ [event.target.name]: event.target.value });
}
render() {
return (
<MuiThemeProvider>
<div>
<AppBarTest />
<FloatingActionButton
style={styles.fab}
backgroundColor={colors.blue_color}
onTouchTap={this.handleOpen}
>
<ContentAdd />
</FloatingActionButton>
<Dialog
open={this.state.open}
onRequestClose={this.handleClose}
title={strings.dialog_create_note_title}
>
<TextField
name="notetext"
hintText="Note"
style={{ width: "48%", float: "left", height: 48 }}
defaultValue={this.state.noteVal}
onChange={this.handleChange}
/>
<div
style={{
width: "4%",
height: "48",
backgroundColor: "red",
float: "left",
visibility: "hidden"
}}
/>
<FlatButton
label={strings.create_note}
style={{ width: "48%", height: 48, float: "left" }}
onTouchTap={this.handleCreateNote}
/>
</Dialog>
</div>
</MuiThemeProvider>
);
}
}
export default App;
First create a copy of existing state array, then use array.push to add a new data, then use setState to update the state array value.
Like this:
handleCreateNote = () => {
let todos = [...this.state.todos]; //creating the copy
//adding new data
todos.push({
id: /*unique id*/,
text: this.state.notetext,
completed: false
});
//updating the state value
this.setState({todos});
};
Check this answer for details about "..." --> What do these three dots in React do?
MDN: Spread Operator
Update:
Instead of spread operator you can also use slice(), that will also return a new array, the key point is we need to create a copy of state array (before doing any change) by using any method.
Check this snippet:
let a = [{key: 1}, {key: 2}];
let b = [...a];
console.log('b', b);
you can use concat to create a new array:
this.setState({
todos: [].Concat(this.state.todos, {id, text, complete})
})