I don't know how to get values from multiple dropdowns created dynamically based on the fetched data [drugs].
I have 1 input and 2 dropdowns.
each one with its own handler:
- Amount : handleChangeDoseAmount
- Unit : handleTimeUnitClose
- Interval : handleToolClose
Can anyone suggest a solution or a proper example?
handleChangeDoseAmount = (index) => {
return (event) => {
console.log(index)
this.setState([ { unitCount: event.target.value } ])
this.props.handleAddDoseAmount(0, [event.target.value])
}
}
handleTimeUnitClose = (index, item) => { // changing here
this.setState({ timeUnitEL: null, timeUnitName: item !== 'close' ? item : this.state.timeUnitName })
this.props.handleAddDoseInterval(0, [item])
}
handleToolClose = (index, item) => {
this.setState({ toolEL: null, toolName: item !== 'close' ? item : this.state.toolName })
this.props.handleAddDoseUnit(0, [item])
}
handleTimeUnitOpen = event => { this.setState({ timeUnitEL: event.currentTarget }) }
handleToolOpen = event => { this.setState({ toolEL: event.currentTarget }) }
And this is the rendered components:
<div>{
<ul>
{drugs.map((drug, index) => {
return [
<h3 key="first">How often do you take {drug.name}?</h3>,
<HowOften
key="second"
onChangeUnitCount={this.handleChangeDoseAmount(index)}
onTimeUnitClose={(i) => this.handleTimeUnitClose(index, i)} // change
onTimeUnitOpen={this.handleTimeUnitOpen}
onToolClose={(ii) => this.handleToolClose(index, ii)} // change
onToolOpen={this.handleToolOpen}
timeUnitEL={timeUnitEL}
timeUnitList={timeUnitList}
timeUnitName={timeUnitName}
toolEL={toolEL}
unitCount={unitCount}
toolList={toolList}
toolName={toolName}
howOftenId={'how-often-id'}
/>]
})}
</ul>
}</div>
Thanks,
Related
I want to add new Objects when user click on checkbox. For example , When user click on group , it will store data {permission:{group:["1","2"]}}. If I click on topgroup , it will store new objects with previous one
{permission:{group:["1","2"]},{topGroup:["1","2"]}}.
1st : The problem is that I can not merge new object with previous one . I saw only one objects each time when I click on the group or topgroup.
onChange = value => checked => {
this.setState({ checked }, () => {
this.setState(prevState => {
Object.assign(prevState.permission, { [value]: this.state.checked });
});
});
};
<CheckboxGroup
options={options}
value={checked}
onChange={this.onChange(this.props.label)}
/>
Here is my codesanbox:https://codesandbox.io/s/stackoverflow-a-60764570-3982562-v1-0qh67
It is a lot of code because I've added set and get to set and get state. Now you can store the path to the state in permissionsKey and topGroupKey. You can put get and set in a separate lib.js.
In this example Row is pretty much stateless and App holds it's state, this way App can do something with the values once the user is finished checking/unchecking what it needs.
const Checkbox = antd.Checkbox;
const CheckboxGroup = Checkbox.Group;
class Row extends React.Component {
isAllChecked = () => {
const { options, checked } = this.props;
return checked.length === options.length;
};
isIndeterminate = () => {
const { options, checked } = this.props;
return (
checked.length > 0 && checked.length < options.length
);
};
render() {
const {
options,
checked,
onChange,
onToggleAll,
stateKey,
label,
} = this.props; //all data and behaviour is passed by App
return (
<div>
<div className="site-checkbox-all-wrapper">
<Checkbox
indeterminate={this.isIndeterminate()}
onChange={e =>
onToggleAll(e.target.checked, stateKey)
}
checked={this.isAllChecked()}
>
Check all {label}
</Checkbox>
<CheckboxGroup
options={options}
value={checked}
onChange={val => {
onChange(stateKey, val);
}}
/>
</div>
</div>
);
}
}
//helper from https://gist.github.com/amsterdamharu/659bb39912096e74ba1c8c676948d5d9
const REMOVE = () => REMOVE;
const get = (object, path, defaultValue) => {
const recur = (current, path) => {
if (current === undefined) {
return defaultValue;
}
if (path.length === 0) {
return current;
}
return recur(current[path[0]], path.slice(1));
};
return recur(object, path);
};
const set = (object, path, callback) => {
const setKey = (current, key, value) => {
if (Array.isArray(current)) {
return value === REMOVE
? current.filter((_, i) => key !== i)
: current.map((c, i) => (i === key ? value : c));
}
return value === REMOVE
? Object.entries(current).reduce((result, [k, v]) => {
if (k !== key) {
result[k] = v;
}
return result;
}, {})
: { ...current, [key]: value };
};
const recur = (current, path) => {
if (path.length === 1) {
return setKey(
current,
path[0],
callback(current[path[0]])
);
}
return setKey(
current,
path[0],
recur(current[path[0]], path.slice(1))
);
};
return recur(object, path, callback);
};
class App extends React.Component {
state = {
permission: { group: [] },
topGroup: [],
some: { other: [{ nested: { state: [] } }] },
};
permissionsKey = ['permission', 'group']; //where to find permissions in state
topGroupKey = ['topGroup']; //where to find top group in state
someKey = ['some', 'other', 0, 'nested', 'state']; //where other group is in state
onChange = (key, value) => {
//use set helper to set state
this.setState(set(this.state, key, arr => value));
};
isIndeterminate = () =>
!this.isEverythingChecked() &&
[
this.permissionsKey,
this.topGroupKey,
this.someKey,
].reduce(
(result, key) =>
result || get(this.state, key).length,
false
);
toggleEveryting = e => {
const checked = e.target.checked;
this.setState(
[
this.permissionsKey,
this.topGroupKey,
this.someKey,
].reduce(
(result, key) =>
set(result, key, () =>
checked
? this.plainOptions.map(({ value }) => value)
: []
),
this.state
)
);
};
onToggleAll = (checked, key) => {
this.setState(
//use set helper to set state
set(this.state, key, () =>
checked
? this.plainOptions.map(({ value }) => value)
: []
)
);
};
isEverythingChecked = () =>
[
this.permissionsKey,
this.topGroupKey,
this.someKey,
].reduce(
(result, key) =>
result &&
get(this.state, key).length ===
this.plainOptions.length,
true
);
plainOptions = [
{ value: 1, name: 'Apple' },
{ value: 2, name: 'Pear' },
{ value: 3, name: 'Orange' },
];
render() {
return (
<React.Fragment>
<h1>App state</h1>
{JSON.stringify(this.state)}
<div>
<Checkbox
indeterminate={this.isIndeterminate()}
onChange={this.toggleEveryting}
checked={this.isEverythingChecked()}
>
Toggle everything
</Checkbox>
</div>
{[
{ label: 'group', stateKey: this.permissionsKey },
{ label: 'top', stateKey: this.topGroupKey },
{ label: 'other', stateKey: this.someKey },
].map(({ label, stateKey }) => (
<Row
key={label}
options={this.plainOptions}
// use getter to get state selected value
// for this particular group
checked={get(this.state, stateKey)}
label={label}
onChange={this.onChange} //change behaviour from App
onToggleAll={this.onToggleAll} //toggle all from App
//state key to indicate what state needs to change
// used in setState in App and passed to set helper
stateKey={stateKey}
/>
))}
</React.Fragment>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
<link href="https://cdnjs.cloudflare.com/ajax/libs/antd/4.0.3/antd.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/antd/4.0.3/antd.js"></script>
<div id="root"></div>
I rewrite all the handlers.
The bug in your code is located on the usage of antd Checkbox.Group component with map as a child component, perhaps we need some key to distinguish each of the Row. Simply put them in one component works without that strange state update.
As the demand during communication, the total button is also added.
And, we don't need many states, keep the single-source data is always the best practice.
import React from "react";
import ReactDOM from "react-dom";
import "antd/dist/antd.css";
import "./index.css";
import { Checkbox } from "antd";
const group = ["group", "top"];
const groupItems = ["Apple", "Pear", "Orange"];
const CheckboxGroup = Checkbox.Group;
class App extends React.Component {
constructor() {
super();
this.state = {
permission: {}
};
}
UNSAFE_componentWillMount() {
this.setDefault(false);
}
setDefault = fill => {
const temp = {};
group.forEach(x => (temp[x] = fill ? groupItems : []));
this.setState({ permission: temp });
};
checkLength = () => {
const { permission } = this.state;
let sum = 0;
Object.keys(permission).forEach(x => (sum += permission[x].length));
return sum;
};
/**
* For total
*/
isTotalIndeterminate = () => {
const len = this.checkLength();
return len > 0 && len < groupItems.length * group.length;
};
onCheckTotalChange = () => e => {
this.setDefault(e.target.checked);
};
isTotalChecked = () => {
return this.checkLength() === groupItems.length * group.length;
};
/**
* For each group
*/
isIndeterminate = label => {
const { permission } = this.state;
return (
permission[label].length > 0 &&
permission[label].length < groupItems.length
);
};
onCheckAllChange = label => e => {
const { permission } = this.state;
const list = e.target.checked ? groupItems : [];
this.setState({ permission: { ...permission, [label]: list } });
};
isAllChecked = label => {
const { permission } = this.state;
return !groupItems.some(x => !permission[label].includes(x));
};
/**
* For each item
*/
isChecked = label => {
const { permission } = this.state;
return permission[label];
};
onChange = label => e => {
const { permission } = this.state;
this.setState({ permission: { ...permission, [label]: e } });
};
render() {
const { permission } = this.state;
console.log(permission);
return (
<React.Fragment>
<Checkbox
indeterminate={this.isTotalIndeterminate()}
onChange={this.onCheckTotalChange()}
checked={this.isTotalChecked()}
>
Check all
</Checkbox>
{group.map(label => (
<div key={label}>
<div className="site-checkbox-all-wrapper">
<Checkbox
indeterminate={this.isIndeterminate(label)}
onChange={this.onCheckAllChange(label)}
checked={this.isAllChecked(label)}
>
Check all
</Checkbox>
<CheckboxGroup
options={groupItems}
value={this.isChecked(label)}
onChange={this.onChange(label)}
/>
</div>
</div>
))}
</React.Fragment>
);
}
}
ReactDOM.render(<App />, document.getElementById("container"));
Try it online:
Please try this,
onChange = value => checked => {
this.setState({ checked }, () => {
this.setState(prevState => {
permission : { ...prevSatate.permission , { [value]: this.state.checked }}
});
});
};
by using spread operator you can stop mutating the object. same way you can also use object.assign like this.
this.setState(prevState => {
permission : Object.assign({} , prevState.permission, { [value]: this.state.checked });
});
And also i would suggest not to call setState in a callback. If you want to access the current state you can simply use the current checked value which you are getting in the function itself.
so your function becomes ,
onChange = value => checked => {
this.setState({ checked });
this.setState(prevState => {return { permission : { ...prevSatate.permission, { [value]: checked }}
}});
};
Try the following
//Inside constructor do the following
this.state = {checkState:[]}
this.setChecked = this.setChecked.bind(this);
//this.setChecked2 = this.setChecked2.bind(this);
//Outside constructor but before render()
setChecked(e){
this.setState({
checkState : this.state.checkState.concat([{checked: e.target.id + '=>' + e.target.value}])
//Id is the id property for a specific(target) field
});
}
//Finally attack the method above.i.e. this.setChecked to a form input.
Hope it will address your issues
updateDishDetails(id, quantity) {
if (quantity !== 0) {
this.setState(
prevState => ({
bookingFormData: {
...prevState.bookingFormData,
dishDetails: {
...prevState.bookingFormData.dishDetails, // WORKING
[id]: quantity, // PERFECTLY
},
},
})
);
}
if (quantity === 0) {
this.setState(
prevState => ({
bookingFormData: {
...prevState.bookingFormData,
dishDetails: {
// ...prevState.bookingFormData.dishDetails, // NEED HELP HERE
// [id]: quantity, // AND HERE
},
},
})
);
}
}
I have the above function where I set the state of dishDetails based on the value of quantity.
What do I want to achieve?
When the quantity !== 0 set the state of dishDetails as shown. ( This is working Perfectly )
When the quantity === 0, I want to remove that particular id from the dishDetails, but the previous state should be maintained. ( I need help in solving this )
The relevant state is a follows:
this.state = {
bookingFormData: {
dishDetails: []
}
}
You can use destructuring assignment and the rest operator to create a new object and remove a prop :
if (quantity === 0) {
this.setState(
prevState => {
const { [id]: removedId, ...newDishDetails } = prevState.bookingFormData.dishDetails;
return {
bookingFormData: {
...prevState.bookingFormData,
dishDetails: newDishDetails,
},
},
}
);
}
Can you nullify the id?
Set the id's value to null to remove the content.
dishDetails: {
...prevState.bookingFormData.dishDetails,
[id]: null,
},
if its an array thats easier
dishDetails: {
...prevState.bookingFormData.dishDetails.filter((item) => item.id !== id),
},
or if input and output are both objects
dishDetails: {
...Object.entries(prevState.bookingFormData.dishDetails)
.filter(([key, item]) => item.id !== id)
.reduce((acc, [key, item]) => ({...acc, [key]: item}), {}),
},
I'll use the delete operator:
if (quantity === 0) {
const dishDetails = {...this.state.bookingFormData.dishDetails}
delete dishDetails[id];
this.setState(
prevState => ({
bookingFormData: {
...prevState.bookingFormData,
dishDetails
}
})
)
}
I am mapping multiple radio buttons (group) options and when the user click on radio buttons, would like to add selected values and uniqueIds to an array.
with my current code I can get the value that I am currently clicking on but can't add to array.
{result !== null && result.length > 0 ? (
<div>
{result.map(item => {
const label = item.question
const listOptions = item.options.map(item => {
return (
<Radio
name={item.uniqueId}
inline
value={item.uniqueId}
key={item.uniqueId}
className="radio-options"
checked={item.checked}
onChange={e => {
this.handleChange(label, uniqueId);
}}
>
{item.options}
</Radio>
);
});
return (
<div className="radio-options-overview">
<p>{label}</p>
{listOptions}
</div>
);
})}
handleChange = (label, uniqueId) => {
console.log(label, uniqueId);
let addToArray = [];
this.setState({ selectedItems: addToArray})
};
array would look something like this,
[
{ "label": "ageRange", "uniquId": 2 },
{ "label": "smoker", "uniquId": 1 },
{ "label": "exOption", "uniquId": 3 }
]
You are nearly there. #Clarity provided good solution.
if you wanting to replace exisiting value and replace it with new one
Try This
handleChange = (label, uniqueId) => {
const { selectedItems } = this.state
// Find items that already exists in array
const findExistingItem = selectedItems.find((item) => {
return item.uniqueId === uniqueId;
})
if(findExistingItem) {
// Remove found Item
selectedItems.splice(findExistingItem);
// set the state with new values/object
this.setState(state => ({
selectedItems: [...state.selectedItems, {
label, uniqueId
}]
}))
} else {
// if new Item is being added
this.setState(state => ({
selectedItems: [...state.selectedItems, {
label, uniqueId
}]
}))
}
};
You can do like this:
handleChange = (label, uniqueId) => {
this.setState(state => ({ selectedItems: [...state.selectedItems, uniqueId]}));
};
By using array spread and functional form of setState you make sure that you don't directly mutate the state and add the items to the latest state.
In case you'd want to add an object with a label: uniqueId pair, you could do like so:
handleChange = (label, uniqueId) => {
this.setState(state => ({
selectedItems: [...state.selectedItems, {
[label]: uniqueId
}]
}));
};
EDIT: If you want to overwrite the items with the same labels, the easiest would be to store them as an object and not an array, so the item with the same label would overwrite an existing one:
handleChange = (label, uniqueId) => {
this.setState(state => {
return { selectedItems: { ...state.selectedItems, [label]: uniqueId } };
});
};
Honestly, I don't understand, what are you trying to do, but if you need to add object (or something else) inside an array, you could use .push() method. For example:
let addToArray = [];
let test = {"label": "ageRange", "uniquId": 2};
addToArray.push(test);
console.log(addToArray); //[{label: "ageRange", uniquId: 2}]
i'm trying make conditional rendering in react list item but it's not working:
here is what i tried:
this is my state:
menuItems: [
{
title: "Home",
partly: false,
icon: home,
route: '/'
}
....
]
here is my list in jsx:
render() {
return (
{this.state.menuItems.map(item => (
<NavLink
className={
item.partly
? "content--item item-partly"
: "content--item"
}
onMouseEnter={() => this.showPartly(item)}
onMouseLeave={() => this.hidePartly(item)}
to={item.route}
>
<p>{item.title}</p>
</NavLink>
))}
)
}
and here is my onMouseEnter and onMouseLeave events:
showPartly = item => {
this.setState(prevState => {
let item = Object.assign({}, prevState.item);
item.partly = true;
console.log(item.partly)
return { item };
})
}
hidePartly = item => {
this.setState(prevState => {
let item = Object.assign({}, prevState.item);
item.partly = false;
console.log(item.partly)
return { item };
})
}
i can see on console true and false when events work. But it's not affect to dom so my classname not changes.
Where i mistake?
You are setting up the only item, so your state looks like:
{
menuItems: [{}, {}, {}],
item: {}
}
I'd rather set up the new menuItems array to the state, or even check partly in another field.
The way, which modifies menuItems:
showPartly = item => {
this.setState(prevState => {
return {
menuItems: prevState.menuItems.map(
current => current === item ? {...current, partly: true} : current
),
};
})
}
hidePartly = item => {
this.setState(prevState => {
return {
menuItems: prevState.menuItems.map(
current => current === item ? {...current, partly: false} : current
),
};
})
}
And the way with the separated field in the state:
render() {
return (
{this.state.menuItems.map(item => (
<NavLink
className={
this.state.partly === item
? "content--item item-partly"
: "content--item"
}
onMouseEnter={() => this.showPartly(item)}
onMouseLeave={() => this.hidePartly(item)}
to={item.route}
>
<p>{item.title}</p>
</NavLink>
))}
)
}
// ...
showPartly = item => {
this.setState({partly: item});
}
hidePartly = item => {
this.setState({partly: null});
}
Important thing: in the second variant you can have only one partly item
I'm trying to customizing this dropdown component.
https://codesandbox.io/s
state = {
activeOptionIndex: -1,
isOpen: false,
};
getAdditionalProps = (index, props) => ({
onSelect: this.onSelect,
index,
selected: index === this.state.activeOptionIndex,
...props,
});
getChildrenOptionssWithProps = () => {
return Children.map(this.props.children, (child, index) =>
cloneElement(child, this.getAdditionalProps(index, child.props)),
);
};
getActiveOptionLabel = () => {
const { children } = this.props;
const { activeOptionIndex } = this.state;
const currentChildren = children[activeOptionIndex];
if (currentChildren) {
return currentChildren.props.children;
}
return false;
};
toggleList = () => {
this.setState({ isOpen: !this.state.isOpen });
};
onSelect = (optionIndex, value) => {
const { onSelect } = this.props;
this.setState({
activeOptionIndex: optionIndex,
isOpen: false,
});
if (onSelect !== 'undefined') onSelect(value);
};
render() {
const childrenOptionssWithProps = this.getChildrenOptionssWithProps();
const label = this.getActiveOptionLabel();
return (
<div className="Dropdown">
<Button onClick={this.toggleList} text={label || 'Выберите...'} />
{this.state.isOpen && (
<div className="Dropdown__list">{childrenOptionssWithProps}</div>
)}
</div>
);
}
}
I don't wanna show the selected list on all the lists.
Let's say there are list A, list B, list C. and when list B is selected, I dont want this list B to be shown on all the lists. so only list A and list B will be showing on the lists.
getAdditionalProps()
getChildrenOptionssWithProps()
I think these two functions are the points to solve this problem but have no idea how to manage it... Before the mapping from getChildrenOptionssWithProps(), I can add filter function i guess?
Can anyone please help me??
I modified your getChildrenOptionssWithProps function as follows and it seems to be doing what you are looking for.
getChildrenOptionssWithProps = () => {
return Children.map(this.props.children, (child, index) => {
if (index !== this.state.activeOptionIndex){
return cloneElement(child, this.getAdditionalProps(index, child.props));
}
return;
}
);
};