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

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

Related

Clearing a Material UI search filter TextField and returning to default

I am new to ReactJS and pairing it with Material UI is really causing me some roadblocks. I have created a reusable search filter component for my data tables and it worked exactly the way I wanted, but now I want to add a button to clear the field and show the unfiltered results, as well as return the InputSearch component back to its default state so it will display the label inside the field again, not up in the field’s border as these Material UI TextFields do then they are focused or have a current value. This is where I am hitting my roadblock. I have tried multiple solutions I found online, like using the inputRef/useCallback method to change the values, but it didn’t seem to work…or maybe I misunderstood and did it wrong. I was also recommended to put my search values to state. As happens with state my searches are now always one render behind (I.E. , results matching ‘US’ for ‘USA’ , ‘USA’ for ‘USAF’, etc…). Then when I run the handleFilterReset function to set the filter values back to an empty string, nothing happens. I just want my search filter to work instantly (like it did before I moved the value to state [commented out]) and be able to be cleared, resetting the table back to its default display.
Can someone please help me figure this out? Suggestions are appreciated, but code snippets are much more helpful since I am really new to React and especially Material UI.
dataTable.js
const [inputValue, setInputValue] = useState('')
const [searchFn, setSearchFn,] = useState({ fn: items => { return items; } });
// Searching Data
const handleSearch = e => {
setInputValue(e.target.value) // value displayed in input field
let query = (e.target.value).toString().toLowerCase();
setSearchFn({
fn: items => {
if (query === "")
return items;
else
return items.filter(x =>
(x.tankName !== null && x.tankName.toLowerCase().includes(query)) ||
(x.dimensions !== null && x.dimensions.toLowerCase().includes(query))
)
}
})
}
// Clearing Filters
const handleFilterReset = () => {
setInputValue('');
setSearchFn({fn: items => {return items;}})
};
// Search and filter Inputs
<div>
<InputSearch
value={inputValue}
onChange={handleSearch}
/>
<Button
text="Reset"
onClick={handleFilterReset}
/>
</div>
InputSearch.js
export default function InputSearch(props) {
const { inputRef, name, value, error=null, onChange, ...other } = props;
return (
<TextField
label="Search..."
name={name}
value={value}
onChange={onChange}
{...other}
{...(error && {error:true, helperText:error})}
>
</TextField>
)
}
You need to pass the value to InputSearch
Heres an example:
https://codesandbox.io/s/morning-brook-durbvd?file=/demo.tsx
React has a pretty good introduction on its site.
https://reactjs.org/docs/components-and-props.html
The code has been updated with a solution to this issue. I created a display value for the input that I passed to state, which was set to a blank string when the reset is pressed as well as passing an unfiltered data set.

How do I delete dynamically rendered component that is part of a list?

