How to remove key from my input upon input reset? - javascript

I have a search form with multiple inputs.
The form has a reset button to start a new search. Currently I have it working so that the value gets cleared from the state. The problem is that the key value is not being removed and so the input is being included in the new search with just an empty key. This is causing the search to include empty keys as part of the query string.
For example. This is a query string with the added empty key:
http://api/ixmasterdocument?filter=IDXT002|&filter=IDXT001|1111
As you can see the filter=IDXT002| is empty and being included in query string with the correct key value pair filter=IDXT001|1111
Here is my reset method that clears the key value from state.
clear = () => {
let emptyValues = JSON.parse(JSON.stringify(this.state.formValues))
Object.keys(emptyValues).forEach(key => emptyValues[key] = "")
this.setState({
formValues: emptyValues,
contracts:[],
})
}
Here is my inputchange method..
handleInputChange = (e) => {
console.log("==handleInputChange==")
let newValues = JSON.parse(JSON.stringify(this.state.formValues))
newValues[e.target.name] = e.target.value
this.setState({
formValues: newValues
})
console.log("newFormValues is: " + JSON.stringify(newValues))
}
Here is the submit method..
handleFormSubmit = event => {
event.preventDefault();
const formData = this.state.formValues
let query = '';
let keys = Object.keys(formData);
keys.forEach(k => {
if (query !== "")
query += `&`;
query += `filter=`
query += `${k}|${formData[k]}`
})
return this.loadContracts(query);
};
Here is the input component with reset button..
<form className="form-inline col-md-12" onReset={this.props.handleFormReset}>
{this.props.labels.map(label => (
<div className="card border-0 mx-auto" style={styles} key={label.Id}>
<ul className="list-inline ">
<span>
<li>
<Labels htmlFor={label.DisplayName} >{label.DisplayName}:</Labels>
</li>
<li >
<div>
<Input
key={label.Id}
onChange={this.props.handleInputChange}
value={this.props.formValues[label.DataField] ||""}
type="search"
maxLength="999"
style={{height:34}}
name={label.DataField ||""}
className={"form-control mb-2 mr-sm-2"}
id={label.DataField}
/>
State: {this.props.formValues[label.DataField]}
</div>
</li>
</span>
</ul>
</div>
))}
<div className=" col-sm-12">
<Button
style={{ float: "left", marginBottom: 10 }}
className="btn btn-success"
type="submit"
onClick={this.props.handleFormSubmit}
>
Search
</Button>
<Help />
<Button
style={{ float: "left", marginBottom: 10 }}
className="btn btn-secondary"
// type="reset"
onClick={this.props.clear}
>
Reset
</Button>
</div>
</form>

Since You want to ignore/skip the key-value pairs in api call where value='', so put the check in handleSubmit function and include only non-empty values.
Like this:
handleFormSubmit = event => {
event.preventDefault();
const formData = this.state.formValues
let query = '';
let keys = Object.keys(formData);
keys.forEach(k => {
// here
if(formData[k]) {
if (query !== "")
query += `&`;
query += `filter=`
query += `${k}|${formData[k]}`
}
})
return this.loadContracts(query);
};
Or another possible way can be, setting formValues as {} in clear method. You are only clearing values not keys from that object, if you reset the variable then only new key-value will be available.
Like this:
clear = () => {
this.setState({
formValues: {},
contracts:[],
})
}

Related

How to create input field dynamically in react?

