I'm trying to integrate some logic into my web application, which is written using React. I have a textArea component, imported from Semantic-UI, in which I'm trying to pull each new line and store into an array. Currently the textArea component only stores data as a string.
render() {
<Form.TextArea key={input.name} name={input.name} label={input.label} value={this.props.values[input.name] || ""} onChange={this.props.onChange}/>;
where value is a string separated by newline like:
test\ntesting\n123
I tried splitting on a every new line, using the code below, which stores the data in an array, like i desire. But this is replacing my attempts to create a new line [pressing ENTER] with a comma in the textArea, which is not my desire for UX.
value={this.props.values[input.name].split('\n')
My textArea component ends up looking something like this:
test,testing,123
where as my desired result is this:
test
testing
123
How can i achieve my desired textArea component UX while still storing the each new line in an array? Many thanks!
If you only want the array to send it to a db or store in localStorage, you don't need to keep it in state. Keep the normal textarea value in state.
onChange={event => this.setState({ text: event.target.value}) }
When you want to send it to db, you can then convert it to array of strings
onSubmit: () => this.postToDb(this.state.text.split('\n'))
The problem is you're trying to assign value of type array to string prop. I guess, somewhere inside of your TextArea component array converted into a string. Since Array.prototype.toString method returns a string representing array items joined by comma, you have comma-separated string.
You need to split your string only when you're sending it to back-end or using for some other logical purposes, but for TextArea component it's better to keep it as is.
It's hard to tell what you're using the array for, and that might have an influence on the answer.
The easiest way would be to not store the text area's contents as an array, and instead store the value normally to state. If you need to e.g. send it as an array to some backend/db, you can then make the transformation when you send the data. For example:
state = {
value: '',
};
...
onChange = (value) => this.setState({ value });
onSubmit = () => {
const myValueArray = this.state.value.split('\n');
// now make your network request with myValueArray
}
If you really need the array in state, one solution could be to simply duplicate your state by setting two state items when your onChange method gets called e.g.
state = {
value: "", // this is the actual text box value with /n characters
valueArray: [], // this is your array where each newline is an item
};
...
onChange = (value) => {
this.setState({ value, valueArray: value.split('\n')} );
};
Alternatively, you can join the array back when it gets passed to your component. The .join() method will join each array item, optionally inserting a string between each item. e.g.
<Form.TextArea ... value={this.props.values[input.name].join('\n')} />;
Note, depending on your use case, this way might have negative implications on performance
Related
I have two ways to fill the same hidden input in a form
Using an import CSV button
Adding data using another inputs
When I use the first option, the hidden input is filled with this
for example:
correct data
[{"url":"http://www.restaurant.com","businessTypeId":"1"},{"url":"http://www.hotel.com","businessTypeId":"2"}]
and works correctly if I store this data
but when I use the second option, the input is filled with this:
incorrect data
{"url":"http://www.google.com","businessTypeId":"3"}
That's incorrect because it doesn't have [brackets] at the beginning neither at the end
Another problem is when I insert data and fill that hidden input (with the first way) and then I try to add more data using the second way,
I get this
[{"url":"http://www.restaurant.com","businessTypeId":"1"},
{"url":"http://www.hotel.com","businessTypeId":"2"}],
{"url":"http://www.google.com","businessTypeId":"3"}
the first 2 data was inserted using the first way, the third data was inserted using the 2nd way
all data should be inside those brackets []
how can I "open" the brackets to push new data and "close" them?
at the beginning of all the code, the variable starts like this
let placesArray = [];
after the first method, data is inserted using this
placesArray.push(JSON.stringify(csvResult));
document.getElementById('places').value = placesArray;
them, after the second method, data is inserted using this
placesArray.push(JSON.stringify(placeData));
console.log('placeData datatable ' + placeData);
document.getElementById('places').value = placesArray;
Note: if I use the first method two times, brackets are duplicated, like this
[{"url":"http://www.restaurant.com","businessTypeId":"1"}
{"url":"http://www.hotel.com","businessTypeId":"2"}],
[{"url":"http://www.beauty-shop.com","businessTypeId":"3"},
{"url":"http://www.dentist.com","businessTypeId":"5"}]
I definitely need to "open" the object so that I can insert the new data and not go through this, how could I do that?
In the console.log, I have this for placeData [object Object], same result for csvResult, both are object
You could flatten the array before every value set
placesArray = placesArray.flat()
document.getElementById('places').value = placesArray;
Seems like csvResult is itself an array and as you stringify it and push it to placesArray, it doesn't look like the result you want.
I'd go with this instead
placesArray.push(...csvResult) // push each `csvResult` item into `placesArray`
document.getElementById('places').value = JSON.stringify(placesArray)
SOLVED:
I erased all JSON.stringify and added it to every line when I push data into inputs
like this
document.getElementById('places').value = JSON.stringify(placesArray);
in all lines when this is used.
thanks to #hgb123
for prevent to accumulate [brackets[more brackets]] I used .flat()
placesArray = placesArray.flat()
document.getElementById('places').value = JSON.stringify(placesArray);
I am learning React and just created a simple todo app using only React. My todo app has the standard structure of having a text input and an "ADD" button next to it. The user would type their todo in the input and every time they click on the "ADD" button next to it, a new ordered list of their inputs would appear underneath the input and "ADD" button.
The user can also delete a todo entry by clicking on the entries individually, like this:
To accomplish this behaviour of deleting entries, I used this delete function:
delete(elem) {
for (var i = 0; i < this.state.listArray.length; i++) {
if (this.state.listArray[i] === elem) {
this.state.listArray.splice(i, 1);
this.setState({
listArray: this.state.listArray
});
break;
}
}
}
My todo app works exactly the way that I want it to work, but as I look at other people's more conventional approach to this delete function, they either just simply use the splice method or the filter method.
For the splice method approach, they apparently just simply "remove" the unwanted entry from the listArray when the user clicks the particular entry. This does not work for me as using this method results in all my entries getting deleted except for the entry that I clicked on, which is the one that I want to delete.
On the other hand, the filter method approach apparently works by comparing the elem, which is the data passed from a child component, with each element in the listArray, and if the element in the for loop does not equal to the elem, then it would be passed onto a new array. This new array would be the one to not be deleted. This approach works better than the simple splice approach, however, one problem that I had encountered with this approach is that if I have more than one entry of the same value, for example, "Feed the dog". I only want one of the "Feed the dog" entries to be deleted, but it deletes both of them.
I thought of an approach to tackle this problem, eventually coming up with the current version of my code, which uses the splice method, but the splice method is used before I set it in the state. As evident here:
this.state.listArray.splice(i, 1);
this.setState({
listArray: this.state.listArray
});
My question can be broken down into three subquestions:
Considering that React states should be immutable, is the first line of the code above mutating my state? Is this approach not okay?
I thought that all React states were only possible to be changed inside a "setState" function, but my first line of code from above is not inside a setState function, yet it changed the state of listArray. How is this possible?
If my approach is mutating the state and is not ideal, how would you go about making the delete function so that it only deletes one entry and not more than one if there are multiple similar entries?
Yes, splice affects the array it acts on so don't use in this way. Instead you need to create a new array of the correct elements:
this.setState({
listArray: this.state.listArray.filter((el, idx) => idx !== i);
});
If you want to remove only the first instance, maybe couple with a findIndex (although indexOf would work in your example as well) first:
delete(elem) {
const idxToFilter = this.state.listArray.findIndex(el => el === elem);
if (idxToFilter < 0) {
return;
}
this.setState({
listArray: this.state.listArray.filter((el, idx) => idx !== idxToFilter);
});
}
This creates a new array without modifying the old which will cause anything that reacts to listArray changing to be notified since the reference has changed.
I am creating input fields dynamically based on the number of object in my state array. Beside each field I am adding a button to remove that field. However, when the button is clicked it behaves in an unexpected way.
Below is the visual demonstration:
When I press "Remove Option" button on "Option 0":
The output is like :
However, when I see from console.log() the correct object is being removed. These are console.log() outputs before:
and after the above button click:
Here is how I loop from the array in my render():
const questions = this.state.values_array.map((question, index) => {
return (
<div key = {question.question_id}>
{this.state.options_array.map((option, i) => (
option.questionID === question.question_id ? //to show only this question's options
<div>
<span>Option {i}:</span>
<TextField type="text" defaultValue={option.description} />
<span>Value:</span>
<TextField type="number" defaultValue={option.value}/>
<button onClick={() => this.removeOption(i)}>Remove Option</button>
</div>
:
null
))}
</div>
)
}
Here is my removeOption() method I am using to remove the input fields:
removeOption(index){
let options = [...this.state.options_array];
options.splice(index, 1);
this.setState({ options_array: options });
}
And here is how I am calling it in my render's return:
return (
<div>{questions}</div>
)
The flaw of this approach is that in JavaScript, objects and arrays are reference types, so when we get an array, we actually get a pointer to the original array's object managed by react. If we then splice it, we already mutate the original data and whilst it does work without throwing an error, this is not really how we should do it, this can lead to unpredictable apps and is definitely a bad practice. A good practice is to create a copy of the array before manipulating it and a simple way of doing this is by calling the slice method. Slice without arguments simply copies the full array and returns a new one which is then stored. And we can now safely edit this new one and then update to react state with our new array. let me give you and example:
We have an array like this const arr=[1,2,3,4,5]. This is original array.
As I told you before, we can do that like this:
const newVar=arr.slice();
newVar.splice(Index,1);
console.log(newVar);
Or
An alternative to this approach would be to use it a ES6 feature, it is the Spread Operator
Our prior code can be something like this:
const newVar=[...arr]
newVar.splice(Index,1);
console.log(newVar);
That's it. Good luck
You are missing the keys for the div containers. React needs to know which DOM Element has been removed so it re-renders it. Also, do not use the index of map as the key, instead use something like the id e.g. option.questionID.
you need to filter out the individual item from the list
removeOption(index) {
const options = this.state.options_array.filter((items, itemIndex) => itemIndex
!== index)
this.setState({ options_array: options });}
There is a page with a lot of different checkbox questions which then get submitted and populate the next page, this page however gets refreshed and the already annoyed potential client needs to go back and fill out the form again.
Now I have localstorage set up so he doesn't need to reselect all the checkbox again, he just needs to resubmit the form and his back in action.
How does one keep the values populated on the problem page so this fella doesn't have to go back to resubmit?
//SIZE SAVE
function save() {
localStorage.setItem('100', checkbox.checked);
var checkbox = document.getElementById('100');
localStorage.setItem('200', checkbox.checked);
var checkbox = document.getElementById('200');
//SIZE LOAD
function load() {
var checked = JSON.parse(localStorage.getItem('100'));
document.getElementById("100").checked = checked;
var checked = JSON.parse(localStorage.getItem('200'));
document.getElementById("200").checked = checked;
//THIS PAGE NEEDS THE CHECKMARK
echo get_site_url().
"/the/checkmark/selected/was/".$_POST['check_group'].
"/.png";
}
I think is much simple for now and especially for the feature if you write some code to make the management for all checkboxes form your form.
First of all it will be best if you group all your checkboxes into a single place.
Into a function like this you can declare all your checkbox selectors you want to save into the localStoarge (now you don't need to make variables for each selector into multiple places into your code)
function getCheckboxItems() {
return ['100', '200']
.map(function(selector) {
return {
selector: selector,
element: document.getElementById(selector)
}`enter code here`
});
}
Then to make things much simpler you can store all the values from the checkbox into a single object instead of save the result in multiple keys, in this way is much simpler to make management (let's say you want to erase all values or to update only a part)
The following function will take as argument all checkbox items from the function above, the point is the function above will return an array with the checkbox id and the checkbox element, than you just reduce all that array into this function into an single object containing all the ids and values, after this you just store the object into the localStorage
function serializeCheckboxes(elements) {
var container = elements.reduce(function (accumulator, item) {
accumulator[item.selector] = item.element.checked;
return accumulator;
}, {})
localStorage.setItem('container', JSON.stringify(container));
}
function save() {
var elements = getCheckboxItems();
serializeCheckboxes(elements);
}
After this you need another function who will read all the values from the localStorge and place them into your checkbox "checked" state
function readCheckboxes() {
var storage = localStorage.getItem('container'), //Your key
container = (storage) ? JSON.parse(storage) : {};
Object.keys(container).forEach(function(key) {
var element = document.getElementById(key);
if(element) {
element.checked = container[key];
}
});
}
This is just a simple service who can manage your problem but I think, for any additional changes you can customize this solution much simpler instead of keeping all into multiple variables, also if you add more checkbox elements into your application with this solution you just add the corresponding id into the array from the first function.
A live example here:
https://jsbin.com/xejibihiso/edit?html,js,output
localStorage has two main functions, getItem and setItem. For setItem you pass in a key and a value. If you write to that key again, it will rewrite that value. So in your case, if a box is checked you would do
localStorage.setItem("checkbox_value", true)
and when it is unchecked you would pass in false instead. To get the value you can look at using jQuery like so:
$(checkbox).is(':checked')
and use a simple if-else clause to pass in true or false. then when you reload your page, on $(document).ready() you can get the values using
localStorage.getItem(key)
and use JavaScript to set the check boxes values.
localStorage only allows you to store strings. What you can do is use a loop to create a string that has all the check boxes values separated by some delimiter. So, for example, if there are four check boxes with values true false false true your string would be "true\nfalse\nfalse\ntrue" where \n is the delimiter. then you can store that string in localStorage and when you retrieve it you can put all the values into an array like so:
array = localStorage.getItem(key).split('\n').
Then you can populate your check boxes with that newly retrieved array. Ask if anything needs clarification.
does anyone know how to store user text input data into an array of objects in React Native?
[
{
note: ‘some note’,
created_at: 123456789
},
{
note: ‘another observation note’,
created_at: 123456789
}
]
I'm currently just using basic Asyncstorage. I have a text input field for a user to type in notes. It currently saves it as a string in an array. I want to know how to do this but with an array of objects. Also, how would I add the created_at time stamp?
Thank you :-)
Sounds more like a basic JavaScript question. In the context of React, you would simply store the list of notes in the component state (or application state, if using something like Redux).
Define an onChange event handler function for the text input and call setState with:
onSomeInputChange = (text) => {
const note = {
note: text,
created_at: Date.now()
};
this.setState({ notes: [ ...this.state.notes, note ] });
}
The above assumes you have the state variable notes that holds an array of note objects. Using the spread operator, you can copy all the previous notes to a new array and then add the new note. You have to do that because state is immutable and cannot be modified directly.
Then pass that function to the onChange of your text input component:
<TextInput ... onChange={this.onSomeInputchange} />
Other props have been omitted.