How to properly change state value of array of objects? - reactjs

Imagine this variable:
let myArray = [
{
name: "animal",
value: "",
},
{
name: "fruit",
value: "",
},
(...)
];
myArray is set in stone - it is hard-coded and its length wont change, but it is a lengthy array of 10 elements. A user will only update myArray objects values via html input. Based on above, can myArray be considered as a state in Svelte?
Is below example the correct way of changing myArray state in Svelte?
(...)
myArray.forEach(element => {
if (element.name === name) element.value = value;
});
I have a button state that its disabled attribute depends on all elements in myArray having some value. Can I use Sveltes $: btnIsDisabled reactive statements to achieve that and how?
<button type="submit" disabled={btnIsDisabled}>
Submit me
</button>

I'm assuming you plan on using your array as the component-state. And that you have an input corresponding to each field.
Try something like this: https://codesandbox.io/s/magical-fog-tfq3q
class App extends React.Component {
state = {
favorites: [
{ name: "animal", value: "" },
{ name: "city", value: "" },
{ name: "song", value: "" },
{ name: "place", value: "" },
{ name: "food", value: "" },
{ name: "sport", value: "" }
],
emptyFields: null
};
handleOnChange = event => {
const { favorites } = this.state;
let updatedArr = favorites.map(favorite => {
if (favorite.name === event.target.name) {
return {
...favorite,
value: event.target.value
};
} else {
return favorite;
}
});
let emptyFields = updatedArr.filter(favorite => {
return favorite.value.length === 0;
});
this.setState({
...this.state,
favorites: updatedArr,
emptyFields: emptyFields
});
};
createFavoriteInputs = () => {
const { favorites } = this.state;
return favorites.map(favorite => {
return (
<div key={favorite.name}>
<label>{favorite.name} :</label>
<input
value={favorite.value}
name={favorite.name}
onChange={this.handleOnChange}
/>
</div>
);
});
};
render() {
const { emptyFields } = this.state;
return (
<div>
{this.createFavoriteInputs()}
<button
disabled={!emptyFields || emptyFields.length > 0 ? true : false}
>
Submit
</button>
{!emptyFields ||
(emptyFields.length > 0 && (
<div>
The following fields are required:
<ul>
{this.state.emptyFields.map(field => {
return <li key={field.name}>{field.name}</li>;
})}
</ul>
</div>
))}
</div>
);
}
}
So now with the emptyFields state, we have a button that is disabled if there are any emptyFields.
handleOnChange() helps us navigate the right state-value to update in our array, creating a new array in our state whenever we make an update to one of the inputs on the form.

Related

(React) Changing True/False Value Based On Checkbox Value - Getting "TypeError: checked.map is not a function"

I am trying to change the 'checked' value to "true" or "false" based on user checkbox selection. I have the below code and I am getting TypeError: checked.map is not a function. I want the "name" key to always be the same for each object in the "checked" array, but the value for the checked property to be either true or false based on a checkbox the user clicks on. Does anyone see why I could be getting this error, and what I could do to make this work? Thanks.
const [checked, setChecked] = useState([
{ name: 'user1', checked: false },
{ name: 'user2', checked: false },
{ name: 'user3', checked: false }
]);
const handleChange = (name, id) => {
let header = id;
let updatedList = checked.map((item) => {
if (item.header === header) {
return { ...item, checked: !item.checked };
}
return item;
});
setChecked(...updatedList);
}
The main problem is that you spread the created updatedList array:
setChecked(...updatedList);
Your checked state is an array -> you should refresh it with an array. Because you spread it, then the setChecked messes up the state & you cannot map it anymore (thus .map() is not a function).
Try setChecked with a function & its argument as an array:
const {
useState
} = React
const App = () => {
const [checked, setChecked] = useState([{
name: 'user1',
checked: false
},
{
name: 'user2',
checked: false
},
{
name: 'user3',
checked: false
}
]);
const handleChange = ({ name, checked }) => {
setChecked((previousChecked) => {
return previousChecked.map(item => {
if (item.name === name) {
item.checked = checked
}
return item
})
})
}
return (
<div> {
checked.map(item => {
return (
<label>
{
item.name
}
<input type = "checkbox"
onChange = {() => handleChange({...item, checked: !item.checked})}
/>
<br />
</label>
)
})
}
{
// just so you see the change of state:
JSON.stringify(checked)
}
</div>
)
}
ReactDOM.render( <App /> , document.querySelector("#app"))
<script src="https://unpkg.com/react#16/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.production.min.js"></script>
<div id="app"></div>
TypeError: checked.map is not a function would mean "checked" is not array.
It is possible that somewhere along the way, the setChecked is called with non-array value. You should investigate, there might be an unintended setChecked happening.
In the meantime, a quick fix is to check if it's an array before calling .map
const handleChange = (name, id) => {
let header = id;
if (Array.isArray(checked)) {
let updatedList = checked.map((item) => {
if (item.header === header) {
return { ...item, checked: !item.checked };
}
return item;
});
setChecked(...updatedList);
} else {
console.log(`checked is not array? Let's see who's behind the mask`, checked);
}
}

