This is my Creatable component:
function optionsForSelect(field) {
return field
.values
.map((fieldOption) => {
return {value: fieldOption, label: fieldOption};
});
}
function PatientSelectInput({field, options, value, onChange, disabled}) {
const className = field.id + '-select';
return (
<label className={cx('input-label', className)}>
<div className="label-text">{field.displayName}</div>
<Creatable
value={value}
onChange={(selectedValue) => onChange(selectedValue ? selectedValue.value : null)}
disabled={disabled}
onBlurResetsInput={false}
onCloseResetsInput={false}
options={options} />
</label>
);
}
It is a functional component. When it renders, I can create a new option but when I hit tab or enter or click on the automatically generated "Create Option..." the newly created option disappears. I just want the default behavior.
What am I missing?
Unfortunately this version of react-select mutates it's options prop.
This is very bad coding and is fixed in version 2: https://github.com/JedWatson/react-select/issues/2484
The issue you're experiencing is coming from the way you construct the options. Your function creates a new object on each render, replacing the array containing the option you just created.
If you can't update your version I suggest you save the output of optionsForSelect to this.state.options, then pass the state variable into react-select. The upshot of doing this is you still have the ability to mutate the state of your element and limits the impact of the mutate.
Related
I have a Checkbox component looking like this:
...
{values.map((value) => (
<div key={value}>
<div
className={`checkbox ${
checked && checked.includes(value) && "checked"
}`}
>
<div className="checkbox__box">
<input
{...register(name, {
required: required,
validate: CustomValidation,
})}
id={name + value}
type="checkbox"
name={name}
value={value}
defaultChecked={defaultData === value}
/>
</div>
</div>
<label htmlFor={name + value}>
<FormattedMessage id={value} />
</label>
</div>
))}
As you can see, the component relays on a checked prop array to add the checked class to the single checkbox element.
The component is being called like so:
<Checkbox
values={SourceOfFundsList}
name="source_of_funds__list"
md={12}
register={register}
errors={errors}
defaultData={
props.data.dataTree.customer.source_of_funds.source_of_funds__list
}
checked={source_of_funds__list__checked}
/>
The checked props is populated by watching the form state, like so:
const source_of_funds__list__checked = watch("source_of_funds__list");
This works as expected, adding the class to every item of the Checkbox once it is clicked. It also logs in the console correctly, by using useEffect, like so:
useEffect(() => {
console.log(source_of_funds__list__checked);
}, [source_of_funds__list__checked]);
The problem arises when I move to another route, and then I come back.
When I come back to the page, 2 things happen:
The state is persistent, and when inspecting the component, the defaultData contains the correct array with the checked items
The checked array is undefined, and therefore the UI doesn't display the correct state (all checkboxes looked unchecked, as class is not applied)
I have tried to add a second useEffect to populate the checked array on load, if the defaultData is there to start with, and it consoles correctly, but still the classes are not applied.
useEffect(() => {
if (props.data.dataTree.customer.source_of_funds.source_of_funds__list) {
source_of_funds__list__checked =
props.data.dataTree.customer.source_of_funds.source_of_funds__list;
}
console.log("source_of_funds__list__checked", source_of_funds__list__checked);
}, []);
I'm pretty sure it has to do with the render cycle and some mistake on my use of useEffect.
Any pointers on what could I be missing?
I think you misuse the react hook form. inside the useffect, use the 'setValue' to update the initial array, and instead of using 'watch', replace it with 'useWatch'. Try it out, hope it helps you!
I am trying to set an array to a state hook. Basically I want to keep a track of a per-row (of grid sort of) Edit Dialog Open State. Basically per row, I have a Edit button, launches a . As all seems rendered initially, I am trying to manage the show hide by keeping an array in the parent grid component. When user clicks on the Edit button, per row, I want to pass the rowData as props.data and want to provide the Edit functionality.
To keep the state of the editDialogs (show/hide), I am making a array of objects useState hook as follows:
const [editDialogsModalState, setEditDialogsModalState] = useState([{}]); // every edit dialog has it's own state
...
function initializeEditDialogsModalState(dataSet) {
let newState = [];
dataSet.map((item) => newState.push({ id: item.id, state: false }));
return setEditDialogsModalState(newState); // **PROBLEM->Not setting**
}
function addUDButtons(currentRowDataMovie) { // my edit/delete button UI code
const currRowDataId = currentRowDataMovie.id;
return (
<span>
<button
type="button"
className="btn btn-info"
onClick={() => setEditDialogsState(currRowDataId)}
>
Edit
</button>
{editDialogsModalState[currRowDataId].state && ( // **PROBLEM->null data even after set call**
<EditMovieComponent
open={editDialogsModalState[currRowDataId].state}
onToggle={toggleEditDialogsModalState(currentRowDataMovie)}
movie={currentRowDataMovie}
/>
)}
}
......
function buildGrid() {
{
if (!ready) {
// data is not there, why to build the grid
return;
}
initializeEditDialogsModalState(movies);
...........
}
However not able to get the editStates. A screen shot from debugger where I can see the movies (REST output), ready, but not the editDialogsModalState state array.
In general, is there a better ways of implementing such per-row basis functionality where on click of a button I want to open a React-bootstrap and pass the row-specific dataitem for doing operations ? (I am learning React, so may not not yet fully aware of all pointers).
Thanks,
Pradip
I have implemented the datepicker and timepicker with add,delete buttons in each row. When I click on add, will add new row and delete will delete row.
I have the code link https://codesandbox.io/s/zen-water-tfyoz?fontsize=14&hidenavigation=1&theme=dark
But how to handle the state for multiple datepicker and timepicker,
When change the date, it doesnot reflect the change in field.
https://codesandbox.io/s/zen-water-tfyoz?fontsize=14&hidenavigation=1&theme=dark
So the problem is in your renderRowData function:
<td key={`tableview-td-${rowId}-${index}`}>
{column.dataFieldId === "pickdate" ? (
<DatePicker
locale="en-GB"
className="datepicker"
name={"pickdate_" + rowId}
onChange={e =>
this.handleDatePicker(
e,
"pickdate_" + rowId,
column.dataFieldId,
row
)
}
value={this.state.pickdate}
/>
)
For value u use this.state.pickdate, but when value changes you set it with:
handleDatePicker = (value, name, field, row) => {
this.props.handleInputChange(value, field, row);
console.log("data", value, "for", name);
this.setState({ [name]: value });
};
wich means that your state is now:
{
["pickdate_" + rowId]: value // where row is selected row
}
you need to change your datepicker to access value like this:
<td key={`tableview-td-${rowId}-${index}`}>
{column.dataFieldId === "pickdate" ? (
<DatePicker
locale="en-GB"
className="datepicker"
name={"pickdate_" + rowId}
onChange={e =>
this.handleDatePicker(
e,
"pickdate_" + rowId,
column.dataFieldId,
row
)
}
value={this.state["pickdate_" + rowId] || this.defaultPickDate} // this will take new selected value or default if there is none
/>
)
Working example: https://codesandbox.io/s/funny-fog-1v9o3
The reason that the date field's changes aren't reflected in the UI is that you've implemented it as a controlled component (you are setting the value of each DatePicker based on the corresponding value in the state and updating the state when you change the value, which effectively synchronizes the component with the state, more or less), whereas the TimePickers do change in the UI when a new time is chosen because they are implemented as uncontrolled components. Controlled components are often the best method, but your date component isn't updating on change because there are problems in your handleDatePicker function, as pointed out by Kaca992's answer.
If you don't know much about controlled vs uncontrolled components, see here.
As for how to handle the state for multiple DatePickers and TimePickers, personally I'd recommend that you store them as an array of rows in the state. For example, this would be a default state:
this.state = {
rows: [
{
date: new Date(),
start: moment(),
end: moment(),
}
]
}
Each row element in the array would correspond to a row in the table, and you can just use array.map to render each row as a component that contains the DatePicker, TimePickers, and buttons, and then just send the array index along with the new value to your onChange functions so that the correct row's changes can be reflected in the new state.
This would require a bit of a re-write of your DynamicDateTimePicker class, but the logic would be much simpler and more readable than how you currently have it structured.
Trying to build a react component where I need to control checked status of checboxes and select options when change event occurs. But I don't know how it is possible to get value of the checked checkbox(es) and set the state.
We're using custom data-binding. On page load, we're assigning selected value of the select, with jQuery.
Programmatically changing value of the select must update matching check-boxes.
When user checks/unchecks a checkbox, corresponding value must be toggled on the select.
With jQuery I would loop trough check-boxes and build array with checked values then assign this value to the select on checkbox change. And when select change event is triggered, I would uncheck all check-boxes and check the ones matching selected items.
This is my simplified code.
state = {
items: [
{Key: 1, Value: "A"},
{Key: 29, Value: "Z"}
],
selected: [1, 29]
}
function onSelectChange(){
// Update checked checkboxes
}
function onCheckboxChange(){
// Update selected options
}
<div>
<select multiple onChange={onSelectChange} className="hidden">
{this.state.items.map((item, i) =>
<option value={item.Key}>{item.Value}</option>
)}
</select>
<div className="checkboxes">
{this.state.items.map((item, i) =>
<input
type="checkbox"
key={i}
checked={this.state.selected.indexOf(item.Key) >= 0}
onChange={onCheckboxChange} />
)}
</div>
</div>
You would use this.setState({}) inside the event handler to update the state of a component in React. This triggers a rerender in React which allows you to query the the updated state (this.state.selected).
Be advised that this.setState() expects an immutable object, so you should never change the previous, but always set a new state object!
Answer to comment:
For selectItem:
onSelectChange = event => this.setState({selected:event.target.value})
and for checkboxes (note the prevState):
onCheckboxChange = item => event => this.setState(({selected,...prevState})=> ({
...prevState,
selected: event.target.checked? selected.concat(item): selected.filter(it=> it!== item)
}))
and usage:
{this.state.items.map((item, i) =>
<input
type="checkbox"
key={i}
checked={this.state.selected.indexOf(item.Key) >= 0}
onChange={onCheckboxChange(item)} />
)}
This has the downside that it will create a new function on each rerender, so it's better to create a custom CheckboxItem and pass the item to it and use a handleClick.
onChange function give event where you could check whether the select box is being checked or not using this you can update your state accordingly.
function onCheckboxChange(e){
console.log("checked", e.target.checked);
// Then, on the basis of boolean you can update your state
}
I want to implement 'min-character-length' feature in react material-ui autocomplete component.
Below is the code .
constructor(props) {
super(props);
this.state = {
// based on this value, trying to maintain autocomplete's menu state open/close
shouldOpenList: false,
};
}
// Method in-built
onUpdateInput(searchText, dataSource, params) {
if( searchText && searchText.length >= 3) {
this.setState({
shouldOpenList: true
})
}
}
//component props
<AutoComplete
hintText={props.placeholder}
dataSource={ props.data }
dataSourceConfig={ {text: props.text, value: props.value} }
className="fabric-autocomplete form-control"
disableFocusRipple={false}
filter={filter}
onNewRequest={ this.onNewRequest.bind(this) }
onUpdateInput={ this.onUpdateInput.bind(this) }
open={this.state.shouldOpenList} // state's value used to show menu
/>
What I understand so far is function onUpdateInput() getting fired on typing each time and it is explicitly showing menu. Props 'open' is not able to deal with state 'shouldOpenList' value.
How do i achieve min-character-length feature for this component ?
thanks for help in advance.
Maybe you can try something like popoverProps={{style: {display: 'none'}}} and change that with state.
In the source of AutoComplete it keeps the bool open in it's state. Your open prop will only be set to the state on componentDidMount and in componentWillReceiveProps. In componentWillReceiveProps it checks for this.props.open !== nextProps.open.
So it checks for false !== false in this case, which does not trigger the setState. I dont really understand why they added this property since it seems a bit useless. Maybe only to open it on the initial render.
The internal handleChange of AutoComplete which calls onUpdateInput will set the components state to open every time a character is added. Completely ignoring your open property.
EDIT:
This solution works better
<AutoComplete
popoverProps={{
open: this.state.shouldOpenList
}}
hintText={props.placeholder}
dataSource={ props.data }
dataSourceConfig={ {text: props.text, value: props.value} }
className="fabric-autocomplete form-control"
disableFocusRipple={false}
filter={filter}
onNewRequest={ this.onNewRequest.bind(this) }
onUpdateInput={ this.onUpdateInput.bind(this) }
/>
But you will also need to set open to false if the length is less than 3.