I am trying to create input field dynamically.
Input field can be consider as three dimensional matrix mat[m][n][2].
to add one more 2d matrix, there is button at the bottom and to create more rows in each matrix there is one button at the end of each matrix.
This is what I have tried. (Ignore the css)
const Counter = () => {
const [info,setInfo] = useState([]);
const addNewCond = (e) => {
let matrix = [["",""]];
setInfo([...info,matrix]);
}
const addNewDetailedCond = (index) => (e) => {
let matrix=info[index];
matrix.push(["",""]);
setInfo(matrix);
}
const deleteCond = (e) => {
}
const deleteDetailedCond = (index) => e => {}
const handleDetailedCond = (i,j,k) => (e) => {}
return (
<div>
<form className="form-horizontal form-label-left">
{
info.map((mat,i) => (
<>
{
mat.map((rows,j) => (
<div className="form-group">
<div className="col-md-6 col-sm-6">
<select className="form-control" value={info[i][j][0]} onChange={handleDetailedCond(i,j,0)}>
<option value="test">Test</option>
</select>
</div>
<div className="col-md-6 col-sm-6">
<input className="form-control" value={info[i][j][1]} onChange={handleDetailedCond(i,j,1)}/>
</div>
</div>
))
}
{
mat.length !== 0 ? <button className="btn btn-primary" type="button" onClick={addNewDetailedCond(i)}>Add New Detailed Info</button> : null
}
</>
))
}
<div className="form-group">
<div className="col-md-6 col-sm-6">
<button type="button" className="btn btn-primary" onClick={addNewCond}>Add New Condition</button>
</div>
</div>
</form>
</div>
)
}
CodePen Link -> https://codepen.io/anonymous0045/pen/ZEMGLrN
Whenever I try to add more list in each matrix it fails, What mistake I am doing during adding more rows in particular matrix? Please help.
I think you need to make changes in the following function in order to add nested form group for one condition.
const addNewDetailedCond = (index) => (e) => {
const newInfo = info.map((mat, i) => {
if (i === index) {
return mat.concat([["", ""]]);
}
return mat;
});
console.log("new matrix => ", newInfo);
setInfo(newInfo);
};
Also, you need to add keys whenever you're rendering elements through the loop. check the warnings in console, they're helpful!
Edited version: https://codepen.io/anonymous0045/pen/ZEMGLrN?editors=0010
Let me know if this works!

Rendering a box when submitting the data