Can't deselect initial values from Select component with options and values as an object

I can't unselect the default values I have set in state.
I'm pulling from an example in Grommet's codesandbox.
Only change thus far is adding an object array instead of an array of strings. See VALUE prop in docs mention object array..
const OPTIONS = [
{
label: "Expansion",
value: "1"
},
{
label: "Rollout",
value: "2"
}
];
export default class extends Component {
state = {
value: [
{
label: "Rollout",
value: "2"
}
],
options: OPTIONS
};
render() {
const { options, value } = this.state;
return (
<Select
multiple={true}
value={value}
labelKey="label"
valueKey="value"
onSearch={searchText => {
const regexp = new RegExp(searchText, "i");
this.setState({ options: OPTIONS.filter(o => o.match(regexp)) });
}}
onChange={event => {
console.log({ event });
this.setState({
value: event.value,
options: OPTIONS
});
}}
options={options}
/>
);
}
}
In the logs, I'm getting selected: [ -1, 1 ] when I attempt to unselect the Rollout option, and Rollout is still visually highlighted/selected in the view.
Here is your working code, You need to check if currently clicked value is already there in selection then remove it from values and if it is not in selected then add in values.
I changed onChange function as below.
Let me know if any issues.
import React, { Component } from "react";
import { Select } from "grommet";
import SandboxComponent from "./SandboxComponent";
const OPTIONS = [
{
label: "Expansion",
value: "1"
},
{
label: "Rollout",
value: "2"
}
];
export default class extends Component {
state = {
value: [
{
label: "Rollout",
value: "2"
}
],
options: OPTIONS
};
render() {
const { options, value } = this.state;
return (
<Select
multiple={true}
value={value}
labelKey="label"
valueKey="value"
selected={0}
onSearch={searchText => {
const regexp = new RegExp(searchText, "i");
this.setState({ options: OPTIONS.filter(o => o.match(regexp)) });
}}
onChange={event => {
let isExist = value.find(item => {
return item.value === event.option.value;
});
let newValue;
if (isExist) {
newValue = value.filter(item => {
return item.value !== event.option.value;
});
} else {
newValue = value;
newValue.push(event.option);
}
this.setState({
value: newValue
});
}}
options={options}
/>
);
}
}

Cannot set property 'indeterminate' of null in React

