Changing multiple states on button's click - javascript

In my react app i have a list of cars returned from an axios call, each car has an attribute inside it called refundable and it's boolean either true or false, and i have a button and a disabled input, i want when i click on this button if the refundable is true to enable the input and if it's false to hide the input and show a sentence that this car is not refundable, the code i made checks if refundable is true or not and it enables the input, but if it was false it shows the false sentence for all of the cars in the list, here is what I'm doing:
Initial state:
state={cars: [],isInputDisabled: [], isVisible: true }
Function on button's click:
changeDisableState = (id, i) => {
const car = this.state.cars.find(x => x.id === id);
let isInputDisabled = this.state.isInputDisabled;
isInputDisabled[i] = !isInputDisabled[i];
if (car.refundable == true) {
this.setState({ isInputDisabled });
} else {
this.setState({ isVisible: false });
}
};
Rendering cars:
renderCars() {
const cars = this.state.cars;
return cars.map((car, i) => (
<div key={car.id}>
<Button onClick={() => this.changeDisableState(car.id, i)}>Check</Button>
{this.state.isVisible ?
<input
disabled={!this.state.isInputDisabled[i]}/> : <p>Can't be refundable</p>}
</div>
));
}

It seems like you're missing something in your code:
changeDisableState = (id, i) => {
const car = this.state.cars.find(x => x.id === id);
let isInputDisabled = this.state.isInputDisabled;
if (car.refundable == true) {
this.setState({ isInputDisabled[i] : !isInputDisabled[i] });
} else {
this.setState({ isVisible: false });
}
};
also
this line
disabled={!this.state.isInputDisabled[i]}/>
is not clear. It reads as "make my input disabled if my state value for isInputDisable is false" - shouldn't it be inveresed?

You should have isVisible state for each car object.
{car.isVisible ? <input ... /> : <p>Can't be refundable</p>}

You need to create another state variable for recently clicked id. And then only render sentence for active id
changeDisableState = (id, i) => {
const car = this.state.cars.find(x => x.id === id);
let isInputDisabled = this.state.isInputDisabled;
isInputDisabled[i] = !isInputDisabled[i];
if (car.refundable == true) {
this.setState({ isInputDisabled,activeid:id });
} else {
this.setState({ isVisible: false, activeid:id });
}
};
renderCars() {
const cars = this.state.cars;
return cars.map((car, i) => (
<div key={car.id}>
<Button onClick={() => this.changeDisableState(car.id, i)}>Check</Button>
{this.state.isVisible ?
<input
disabled={!this.state.isInputDisabled[i]}/> : this.state.activeid === car.id && <p>Can't be refundable</p>}
</div>
));
}

Related

Problem with refreshing part of component

Hello I've got a problem with refreshing component, it doesn't work, in console log shows properly data. I want after click on div to change clicked element on true and add to this element changed class name
below is jsx
{tabObjects.map((item) => (
<div
key={item.key}
id={item.key}
className={item.isChecked ? checked : notChecked}
onClick={() => selectItem(item.key)}
>
<i className={item.icon}></i>
<p className="hide-sm">{item.pText}</p>
</div>
))}
after clicking selectItem I want to change class name to checked and rest of them set checked as false so:
const selectItem = (e) => {
tabObjects.map((item) => {
item.isChecked = false;
if (e === item.key) {
item.isChecked = true;
}
});
setTabObjects(tabObjects);
};
and sample data json
const [tabObjects, setTabObjects] = useState([
{
key: "sample1",
isChecked: true,
icon: "sample1i",
pText: "Test text",
},
{
key: "sample2",
isChecked: false,
icon: "sample2i",
pText: "Test text",
},
]);
let checked = "sampleClass checked";
let notChecked = "sampleClass";
What Am I doing wrong? Clicking on any div with console log working fine
Missing return statement is the reason.
const selectItem = (e) => {
const objects = tabObjects.map((item) => {
item.isChecked = false;
if (e === item.key) {
item.isChecked = true;
}
return item;
});
setTabObjects(objects);
};

Why do i need to specifically check for false using react and javascript?

i want to render a div element only if condition1 or condition2 is true.
below is my code.
const Parent = () => {
const {notify} = useNotification();
return (
<SomeComponent
notify ({
actions: showInfo(item),
});
/>
);
}
const showInfo = (item) => {
return (
<>
{condition === 'value1' || condition === 'value2' ?
<div>header</div>
: undefined
}
</>
);
}
const useNotifications = () => {
const [activeNotifications, setActiveNotifications] = React.useContext(NotificationContext);
const notify = React.useCallback(
(notifications: Notification | Notification[]) => {
setActiveNotifications(activeNotifications => [
...activeNotifications,
]);
}
[setActiveNotifications]
);
return notify;
}
const Notification: React.FC<Props> = ({
description,actions}) => {
console.log('actions',actions) //here actions is printed false
return (
{(description || actions) && ( //why doesnt this condition work
<Body>//this is displayed even if actions is false
{actions && <div> {actions} </div>}
</Body>
}
);
}
})
in the above code the Body div is displayed even though actions is false as seen from console log statement.
if i change the condition to
{(description || actions === true) &&
<Body>//this is not displayed
{actions && <div>{actions}</div>}
</Body>
}
could someone help me understand why i have to explicitly check if {actions === true} and why {actions} doesnt work.
thanks.
EDIT: the type for the action is set like below
export type NotificationProps = {
actions?: React.ReactNode;
}

how to add multiple objects in reactjs?

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

React - Filter list by gender or age

I have a list of users with name, gender, age, etc from the following api: [https://gorest.co.in/public-api/users]. At the moment I can filter by gender === female, but doesn't work when I filter for gender === male.
Also, I try to filter the list of users by age. I get the date of birth and sorted afterwards, but it doesn't seem enough.
I have a LIVE EXAMPLE HERE
Here is the code:
import React from "react";
import axios from "axios";
export default class UserList extends React.Component {
constructor(props) {
super(props);
this.state = {
list: [],
search: ""
};
}
componentDidMount() {
this.getList();
}
/* get users list */
getList = async () => {
const api = 'https://gorest.co.in/public-api/users?_format=json&access-token=3qIi1MDfD-GXqOSwEHHLH73Y3UitdaFKyVm_';
await axios
.get(api)
.then(response => {
const list = response.data.result;
this.setState({
list,
isLoading: false
});
})
.catch(err => {
console.log(err);
});
};
/* handler for search bar */
handleChange = e => {
this.setState({
search: e.target.value
});
};
filterGender = gender => {
const lowerCaseGender = gender.toLowerCase();
const filteredGender = this.state.list.filter(
user => user.gender.toLowerCase().indexOf(lowerCaseGender) !== -1
);
this.setState({ list: filteredGender }, () => console.log(this.state.list));
};
filterAge = () => {
const ageList = this.state.list.map(age => {
return age.dob;
});
const filteredAge = this.state.list.filter(
e => e.dob.indexOf(ageList) !== -1
);
this.setState({ list: filteredAge }, () => console.log(this.state.list));
};
render() {
let style = {
display: "grid",
gridTemplateColumns: "repeat(auto-fill, minmax(250px, 1fr))",
padding: "1rem",
gridGap: "1rem 1rem"
};
return (
<div>
<input
placeholder="Search for a user..."
onChange={e => this.handleChange(e)}
/>
<button onClick={() => this.filterGender("male")}>Male</button>
<button onClick={() => this.filterGender("female")}>Female</button>
<button onClick={() => this.filterAge()}>Age</button>
<ul style={style}>
{this.state.list.map(user => {
return (
<li key={user.id}>
<div>
<img className="thumb" alt="" src={user._links.avatar.href} />
</div>
<div className="userInfo">
<p>
{user.first_name} {user.last_name}
</p>
</div>
</li>
);
})}
</ul>
</div>
);
}
}
Thank you!
The condition you are using,
user.gender.toLowerCase().indexOf(lowerCaseGender) !== -1
is matching male in female due to indexOf.
You should do this,
const filteredGender = this.state.list.filter(
user => user.gender.toLowerCase() === lowerCaseGender
);
There are two problems in your code.
Here I have updated your code.
https://codesandbox.io/s/epic-swartz-iu3qp
Problem 1: use of indexOf in filterGender function
Solution 1: directly compare the gender instead of indexOf
const filteredGender = this.list.filter(
user => user.gender.toLowerCase() === lowerCaseGender
);
Problem 2: You are filtering records from the this.state.list variable and then update it in same state variable.
Solution 2: Use another variable this.list in constructor and use it to filter the records.
Define variable in constructor
constructor(props) {
super(props);
this.state = {
list: [],
search: ""
};
this.list = []; // define new list here...
}
You have to assign upcoming list to this.list variable in getList function
this.list = response.data.result;
getList function should be look like below.
getList = async () => {
const api =
"https://gorest.co.in/public-api/users?_format=json&access-token=3qIi1MDfD-GXqOSwEHHLH73Y3UitdaFKyVm_";
await axios
.get(api)
.then(response => {
this.list = response.data.result; // assign list in variable
this.setState({
list:this.list,
isLoading: false
});
})
.catch(err => {
console.log(err);
});
};
Now you have to update both filter function as below. Instead of this.state.list use this.list.
filterGender = gender => {
const lowerCaseGender = gender.toLowerCase();
const filteredGender = this.list.filter(
user => user.gender.toLowerCase() === lowerCaseGender
);
this.setState({ list: filteredGender }, () => console.log(this.state.list));
};
filterAge = () => {
const ageList = this.list.map(age => {
return age.dob;
});
const filteredAge = this.list.filter(
e => e.dob.indexOf(ageList) !== -1
);
this.setState({ list: filteredAge }, () => console.log(this.state.list));
};
Hope this will work for you!
Full working demo with corrected code in same environment
https://codesandbox.io/s/brave-carson-6zilx
Gender
Issue - indexOf function use does't achieves anything.
Resolution -:
Using simple Array.filter with gender equality.
This function needs to be changed for gender filter -:
filterGender = gender => {
const filteredGender = this.state.list.filter(
user => user.gender.toLowerCase() === gender
);
this.setState({ list: filteredGender }, () => console.log(this.state.list));
};
Age
Issue - Filtering won't be helpful to sort data.
Resolution -:
Using Array.sort and adding a comparator function.
This function needs to be changed for age sorting -:
filterAge = () => {
const filteredAge = this.state.list.sort(function(a, b) {
return new Date(b.dob) - new Date(a.dob);
});
this.setState({ list: filteredAge }, () => console.log(this.state.list));
};

Reactjs Trying to make dropdown, how to not show the selected list on the lists?

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;
}
);
};

Categories

Resources