I have a react form which has dynamic rows, but I'm stuck at line 76 (Demo: https://codesandbox.io/s/pw58j0vzoq), not sure what can I do to update the value in the row
class App extends React.Component {
state = {
acceptedValues: [
{
id: 1,
_arguments: ["Samsung", "xiaomi"]
},
{
id: 2,
_arguments: ["OR", "AND"]
}
]
};
handleChange = (name, index, argumentIndex) => e => {
const { acceptedValues } = this.state;
if (name === "_arguments") {
updatedState = acceptedValues.map((o, i) => {
if (i === index) {
return {
...o,
_arguments: o._arguments.map((o2, index2) => {
if (index2 === argumentIndex) {
//what to do here?
}
return o;
})
};
}
return o;
});
this.setState({
acceptedValues: updatedState
});
}
};
render() {
const { acceptedValues } = this.state;
return (
<div>
{acceptedValues.map(({ operator, _arguments }, index) => (
<div style={{ marginBottom: 20 }}>
<div>
{_arguments.map((val, argumentIndex) => (
<div>
<input
onChange={this.handleChange(
"_arguments",
index,
argumentIndex
)}
id="_arguments"
type="text"
value={val}
/>
<button onClick={this.removeArgument(index, argumentIndex)}>
-
</button>
</div>
))}
</div>
</div>
))}
</div>
);
}
}
I'm able to navigate until the correct array but stuck at how to update the value within the array, I've 2 indexs in my handleChange function.
There you are mapping an array into another, so you should just pick which string to return, like this:
_arguments: o._arguments.map(
(o2, index2) => {
if (index2 === argumentIndex) return e.target.value
return o2
}
)
Or in an even cleaner way:
_arguments: o._arguments.map(
(o2, index2) => index2 === argumentIndex ? e.target.value : o2
)
Related
Below is my attempt to create an array of classes. The functionality of app is next: one can add or delete extra Input box and increase or decrease its value. As a result the app displays the sum of the all present tags. The issue comes with Delete function, when deleting any of components from created list it does correct math in array but rerenders the elements incorrectly. It always deletes the last component on the list even when you try to remove any others. Any hint why it's happening? Thanks
class Trade1 extends React.Component {
state = {
vl: this.props.value
}
change = (v) => {
let newValue
if (v) {
newValue = this.state.vl + 1
} else {
newValue = this.state.vl - 1
}
this.setState({vl: newValue})
this.props.onChange(newValue, this.props.index)
}
render() {
const {value, index} = this.props
return (
<div>
<button onClick={() => this.change(false)}>Down</button>
<input class="v_price" value={`${this.state.vl}`}/>
<button onClick={() => this.change(true)}>Up</button>
<button onClick={() => this.props.delete(this.props.index)}>Delete</button>
</div>
)
}
}
class Parent extends React.Component {
constructor(props){
super(props);
this.state = {
arr: [0,0,0]
}
}
onChange = (v, i) => {
let newArr = this.state.arr
newArr[i] = v
this.setState(newArr)
}
plus = () => {
let a = this.state.arr
a.push(0)
this.setState({arr: a})
}
minus = i => {
let a = this.state.arr
a.splice(i, 1)
console.log(a)
this.setState({arr: a})
}
render() {
return (
<div>
{this.state.arr.map((v, i) =>
{
return <Trade1 value={v} index={i} onChange={this.onChange} delete={this.minus}/>
}
)}
<div>{
this.state.arr.reduce((a, b) => a+b, 0 )
}</div>
<div><button onClick={this.plus}>Plus</button></div>
</div>
)
}
}
ReactDOM.render(<Parent />, document.getElementById('root'));
You are mutating the array, you should use filter and remove the element at index which you pass as an argument
minus = i => {
this.setState({
arr: this.state.arr.filter((x, j) => j !== i)
})
}
Issue
You've some state mutations. Try to use functional state updates and always return new state objects.
onChange = (v, i) => {
this.setState(prevState => ({
arr: prevState.arr.map((el, index) => index === i ? v : el)
}));
}
plus = () => {
this.setState(prevState => ({
arr: [...prevState.arr, 0],
}));
}
minus = i => {
this.setState(prevState => ({
arr: prevState.arr.filter((_, index) => index !== i),
}));
}
Simply i have a list item, that contain a list of names, clicking any list item change the color of that item, this is my logic:
const App = () => {
const items = [
{
name: 'peter',
id: 1
},
{
name: 'Mark',
id: 2
},
{
name: 'john',
id: 3
}
]
const [id, setId] = useState(null);
const [names, setNames] = useState(items)
const setClickedItemId = (id) => {
setId(id)
}
const turnItemRed = () => {
setNames(prev => prev.map(i => i.id === id ? {...i, color: 'red' } : i))
}
return (
<div className="app">
<ul className="items">
{
names.map(i => {
return (
<Item
item={i}
setClickedItemId={setClickedItemId}
turnItemRed={turnItemRed}
/>
)
})
}
</ul>
</div>
)
}
function Item({item, ...props}) {
const { name, id} = item;
const { setClickedItemId, turnItemRed } = props;
return (
<li
className={`${item.color === 'red' ? 'red' : ''}`}
onClick={() => {
setClickedItemId(id);
turnItemRed()
}}
>{name}</li>
)
}
ReactDOM.render(<App />, document.getElementById('root'))
This renders a list of items, i need two clicks to have an item turning into red, which means the child component doesn't catch the most recent version of state, but:
Just adding that line of code before the return statement in parent components,
const showingItems = names.map(i => i.id === id ? {...i, color: 'red'} : i)
and then using that variable showingItems to render the list instead of state variable names make it right and don't know why
So, why the child components Items doesn't get the most recent version of the state while storing the state in a variable makes it work??
State updates are batched and your onClick triggers 2 functions which does state updates. The second function doesn't receive updated value due to the async behaviour.
Just pass the id to turnItemRed function instead of grabbing it from state.
App
const turnItemRed = (id) => { //<----take the id
setNames(prev => prev.map(i => i.id === id ? {...i, color: 'red' } : i))
}
Item
function Item({item, ...props}) {
const { name, id} = item;
const { setClickedItemId, turnItemRed } = props;
return (
<li
className={`${item.color === 'red' ? 'red' : ''}`}
onClick={() => {
setClickedItemId(id);
turnItemRed(id) //<---- pass the id
}}
>{name}</li>
)
}
Edit
A quick demo of the above issue and the fix is here in the demo. . Just adding this so it might help other readers in future.
import React,{useState} from 'react';
export default () => {
const items = [
{
name: 'peter',
id: 1
},
{
name: 'Mark',
id: 2
},
{
name: 'john',
id: 3
}
]
const [id, setId] = useState(null);
const [names, setNames] = useState(items)
const setClickedItemId = (id) => {
setId(id);
turnItemRed(id);
}
const turnItemRed = (id) => {
setNames(prev => prev.map(i => i.id === id ? { ...i, color: 'red' } : i))
}
return (
<div className="app">
<ul className="items">
{
names.map(i => {
return (
<Item
item={i}
setClickedItemId={setClickedItemId}
turnItemRed={turnItemRed}
/>
)
})
}
</ul>
</div>
)
}
function Item({ item, ...props }) {
const { name, id } = item;
const { setClickedItemId, turnItemRed } = props;
return (
<li
style={{ color: item.color === 'red' ? 'red' : ''}}
onClick={() => {
setClickedItemId(id);
}}
>{name}</li>
)
}
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
class LifeCycleComps extends Component {
constructor(props) {
super(props);
this.state = {
data: 0,
names: [{
name: "sam"
},
{
name: "hammer"
},
{
name: "jellyfish"
}
]
};
//below is sortAlphabet function
sortAlphabet = () => {
this.setState({
names: this.state.names.sort()
});
};
//sortNames component
class SortNames extends Component {
render() {
return <span > {
this.props.names.name
} < /span>;
}
}
<button onClick={this.sortAlphabet}>sort</button>
<ul>
{this.state.names.map((item, i) => (
<SortNames key={i} names={item} /> ))}
</ul>
Above is my code. I am not sure what is the main problem. In the above code I want to get sorted names by onClick. But I am not getting any positive results from the above snippet. Please let me know folks what I did wrong?
You can not directly use sort function in array of object. For that you need to write a sort function or write a callback function which you can modify according your need. Here is working code(https://stackblitz.com/edit/react-31un7h) :
import React, { Component } from 'react';
import { render } from 'react-dom';
const SortNames = (props) => {
return (
<span >
{props.names.name}
</span>
)
}
class LifeCycleComps extends Component {
constructor(props) {
super(props);
this.state = {
data: 0,
names: [{
name: "sam"
},
{
name: "hammer"
},
{
name: "jellyfish"
}
]
};
}
compare = ( a, b ) => {
if ( a.name < b.name ){
return -1;
}
if ( a.name > b.name ){
return 1;
}
return 0;
}
//below is sortAlphabet function
sortAlphabet = () => {
this.setState({
names: this.state.names.sort(this.compare)
});
};
render(){
return (
<div>
<button onClick={this.sortAlphabet}>sort</button>
<ul>
{this.state.names.map((item, i) => (
<SortNames key={i} names={item} /> ))}
</ul>
</div>
);
}
}
//sortNames component
class App extends Component {
constructor() {
super();
this.state = {
name: 'React'
};
}
render() {
return (
<div>
<LifeCycleComps/>
</div>
);
}
}
render(<App />, document.getElementById('root'));
Here is sorted values
let arr = [{
name: "sam"
},
{
name: "hammer"
},
{
name: "jellyfish"
}]
function sortIt(x,y) {
if ( x.name < y.name ){
return -1;
}
if ( x.name > y.name ){
return 1;
}
return 0;
}
arr.sort(sortIt);
console.log(arr);
And here is in reactjs
sortIt(x,y) {
if ( x.name < y.name ){
return -1;
}
if ( x.name > y.name ){
return 1;
}
return 0;
}
sortAlphabet = () => {
this.state.names.sort(this.sortIt)
this.setState({
names: this.state.names.sort()
});
};
render() {
return (
<>
<button onClick={this.sortAlphabet}>sort</button>
<ul>
{this.state.names.map((item, i) => (
<li key={i} names={item}>{item.name}</li> ))}
</ul>
</>
);
Its to just adapt the javascript code provided in the comments as below.
compare = ( a, b ) => {
if ( a.name < b.name ){
return -1;
}
if ( a.name > b.name ){
return 1;
}
return 0;
}
//below is sortAlphabet function
sortAlphabet = () => {
let objs = [...this.state.names] //create a copy here as you will not want to directly mutate the state by calling sort.
this.setState({
names: objs.sort( compare );
});
};
I've made an StackBlitz in which you could see the solution. Hope this helps.
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