I am rendering in my React application an Array and for each Array I have an input element.
This elements gets as name the ID from the Array entryand a value can be entered.
I have a handleChange() functions, which gets the ID and the input value.
This data should be added to an Array, which I want to save to my State.
Later on, I have for each Array entry a Submit button, where I have to send the input value to an API endpoint.
constructor(props: Props) {
super(props);
this.state = {
sendTransferObj: []
};
this.handleChange = this.handleChange.bind(this);
}
...
handleChange(transId: string, event: React.ChangeEvent<HTMLInputElement>) {
console.log(transId, event.target.value); // returns => '123', '1'
let inputObject = [{ id: transId, value: event.target.value }]
let arrayForState = [];
const index = inputObject.findIndex(inputObject => inputObject.id != transId)
if (index === -1) {
arrayForState.push(inputObject);
} else {
console.log("object already exists")
}
console.log(arrayForState)
this.setState({
...this.state,
sendTransferObj: arrayForState
}); // error!
}
...
<Form.Control as="input" name={trans.id} onChange={this.handleChange.bind(this, trans.id)} />
My problem is currently in my handleChange(), where the input value gets saved to my arrayForObject for one input, but if I enter in a second input element a value, it gets overwritten.
Also I get various errors, when I later want to do a setState() for save this Array.
This is my whole component which I am using for reference (getting the Array that I render from my state with Redux): https://codesandbox.io/s/practical-ptolemy-69zbu?fontsize=14&theme=dark
You need to append the inputObject to the sendTransferObj array in the current state instead of appending it to a new empty array (which will cause the overwriting). You can do this using the spread syntax
handleChange(transId: string, event: React.ChangeEvent<HTMLInputElement>) {
let inputObject = [{ id: transId, value: event.target.value }]
const index = inputObject.findIndex(inputObject => inputObject.id != transId)
this.setState({
...this.state,
sendTransferObj: index === -1 ? [
...this.state.sendTransferObj,
inputObject
] : this.state.sendTransferObj
});
}
This won't update the field when the inputObject is found, though. I would recommend storing the values in an object with the ID as keys instead (and formatting it into the desired format when requesting the API). This way, you don't have to iterate through the array to find the matching object.
constructor(props: Props) {
super(props);
this.state = {
sendTransferObj: {}
};
this.handleChange = this.handleChange.bind(this);
}
...
handleChange(transId: string, event: React.ChangeEvent<HTMLInputElement>) {
this.setState({
...this.state,
sendTransferObj: {
...sendTransferObj,
[transId]: event.target.value
}
});
}
Related
I am trying to build a chat application with the functionality of input field which can be used as filter for chat_groups array which is in the state as chat_groups. Here is how my code looks:
constructor(props) {
super(props);
this.state = {
currUserId: "--id--",
chats: [],
chat_groups: [],
users: [],
};
}
.
.
.
<input
className="chat-group__search__input"
placeholder="Search for group..."
onChange={(ev) => {
console.log(ev.currentTarget.value);
var thatState = this.state;
thatState.chat_groups = thatState.chat_groups.map(
(gp) => {
gp["visible"] = gp.group_name
.toLowerCase()
.includes(ev.currentTarget.value);
return gp;
}
);
// getting correct state in thatState variable
this.setState(thatState);
}}
/>
// getting old state in setState callback and componentDidUpdate lifecycle
The weird problem is I am getting the correct value in thatState variable before setting state. But after setState function is called, if I try to check the state in setState callback or componentDidUpdate lifecycle, I am getting the old state only.
I tried that for keydown and change events also. So, seems to be less of an issue of event as well.
I would like to know if some issue in the code is evident or there is something that I can do to debug the issue.
Edit: After changes, my current onChange looks as below, but the issue is still there; the setState function does not seem to change the state as I can see only the old state in componentDidUpdate lifecycle and setState callback.
onChange={(ev) => {
console.log(ev.currentTarget.value);
let chat_groups = this.state.chat_groups.map((gp) => ({
...gp,
visible: gp.group_name
.toLowerCase()
.includes(ev.currentTarget.value),
}));
console.log(
"Before",
chat_groups.map((gp) => gp.visible)
);
this.setState({ chat_groups: chat_groups });
}}
The problem is that you are mutating the state.
When you do var thatState = this.state; the reference is still the same for both the objects. So automatically when you update thatState.chat_groups you are updating/mutating state as well.
Change your onChange method to like below
onChange = ev => {
console.log(ev.currentTarget.value);
let { chat_groups } = this.state;
chat_groups = chat_groups.map(gp => ({
...gp,
visible: gp.group_name.toLowerCase().includes(ev.currentTarget.value)
}));
this.setState(state => ({
...state,
chat_groups
}));
};
//Other code
//....
//....
<input
className="chat-group__search__input"
placeholder="Search for group..."
onChange={this.onChange} />
I suspect there's one problem while checking the group_name with the input value i.e., you are converting the group_name to lower case using gp.group_name.toLowerCase() but the input value you are not converting to lower case. This could be one issue why the visible attribute value is not getting updated. So in the below snippet I have converted the input value also to lower case while comparing.
Here, below is a runnable snippet with your requirement. Doing console.log in the setState's callback function and the state is getting updated.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
currUserId: "--id--",
chats: [],
chat_groups: [{
group_name: "Group One"
}, {
group_name: "Group Two"
}],
users: []
}
}
onChange = ev => {
console.log(ev.currentTarget.value);
let {
chat_groups
} = this.state;
chat_groups = chat_groups.map(gp => ({
...gp,
visible: gp.group_name.toLowerCase().includes(ev.currentTarget.value.toLowerCase())
}));
this.setState(state => ({
...state,
chat_groups
}), () => { console.log(this.state.chat_groups); });
};
render() {
return <input
placeholder="Search for group..."
onChange={this.onChange} />
}
}
ReactDOM.render(<App />, document.getElementById("react"));
.as-console-wrapper {
max-height: 100% !important;
}
<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="react"></div>
No, don't do this var thatState = this.state it's just an object it will easily get mutate and you will not get the update as for react state change never occured.
instead do this var { chat_groups } = this.state and then use it further and inlast set the state this.setState({ chat_groups: chat_groups }) also try to avoid the mutation as much as possible means copy the values of chat_groups also
Seems like you are trying to manipulate state directly which is a big no in React.
onChange={(ev) => {
this.setState({
chat_groups: this.state.chat_groups.map((gp) => {
gp["visible"] = gp.group_name
.toLowerCase()
.includes(ev.currentTarget.value);
return gp;
})
});
}}
State should only be updated using the setState method.
You are mutating the state directly in your code above - this isn't recommended. You would get unexpected results and it's not predictable.
This is how you should do it - create a new updated object and pass it to the setState.
onChange={(ev) => {
console.log(ev.currentTarget.value);
const updatedChatGroups = this.state.chat_groups.map((gp) => {
const visible = gp.group_name.toLowerCase().includes(ev.currentTarget.value);
return {
...gp,
visible,
};
});
// Update the modified object using this.setState().
this.setState({ chat_groups: updatedChatGroups });
}}
Read More
How to update my associative array while the key already exists else I want to create a new key and store that array in the state. I had tried to store a value in the associative array.
When I entering data in the input field it can call handleChange after that I want to do that above I mentioned operations
import React, { Component } from 'react';
class App extends Component {
constructor(props) {
super(props);
this.onChangeEvent = this.onChangeEvent.bind(this);
this.onChangeCount = this.onChangeCount.bind(this);
this.onSubmit = this.onSubmit.bind(this);
this.state = {
name: [],
inputCount:5
}
}
// associtive array
handleChange = (e) => {
// I want to check the key is already exist or not if exist means create update the value else create a new key and also I want to validate all the data.
const check = e.target.name
const value = e.target.value
const name = []
name[check] = value
console.log(name)
this.setState({
name,
})
}
render() {
let options = []
for (let i = 0; i < this.state.inputCount; i += 1) { options.push(i); }
return (
{this.state.inputCount && (
options1.map((i) => {
return (<div key={i}>
<input onChange={this.handleChange} type="text" className="name" name={`name${i}`} placeholder="Name" /><br />
</div>)
})
)}
);
}
}
export default App;
First, there are no "associative arrays" in JavaScript, though there are Objects, which is basically the same. So right off the bat, you should change the initialization of name to be {} instead of [].
To check if a key exists in an Object, simply do check in obj or even name.hasOwnProperty(check), where check is the key you want to check.
Setting and updating a value is done the same way in JavaScript, regardless if the key exists or not: name[check] = value.
The check box function works fine and updating selected input value in to this.state = { answers: [] }; At the same time the checkboxes doesn't show as selected on page load when page loading with
this.state = {
answers: [
{
questionID: 4,
answerValues: ["1", "2", "3", "4"]
}
]
}
Demo: https://codesandbox.io/s/7jlq387686
{options.map(option => {
var tick = checked.answerValues.find(answer => answer == option.id );
return (
<div className="form-check" key={option.id}>
<label className="radio-inline" htmlFor={`${name}-${option.id}`}>
<input
name={name}
id={`${name}-${option.id}`}
type="checkbox"
value={option.id}
onChange={e => this.handleChange(e, option.id)}
checked={option.id == tick}
/>{" "}
{option.value}
</label>
</div>
);
})}
There are two issues in the current snippet
1) When select from checkbox it works but the preselected items are becoming unselected
2) when page load the checkbox doesn't work it says Cannot read property 'find' of undefined
Problem 1
You miss to initialize the selected attribute corresponding with the checked prop:
constructor(props, context) {
super(props, context);
this.selected = this.answerValues
.reduce((obj, curr) => ({ ...obj, [curr]: true }), {});
this.handleChange = this.handleChange.bind(this);
}
Also add this getter to avoid errors when is not defined:
get answerValues() {
const { answerValues = [] } = this.props.checked || {};
return answerValues;
}
Note: Also you can use directly defaultProps to avoid this.
Problem 2
Instead of this line on render:
var tick = checked.answerValues.find(answer => answer == option.id);
Use:
var tick = this.answerValues.find(answer => answer == option.id);
Clarifications
On the handleChange from InputCheckbox you were saving the new answerValues as Object.keys(this.selected), and at the beginning this selected was an empty object, this mean, that you was losing the first state. To keep the first state, you need to initialize the selected attribute.
Find of undefined was because there isn't an answers array yet, you can't fix this using the getter that I proposed, or also using defaultProps setting to a default value when this array is undefined.
Sandbox: https://codesandbox.io/s/wnv238j1ww
I have data that push to a state named value. and i want to display them based on number of array. i'm trying to make initiate but not work.
constructor(props) {
super(props)
this.state = {
json: [],
search: '',
value: [],
jumlah : "",
nama_barang : "Bolu kuwuk",
harga : 10000
}
}
i push jumlah, nama_barang and harga into value [] like this
handleSubmit(e){
e.preventDefault();
alert("Data Telah diinputkan!");
this.state.value.push(
this.state.jumlah,
this.state.nama_barang,
this.state.harga
)
this.setState(
this.state
)
console.log(this.state.value)
}
and display them like this
{this.state.value.map((v, i) => {
return <div key={i}>
<h1>{v.jumlah}</h1>
<h1>{v.nama_barang}</h1>
<h1>{v.harga}</h1>
</div>
})}
it works when i write {v} but it print whole array. i just want display it by number of array
When you push into your array, you are adding 3 new elements to that array as opposed to one object that contains jumlah, nama_barang, and harga.
What you should be doing is:
this.state.value.push({
jumlah: this.state.jumlah,
nama_barang: this.state.nama_barang,
harga: this.state.harga,
});
This will make it work as expected, but you are mutating state and setting setState to this.state -- these 2 things are against best practice.
Try this instead:
this.setState((prevState) => {
return {
value: prevState.value.concat([{
jumlah: this.state.jumlah,
nama_barang: this.state.nama_barang,
harga: this.state.harga,
}])
}
});
In the code above we are not mutating the existing state object, and we are using an updater callback in setState that contains a reference to our previous state (the rule is that we should use the updater syntax whenever our new state depends on our previous state)
Using an api endpoint that holds the an an array of objects for specific crypto currency coins.
I created a form where users can type in a specific coin and hit submit and it will return the price. That coin will then check if its in an array of object in the api. If it's valid then I push that into the filtered results array in the constructor.
My first search query works, but when I do my second query search and hit the submit button, it fails and just reloads the page.
constructor() {
super();
this.state = {value: ''};
this.state = {coin: []};
this.state = {items: []};
this.state = {filteredResults: []};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
}
handleSubmit(event) {
let coin = this.state.value;
this.findCoin(coin);
event.preventDefault();
}
findCoin(id) {
this.state.items.forEach(function(currency){
if(currency.id === id) {
this.state.filteredResults.push(currency)
}
}, this);
this.setState({filteredResults: this.state.filteredResults[0]});
}
componentDidMount() {
fetch(`https://api.coinmarketcap.com/v1/ticker/`)
.then((result)=> {
result.json()
.then(json => {
this.setState({items: json})
});
});
}
render() {
return (
<div className="App">
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input type="text" value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
<div> Price: $ {this.state.filteredResults.price_usd}
</div>
</div>
);
}
}
The problem in this method:
findCoin(id) {
this.state.items.forEach(function(currency){
if(currency.id === id) {
this.state.filteredResults.push(currency)
}
}, this);
this.setState({filteredResults: this.state.filteredResults[0]});
}
At line
this.setState({filteredResults: this.state.filteredResults[0]});
you are setting filteredResults (which is an array) to an object and on the second search the line
this.state.filteredResults.push(currency)
gives you an error, as filredResults being a string doesn't have push method.
And as you have event.preventDefault on the last line of handleSubmit method it's not executing because of previous error and form is submitting.
This method is mutating state, which circumvents React's state checking;
findCoin(id) {
this.state.items.forEach(function(currency){
if(currency.id === id) {
this.state.filteredResults.push(currency)
}
}, this);
this.setState({filteredResults: this.state.filteredResults[0]});
}
Use a method such as filter, that gives a new array reference:
const filtered = this.state.items.filter(ccy=> ccy.id === id);
this.setState({filteredResults: filtered[0]};
Also as one of the other posters has mentioned, declare the filterResults as an object (if you are only ever going to display one filtered result), as it changes from array to object.
this.state = {filteredResults: {}};