How to create input field dynamically in react? - javascript

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!

Related

onClick function is not called after I have enabled the button in Reactjs

I have a textarea and a button. The button is disabled by default and when the user starts typing, I enable the button to be clicked. But the problem is that, the onClick function is not called while already disabled = false was set.
I've seen this: button onClick doesn't work when disabled=True is initialized (Reactjs)
Seems to be a good idea, but after I setState with the new value, my component is re-rendering, and I don't really want that.
const refText = useRef(null);
const refBtn = useRef(null);
function handleBtnStatus(e) {
let text = e.target.value;
if(text.replace(/\s/g, "").length > 0) {
refBtn.current.disabled = false;
}
else {
refBtn.current.disabled = true;
}
}
function postThis() {
console.log("You posted! Text:", refText.current.value);
// disable again
refBtn.current.disabled = true;
// delete previous text wrote
refText.current.value = "";
}
return (
<>
{isLogged && (
<div className="container">
<div className="content">
<div className="utool-item-text">
<textarea name="textArea" placeholder="Write something.." ref={refText} onChange={(e) => handleBtnStatus(e)}></textarea>
</div>
<div className="utool-item-post">
<button className="ust-btn-post" ref={refBtn} disabled={true} onClick={postThis}>Da Tweet</button>
</div>
</div>
<div className="posts-section">
<div className="list-posts">
{posts.map((p) => {
return (p.hidden === false ? (
<div className="post" key={p.id}>
<div className="post-text">
<span>{p.text}</span>
</div>
</div>
) : (''))
})}
</div>
</div>
</div>
)}
</>
)
Any help?
Use state instead of refs, re-rendering is ok for your case
Simplified example:
import React, { useState } from 'react';
const SimpleExample = () => {
const [textAreaValue, setTextAreaValue] = useState('');
return (
<>
<button disabled={!textAreaValue} onClick={() => console.log('onClick handler')}>
click me
</button>
<textarea value={textAreaValue} onChange={(e) => setTextAreaValue(e.target.value)} />
</>
);
};
And I would recommend checking this Use state or refs in React.js form components?

react - map is not a function

so i need to uplift state from SingleRowComponent to UpdateMessageBoxComponent, in which i want to update the payload, which looks like this
[
{
"id":80,
"title":"nowe",
"content": [
{
"id":159,
"checked":true,
"content":"cwelowe"
},
{
"id":160,
"checked":false,
"content":"guwno"
},
{
"id":161,
"checked":true,
"content":"jeabne"
}
]
}
]
i want to achieve that by adding a count prop in box.content.map()
export default function UpdateMessageBoxComponent(props) {
let [state, setState] = useState([])
useEffect(() => {
console.log(props.id);
RestService.getMessage(props.id).then(res => {
setState(res.data)
})
}, [props])
function handleRowState(i, data) {
let copy = state
copy[i] = data
setState(copy)
}
return (
state.map(box => (
<div key={box.id}>
<div>
<label htmlFor="title">Podaj nazwę</label>
<input type="text" name="title" id="title" value={box.title}
onChange={e => setState([{ title: e.target.value, id: box.id, content: box.content }])} />
</div>
<div className="additional">
{
----------------- box.content.map((row, idx) => ( // THIS ONE HERE -------------------------
<SingleRowComponent key={row.id} count={idx} onRowChange={handleRowState} state={row}/>
))
}
</div>
<div className="buttons">
<button onClick={() => {console.log(state); RestService.updateMessage(box.id, state) }}>add</button>
<button onClick={() => {
}}>finish</button>
</div>
</div>
))
)
}
the issue is that an box.content.map is not a function error is thrown, when i don't use the count prop, everything works fine (except the fact that i don't actually update the state in the payload). i don't understand what's the issue here
function SingleRowComponent(props) {
let payload;
if (typeof props.state !== 'undefined')
payload = {id:props.state.id, content:props.state.content, checked: props.state.checked}
else
payload = {content:'', checked: false}
let [values, setValues] = useState(payload)
useEffect(() => {
props.onRowChange(props.count, values)
})
//uplifting state
function changeCheckedHandler(e) {
setValues({content:values.content,checked:e.target.checked})
props.onRowChange(props.count, values)
}
function changeContentHandler(e) {
setValues({content:e.target.value, checked:values.checked})
props.onRowChange(props.count, values)
}
return (
<div key={props.count}>
<div>
<label htmlFor="content">Podaj treść</label>
<textarea name="content" id="content" cols="30" rows="10" value={values.content} onChange={changeContentHandler}></textarea>
</div>
<div>
<label htmlFor="checked">checked</label>
<input type="checkbox" name="checked" id="checked" checked={values.checked} onChange={changeCheckedHandler}/>
</div>
</div>
)
}
EDIT 1
i noticed that box after a few iterations becomes what row should be, hence the errors
the issue was the handleRowState function inside UpdateMessageBoxComponent wrongfully assigning the inner content from single row as the entire state
not working
function handleRowState(i, data) {
let copy = state
copy[i] = data
setState(copy)
}
solution
function handleRowState(i, data) {
let copy = state
copy[0][i] = data // <--------- here, the updated state is a 2-dimensional array
setState(copy)
}
You have your object wrapped in an Array, you need to call
box[0].content.map()

How do I simplify this piece of React code? (Progressive input form)

