Update property of certain object inside array in state - arrays

I am trying to update my code: https://repl.it/#colegonzales1/HeftyJoyfulAbilities
I am trying to make my handleToggle function work properly. It is blank now for the sake of tinkering, but I have spent the last 2-3 hours trying to make it work to my own knowledge but I cannot figure out how to access a specific item in state. I know how to overwrite it all, but that is not what I want to do. Say I have:
this.state = {
todos: [
{
title: 'Wash car',
id: 1,
done: false
},
{
title: 'Go shopping',
id: 2,
done: false
}
],
inputValue: ''
}
How can I ONLY change the value of done on the 'Go shopping' todo, to true?

Use an array.map to toggle the done flag only on the element which matches the clicked id as follows. The other properties of the todo are copied with an object spread:
handleToggle (e) {
const id = parseInt(e.target.id,10)
this.setState((prevState) => ({
todos: prevState.todos.map(t => t.id === id ? {...t, done: !t.done} : t)
}))
}

You can find the index of the object with the given id with findIndex and create a new array with a copy of this object in it with its done flag toggled.
Example
class App extends React.Component {
state = {
todos: [
{
title: "Wash car",
id: 1,
done: false
},
{
title: "Go shopping",
id: 2,
done: false
}
],
inputValue: ""
};
handleToggle = id => {
this.setState(prevState => {
const todos = [...prevState.todos];
const todoIndex = todos.findIndex(todo => todo.id === id);
todos[todoIndex] = { ...todos[todoIndex], done: !todos[todoIndex].done };
return { todos };
});
};
render() {
return (
<div>
<div>{JSON.stringify(this.state)}</div>
<button onClick={() => this.handleToggle(2)}>
Toggle todo with id 2
</button>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>

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

How to add values to Array using map in React

I have the following Array
arrayOfItems: [{
0:
description: "item1"
id: 11
name: "item1Name"
},
1:
description: "item2"
id: 12
name: "item2Name"
},
2:
description: "item3"
id: 13
name: "item3Name"
},
3:
description: "item4"
id: 14
name: "item4Name"
}]
I want to add a new pair
{
description: "item5"
id: 15
name: "item5Name"
}
I am still very new to React and have been working on this problem. I do understand how Map works but not sure how I can add new pair in React
This component is a dropdown list so there is no input or button click related to it.
{dataArray.arrayOfItems!.map((item: any) => {
return (
<ComponentName key={item.id} value={item.description}>
{item.description}
</ComponentName>
);
})}
if you want to add item to array on page load use componentDidMount() method:
class List extends React.Component {
constructor(props) {
super(props);
this.state = {
items:[
{id:1,name:'aaa', description:'this is description aaa'},
{id:2,name:'bbb', description:'this is description bbb'},
]
}
}
componentDidMount(){
let items=this.state.items;
let newItem={id:5,name:'ccc',description:'this is description ccc'};
let updatedItems=items.push(newItem);
// or you can use ... spread operator
// let updatedItems=[...items,newItem];
this.setState({items:updatedItems});
}
}
You can store your array into state, and then modify the state.
Here's an example
function MyComponent() {
const [items, setItems] = React.useState([{ id: 0, description: 'Old Item' }])
const loadMoreItems = () => {
setItems([...items, { id: 1, description: 'New Item' }])
}
return (
<>
{items.map((item) => (
<div key={item.id} value={item.description}>
<p>{item.description}</p>
</div>
))}
<button onClick={loadMoreItems}>Load more items</button>
</>
)
}
Add on change event to your dropdown.
onChange = (event) => {
console.log(event.target.value)
// add your value to array here
this.setState((prevState) => {
arrayOfItems: [...prevState.arrayOfItems, yourItem],
})
}
<select onChange={this.onChange}>
</select>
EDIT
Adding values on page load. Don't use push to add items to array in state.
componentDidMount = () => {
this.setState((prevState) => {
arrayOfItems: [...prevState.arrayOfItems, yourItem],
})
}
let fileInfos=this.state.fileInfos;
fileInfos.push({
"name": file.name,
"content": e.target.result
});
this.setState({fileInfos});

React API mapping issue