I currently dynamically render the same component when clicking a button and the latest component is rendered on the top of the list.
Now, I want to delete the component. Each component has a cancel button to delete the rendered component. So I should be able to delete the component no matter where it is in the list.
Here's what I have so far:
local state:
state = {
formCount: 0
}
add and cancel:
onAddClicked = () => {
this.setState({formCount: this.state.formCount + 1});
}
onCancelButtonClicked = (cancelledFormKey: number) => {
const index = [...Array(this.state.formCount).keys()].indexOf(cancelledFormKey);
if (index > -1) {
const array = [...Array(this.state.formCount).keys()].splice(index, 1);
}
}
Parent component snippet:
{ [...Array(this.state.formCount).keys()].reverse().map(( i) =>
<Form key={i}
onCancelButtonClicked={() => this.onCancelButtonClicked(i)}
/>)
}
The only thing is I'm not sure how to keep track of which form was cancelled/deleted. I think I would need to create a new object in my local state to keep track but how do I know which index of the array I deleted as part of state? I'm not sure how do that? As I am using the count to make an array above.
Usually, this isn't how you'd generate a list of items. You're not storing the form data in the parent, and you're using index based keys which is a no-no when you're modifying the array. For example, I have an array of size 5 [0, 1, 2, 3, 4], when I remove something at position 2, the index of all the items after it changes causing their key to change as well, which will make react re-render them. Since you're not storying the data in the parent component, you will lose them.
Just to humor you, if we want to go with indexed based keys, we may have to maintain a list of removed indexes and filter them out. Something like this should do the trick:
state = {
formCount: 0,
deletedIndex: []
}
onCancelButtonClick = (cancelledIndex: number) => setState((prevState) => ({
deletedIndex: [...prevState.deletedIndex, cancelledIndex]
});
And your render would look like:
{
[...Array(this.state.formCount)].keys()].reverse().map((i) => (
if (deletedIndex.includes(i) {
return null;
} else {
<Form key={i} ... />
}
))
}
As a rule of thumb though, avoid having index based keys even if you don't care about performance. It'll lead to a lot of inconsistent behavior, and may also cause the UI and the state to be inconsistent. And if you absolutely want to for fun, make sure the components that are being rendered using index based keys have their data stored at the parent component level

React display not updating correctly

I'm updating an object within react's state which I use to display a list. The state updates correctly, however the display breaks.
This is the section of the code from inside my render function which produces the list.
this.state.shoppingItems ? this.state.shoppingItems.currentShoppingItems.map((item, index) => {
console.log(item)
return <ItemSummary key={index} onClickHandler={this.selectItem} updateShoppingItem={this.updateCurrentShoppingItem} shoppingItem={item} removeFromCurrentItems={this.removeFromCurrentItems} addToCurrentList={this.addToCurrentList} />
}) : undefined}
Here is the code that produces the previous items list:
this.state.shoppingItems ? this.state.shoppingItems.previousShoppingItems.map((item, index) => {
console.log(item)
return <ItemSummary key={index} onClickHandler={this.selectItem} updateShoppingItem={this.updateCurrentShoppingItem} shoppingItem={item} removeFromCurrentItems={this.removeFromCurrentItems} addToCurrentList={this.addToCurrentList} />
}) : undefined}
This is the method which removes the item from the current list and adds it to the previous list, where the issue occurs.
removeFromCurrentItems(shoppingItem) {
const items = this.state.shoppingItems.currentShoppingItems.filter(item => item._id !== shoppingItem._id);
let shoppingItems = this.state.shoppingItems;
shoppingItems.currentShoppingItems = items;
shoppingItem.number = 0;
shoppingItem.completed = false;
shoppingItems.previousShoppingItems.push(shoppingItem);
this.setState({
shoppingItems: shoppingItems
});
// call to API to update in database
}
Here is the list before I remove the item.
Here is the list after I remove the middle item:
Finally here is the console.log output which shows that the items have updated properly but the display hasn't updated:
I'm entirely new to react coming from angular so I have no idea if this is the correct way to do this or if there is a better way. But could somebody help me figure out why the display isn't updating?
The issue seemed to be the key on the item in the map. I replaced the index with the item's id from the database as below and now it renders properly.
return <ItemSummary key={task._id} updateShoppingItem={this.updateCurrentShoppingItem} shoppingItem={task} removeFromCurrentItems={this.removeFromCurrentItems} addToCurrentList={this.addToCurrentList} />
Similar answer here:
Change to React State doesn't update display in functional component
The issue is the update for shoppingItems. You save a reference to the current state object, mutate it, and store it back in state. Spreading this.state.shoppingItems into a new object first will create a new object reference for react to pick up the change of.
React uses shallow object comparison of previous state and prop values to next state and prop values to compute what needs to be rerendered to the DOM and screen.
removeFromCurrentItems(shoppingItem) {
const items = this.state.shoppingItems.currentShoppingItems.filter(item => item._id !== shoppingItem._id);
const shoppingItems = {...this.state.shoppingItems};
shoppingItems.currentShoppingItems = items;
shoppingItem.number = 0;
shoppingItem.completed = false;
shoppingItems.previousShoppingItems.push(shoppingItem);
this.setState({
shoppingItems: shoppingItems
});
// call to API to update in database
}
I had a similar issue with my application in which I had to delete comments made.
<textarea disabled key={note._id} className="form-control">{note.note}</textarea>
But the issue got resolved when I added the Key attribute to the list item.

How to change the property of an object inside an array propertly in react

this.state = {
myArray = [
{
name:"cat",
expand:false
}
]
}
clickItem(item){
item.expand = true;
this.setState({})
}
this.state.myArray.map((item) =>{
return <div onClick={()=>this.clickItem(item)}>{item.name}</div>
})
In React, i have a simple array of objects,
when i click on one of theses object, i want to change their prop and update the state, what is the proper way of doing this.
i feel like there could be a better way
You need to copy your state, update the copied state and the set the state.
this.state = {
myArray = [
{
name:"cat",
expand:false
}
]
}
clickItem(key){
let items = this.state.myArray;
items[key].expand = true;
this.setState({items})
}
this.state.myArray.map((key, item) =>{
return <div onClick={()=>this.clickItem(key)}>{item.name}</div>
})
Okay, a couple of things.
You're mutating the state directly which is going to fail silently and you're also missing the key prop on your <div.
This is easily resolved though by using the data you have available to you. I don't know whether each name is unique but you can use that as your key. This helps React decide which DOM elements to actually update when state changes.
To update your item in state, you need a way to find it within the state originally, so if name is unique, you can use Array.prototype.find to update it.
clickItem(item) {
const targetIndex = this.state.items.find(stateItem => stateItem.name === item.name)
if (targetIndex === -1)
// Handle not finding the element
const target = this.state.items[targetIndex]
target.expand = !target.expand // Toggle instead of setting so double clicking works as expected.
this.setState({
items: this.state.items.splice(targetIndex, 1, target) // This replaces 1 item in the target array with the new one.
})
}
This will update state and re-render your app. The code is untested but it should work.

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

Categories