Splice() deletes wrong element in array - javascript

My problem is when I'm deleting inputs that added dynamically it delete's wrong input. I reproduced my code in jsfiddle https://jsfiddle.net/armakarma/qwg3j2fa/24/ . Try to add five more inputs, type something in each input and try to delete second input. It will delete last one. Where I'm doing mistake?
addNewInputs() {
let newInputValues = {
datetime: "10.05.2019 14:00",
position_id: 1,
contact: "",
address_id: "",
new_address: "",
}
this.setState(prevState => ({
arrayOfAddresses: [...prevState.arrayOfAddresses, newInputValues],
}))
}
deleteInput(idx) {
let tempObj = this.state.arrayOfAddresses
tempObj.splice(idx, 1)
this.setState(prevState => ({
arrayOfAddresses: tempObj,
}))
}
onChooseAddress(e, idx) {
console.log(e.target.value)
}
render() {
return ( <
div > {
this.state.arrayOfAddresses.map((item, idx) => {
return (
<div key = {idx} >
<input name = "contact"
onChange = {(e) => this.onChooseAddress(e)}
/>
<button onClick = {() => this.deleteInput(idx)} > x < /button>
</div>
)
})
}
<button onClick = {() => this.addNewInputs()} > Add new input < /button>
/div>
)
}
}

The problem is with the chooseAddress method, you're not passing the index from the onChange callback, that's why the state is not updating, and also you have not added value prop to the input, that's why rendering was wrong, because of input's internal state
class TodoApp extends React.Component {
constructor(props) {
super(props)
this.state = {
adresses:[
{
"id": 1,
"address": "address 1",
},
{
"id": 2,
"address": "address 2",
},
{
"id": 3,
"address": "address 3",
},
{
"id": 4,
"address": "address 4",
}
],
arrayOfAddresses: [
{
datetime: "10.05.2019 14:00",
position_id: 1,
contact: "",
address_id: "",
new_address: "",
},
],
}
}
addNewInputs() {
let newInputValues = {
datetime: "10.05.2019 14:00",
position_id: 1,
contact: "",
address_id: "",
new_address:"",
}
this.setState(prevState => ({
arrayOfAddresses: [...prevState.arrayOfAddresses, newInputValues],
}))
}
deleteInput(idx) {
this.setState(prevState => {
let tempObj = [...prevState.arrayOfAddresses]
tempObj.splice(idx, 1)
console.log(tempObj)
return {
arrayOfAddresses: tempObj,
}
})
}
onChooseAddress(e, idx) {
const {value} = e.target;
this.setState(state=>{
let tempObj = [...this.state.arrayOfAddresses]
tempObj[idx].new_address = value
return {
arrayOfAddresses: tempObj,
}
})
}
render() {
return (
<div>
{this.state.arrayOfAddresses.map((item,idx)=>
<div>
<input
name="contact"
value={item.new_address}
onChange={(e) => this.onChooseAddress(e, idx)}
/>
<button onClick={() => this.deleteInput(idx)}> x</button>
</div>
)}
<button onClick={() => this.addNewInputs()}> Add new input </button>
</div>
)
}
}
ReactDOM.render(<TodoApp />, document.querySelector("#app"))

There are two things you need to change:
Set the value of <input>. The problem is that the arrayOfAddresses is set correctly, but correct values are not reflected in the input.
Add the corresponding idx value to the onChange of <input>
Here's the relevant code change:
<input name="contact" value={item.new_address} onChange={(e) => this.onChooseAddress(e, idx)}
/>
Here's the fiddle:
JSFiddle

Related

setState for multiple values in objects list

