How do I update the textarea using useState? - javascript

I have a textarea array with values that can be updated. The text values in the array are updated when text is entered into the textarea. The array can also be updated externally.
The problem is that Textarea doesn't want to update its values with setState() like regular text does.
export function GameActions({}) {
const [array, setArray] = useState<Type>([]);
const changeText = (id: number, text: any) => {
actions[id].text = text;
setActions(actions);
};
return {actions.map((action, index) => (<Textarea
defaultValue={action.text}
onChange={(e) =>
changeText(index, e.currentTarget.value)
}
/>))};
};

Provided actions is an array state property, it should be:
setActions((actions)=>{
return actions.map((act,i)=>{
if(i == id) {
act.text = text;
}
return act;
});
});
When a state property is updated using its previous value, the callback argument should be used.
Also, to update an element of a state array, map should be used, rather than the indexation operator [].
Please read this article, to learn how to update state arrays.

Related

Select and deselect values with react and hooks

I am trying to change the state by selecting and deselecting the language option in the code below. So far I can update the state by adding a language, my problem is that, if I click on the same language again, I will add it another time to the array. Can anyone explain me how to add or remove the language from the array when clicked one more time?
export default function Dashboard(props) {
const [language, setLanguage] = useState('');
const handleLanguageChange = changeEvent => {
changeEvent.persist()
setLanguage(prevState => [...prevState, changeEvent.target.value])
};
}
It looks like your only issue is your logic in the place where you are handling update. Usage of hooks is correct
So first of all you need to set proper initial value. As you plan to store your languages in an array.
Second part is updating the array. So you can either find clicked language in the array and if it is exist - then use filter to set your new value or filter and compare length of existing array and new one.
const [language, setLanguage] = useState([]);
const handleLanguageChange = changeEvent => {
changeEvent.persist()
setLanguage(prevState => {
const lang = changeEvent.target.value;
if (prevState.includes(lang) {
return prevState.filter(el => el !== lang);
}
return [...prevState, lang]
})
};
You will need a good old check.
if (!languages.includes(changeEvent.target.value) {
// code to add the language
}
Check the selected value using find() method on language array if it returns undefined, then push into array. Rename the state variable as languages otherwise it's confusing (Naming convention standard).
const [languages, setLanguages] = useState('');
const handleLanguageChange = changeEvent => {
changeEvent.persist()
if (!languages.find(value => value == changeEvent.target.value)) {
setLanguages(prevState => [...prevState, changeEvent.target.value])
}
};
2 Things here
Instead of having
<option value="Deutsch">Deutsch</option>
<option value="Englisch">Englisch</option>
use an languages array of json so it bacomes easy for you to add them like
languages= [{value='Deutsch',name= 'Deutsch',...}]
2.setLanguage sa a direct value
setLanguage(changeEvent.target.value)

Edit TextField with value already filled doesn't work

I'm beginner with React JS and I'm doing a test project to train my skills.
I have a list of farms being rendered on the screen. The user can click on the button to register a new farm or can click on the property to be able to edit the existing property.
In a single Dialog Modal I do both. The problem is when I try to edit the Input field, it is not working. It doesn't matter what I type and nothing happens.
That's my input field, I'm using TextField from React Material:
<TextField
id="farmer-name"
label="Farm Name"
value={propertyData.farmerName}
onChange={(event) =>
changeField("farmerName", propertyData.id, event.target.value)
}
className={classes.input}
InputProps={{
className: classes.inputContent
}}
InputLabelProps={{
className: classes.inputLabel
}}
/>
And here's my function that will be able update my data:
const changeField = (field, id, value) => {
const newPropertyData = { ...propertyData };
if (newPropertyData.id === id) {
newPropertyData.field = value;
}
};
Here's my code, I put in CodeSandBox: https://codesandbox.io/s/spring-breeze-xnv3r?file=/src/index.js
Screen of my application
Can someone help me to edit that´s values? Thanks
You should save state on change in text field
const changeField = (field, id, value) => {
const newPropertyData = { ...propertyData };
if (newPropertyData.id === id) {
// change field value for specific id
newPropertyData[field] = value;
}
// set updated field value into state to show on form
setPropertyData(newPropertyData);
};
You are not actually changing the propertyData stored in the useState hook.
Currently, there will be a new property added called field in the newPropertyData object. However, this variable is never used or stored and you probably don't want the value to be stored in the field property.
The shortest answer is to pass a function to the setPropertyData which will receive the previous value of propertyData.
The previous propertyData can be extended with a dynamic property by using the following syntax: [field]: value.
const changeField = (field, id, value) => {
if (propertyData.id === id) {
setPropertyData(data => ({ ...data, [field]: value }));
}
};

How to add dynamic input values to the local state for retrieval

I have a React Native form that allows me to add an Input UI in the form, by clicking a button with this function. This allow me to generate it on the fly. The code for that is this.
addClick() {
this.setState(prevState => ({ values: [...prevState.values, ""] }));
console.log(this.values[0].name);
}
That part works well, but I'm having a problem extracting the data from the dynamic inputs, and add it to an array. So far I have tried this
setVal = value => {
const values = this.state.values[0];
if (values[0].name === "" || values[0].description === "") return;
[...this.state.values, value];
this.setState(values);
console.log(values);
};
How do I organize my states properly so that I can add as many inputs I need, and when I'm finished, I can update the state, and access the new data in my list component?
How do I update my state to the new Array? at the moment, this.state only shows the initial state set at the top.
I'm missing a few things
Please take a look at the full code sandbox HERE so you can see:
See...your created isssue is not so obvious we need to see where you call setVal() function but....
i think you will be more comfortable if you render your <input/> s directly from your state array, not from const x = [] variant. because it seems like you want a dynamic view and in such a cases you will need to bind your loop quantity from state. so:
this.state = {
x: [0,1,2,3,4]
}
and inside your render :
{this.state.x.map(x => {
return (
<TextInput
placeholder={`name${x}`}
value={values[x.toString()]}
handleBlur={() => handleBlur(x.toString())}
onChangeText={handleChange(x.toString())}
style={styles.input}
/>
);
})}

multiple filters, one onChange function

My structure:
index.js
--> Filters
--> {list}
Filters contains multiple input elements that set the state in index.js via props.
list displays as you guessed it, a list of elements. I now want to filter this list based on the values returned by the filters. Pretty standard I think and found on millions of sites.
The issue is that I want to make the input onChange function reusable. With only one input in the Filters component I had this (shortened):
<input
value={this.state.anySearch}
onChange={this.handleChange}
/>
handleChange = event => {
const value = event.target.value;
this.setState({ anySearch: value });
};
With multiple inputs I tried this, aka reusable for any input:
handleChange = name => event => {
const value = event.target.value;
this.setState({ name: value });
};
onChange={this.handleChange("anySearch")}
But this doesn't work anymore. State now shows only one letter at a time when console logged.
What is best practice to filter according to multiple different criteria à la kayak, and how do I rewrite the handleChange function without pushing each letter into an array?
handleChange = name => event => {
const value = event.target.value;
this.setState({ name: value });
};
your idea of returning a function is correct, you just have an error when setting the state
this.setState({ name: value });
change it to
this.setState({ [name]: value });
Regarding the second question, you can simply iterate over your array and filter out the objects that match that specific criteria, to avoid unnecessary overhead you can implement a caching mechanism that keep track of the searches the user has already done.
something like this:
function filter(criteria) {
if(cache[criteria]) {
return cache[criteria];
}
cache[criteria] = myArray.filter(elem => elem.criteria === criteria);
return cache[criteria];
}

Why do I lose the current object state when 'this.props.actions.updateComment(event.target.value)' is called?

I trying trying to achieve the following: There is a textfield and once a user enters in a text, an object is created with the text assigned to a state property called 'commentText' which is located inside the 'comments' array which is inside the object (todo[0]) of 'todos' array. 'commentInput' is just a temporary storage for the input entered in the textfield, to be assigned to the 'commentText' of 'todo[0]' object's 'comments' array.
I retrieve the current state object via following:
const mapStateToProps=(state, ownProps)=>({
todo:state.todos.filter(todo=>todo.id==ownProps.params.id)
});
and dispatch and actions via:
function mapDispatchToProps(dispatch){
return{
actions: bindActionCreators(actions, dispatch)
}
So the retrieved object 'todo' has an array property named comments. I have a text field that has:
onChange={this.handleCommentChange.bind(this)}
which does:
handleCommentChange(event){
this.props.actions.updateComment(event.target.value)
}
Before handleCommentChange is called, the object 'todo[0]' is first fetched correctly:
But as soon as a text is inputted into the text field, onChange={this.handleCommentChange.bind(this)} is called and all of a sudden, 'todo[0]' state is all lost (as shown in the 'next state' log):
What may be the issue? Tried solving it for hours and hours but still stuck. Any guidance or insight would be greatly appreciated. Thank you in advance.
EDIT **:
{
this.props.newCommentsArray.map((comment) => {
return <Comment key={comment.id} comment={comment} actions={this.props.actions}/>
})
}
EDIT 2 **
case 'ADD_COMMENT':
return todos.map(function(todo){
//Find the current object to apply the action to
if(todo.id === action.id){
//Create a new array, with newly assigned object
return var newComments = todo.comments.concat({
id: action.id,
commentTxt: action.commentTxt
})
}
//Otherwise return the original array
return todo.comments
})
I would suspect that your reducer is not correctly updating the todo entry. It's probably replacing the contents of the entry entirely, rather than merging the incoming value in in some fashion.
edit:
Yup, after seeing your full code, your reducer is very much at fault. Here's the current code:
case 'ADD_COMMENT':
return todos.map(function(todo){
if(todo.id === action.id){
return todo.comments = [{
id: action.id,
commentTxt: action.commentTxt
}, ...todo.comments]
}
})
map() should be returning one item for every item in the array. Instead, you're only returning something if the ID matches, and even then, you're actually assigning to todo.comments (causing direct mutation) and returning the result of that statement (which might be undefined?).
You need something like this instead (which could be written shorter, but I've deliberately written it out long-form to clarify what's happening):
case 'ADD_COMMENT':
return todos.map(function(todo) {
if(todo.id !== action.id) {
// if the ID doesn't match, just return the existing objecct
return todo;
}
// Otherwise, we need to return an updated value:
// Create a new comments array with the new comment at the end. concat() will
// You could also do something like [newComment].concat(todo.comments) to produce
// a new array with the new comment first depending on how you want it ordered.
var newComments = todo.comments.concat({
id : action.id,
commentTxt : action.commentTxt
});
// Create a new todo object that is a copy of the original,
// but with a new value in the "comments" field
var newTodo = Object.assign({}, todo, {comments : newComments});
// Now return that instead
return newTodo;
});

Categories