I have the following code in my React:
const [property, setProperty] = useState([]);
const [state, setState] = React.useState({ type: "", propertyName: "" });
const handleChange = (e, inputField) => {
setState((prevState) => ({
...prevState,
[inputField]: e.target.value,
}));
};
const handleSubmit = () => {
if (state.type !== "" && state.propertyName !== "") {
const newObject = { type: state.type, propertyName: state.propertyName };
property.push(newObject);
console.log(property);
setState({
type: "",
propertyName: "",
});
}
};
And html:
<div>
<label htmlFor='properties' className='properties-label'>
Properties
</label>
<div className='property-box'>
<input
type='text'
id='type'
value={state.type}
placeholder='Type'
className='type-element'
required
onChange={(e) => handleChange(e, "type")}
></input>
<input
type='text'
id='name'
value={state.propertyName}
className='type-element'
placeholder='Name'
required
onChange={(e) => handleChange(e, "propertyName")}
></input>
<button
className='buttonAccount'
type='submit'
onClick={handleSubmit}
>
Add Property
</button>
</div>
</div>
What I want is when I press the add Property button a new html tag will render on the page(a box or something like that containing the two fields that has been inputted). Can you help me find a way to do that?
You have to print the elements in your property array. For exmaple:
{
property.map((element) => (
<div key={element.propertyName}>
<span>
{element.type}
</span>
<span>
{element.propertyName}
</span>
</div>
)
}
You can use the Javascript array map method to map each item in your property state into an HTML element.
For example:
Make a function that returns the mapped property state into HTML elements.
const renderProperties = () => {
return property.map((item, index) => (
// `item` is a representation of each of your object in the property array
// In this case, item contains { type: string, propertyName: string }
<div key={index}> // React requires user to put a key in each of the mapped component
<p>{item.propertyName}</p>
<p>{item.type}</p>
</div>
))
}
Call this function inside the HTML part of your JSX.
...
<button
className='buttonAccount'
type='submit'
onClick={handleSubmit}
>
Add Property
</button>
</div>
{renderProperties()} // <-- Here
</div>
https://reactjs.org/docs/lists-and-keys.html

How to know if form input is empty (React Hooks)

I have a form where I want to know if the input values ​​are empty when onSubmit, they are not sent. I have tried to do it through the if of handleInputChange but this isn't working:
const handleInputChange = (e: React.FormEvent<HTMLInputElement>) => {
if ((e.target as HTMLInputElement).value) {
setNewPost({
...newPost,
[(e.target as HTMLInputElement).name]: (e.target as HTMLInputElement).value
})
}
e.preventDefault();
};
All the code:
const New: React.FC = () => {
// const [newPost, setNewPost] = useState("");
const [newPost, setNewPost] = useState({
title: '',
author: '',
content: ''
})
const handleInputChange = (e: React.FormEvent<HTMLInputElement>) => {
if ((e.target as HTMLInputElement).value) {
setNewPost({
...newPost,
[(e.target as HTMLInputElement).name]: (e.target as HTMLInputElement).value
})
}
e.preventDefault();
};
const createPost = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault(); //Detiene el formulario para que no actualize la página
setPost(newPost)
}
return (
<div className="containerHomepage">
<form className="formulari" onSubmit={createPost}>
<div className="containerBreadCrumb">
<ul className="breadCrumb">
<li>Posts</li>
</ul>
</div>
<div className="containerTitleButton">
<input
className=""
type="text"
placeholder='Post title'
name="title"
onChange={handleInputChange}
></input>
<button
className="button"
type="submit"
>Save</button>
</div>
<div className="containerEdit">
<input
className=""
type="text"
placeholder='Author'
name="author"
onChange={handleInputChange}
></input>
<input
className=""
type="text"
placeholder='Content'
name="content"
onChange={handleInputChange}
></input>
</div>
</form>
</div>
);
};
// ========================================
export default New;
Your current handleInputChange makes it so that the user cannot change any input to an empty string. There's a major usability flaw here. Once the user types the first character, they cannot delete it! You should allow the inputs to be empty, but disallow submitting the form unless all fields are filled out.
You can use e.currentTarget instead of e.target to avoid a lot of type assertions. There is more information in this question, but what's important here is that e.currentTarget will always be the HTMLInputElement.
const handleInputChange = (e: React.FormEvent<HTMLInputElement>) => {
setNewPost({
...newPost,
[e.currentTarget.name]: e.currentTarget.value
});
};
#rax's answer is on the right track, but I'm going to go further down that path.
An any point in time, you can know whether the form is valid or not by looking at the current state of newPost. There are lots of ways to write this, which all do the same thing:
const isValid = Boolean(newPost.title && newPost.author && newPost.content);
Using type coercion. All strings are truthy except for the empty string.
const isValid = newPost.title !== '' && newPost.author !== '' && newPost.content !== '';
From #Vladimir Trotsenko's answer.
const isValid = Object.values(newPost).every(value => value.length > 0)
Looping over all values of newPost so you don't need to change anything if you add an extra field.
You can use this isValid variable to conditionally disable the "Save" button.
<button type="submit" disabled={!isValid}>Save</button>
You can also use isValid to show messages or other visible feedback to the user. For example, you can show a message when hovering over the disabled button which tells them why it has been disabled.
<button
type="submit"
disabled={!isValid}
title={isValid ? "Create your post" : "All fields must be filled out."}
>
Save
</button>
I'm checking if (isValid) in the createPost function to be on the safe side, but I believe that this is not actually necessary as the form won't be submitted (even when hitting Enter) if the submit button is disabled.
const createPost = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault(); // stop the form from reloading the page.
if (isValid) {
// put your actual code here instead.
alert("submit success");
}
};
Complete code:
import React, { useState } from "react";
const New: React.FC = () => {
const initialState = {
title: "",
author: "",
content: ""
};
const [newPost, setNewPost] = useState(initialState);
const isValid = Boolean(newPost.title && newPost.author && newPost.content);
const handleInputChange = (e: React.FormEvent<HTMLInputElement>) => {
setNewPost({
...newPost,
[e.currentTarget.name]: e.currentTarget.value
});
};
const createPost = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault(); //stop the form from reloading the page
if (isValid) {
alert("submit success");
}
};
return (
<div className="containerHomepage">
<form className="formulari" onSubmit={createPost}>
<div className="containerBreadCrumb">
<ul className="breadCrumb">
<li>Posts</li>
</ul>
</div>
<div className="containerTitleButton">
<input
className=""
type="text"
placeholder="Post title"
name="title"
onChange={handleInputChange}
value={newPost.title}
/>
<button
className="button"
type="submit"
disabled={!isValid}
title={
isValid ? "Create your post" : "All fields must be filled out."
}
>
Save
</button>
</div>
<div className="containerEdit">
<input
className=""
type="text"
placeholder="Author"
name="author"
onChange={handleInputChange}
value={newPost.author}
/>
<input
className=""
type="text"
placeholder="Content"
name="content"
onChange={handleInputChange}
value={newPost.content}
/>
</div>
</form>
</div>
);
};
export default New;
CodeSandbox
You can compare your input value to empty string like that :
inputValue === ''
or the size of the string :
inputValue.length === 0
And check the value in if statement with inside your submit.
You can validate the empty field inside createPost which should look something like:
const createPost = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault(); //stop the form from reloading the page
if (!newPost.title || !newPost.author || !newPost.content) {
//show some error message
} else {
//perform further action
setPost(newPost);
}
}
For a full working example click here.
When you try to submit, firstly, you need to check whether input values are present. In order to do that you check each input value to equal empty string.
For a better visualization, create variable that would be responsible for that:
const isValid = newPost.title !== '' && newPost.author !== '' && newPost.content !== ''
Now, if isValid is true, we submit the form, if not, we do not.
const createPost = (e) => {
e.preventDefault()
if (isValid) {
// make api request
} else {
// handle empty fields
}
}