I have problem and I don't know how to fix it.
So i have component in which I've declared an array of objects.
I want to set its state separately but I don't want to declare multiple useStates.
I have an array of objects which look like this:
const [card, setCard] = useState({
name: "",
questions: [
{
question: "",
answer: "",
},
{
question: "",
answer: "",
},
{
question: "",
answer: "",
},
{
question: "",
answer: "",
},
{
question: "",
answer: "",
},
{
question: "",
answer: "",
},
{
question: "",
answer: "",
},
{
question: "",
answer: "",
},
{
question: "",
answer: "",
},
],
});
and here's component:
const NewCard = () => {
const handleNameChange = (event) => {
setCard({ name: event.target.value, ...questions });
};
return (
<div className="newcard-container">
<div className="card-container">
<h3>Podaj nazwe fiszki</h3>
<input type="text" value={card.name} />
</div>
<div className="questions-container">
{card.questions.map((q) => {
return (
<div className="question">
<h4>Podaj pytanie </h4>
<input type="text" value={q.question} />
<h4>Podaj odpowiedź</h4>
<input type="text" value={q.answer} />
</div>
);
})}
<button>Dodaj pytanie</button>
</div>
</div>
);
};
I've tried to figure out how to change the setState to get that approach but I didn't made it. Any ideas how can I get that?
Again, not sure if this is what you needed so let me know.
import React, { useState, useCallback } from 'react';
export function App() {
const [card, setCard] = useState({
name: "",
questions: [
{
id: 'question-1',
question: "Question 1",
answer: "",
},
{
id: 'question-2',
question: "Question 2",
answer: "",
},
{
id: 'question-3',
question: "Question 3",
answer: "",
},
]
});
const handleCardNameChange = useCallback((ev) => {
setCard((c) => ({ ...c, name: ev.target.value }))
}, [setCard]);
const handleAnswerChange = useCallback((cardId, value) => {
const updatedQuestions = card.questions.map((c) => {
if (c.id !== cardId) {
return c;
}
return {
...c,
answer: value,
}
});
setCard({
...card,
questions: updatedQuestions,
})
}, [card, setCard]);
return (
<div>
<input placeholder="Card Title" value={card.name} onChange={handleCardNameChange} />
{card.questions.map((c) => (
<div key={c.id}>
<p>Q: {c.question}</p>
<input placeholder="Answer" value={c.answer} onChange={(ev) => handleAnswerChange(c.id, ev.target.value)} />
</div>
))}
</div>
);
}
This handles answer change per question and card title change separately. I wrote this in a some weird editor online so it might not be perfect but it should work.
it should be
setCard((card) => { ...card , name: event.target.value });
You have a few approaches to do this.
const [ card, setCard ] = useState( {
name: "",
questions: {
1: {
statement: "",
answer: "",
},
2: {
statement: "",
answer: "",
},
//...
}
} );
// To set an especifique answer or question, you can set the state like this:
setCard( prev => ( {
...prev,
questions: {
...prev.questions,
1: {
...prev.questions[ 1 ],
answer: "New answer"
}
}
} ) );
// To add a new question, you can set the state like this:
setCard( prev => ( {
...prev,
questions: {
...prev.questions,
[ Object.keys( prev.questions ).length + 1 ]: {
statement: "",
answer: "",
}
}
} ) );
// To remove a question, you can set the state like this:
setCard( prev => {
const questions = { ...prev.questions };
delete questions[ 1 ];
return {
...prev,
questions
};
} );
But if you wanna use with array, you can do like this:
// Solution with array
const [card, setCard] = useState({
name: "",
questions: [
{
question: "",
answer: "",
},
{
question: "",
answer: "",
},
//...
],
} );
// To set an especifique answer or question, you will need the index of the question, or null to set a new question.
const setCardQuestions = ( index, question, answer ) => {
setCard( ( prev ) => {
const questions = [...prev.questions];
if ( index === null ) {
questions.push( {
question,
answer,
} );
} else {
questions[ index ] = {
question,
answer,
};
}
return {
...prev,
questions,
};
});
};
// To remove a question, you will need the index of the question.
const removeCardQuestion = ( index ) => {
setCard( ( prev ) => {
const questions = [...prev.questions];
questions.splice( index, 1 );
return {
...prev,
questions,
};
});
}