In my application, I need something like:
When a questions value is null then the checkbox should be shown as indeterminate, otherwise should be checked or not-checked.
But the problem is that when I update the questions, it shows me the error:
TypeError: Cannot set property 'indeterminate' of null
My questions object in state is like this:
questions: [{
id: 1,
title: 'First Question',
answers: [
{
id: 2,
title: 'Java',
value: ''
},
{
id: 3,
title: 'Python',
value: ''
},
{
id: 4,
title: '.NET',
value: true
}
]
}]
So it means that the third checkbox should be checked, and other two should be shown as indeterminate.
See picture below:
So when I click on the first one, it should become unchecked,and after clicking it again, its value should be true and should become checked. And their value will never be '' ever, except that it can be the first time.
Here's the question.jsx
import React, { Component } from 'react';
class Question extends Component {
state = {
questions: []
}
componentDidMount() {
const questions = [{
id: 1,
title: 'First Question',
answers: [
{
id: 2,
title: 'Java',
value: ''
},
{
id: 3,
title: 'Python',
value: ''
},
{
id: 4,
title: '.NET',
value: true
}
]
}, {
id: 2,
title: 'Second Question',
answers: [
{
id: 5,
title: 'MongoDB',
value: ''
},
{
id: 6,
title: 'MSSQL',
value: ''
},
{
id: 7,
title: 'MySQL',
value: ''
}
]
}, {
id: 3,
title: 'Third Question',
answers: [
{
id: 8,
title: 'ReactJs',
value: ''
},
{
id: 9,
title: 'Angular',
value: ''
},
{
id: 10,
title: 'VueJs',
value: ''
}
]
}]
this.setState({
questions
})
}
setIndeterminate = (elm, value) => {
if (value !== '') {
elm.checked = value;
elm.indeterminate = false;
}
else {
elm.checkbox = false;
elm.indeterminate = true;
}
}
handleOnChange = ({ currentTarget: checkbox }) => {
var questions = [...this.state.questions];
questions.map(p => {
p.answers.map(a => {
if (a.id == checkbox.id) {
a.value = (a.value === '') ? false : !a.value;
return;
}
})
})
this.setState({
questions
})
}
render() {
const { questions } = this.state
return (
<div>
{questions.map(question =>
<div key={question.id} className='question-wrapper'>
<div className="row">
<h6 className='text-left'>{question.title}</h6>
</div>
{question.answers.map((answer, i) =>
<div key={answer.id} className="form-group row">
<div className="form-check">
<input onChange={this.handleOnChange} ref={elm => this.setIndeterminate(elm, answer.value)} value={answer.value} className="form-check-input" type="checkbox" id={answer.id} name={answer.id} />
<label className="form-check-label" htmlFor={answer.id}>
{answer.title}
</label>
</div>
</div>
)}
</div>
)}
</div>
);
}
}
export default Question;
How is that possible of happening since as you can see I am already setting the value of intermediate to either true or false?
SOLUTION
I removed that setIndeterminate function, and did this inside ref in input element:
<input onChange={this.handleOnChange} ref={elm => {
if (elm) {
elm.checked = (answer.value !== '') ? answer.value : false;
elm.indeterminate = (answer.value === '') ? true : false;
}
}} value={answer.value} className="form-check-input" type="checkbox" id={answer.id} name={answer.id} />
I guess the problem whas that I needed to add that if (elm) to check that first.
I found this solution here (Thanks to ROBIN WIERUCH for this awesome article ) and works fine for me:
We want to extend the functionality of this checkbox for handling a tri state instead of a bi state. First, we need to transform our state from a boolean to an enum, because only this way we can create a tri state:
const CHECKBOX_STATES = {
Checked: 'Checked',
Indeterminate: 'Indeterminate',
Empty: 'Empty',
};
and now we can use it in pur component:
const Checkbox = ({ label, value, onChange }) => {
const checkboxRef = React.useRef();
React.useEffect(() => {
if (value === CHECKBOX_STATES.Checked) {
checkboxRef.current.checked = true;
checkboxRef.current.indeterminate = false;
} else if (value === CHECKBOX_STATES.Empty) {
checkboxRef.current.checked = false;
checkboxRef.current.indeterminate = false;
} else if (value === CHECKBOX_STATES.Indeterminate) {
checkboxRef.current.checked = false;
checkboxRef.current.indeterminate = true;
}
}, [value]);
return (
<label>
<input ref={checkboxRef} type="checkbox" onChange={onChange} />
{label}
</label>
);
};