I'm trying to make a simple fetch request and trying to map through the response to display on the page.
The API results correctly display when I console.log(data) but once I add the mapping function they display as undefined. I have tried both data.map and data.results.map
I couldn't find a working solution in any other thread!
componentDidMount() {
fetch("http://private-cc77e-aff.apiary-mock.com/posts")
.then(results => results.json())
.then(data => {
let posts = data.results.map(post => {
console.log(posts);
});
});
}
Any help would be appreciated!
Not sure what is the structure of your data but part of your problem is that you are trying to log posts before .map has finish populating it.
What you could do is storing the data received from the API in state (use map if you need to normalize the data) and then in render reference it using this.state.posts.
Don't forget to start with an initial value of an empty array, and you can conditionally render the posts or a loader based on the length of the array.
Here is a small example with code similar to your use case:
const Joke = ({joke}) => <div style={{border: '1px solid #ccc', padding: '15px'}}>{joke}</div>
class App extends React.Component {
// initial value (array)
state = { jokes: [] }
componentDidMount() {
// get data when component monuted
fetch("https://api.icndb.com/jokes/random/10")
.then(data => data.json())
.then(result => {
// normalize data with map
const jokes = result.value.map(obj => obj.joke);
// update the state for next render
this.setState({jokes})
})
}
render() {
const { jokes } = this.state;
return (
<div>
{
// conditionally render the array or loading (if data isn;t ready yet)
jokes.length
? jokes.map(joke => <Joke joke={joke} />)
: "Loading..."
}
</div>
);
}
}
const root = document.getElementById("root");
ReactDOM.render(<App />, root);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root" />
We are not quite sure how is your API response but you are doing the log operation somehow wrong. You are assigning the posts to a variable, but you re trying to use it inside the .map method.
I assume that your API response is something like that:
{
results: [
{ id: 1, name: "foo" },
{ id: 2, name: "bar" },
{ id: 3, name: "baz" },
],
};
This is data and since you are assigning data.results as posts this is why I assume a response like that. I'm providing the options you can try. Here, I am mimicking the API request with a function, so do not bother with this part, please.
Just log the whole results array.
const data = {
results: [
{ id: 1, name: "foo" },
{ id: 2, name: "bar" },
{ id: 3, name: "baz" },
],
};
const fakeRequest = () =>
new Promise(resolve => setTimeout(() => resolve(data), 1000));
class App extends React.Component {
componentDidMount() {
fakeRequest()
.then( data => {
console.log( data.results );
})
}
render() {
return <div>Look the console. We are logging the whole array.</div>;
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
You can map the array and log each item one by one
Since we just want to log the items, we don't need to use map here, forEach is enough.
const data = {
results: [
{ id: 1, name: "foo" },
{ id: 2, name: "bar" },
{ id: 3, name: "baz" },
],
};
const fakeRequest = () =>
new Promise(resolve => setTimeout(() => resolve(data), 1000));
class App extends React.Component {
componentDidMount() {
fakeRequest()
.then( data => {
data.results.forEach( post => console.log( post) )
// or as a shorthand
// data.results.forEach(console.log)
})
}
render() {
return <div>Look the console. We are logging each post one by one.</div>;
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
Set your state with the results and map it in your render method
This is most of the time you want to do. Just logging is not enough as we know.
const data = {
results: [
{ id: 1, name: "foo" },
{ id: 2, name: "bar" },
{ id: 3, name: "baz" },
],
};
const fakeRequest = () =>
new Promise(resolve => setTimeout(() => resolve(data), 1000));
class App extends React.Component {
state = {
posts: [],
}
componentDidMount() {
fakeRequest()
.then( data => {
this.setState( { posts: data.results})
})
}
render() {
if ( !this.state.posts.length ) {
return <p>No posts yet</p>
}
return (
<div>
{
this.state.posts.map( post =>
<div key={post.id}>
<p>{post.id}</p>
<p>{post.name}</p>
</div>
)
}
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></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

How to add an object in an array of objects? ReactJS?

My Project is, an array of objects where i get only the names and render on screen of form 3 in 3, with button next and previous change the names, and can to filter for letters.
I would want add a new value, typped on input and clicked in the button add.
My code button:
addItem = () => {
const inValue = {
id: 0,
name: this.state.input
}
this.setState({
filtered: this.state.filtered.concat(inValue),
currentPage: 0
})
}
I would want the value inserted in the filtered array.
My code all:
import React, { Component } from 'react';
class App extends Component {
constructor() {
super();
const peoples =[{id:0, name:"Jean"},
{id:1, name:"Jaha"},
{id:2, name:"Rido"},
{id:3, name:"Ja"},
{id:4, name:"Letia"},
{id:5, name:"Di"},
{id:6, name:"Dane"},
{id:7, name:"Tamy"},
{id:8, name:"Tass"},
{id:9, name:"Ts"},
{id:10, name:"Abu"},
{id:11, name:"Ab"}];
this.state = {
elementsPerPage:3,
currentPage:0,
peoples,
input: "",
filtered: peoples,
teste: '',
};
}
getValueInput = (evt) => {
const inputValue = evt.target.value;
this.setState({ input: inputValue });
this.filterNames(inputValue);
}
filterNames = (inputValue)=> {
const { peoples } = this.state;
this.setState({
filtered: peoples.filter(item =>
item.name.includes(inputValue)),
currentPage:0
});
const Oi = this.state.filtered.map(item=>item.name);
if(Oi.length<=0){
alert('Você está adicionando um nome')
}
console.log(Oi)
}
elementsOnScreen = () => {
const {elementsPerPage, currentPage, filtered} = this.state;
return filtered
.map((item) => <li key={item.id}> {item.name} <button onClick={() => this.remove(item.name)}> Delete </button> </li>)
.slice(currentPage*elementsPerPage, currentPage*elementsPerPage + elementsPerPage);
if(this.state.filtered.length < 1){
this.setState({currentPage: this.state.currentPage - 1})
}
}
remove = (id) => {
console.log(this.state.filtered.length)
if(this.state.filtered.length < 0){
this.setState({currentPange: this.state.currenPage - 1})
}
this.setState({filtered: this.state.filtered.filter(item => item.name !== id) })
}
nextPage = () => {
console.log(this.state.filtered)
const {elementsPerPage, currentPage, filtered} = this.state;
if ((currentPage+1) * elementsPerPage < filtered.length){
this.setState({ currentPage: this.state.currentPage + 1 });
}
}
previousPage = () => {
const { currentPage } = this.state;
if(currentPage - 1 >= 0){
this.setState({ currentPage: this.state.currentPage - 1 });
}
}
addItem = () =>{
const inValue = {id:0 ,name: this.state.input}
this.setState({filtered: this.state.filtered.concat(inValue), currentPage: 0})
}
render() {
return (
<div>
<button onClick={this.addItem}> Add </button>
<input type="text" onChange={ this.getValueInput }></input>
<button onClick={this.previousPage}> Previous </button>
<button onClick={this.nextPage}> Next </button>
<h3>Current Page: {this.state.currentPage}</h3>
<ul>Names: {this.elementsOnScreen()}</ul>
</div>
);
}
}
export default App;
You would have the array of objects contained within your state, then use setState
this.state = {
elementsPerPage:3,
currentPage:0,
peoples,
input: "",
filtered: peoples,
teste: '',
peoples: [
{id:0, name:"Jean"},
{id:1, name:"Jaha"},
{id:2, name:"Rido"},
{id:3, name:"Ja"},
{id:4, name:"Letia"},
{id:5, name:"Di"},
{id:6, name:"Dane"},
{id:7, name:"Tamy"},
{id:8, name:"Tass"},
{id:9, name:"Ts"},
{id:10, name:"Abu"},
{id:11, name:"Ab"}];
};
To update the peoples array, you would first need to create a copy of the peoples array, modify the copy, then use setState to update.
let { peoples } = this.state;
peoples.push({ id:12, name:"Jean"})
this.setState({peoples: peoples})
Looks like you are already updating your state with the typed input.
So in your add button you can get the state value and push it to your people array. Something like this:
addItem = () => {
const { inputValue, people } = this.state;
if (!inputValue) return; // check if inputValue has any value
people.push({ id: people.length+1, name: inputValue )} // I don't recommend using sequencial ids like this, you'd better have a handler to generate it for you
this.setState({ people, inputValue: '' }); // update people and clear input
}
Hope it helps

Resources