Modifying a value in a nested object [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 2 years ago.
Improve this question
I have a nested object:
{
id: "id",
name: "Name",
type: "SC",
allgemein: {
charname: "Name",
spieler: "Jon",
},
eigenschaften: {
lebenspunkte: "30",
},
talente: {},
zauber: {},
}
With my form I'm trying to create a new object. Most of it works, but in the function handleSubmit, I'm trying to set the nested spieler to "TEST".
import React from "react";
import { TextField, Button } from "#material-ui/core/";
export default class extends React.Component {
state = this.getInitState();
getInitState() {
const { charakterID } = this.props;
return charakterID
? charakterID
: {
name: "",
allgemein: {
charname: "",
spieler: "",
},
eigenschaften: {},
talente: {},
zauber: {},
};
}
componentWillReceiveProps({ charakterID }) {
this.setState({
...charakterID,
});
}
handleChange = (n) => ({ target: { value } }) => {
this.setState({
[n]: value,
});
};
handleChangeAllg = (n) => ({ target: { value } }) => {
this.setState((prevState) => ({
...prevState,
allgemein: {
...prevState.allgemein,
charname: value,
},
}));
};
handleSubmit = () => {
this.props.onSubmit({
id: this.state.name.toLocaleLowerCase().replace(/ /g, "-"),
type: "SC",
allgemein: {spieler: "TEST"},
...this.state,
});
this.setState(this.getInitState());
};
render() {
const {
name,
allgemein: { charname },
} = this.state,
{ charakterID } = this.props;
console.log("fired");
console.log(this.props.onCreate);
return (
<form>
<TextField
label="name"
value={name}
onChange={this.handleChange("name")}
margin="dense"
fullWidth
/>
<br />
<TextField
label="charname"
value={charname}
onChange={this.handleChangeAllg("charname")}
margin="dense"
fullWidth
/>
<br />
<Button color="primary" variant="contained" onClick={this.handleSubmit}>
{charakterID ? "Edit" : "Neu"}
</Button>
</form>
);
}
}
It wont work and I don't know why. Can you help me?
Try if this works
handleSubmit = () => {
this.props.onSubmit({
...this.state, // Changed Position
id: this.state.name.toLocaleLowerCase().replace(/ /g, "-"),
type: "SC",
allgemein: {...this.state.allgemein,spieler: "TEST"},
});
this.setState(this.getInitState());
};

Adding clicked items to new array in React

I am making API calls and rendering different components within an object. One of those is illustrated below:
class Bases extends Component {
constructor() {
super();
this.state = {
'basesObject': {}
}
}
componentDidMount() {
this.getBases();
}
getBases() {
fetch('http://localhost:4000/cupcakes/bases')
.then(results => results.json())
.then(results => this.setState({'basesObject': results}))
}
render() {
let {basesObject} = this.state;
let {bases} = basesObject;
console.log(bases);
//FALSY values: undefined, null, NaN, 0, false, ""
return (
<div>
{bases && bases.map(item =>
<button key={item.key} className="boxes">
{/* <p>{item.key}</p> */}
<p>{item.name}</p>
<p>${item.price}.00</p>
{/* <p>{item.ingredients}</p> */}
</button>
)}
</div>
)
}
}
The above renders a set of buttons. All my components look basically the same.
I render my components here:
class App extends Component {
state = {
ordersArray: []
}
render() {
return (
<div>
<h1>Bases</h1>
<Bases />
<h1>Frostings</h1>
<Frostings />
<h1>Toppings</h1>
<Toppings />
</div>
);
}
}
I need to figure out the simplest way to, when a button is clicked by the user, add the key of each clicked element to a new array and I am not sure where to start. The user must select one of each, but is allowed to select as many toppings as they want.
Try this
We can use the same component for all categories. All the data is handled by the parent (stateless component).
function Buttons({ list, handleClick }) {
return (
<div>
{list.map(({ key, name, price, isSelected }) => (
<button
className={isSelected ? "active" : ""}
key={key}
onClick={() => handleClick(key)}
>
<span>{name}</span>
<span>${price}</span>
</button>
))}
</div>
);
}
Fetch data in App component, pass the data and handleClick method into Buttons.
class App extends Component {
state = {
basesArray: [],
toppingsArray: []
};
componentDidMount() {
// Get bases and toppings list, and add isSelected attribute with default value false
this.setState({
basesArray: [
{ key: "bases1", name: "bases1", price: 1, isSelected: false },
{ key: "bases2", name: "bases2", price: 2, isSelected: false },
{ key: "bases3", name: "bases3", price: 3, isSelected: false }
],
toppingsArray: [
{ key: "topping1", name: "topping1", price: 1, isSelected: false },
{ key: "topping2", name: "topping2", price: 2, isSelected: false },
{ key: "topping3", name: "topping3", price: 3, isSelected: false }
]
});
}
// for single selected category
handleSingleSelected = type => key => {
this.setState(state => ({
[type]: state[type].map(item => ({
...item,
isSelected: item.key === key
}))
}));
};
// for multiple selected category
handleMultiSelected = type => key => {
this.setState(state => ({
[type]: state[type].map(item => {
if (item.key === key) {
return {
...item,
isSelected: !item.isSelected
};
}
return item;
})
}));
};
// get final selected item
handleSubmit = () => {
const { basesArray, toppingsArray } = this.state;
const selectedBases = basesArray.filter(({ isSelected }) => isSelected);
const selectedToppings = toppingsArray.filter(({ isSelected }) => isSelected);
// submit the result here
}
render() {
const { basesArray, toppingsArray } = this.state;
return (
<div>
<h1>Bases</h1>
<Buttons
list={basesArray}
handleClick={this.handleSingleSelected("basesArray")}
/>
<h1>Toppings</h1>
<Buttons
list={toppingsArray}
handleClick={this.handleMultiSelected("toppingsArray")}
/>
</div>
);
}
}
export default App;
CSS
button {
margin: 5px;
}
button.active {
background: lightblue;
}
I think the following example would be a good start for your case.
Define a handleClick function where you can set state with setState as the following:
handleClick(item) {
this.setState(prevState => {
return {
...prevState,
clickedItems: [...prevState.clickedItems, item.key]
};
});
}
Create an array called clickedItems in constructor for state and bind handleClick:
constructor() {
super();
this.state = {
basesObject: {},
clickedItems: [],
}
this.handleClick = this.handleClick.bind(this);
}
You need to add a onClick={() => handleClick(item)} handler for onClick:
<button key={item.key} className="boxes" onClick={() => handleClick(item)}>
{/* <p>{item.key}</p> */}
<p>{item.name}</p>
<p>${item.price}.00</p>
{/* <p>{item.ingredients}</p> */}
</button>
I hope that helps!

Reactjs: how to update vote counts in reactjs

The code below was designed to update a voting system. It works fine by displaying the results as the page loads.
Here is my problem: I need to update each user's vote any time the Get Vote Count button is clicked.
In the backend, I have php code which returns the array data as per below.
Can someone help me with displaying the array values and updating eg (vote to 11) depending on how the user voted?
<?php
// Update user response on a post
$return_arr[]= array("vote"=>"11");
echo json_encode($return_arr);
exit;
?>
Here is the array return by axios API Call
[{"vote":"11"}]
Here is the code
import React, { Component, Fragment } from "react";
import { render } from "react-dom";
import axios from 'axios';
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [],
loading: false
};
}
componentDidMount() {
this.setState({
data: [
{ id: "1", name: "Tony", vote: "3" },
{ id: "2", name: "Mark", vote: "6" },
{ id: "3", name: "Joy", vote: "2" }
]
});
}
handleVote(person_id, person_vote) {
const data_vote = {
person_id: person_id,
person_vote: person_vote
};
axios
.get("http://localhost/vote.php", { data_vote })
.then(response => {
this.setState({ result_vote: response.data });
console.log(this.state.result_vote);
})
.catch(error => {
console.log(error);
});
}
render() {
return (
<span>
<label>
<ul>
{this.state.data.map((person, i) => (
<li key={i}>
{person.name} --(vote count: {person.vote})
<br />
<input
type="button"
value="Get Vote Counts"
onClick={() => this.handleVote(person.id, person.vote)}
/>
</li>
))}
</ul>
</label>
</span>
);
}
}
You should set your data state after getting the vote data from the fetch response. You have person_id in your handler and getting an array including vote value. So, map through your data state find the relevant person and update its vote value.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [],
loading: false
};
}
componentDidMount() {
this.setState({
data: [
{ id: "1", name: "Tony", vote: "3" },
{ id: "2", name: "Mark", vote: "6" },
{ id: "3", name: "Joy", vote: "2" }
]
});
}
handleVote(person_id, person_vote) {
const data_vote = {
person_id: person_id,
person_vote: person_vote
};
axios
.get("http://localhost/vote.php", { data_vote })
.then(response => {
const newData = this.state.data.map(person => {
if (person.id !== person_id) return person;
return { ...person, vote: response.data[0].vote };
});
this.setState(state => ({
data: newData
}));
})
.catch(error => {
console.log(error);
});
}
render() {
return (
<span>
<label>
<ul>
{this.state.data.map(person => (
<li key={person.id}>
{person.name} --(vote count: {person.vote})
<br />
<input
type="button"
value="Get Vote Counts"
onClick={() => this.handleVote(person.id, person.vote)}
/>
</li>
))}
</ul>
</label>
</span>
);
}
}
Try to avoid using an index as a key. You have a person.id so use it in your map method. Also, as an enhancement, you can refactor your code and create a Person component. You can pass the related data and vote handler then setup the update logic there.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [],
loading: false,
};
}
componentDidMount() {
this.setState({
data: [
{ id: "1", name: "Tony", vote: "3" },
{ id: "2", name: "Mark", vote: "6" },
{ id: "3", name: "Joy", vote: "2" },
],
});
}
handleVote = (person) => {
const data_vote = {
person_id: person.id,
person_vote: person.vote,
};
axios
.get("http://localhost/vote.php", { data_vote })
.then((response) => {
const newData = this.state.data.map((el) => {
if (el.id !== person.id) return el;
return { ...el, vote: response.data[0].vote };
});
this.setState({ data: newData });
})
.catch((error) => {
console.log(error);
});
};
render() {
return (
<span>
<label>
<ul>
{this.state.data.map(person => (
<Person
key={person.id}
person={person}
handleVote={this.handleVote}
/>
))}
</ul>
</label>
</span>
);
}
}
const Person = (props) => {
const { person, handleVote } = props;
const onVote = () => handleVote(person);
return (
<li>
{person.name} --(vote count: {person.vote})
<br />
<input type="button" value="Get Vote Counts" onClick={onVote} />
</li>
);
};
So, since your handler function is getting the person_id and your call is returning the new vote count, you should update the current person object in your data table in state.
Here is an example:
Updating the vote count for the current user