ReactJS Render Table for each value on Array

I have a MultiSelect and a React Table..
the Select stores the values into value Array..
The way it is now i´m able to select ONE option and the table displays the data correctly. But, i´m looking to render a table for each selected option. How could i achieve something like this?
handleSelectChange (value) {
console.log('You\'ve selected:', value);
this.setState({ value: value }, () => this.fetchTable());
}
fetchTable() {
const url = 'http://localhost:8000/issues/from/';
const value = this.state.value;
const string = url+value;
fetch(string)
.then(function(response) {
return response.json();
})
.then((myJson) => this.setState({data: myJson.issues}));
}
componentDidMount() {
this.fetchData();
}
render() {
const filteredResult = this.state.boards.map(item => (
{
value: item.key,
label: item.name,
}
));
const filteredResult1 = this.state.data.map(item => (
{
name: item.fields,
id: item.id,
key: item.key,
}
));
return (
<div>
<Select
closeOnSelect={!stayOpen}
disabled={disabled}
multi
onChange={this.handleSelectChange}
options={filteredResult}
placeholder="Select Assignee(s)..."
removeSelected={this.state.removeSelected}
rtl={this.state.rtl}
simpleValue
value={value}
/>
<ResponseTable data={filteredResult1} />
</div>
);
}
}
How does your ResponseTable component look like? I guess you can just use the map function to loop and display the table rows. Sth like this:
const data = [{name: 'Test 1', id: 1, key: 'key_1'}, {name: 'Test 2', id: 2, key: 'key_2'}, {name: 'Test 3', id: 3, key: 'key_3'}];
_renderTableBody = () => {
return data.map((item) => (
return (
<TableRow>
<TableCell>item.name</TableCell>
<TableCell>item.id</TableCell>
<TableCell>item.key</TableCell>
</TableRow>
)
))
}
Then inside your render function, you can just replace this
<ResponseTable data={filteredResult1} />
into the code like this:
{this._renderTableHead()} // same method as _renderTableBody() to generate the table head title
{this._renderTableBody()}
Hope this can help!
Just keep some dummy key in state which as empty array initially. It will push the selected value of select option in to it. like below
constructor(props){
this.state = {
selectedValues: []
}
}
Alter your handleSelectChange like below. It needs to update the current selected value in this array
handleSelectChange (value) {
console.log('You\'ve selected:', value);
//this.setState({ value: value }, () => this.fetchTable());
let currentSelectedValue = this.state.selectedValues.filter(selected => selected == value)
//it will return a value if it is found otherwise empty array will be returned
if(currentSelectedValue.length == 0){
let updatedSelectedValue = this.state.selectedValues.push(value)
this.setState({ selectedValues: updatedSelectedValues }, () => this.fetchTable());
}
}
removeSelected (value) {
console.log('You\'ve selected:', value);
//this.setState({ value: value }, () => this.fetchTable());
let currentSelectedValue = this.state.selectedValues.filter(selected => selected !== value) //this will delete the removed option from array
this.setState({ selectedValues: currentSelectedValue }, () => this.fetchTable());
}
fetchTable() {
if( this.state.selectedValues.length > 0 ){
this.state.selectedValues.map((value)=>{
const url = 'http://localhost:8000/issues/from/';
const string = url+value;
fetch(string)
.then(function(response) {
return response.json();
})
.then((myJson) => this.setState({data: [...this.state.data, myJson.issues]})); //use spread operator to combine the result of each selectedValue
});
}
}
render() {
const filteredResult = this.state.boards.map(item => (
{
value: item.key,
label: item.name,
}
));
const filteredResult1 = this.state.data.map(item => (
{
name: item.fields,
id: item.id,
key: item.key,
}
));
return (
<div>
<Select
closeOnSelect={!stayOpen}
disabled={disabled}
multi
onChange={this.handleSelectChange}
options={filteredResult}
placeholder="Select Assignee(s)..."
removeSelected={this.state.removeSelected}
rtl={this.state.rtl}
simpleValue
value={value}
/>
this.state.data.map((item) => { // item here will hold the json object of { id: item.id, key: item.key, name: item.fields }
<ResponseTable data={item} />
})
</div>
);
}
}

