Using https://github.com/ericgio/react-bootstrap-typeahead
this.state = {
pset: 'C'
};
...
setValue = (key, value) => {
const property = { ...this.state };
property[key] = value;
this.setState(property);
};
onTypeaheadChange = (key, arrVal) => {
this.setValue(key, arrVal[0])
};
...
<Typeahead
id="1"
onInputChange={e => setValue('pset', e)}
onChange={(arrVal) => onTypeaheadChange('pset', arrVal)}
selected={[this.state.pset]}
options={['A','B']}
/>
I want to be able to have a pre-selected value, but not necessarily one of the options.
https://github.com/ericgio/react-bootstrap-typeahead/blob/master/docs/Usage.md#controlled-vs-uncontrolled
Similar to other form elements, the typeahead can be controlled or
uncontrolled. Use the selected prop to control it via the parent, or
defaultSelected to optionally set defaults and then allow the
component to control itself. Note that the selections can be
controlled, not the input value.
What does this mean? I can't do what I want? Is there any workaround?
Related
I have several checkboxes running in several checkbox groups. I can't figure out how to uncheck (thus changing the state) on a particular checkbox. FOr some reason I can't reach e.target.checked.
<Checkbox
size="small"
name={item}
value={item}
checked={checkboxvalues.includes(item)}
onChange={(e) => handleOnChange(e)}
/>
and my function
const handleOnChange = (e) => {
const check = e.target.checked;
setChecked(!check);
};
I made a working sample of the component in this sandbox.
You need to create handleOnChange function specific to each group. I have created one for Genre checkbox group in similar way you can create for other groups.
Here is handler function.
const handleOnChangeGenre = (e) => {
let newValArr = [];
if (e.target.checked) {
newValArr = [...state.pillarGenre.split(","), e.target.value];
} else {
newValArr = state.pillarGenre
.split(",")
.filter((theGenre) => theGenre.trim() !== e.target.value);
}
setState({ ...state, pillarGenre: newValArr.join(",") });
};
pass this function as handleOnChange prop to CustomCheckboxGroup as below.
<CustomCheckboxGroup
checkboxdata={genres}
checkboxvalues={state.pillarGenre}
value={state.pillarGenre}
sectionlabel="Genre"
onToggleChange={handleGenreSwitch}
togglechecked={genreswitch}
handleOnChange={handleOnChangeGenre}
/>
comment your handleOnChange function for testing.
check complete working solution here in sandbox -
complete code
Here's how I'd do it: https://codesandbox.io/s/elastic-pateu-flwqvp?file=/components/Selectors.js
I've abstracted the selection logic into a useSelection() custom hook, which means current selection is to be found in store[key].selected, where key can be any of selectors's keys.
items, selected, setSelected and sectionLabel from each useSelection() call are stored into store[key] and spread onto a <CustomCheckboxGroup /> component.
The relevant bit is the handleCheck function inside that component, which sets the new selection based on the previous selection's value: if the current item is contained in the previous selected value, it gets removed. Otherwise, it gets added.
A more verbose explanation (the why)
Looking closer at your code, it appears you're confused about how the checkbox components function in React.
The checked property of the input is controlled by a state boolean. Generic example:
const Checkbox = ({ label }) => {
const [checked, setChecked] = useState(false)
return (
<label>
<input
type="checkbox"
checked={checked}
onChange={() => setChecked(!checked)}
/>
<span>{label}</span>
</label>
)
}
On every render, the checked value of the <input /> is set according to current value of checked state. When the input's checked changes (on user interaction) the state doesn't update automatically. But the onChange event is triggered and we use it to update the state to the negative value of the state's previous value.
When dealing with a <CheckboxList /> component, we can't serve a single boolean to control all checkboxes, we need one boolean for each of the checkboxes being rendered. So we create a selected array and set the checked value of each <input /> to the value of selected.includes(item) (which returns a boolean).
For this to work, we need to update the value of selected array in every onChange event. We check if the item is contained in the previous version of selected. If it's there, we filter it out. If not, we add it:
const CheckboxList = ({ items }) => {
const [selected, setSelected] = useState([])
const onChecked = (item) =>
setSelected((prev) =>
prev.includes(item)
? prev.filter((val) => val !== item)
: [...prev, item]
)
return items.map((item) => (
<label key={item}>
<input
type="checkbox"
checked={selected.includes(item)}
onChange={() => onChecked(item)}
/>
<span>{item}</span>
</label>
))
}
Hope that clears things up a bit.
The best way to do it, it's to save selected checkboxes into a state array, so to check or uncheck it you just filter this state array based on checkbox value property that need to be unique.
Try to use array.some() on checkbox property checked. To remove it it's just filter the checkboxes setted up in the state array that are different from that single checkbox value.
I am trying to get my Redux store fields to automatically populate a method I have imported. Am I going about this the right way in order to get this done? Do I need to create a mapping options for each field?
I have each of my dropdowns inserted with a PopulateDropdown list and the fields in each of them but I need them split as per the id and text.
Am I accessing my redux store correctly below? I have the array declared on up my function component by using const fields = useSelector(state => state.fields);
Update
I have the method inserted into where the dropdowns should be however I don't think I am accessing the data correctly which is causing the problem. The fields array has been de-structured into the six different fields for each dropdown and different mappingOptions have been created for each one.
What do I need to do to get the data into the method? the examples I have seen have static arrays declared on the component rather than use the Redux store.
const fields = useSelector(state => state.fields);
// can destructure individual fields
const { diveSchoolList, currentList, regionList, diveTypeList, visibilityList, diveSpotList } = fields;
populateDropdown method that I have imported
export const PopulateDropdown = ({ dataList = [], mappingOptions, name, label }) => {
const { title, value } = mappingOptions;
return (
<FormControl style={{ width: 200 }} >
<InputLabel id={label}>{label}</InputLabel>
<Select labelId={label} name={name} >
{dataList.map((item) => (
<MenuItem value={item[value]}>{item[title]}</MenuItem>
))}
</Select>
</FormControl>
);
};
imported dropdown menu
<PopulateDropdown
dataList={diveType}
mappingOptions={mappingOptions}
name="fieldName"
label="Select dive type"
value={dive.typeID}
onChange={handleChange}/>
Update
I have updated my action, reducer and populateFields method however I am still having trouble mapping the redux data to my two property fields. In the Redux tree the fields should be under the fields.data.fieldlists as they print when I console log them.
What way should I be populating them into the titleProperty etc? It is currently looking like it might be populating but a large box drops downs that I can't see any values inside.
// select user object from redux
const user = useSelector(state => state.user);
// get the object with all the fields
const fields = useSelector(state => state.fields);
// can destructure individual fields
const { diveSchoolList = [],
currentList = [],
regionList = [],
diveTypeList = [],
visibilityList = [],
diveSpotList = [],
marineTypeList = [],
articleTypeList = []
} = fields;
.........
<PopulateDropdown
dataList={fields.data.diveTypeList} // the options array
titleProperty={fields.data.diveTypeList.diveTypeID} // option label property
valueProperty={fields.data.diveTypeList.diveType} // option value property
label="Dive Type Name" // label above the select
placeholder="Select dive type" // text show when empty
value={dive.typeID} // get value from state
onChange={handleChange(setDive.typeID)} // update state on change
/>
Your PopulateDropdown component looks correct except that we need it to use the value and onChange that we passed down as props.
My personal preference would be to use separate properties valueProperty and titleProperty instead of passing a single mappingOptions. That way you don't need to create objects for every dropdown, you just set the two properties in your JSX. You could get rid of this part entirely if you normalized your data such that the elements of every list have the same properties id and label.
<PopulateDropdown
dataList={diveTypeList} // the options array
titleProperty={"diveTypeId"} // option label property
valueProperty={"diveType"} // option value property
label="Dive Type Name" // label above the select
placeholder="Select dive type" // text show when empty
value={dive.typeID} // get value from state
onChange={handleChange("typeId")} // update state on change
/>
export const PopulateDropdown = ({
dataList = [],
valueProperty,
titleProperty,
label,
...rest // can just pass through all other props to the Select
}: Props) => {
return (
<FormControl style={{ width: 200 }}>
<InputLabel id={label}>{label}</InputLabel>
<Select {...rest} labelId={label}>
{dataList.map((item) => (
<MenuItem value={item[valueProperty]}>{item[titleProperty]}</MenuItem>
))}
</Select>
</FormControl>
);
};
It looks like the ids in currentId are actually a number, so at some point in your code you will want to convert that with parseInt because e.target.value is always a string, though maybe the backend can handle that.
Loading the API Data
It looks like you figured out how to fetch all of the fields in one API call which is great. You are saving it to a property fields on the fields reducer which creates the structure state.fields.fields. Since you are replacing the whole state, you can just return the whole thing as the entire slice state.
You can initialize your state object with empty arrays, or you can use an empty object {} as your initial state and fallback to an empty array when you destructure the arrays off of it, like const {diveSchoolList = [], currentList = []} = fields.
export const requireFieldData = createAsyncThunk(
"fields/requireData", // action name
// don't need any argument because we are now fetching all fields
async () => {
const response = await diveLogFields();
return response.data;
},
// only fetch when needed: https://redux-toolkit.js.org/api/createAsyncThunk#canceling-before-execution
{
// _ denotes variables that aren't used - the first argument is the args of the action creator
condition: (_, { getState }) => {
const { fields } = getState(); // returns redux state
// check if there is already data by looking at the didLoadData property
if (fields.didLoadData) {
// return false to cancel execution
return false;
}
}
}
);
const fieldsSlice = createSlice({
name: "fields",
initialState: {
currentList: [],
regionList: [],
diveTypeList: [],
visibilityList: [],
diveSpotList: [],
diveSchoolList: [],
marineTypeList: [],
articleTypeList: [],
didLoadData: false,
},
reducers: {},
extraReducers: {
// picks up the pending action from the thunk
[requireFieldData.pending.type]: (state) => {
// set didLoadData to prevent unnecessary re-fetching
state.didLoadData = true;
},
// picks up the success action from the thunk
[requireFieldData.fulfilled.type]: (state, action) => {
// want to replace all lists, there are multiple ways to do this
// I am returning a new state which overrides any properties
return {
...state,
...action.payload
}
}
}
});
So in the component we now only need to call one action instead of looping through the fields.
useEffect(() => {
dispatch(requireFieldData());
}, []);
I was trying to update the state after user selects the dropdown, however, the selected option is never changed. See the gif -- https://recordit.co/KH2Pqn34bp.
I am confused that ideally, after using setFilterOptions to update state, it's supposed to re-render this component with a new value, but it doesn't happen. Could anyone help take a look? What am I missing here? Thanks a lot!
Example code on sandbox -- https://codesandbox.io/s/react-select-default-value-forked-1ybdk?file=/index.js
const SearchFilter = () => {
const [filterOptions, setFilterOptions] = useContext(SearchFilterContext);
let curSort = filterOptions['sortType'] || DEFAULT_SORT_OPTION;
const handleSortChange = (option) => {
setFilterOptions(previous => Object.assign(previous, { 'sortType': option }))
};
return (
<span className='filter-container'>
<Select options={SORT_TYPE_OPTIONS} value={curSort} onChange={handleSortChange}/>
</span>
);
};
Couple of problems in your code:
To set the default value of the Select component, you have written some unnecessary code. Instead, you could just use the defaultValue prop to set the default value of the Select component.
<Select
options={OPTIONS}
defaultValue={OPTIONS[0]}
onChange={handleSortChange}
/>
You are mutating the state directly. Object.assign(...) returns the target object. In your case, the target object is the previous state.
Instead of returning the new state object, you mutate the state directly and return the previous state object which prevents a re-render.
Using the spread-syntax, you can update the state correctly as shown below:
const handleSortChange = (option) => {
setFilterOptions({ ...filterOptions, sortType: option });
};
Following code fixes the above mentioned problem in your component:
const SearchFilter = () => {
const [filterOptions, setFilterOptions] = useState({});
const handleSortChange = (option) => {
setFilterOptions({ ...filterOptions, sortType: option });
};
return (
<span className="filter-container">
<Select
options={OPTIONS}
defaultValue={OPTIONS[0]}
onChange={handleSortChange}
/>
</span>
);
};
The reason you're not seeing a change is that this functional component will only re-render when it sees that your state changed (based on your sandbox script). At the moment when you use Object.assign(previous, { 'sortType': option}) you're changing the sortType property in the object but the object itself doesn't change and so the functional component doesn't see a change.
We can resolve this by using either Object.assign({}, previous, { 'sortType': option}) which will create a NEW object with the previous state attributes and the changed sortType (the first param to Object.assign is the object where the following object properties will get copied into. if we use an empty object that's the equivalent of a new object) or we can use a spread operator and replace it with ({...sortType, 'sortType': option}) which will also create a new object that the functional component will recognize as a changed state value.
const handleSortChange = (option) => {
setFilterOptions(previous => Object.assign({}, previous, { 'sortType': option }))
};
or
const handleSortChange = (option) => {
setFilterOptions(previous => ({...previous, 'sortType': option})
};
Keep in mind these are shallow object copies.
I have a Form with a couple Form.Select attributes. My onChange() works for the <Form.Select> attributes without multiple set. However, it cannot handle selections from <Form.Select> attributes that do have multiple set.
I would like to have a single onChange function that can handle data changing for instances of Form.Select with or without the "multiple" flag set.
The Form Class:
class SomeForm extends React.Component {
handleSubmit = event => {
event.preventDefault();
this.props.onSubmit(this.state);
};
onChange = event => {
const {
target: { name, value }
} = event;
this.setState({
[name]: value
});
console.log("name: " + name)
console.log("value: " + value)
};
render() {
return (
<Form onSubmit={this.handleSubmit} onChange={this.onChange}>
<Form.Select label="Size" options={sizes} placeholder="size" name="size" />
<Form.Select
label="Bit Flags"
placeholder="Bit flags"
name="flags"
fluid
multiple
search
selection
options={bits}
/>
<Form.Button type="submit">Submit</Form.Button>
</Form>
);
}
}
The logs are never called when I select options from the Form with multiple set.
Some possible bits to populate the options prop of Form.Select:
const bits = [
{key: '1', text: "some text 1", value: "some_text_1"},
{key: '2', text: "some text 2", value: "some_text_2"},
];
How can I modify onChange() to handle both Form.Select attributes as listed above?
Please note that this question is different from question on StackOverflow which are concerned only with an onChange that can only be used for updating an array.
Multiple selects are a weird beast. This post describes how to retrieve the selected values.
If you want to define a form-level handler, you need to make an exception for multiple selects:
const onChange = (event) => {
const value = isMultipleSelect(event.target)
? getSelectedValuesFromMultipleSelect(event.target)
: event.target.value
this.setState({[event.target.name]: value})
};
const isMultipleSelect = (selectTag) => selectTag.tagName === 'SELECT' && selectTag.multiple
const getSelectedValuesFromMultipleSelect = (selectTag) => [...selectTag.options].filter(o => o.selected).map(o => o.value)
I am trying to use Semantic UI react for layout. These are the following sample code I am having trouble with onChange. I can check to click the toggle but resets everytime I refresh.
import {
Checkbox
} from 'semantic-ui-react'
onChangeInput(event) {
let name = event.target.name
let value = event.target.value
let talent = this.state.newTalentProfile
talent[name] = value
this.setState({
newTalentProfile: talent
})
}
<Select
name = "willing_to_relocate"
ref = "willing_to_relocate"
defaultValue = {this.props.talent.willing_to_relocate}
onChange = { this.onChangeInput.bind(this)} >
<Option value = ""label = "" / >
<Option value = "YES"label = "YES" / >
<Option value = "NO"label = "NO" / >
</Select>
the below code doesn't work, but the above one works when i make changes it saves it to database
<Checkbox toggle
name = "willing"
ref = "willing"
label = "Willin To Relocate"
onChange = {this.onChangeInput.bind(this)
}
/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
onChangeCheckbox = (evt, data) => {
let checked = data.checked
console.log(checked)
}
<Checkbox toggle label='Running discounted price?'
onClick={(evt, data)=>this.onChangeCheckbox(evt, data)}
/>
This should do the trick.
Your code suggests you expect event.target to be a checkbox element, but it is not. If you examine the variable event.target within your browser, you'll notice that it points to a label element, not the checkbox itself. Therefore, I'm guessing that your event.target.value and event.target.label are evaluating to null, causing the rest of your code to not function the way you expect.
There's many ways to get around this. One way is to set a state variable for your component to represent the state of the checkbox:
class TheComponentThatContainsMyCheckbox extends Component {
state = {
textValue: null,
willingToRelocate: true
}
...
Then, you can create a handler that toggles this state when checked:
toggleCheckBox = () => {
const willingToRelocate = !(this.state.willingToRelocate);
this.setState({willingToRelocate});
// Note: if you have other logic here that depends on your newly updated state, you should add in a callback function to setState, since setState is asynchronous
}
Notice that I'm using arrow syntax here, which will automatically bind this for me. Then, hook it up to the onChange event of the Semantic UI Checkbox:
<Checkbox label='Willing to Relocate.' onChange={this.toggleCheckBox}/>
You can now use your this.willingToRelocate state to decide other application logic, passing it up or down using whatever state management pattern you like (state container/store like Redux, React Context, etc.).
I assume your this.onChangeInput handles database updates.
semantic ui react offers a prop checked for Checkbox.
You should have a state {checked: false} in your code, then this.setState whatever in componentDidMount after retrieving the database record.
Pass this.state.checked into checked prop of Checkbox.