select menu and input value changing same state value

I have a small problem with my dynamic form. In the code below the render method I have code that maps an input and a dropdown select menu to fill in state.questions[{title: "", type: ""}]. You can see the addQuestionsHandler method to add more questions and the questionsInputHandler to handle the questions values.
The surveyInputHandler method handles the static questions in the return function.
The problem I'm having is that in my code for the dynamic questions the input value and the select dropdown value are ending ending up the same in state.questions[{title: "", type: ""}]. If I input "Test" - both title and type will be "Test". If I input "Test" and select value = "Radio Button" - both title and type will be "Radio Button". If I don't select a dropdown option value, then both will be the input value. If I do select a dropdown option value then the input value will be overridden by the dropdown select value.
I've racked my brain for a while but I need more eyes on it. Can you please let me know what I'm not doing correctly? Thanks so much.
const questionTypes = [
"Select Question Type",
"Textbox",
"Radio Button",
"Checkbox"
];
class SurveyQuestions extends Component {
constructor(props) {
super(props);
this.state = {
title: "",
description: "",
pointsValue: 0,
questions: [
{
title: "",
type: ""
}
]
};
}
surveyInputHandler = e => {
console.log(e.target.value);
this.setState({
[e.target.name]: e.target.value,
[e.target.title]: e.target.value,
[e.target.description]: e.target.value,
[e.target.pointsValue]: e.target.value
});
};
questionsInputHandler = idx => e => {
console.log(e.target.value);
const newQuestions = this.state.questions.map((question, qidx) => {
if (idx !== qidx) return question;
return {
...question,
title: e.target.value,
type: e.target.value
};
});
this.setState({
questions: newQuestions
});
};
addQuestionHandler = () => {
this.setState(prevState => ({
questions: [...prevState.questions, { title: "", type: "" }]
}));
};
submitHandler = e => {
const { title, description, pointsValue, questions } = this.state;
console.log(
title,
description,
pointsValue,
questions.map(question => ({ ...question }))
);
this.setState({
title: "",
description: "",
pointsValue: "",
questions: [{ title: "", type: "" }]
});
e.preventDefault();
};
render() {
const { title, description, pointsValue, questions } = this.state;
const questionsDisplay = questions.map((question, idx) => (
<div key={idx} className="SurveyQuestions__QuestionContainer">
<h5>Question {idx + 1}</h5>
<label htmlFor="questionTitle">Question Title</label>
<input
type="text"
id="questionTitle"
placeholder={`Question Title #${idx + 1}`}
value={question.title}
onChange={this.questionsInputHandler(idx)}
/>
<label htmlFor="questionType">Type</label>
<select
className="SurveyQuestions__QuestionTypesDropdown"
value={question.type}
onChange={this.questionsInputHandler(idx)}
>
{questionTypes.map((type, tidx) => (
<option key={tidx} id={`${type}-${tidx}`} value={type}>
{type}
</option>
))}
</select>
</div>
));
Solved:
So the simple solution was to create a separate input handler for the select dropdown menu. The code is below:
questionTypeInputHandler = idx => e => {
const newQuestions = this.state.questions.map((question, qidx) => {
if (idx !== qidx) return question;
return {
...question,
type: e.target.value
};
});
this.setState({
questions: newQuestions
});
};

Categories

Resources