input text field for a particular item in array while mapping?

Im trying to map over an array and trying to update a particular item based on a button click.
<div className="read-data">
{readData.length >= 1 ? readData.map((res)=>(
<div>
<h2>{res.data.message}</h2>
<button onClick={()=>{
(setUpdateBox(true))
}}>
Update
</button>
{updateBox ? (
<div>
<form>
I<input type="text" name="textarea" placeholder="Update message"/>
</form>
</div>
):null}
<button onClick={()=>{
deleteMessage(res)}}> Delete</button>
</div>
)):null}
</div>
it opens text area for every item in array. but i want input text box for the only item whos corresponding button is clicked.
plz help how to do it.
you may find that solution useful for you.
you need to set a unique index or any identifier to every element.
on every update or delete operation, you can access the element by its index
reference the input to get the value by using ref
I assume that you pass an array of object as a props
const readData = [
{
data: {
message: "new message 1"
}
},
{
data: {
message: "new message 2"
}
},
{
data: {
message: "new message 3"
}
},
{
data: {
message: "new message 4"
}
}
];
ReadData Component
function ReadDataComponent({readData}) {
let inputRef = React.useRef();
const [updateBox, setUpdateBox] = React.useState(readData);
const deleteMessage = (i) => {
const updatedBox = [...updateBox];
let filteredBox = updatedBox.filter((_, index) => i !== index);
setUpdateBox(filteredBox);
};
const showUpdateBox = (i) => {
const updatedBox = [...updateBox];
updatedBox[i].isOpen = updatedBox[i].isOpen ? !updatedBox[i].isOpen : true;
updatedBox[i].data.message = inputRef.current
? inputRef.current.value
: updatedBox[i].data.message;
setUpdateBox(updatedBox);
};
return (
<div className="read-data">
{readData.length >= 1
? updateBox.map((res, i) => (
<div key={i}>
<h2>{res.data.message}</h2>
<button
onClick={() => {
showUpdateBox(i);
}}
>
Update
</button>
{res.isOpen ? (
<div>
<form>
<input
ref={inputRef}
type="text"
name="textarea"
placeholder="Update message"
/>
</form>
</div>
) : null}
<button
onClick={() => {
deleteMessage(i);
}}
>
{" "}
Delete
</button>
</div>
))
: null}
</div>
);
}
live demo
You could do something like this. Not sure if you want to use state or props or both etc.
Have a updateBox property on every object in the array, and toggle it between true and false.
You should probably add another function to update the message property as the user types in the input box?
function App(){
function setUpdateBox(id){
const copyState = [...readData];
const thisItem = copyState.find(x => x.id == id);
thisItem.updateBox = !thisItem.updateBox;
setData(copyState);
}
function deleteMessage(id){
const copyState = [...readData].filter(x => x.id != id);
setData(copyState);
}
const initData = [
{id: 1, updateBox: true, data: {message: "hello"}},
{id: 2, updateBox: true, data: {message: "world"}},
]
const [readData, setData] = React.useState(initData);
return (
<div className="read-data">
{readData.length >= 1 ? readData.map((res)=>(
<div>
<h2>{res.data.message}</h2>
<button onClick={()=>setUpdateBox(res.id)}>
Update
</button>
{res.updateBox ? (
<div>
<form>
<input type="text" name="textarea" placeholder="Update message"/>
</form>
</div>
):null}
<button onClick={()=>deleteMessage(res.id)}> Delete</button>
</div>
)):null}
</div>
);
}
ReactDOM.render(<App/>, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>

How to get data from cloud firestore then filter it, map it then return it?

I am trying to get data from the firestore and then filter it then map it like so:
return Inventory
.filter(x =>x["sub_category"] === item && x["category"] === category)
.map(({id, item, price, quantity, image})=>{
//sets the default value for the item with that specific id
const defVal = parseInt((selectedQty.id === id)?selectedQty.qty:0)
return (
<React.Fragment key={uuid()}>
<div className="card">
<img src={image()} alt={item} />
<p>{item}</p>
<div className="innerBox">
<div className="dropdown">
<label htmlFor="quantity">Qty:</label>
<select id="quantity" defaultValue={defVal===0?1:defVal} onChange={e => {
e.preventDefault()
setSelectedQty({id, qty: parseInt(e.target.value)})
}}>
{
Array(quantity).fill().map((_, i)=> {
if(i===0){
return <option key={uuid()} value={0}>-</option>
}else{
return <option key={uuid()} value={i} >{i}</option>
}
})
}
</select>
</div>
<b>$ {price}</b>
</div>
<button type="submit" onClick={()=> {
addToCart(id, item, parseInt(finalQty), price, image(), parseInt(finalQty)*parseFloat(price))
setSelectedQty({id:null, qty: 0})
}}>Add to Cart</button>
</div>
</React.Fragment>
)})
Currently I am using Inventory Array but I want to switch to firestore but I have no clue how to do it. I am aware of the step db.collection().get().then(etc...) but i don't know how to map it to return it inside the Then
When you fetch data from cloud firestore, it returns a document / collection snapshot.
Document:
db.collection("colName").doc("docID").get().then(docSnapShot=>{
const docID = docSnapShot.id;
const docData = docSnapShot.data();
})
Collection:
db.collection("colName").get().then(colSnapShot=>{
const isEmpty = colSnapShot.empty;
const docsData = colSnapShot.docs.forEach(docSnapShot=>{
return docSnapShot.data();
})
})
I believe your solution will look something like this:
let arrOfDocs = await db.collection("colName").get().then(colSnapShot=>{
return colSnapShot.docs.map(docSnapShot=>{
return docSnapShot.data();
})
})
Note that to listen to live updates, replace get().then(snapshot=>{}) with onSnapshot(snapshot=>{})

Categories

Resources