The purpose of the sample of React code below is to create progressive form which only displays one question to begin with and then reveals the next question when the user clicks a button (while keeping the previous questions visible). The actual piece of code I've written contains 12 questions - the number, content and order of which could change over time. Currently the code works but it's very long and difficult to update and so I'm sure there must be a better more dynamic way of doing this. One way I've experimented with is having a sperate .js file containing an array of variables for each question but I've not been able to keep the "progressive" aspect of the form working with is method.
Any thoughts or advice would be greatly appreciated!
import React, { useState, useRef } from 'react';
import { useNavigate } from 'react-router-dom';
import classes from './InputForm2.module.css';
const InputForm2 = () => {
const dateOfBirthInputRef = useRef();
const membershipTypeInputRef = useRef();
const dateOfJoiningInputRef = useRef();
let navigate = useNavigate();
function routeChange() {
let path = '/results';
navigate(path);
}
function dataHandler() {
const enteredDateofBirth = dateOfBirthInputRef.current.value;
const enteredMembershipType = membershipTypeInputRef.current.value;
const enteredDateOfJoining = dateOfJoiningInputRef.current.value;
const inputData = {
dateOfBirth: enteredDateofBirth,
membershipType: membershipTypeRef,
dateOfJoining: enteredDateOfJoining,
};
console.log(inputData);
}
function submitHandler(event) {
event.preventDefault();
dataHandler();
routeChange();
}
const [q2IsOpen, setQ2IsOpen] = useState(false);
const [q3IsOpen, setQ3IsOpen] = useState(false);
const btn1ClickHandler = (event) => {
event.preventDefault();
setQ2IsOpen(true);
};
const btn2ClickHandler = (event) => {
event.preventDefault();
setQ3IsOpen(true);
};
const btn3ClickHandler = (event) => {
event.preventDefault();
setQ4IsOpen(true);
};
return (
<div className={classes.formbox}>
<form>
<section className={`${classes.active}`}>
<div className={classes.textbox}>
<b>Question 1</b>
<p>What is your date of birth?</p>
<input
className={classes.input}
type="date"
required
ref="dateOfBirthInputRef"
></input>
</div>
<div className={classes.btn__container}>
<button className={classes.button} onClick={btn1ClickHandler}>
Next
</button>
</div>
</section>
<section className={`${q2IsOpen ? classes.active : classes.inactive}`}>
<div className={classes.textbox}>
<b>Question 2</b>
<p>
What is your membershiptype?
</p>
<select
className={classes.input}
required
ref="membershipTypeInputRef"
>
<option></option>
<option value="Platinum">Platinum</option>
<option value="Gold">Gold</option>
<option value="Basic">Basic</option>
</select>
</div>
<div className={classes.btn__container}>
<button className={classes.button} onClick={btn2ClickHandler}>
Next
</button>
</div>
</section>
<section className={`${q3IsOpen ? classes.active : classes.inactive}`}>
<div className={classes.textbox}>
<b>Question 3</b>
<p>What date did you start your membership?</p>
<input
className={classes.input}
type="date"
required
ref="dateOfJoiningInputRef"
></input>
</div>
<div className={classes.btn__container}>
<button className={classes.button} onClick={btn3ClickHandler}>
Next
</button>
</div>
</section>
<div className={classes.btn__container}>
<button
className={`${classes.submitbutton} ${
q4IsOpen ? classes.active : classes.inactive
}`}
onClick={submitHandler}
>
Calculate
</button>
</div>
</form>
</div>
);
};
export default InputForm2;
This isn't fully working code, just an idea of how you would structure this. As you suggested yourself, making an array of question objects is a good idea. You could also break out the question structure into it's own component:
const Question = ({thisIndex, currentIndex, title, text, ref, handler}) => {
return(
<section className={`${thisIndex >= currentIndex-1 ? classes.active : classes.inactive}`}>
<div className={classes.textbox}>
<b>{title}</b>
<p>{text}</p>
<input
className={classes.input}
type="date"
required
ref=ref
></input>
</div>
<div className={classes.btn__container}>
<button className={classes.button} onClick={handler}>
Next
</button>
</div>
</section>
)
}
The props input to Question component can be mapped from a QUESTIONS object array, assuming an object structure like {title: 'Question 1', text: 'Why are we here?', //and so on}.
I would keep track of visibility simply by keeping track of the index of the last answered question in the main form, and passing that in to each question in the array - that way it only becomes visible when the question before is answered.
const InputForm2 = () => {
const [currentIndex, setCurrentIndex] = useState(0);
//all your other code
const handler = (i) => {
setCurrentIndex(i)
//do more stuff
}
const questions = QUESTIONS.map((question, i) => {
return(
<Question key={i}
thisIndex=i currentIndex={currentIndex}
title={question.title} text={question.text}
handler={handler(i)}
//and so on
/>
)
});
return (
<div>{questions}</div>
)
}
Then, in your form you can return the questions array of components.
EDIT
In a similar way, since you have different types of input / select for different questions, you can make separate components and pass those as props to you question.
const QSelector = ({options}) => {
const optionElements = options.map((option, i) => {
return(
<option //get your data from option element
)
})
return(
<selector>
{options}
</selector>
)
}
Obviously for three different input types and three questions, this isn't super useful but as you go in, if you have 5 selector q's, 5 inputs, you'll find you can reuse a lot of structures, just passing in any data that is different.
Below, I'm updating the Question component to receive this as an 'answer' prop.
const Question = ({thisIndex, currentIndex, title, text, ref, handler, answer}) => {
return(
<section className={`${thisIndex >= currentIndex-1 ? classes.active : classes.inactive}`}>
<div className={classes.textbox}>
<b>{title}</b>
<p>{text}</p>
{answer}
</div>
<div className={classes.btn__container}>
<button className={classes.button} onClick={handler}>
Next
</button>
</div>
</section>
)
}

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=>{})

How to remove key from my input upon input reset?

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:[],
})
}

Categories

Resources