How to update state in map function in reactjs

I am having 4 buttons each button have name id and selected boolean flag.
What I am trying to achieve is, on click of button, boolean button flag should be changed of that particular button. For this, I need to setState in map function for that particular button Id.
My issue is I am unable to setState in map function for that particular clicked button, its btnSelected should be changed
My aim is to create a multi-select deselect button.Its kind of interest selection for the user and based on that reflect the UI as well my array. Here is my code.
Thanks in anticipation.
import React, { Component } from "react";
import { Redirect } from "react-router-dom";
export default class Test extends Component {
constructor(props, context) {
super(props, context);
this.handleChange = this.handleChange.bind(this);
this.state = {
value: "",
numbers: [1, 2, 3, 4, 5],
posts: [
{
id: 1,
topic: "Animal",
btnSelected: false
},
{
id: 2,
topic: "Food",
btnSelected: false
},
{
id: 3,
topic: "Planet",
btnSelected: false
},
{ id: 4, topic: "Nature", btnSelected: false }
],
allInterest: []
};
}
handleChange(e) {
//console.log(e.target.value);
const name = e.target.name;
const value = e.target.value;
this.setState({ [name]: value });
}
getInterest(id) {
this.state.posts.map(post => {
if (id === post.id) {
//How to setState of post only btnSelected should change
}
});
console.log(this.state.allInterest);
if (this.state.allInterest.length > 0) {
console.log("Yes we exits");
} else {
console.log(id);
this.setState(
{
allInterest: this.state.allInterest.concat(id)
},
function() {
console.log(this.state);
}
);
}
}
render() {
return (
<div>
{this.state.posts.map((posts, index) => (
<li
key={"tab" + index}
class="btn btn-default"
onClick={() => this.getInterest(posts.id)}
>
{posts.topic}
<Glyphicon
glyph={posts.btnSelected === true ? "ok-sign" : "remove-circle"}
/>
</li>
))}
</div>
);
}
}
Here's how you do something like this:
class App extends Component {
state = {
posts: [{
name: 'cat',
selected: false,
}, {
name: 'dog',
selected: false
}]
}
handleClick = (e) => {
const { posts } = this.state;
const { id } = e.target;
posts[id].selected = !this.state.posts[id].selected
this.setState({ posts })
}
render() {
return (
<div>
<form>
{this.state.posts.map((p, i) => {
return (
<div>
<label>{p.name}</label>
<input type="radio" id={i} key={i} checked={p.selected} onClick={this.handleClick} />
</div>
)
})}
</form>
</div>
);
}
}
render(<App />, document.getElementById('root'));
Working example here.
You can do this by passing the index from the map into each button's handleClick function, which would then return another function that can be triggered by an onClick event.
In contrast to Colin Ricardo's answer, this approach avoids adding an id prop onto each child of the map function that is only used for determining the index in the handleClick. I've modified Colin's example here to show the comparison. Notice the event parameter is no longer necessary.
class App extends Component {
state = {
posts: [{
name: 'cat',
selected: false,
}, {
name: 'dog',
selected: false
}]
}
handleClick = (index) => () => {
const { posts } = this.state;
posts[index].selected = !this.state.posts[index].selected
this.setState({ posts })
}
render() {
return (
<div>
<form>
{this.state.posts.map((p, i) => {
return (
<div>
<label>{p.name}</label>
<input type="checkbox" key={i} checked={p.selected} onClick={this.handleClick(i)} />
</div>
)
})}
</form>
</div>
);
}
}
render(<App />, document.getElementById('root'));
Working example here

Resources