In my app I am adding new product. But after I click second time it appears on a list. In AddProducts I console.log the array with changed list and it had been updated. But in mu ProductList it doesnt work after click Add so I apply componentDidUpdate to react on changes. Also I use useEffect in App to react on changes sending from AddProduct but it works only I click second times.
AddProduct
function AddProducts(props) {
const [newProduct, setNewProduct] = useState({
productName: ``,
category: ``,
groceries: false,
});
const [newList, setNewList] = useState(props.productsToDisplay);
function handleChange(event) {
event.preventDefault();
setNewProduct({
...newProduct,
[event.target.name]:
event.target.type === "checkbox"
? event.target.checked
: event.target.value,
});
}
function handleAddNewProduct() {
const addProduct = newList.concat([
{
nazwa: newProduct.productName,
kategoria: newProduct.category,
produktSpozywczy: newProduct.groceries,
},
]);
setNewList(addProduct);
props.sendNewProductsToParent(newList);
}
return (
<div className={styles.Wrapper}>
{console.log(newList)}
<p>Add products</p>
<input
name="productName"
value={newProduct.productName}
onChange={handleChange}
></input>
<input
name="category"
value={newProduct.category}
onChange={handleChange}
></input>
<input
name="groceries"
type="checkbox"
value={newProduct.groceries}
onChange={handleChange}
></input>
<p>Is it groceries?</p>
<button onClick={handleAddNewProduct}>Add new product</button>
</div>
);
}
ProductList
class ProductsList extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div className={commonColumnsStyles.App}>
{console.log(this.props.productsToDisplay)}
<header className={commonColumnsStyles.AppHeader}>
<p>Products list</p>
<ul>
{this.props.productsToDisplay.map((currProduct, index) => (
<li key={index} onClick={() => this.addProduct(index)}>
{currProduct.nazwa}
</li>
))}
</ul>
</header>
</div>
);
}
}
function App() {
const [resultToDisplay, setResultToDisplay] = useState(``);
const [newProductsList, setNewProductsList] = useState(produkty);
return (
<div className={styles.appWrapper}>
{console.log(newProductsList)}
<AddProducts
productsToDisplay={produkty}
sendNewProductsToParent={setNewProductsList}
/>
<div className={styles.columnsWrapper}>
<ProductsList
productsToDisplay={newProductsList}
sendAddedProductsToParent={setResultToDisplay}
/>
</div>
</div>
);
}
Solution for community:
AddProduct
function AddProducts(props) {
const [newProduct, setNewProduct] = useState({
productName: ``,
category: ``,
groceries: false,
});
function handleChange(event) {
event.preventDefault();
setNewProduct({
...newProduct,
[event.target.name]:
event.target.type === "checkbox"
? event.target.checked
: event.target.value,
});
}
function handleAddNewProduct() {
const addProduct = {
nazwa: newProduct.productName,
kategoria: newProduct.category,
produktSpozywczy: newProduct.groceries,
};
props.sendNewProductToParent((listOfPrimiaryProducts) => [
...listOfPrimiaryProducts,
addProduct,
]);
}
return (
<div className={styles.Wrapper}>
<p>Add products</p>
<input
name="productName"
value={newProduct.productName}
onChange={handleChange}
></input>
<input
name="category"
value={newProduct.category}
onChange={handleChange}
></input>
<input
name="groceries"
type="checkbox"
value={newProduct.groceries}
onChange={handleChange}
></input>
<p>Is it groceries?</p>
<button onClick={handleAddNewProduct}>Add new product</button>
</div>
);
}
export default AddProducts;
ProductList
class ProductsList extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div className={commonColumnsStyles.App}>
<header className={commonColumnsStyles.AppHeader}>
<p>Products list</p>
<ul>
{this.props.productsInShop.map((currProduct, index) => (
<li key={index} onClick={() => this.addProduct(index)}>
{currProduct.nazwa}
</li>
))}
</ul>
</header>
</div>
);
}
}
export default ProductsList;
App
function App() {
const [productsList, setProductsList] = useState(produkty);
return (
<div className={styles.appWrapper}>
<AddProducts sendNewProductToParent={setProductsList} />
<div className={styles.columnsWrapper}>
<ProductsList
productsInShop={productsList}
/>
</div>
</div>
);
}
export default App;
Related
I have a problem with the access of the input value, I used here is .map().
Here is the code, <QuestionLabel/>is the children component. projectsData.projectDetail is an available data
//..
{projectsData.projectDetail.questions.map((question) => (
<QuestionLabel
questionTitle={question}
/>
))}
//child component
const QuestionLabel=(props)=>{
const [answerInput, setAnswerInput] = React.useState("");
return(
<div className="contact">
<form className="contact-form" autocomplete="off">
<div class="contact-form-group">
<label
for="name"
class="contact-form-label"
>
{props.questionTitle}
</label>
<input
id="name"
type="text"
class="contact-form-input"
value={answerInput}
onChange={(answer) => setAnswerInput(answer.target.value)}
/>
</div>
</form>
</div>
);
}
export default QuestionLabel;
There are many way to get value from child component for parent component. You can call a function pass from parent to children to set value for parent state when it's changed. Exmaple:
const ParentComponent =(props)=>{
const [valueFromChild, setValueFromChild] = useState('');
return <>
{valueFromChild}
<QuestionLabel questionTitle={'Title'} setValueFromChild={setValueFromChild}/>
</>
}
const QuestionLabel=(props)=>{
const [answerInput, setAnswerInput] = React.useState("")
useEffect(() => {
props.setValueFromChild(answerInput);
}, [answerInput]);
return(
<div className="contact">
<form className="contact-form" autoComplete="off">
<div class="contact-form-group">
<label
for="name"
class="contact-form-label"
>
{props.questionTitle}
</label>
<input
id="name"
type="text"
class="contact-form-input"
value={answerInput}
onChange={(answer) => setAnswerInput(answer.target.value)}
/>
</div>
</form>
</div>
);
}
So you need to have the input state up in the parent.
Since you are mapping through questions array to render the QuestionLabel we can try this...
//..
const ParentComponent = () => {
const [answers, setAnswers] = React.useState({})
useEffect(() => {
projectsData.projectDetail.questions.forEach((_, i) => {
setAnswers(previousAnswers => {...previousAnswers, ["name" + i]: ""})
})
}, [])
const handleAnswers = (e) => {
setAnswers(previousAnswers => {...previousAnswers, [e.target.name]: [e.target.value]})
}
//..
then
//..
{
projectsData.projectDetail.questions.map((question, i) => {
return (
<QuestionLabel questionTitle={question} inputName={"name" + i} answers={answers} handleAnswers={handleAnswers} />
)
})
}
and finally...
//child component
const QuestionLabel=(props)=>{
return(
<div className="contact">
<form className="contact-form" autocomplete="off">
<div class="contact-form-group">
<label
for={props.inputName}
class="contact-form-label"
>
{props.questionTitle}
</label>
<input
id={props.inputName}
name={props.inputName}
type="text"
class="contact-form-input"
value={answers[props.inputName]}
onChange={handleAnswers}
/>
</div>
</form>
</div>
);
}
export default QuestionLabel;
I am created a dynamic form in react.js but i can not type anything value in input because onchnage function not working i don't know why i tried a lot of times but i am getting failed and adding form and deleting form is working all right only input value not working here is my code and codesandbox link https://codesandbox.io/s/reactdynamicform-02cho .
import React, { Component } from "react";
import "bootstrap/dist/css/bootstrap.css";
class App extends Component {
constructor(props) {
super(props);
this.state = {
inputFields: [
{
firstName: "",
lastName: ""
}
]
};
}
handleAddFields = () => {
const values = this.state.inputFields;
values.push({ firstName: "", lastName: "" });
this.setState({
values
});
};
handleRemoveFields = index => {
const values = this.state.inputFields;
values.splice(index, 1);
this.setState({
values
});
};
async onChange(e, index) {
if (
["firstName","lastName"].includes(e.target.name)
) {
let cats = [...this.state.inputFields];
cats[index][e.target.name] = e.target.value;
await this.setState({
cats
});
}else{
this.setState({ [e.target.name]: e.target.value.toUpperCase() });
}
console.log(this.state.inputFields);
}
handleSubmit = e => {
e.preventDefault();
console.log("inputFields", this.state.inputFields);
};
render() {
return (
<>
<h1>Dynamic Form Fields in React</h1>
<form onSubmit={this.handleSubmit.bind(this)}>
<div className="form-row">
{this.state.inputFields.map((inputField, index) => (
<div key={`${inputField}~${index}`}>
<div className="form-group col-sm-6">
<label htmlFor="firstName">First Name</label>
<input
type="text"
className="form-control"
id="firstName"
name="firstName"
value={inputField.firstName}
onChange={this.onChange.bind(index)}
/>
</div>
<div className="form-group col-sm-4">
<label htmlFor="lastName">Last Name</label>
<input
type="text"
className="form-control"
id="lastName"
name="lastName"
value={inputField.lastName}
onChange={this.onChange.bind(index)}
/>
</div>
<div className="form-group col-sm-2">
<button
className="btn btn-link"
type="button"
onClick={() => this.handleRemoveFields(index)}
>
-
</button>
<button
className="btn btn-link"
type="button"
onClick={() => this.handleAddFields()}
>
+
</button>
</div>
</div>
))}
</div>
<div className="submit-button">
<button
className="btn btn-primary mr-2"
type="submit"
// onSubmit={this.handleSubmit}
>
Save
</button>
</div>
<br />
<pre>{JSON.stringify(this.state.inputFields, null, 2)}</pre>
</form>
</>
);
}
}
export default App;
You approach is not the correct. Use object to contain form values
state = {
inputFields: { firstName: '', lastName: '' }
}
onChange = (e) => {
const { name, value } = e.target;
this.setState(prevState => ({ inputFields: { ...prevState.inputFields, [name]: value } }));
}
// in jsx
<input name="firstName" onChange={this.onChange} />
try this
onChange={(e)=>{this.onChange(e, index)}}
instead of
onChange={this.onChange.bind(index)}
1) Since your inputFields state is an array, you can't just call this.state.inputFields.firstName and even less inputField.firstName.
You have to call this.state.inputsFields[0].firstName.
2) If you want the index AND the event, you have to pass the onChange event like this :
<input
type="text"
className="form-control"
id="lastName"
name="lastName"
onChange={event => this.handleChange(event, index)}
/>
handleChange = (event, index) => {
console.log(event.currentTarget.value, index);
};
// output : {whatever you type} {index of the form}
// exemple : "hello 1"
I have this code, but I can't make it work. The input lines simply won't accept anything. I tried searching all over the place to no avail, so i decided to finally ask the question.
P.S. I am new to react
class App extends React.Component {
state = { inputValue: [{item:'', name:''}] }
handleChange = e => {
const newValue = [...this.state.inputValue];
newValue[0][e.target.name] = e.target.value;
this.setState({inputValue: newValue});
}
render(){
return(
<div className='container jumbotron'>
<div className="row">
<div className="col">
<FirstInput handleChange={this.handleChange} inputValue={this.state.inputValue[0].name}/>
</div>
<div className="col">
<SecondInput handleChange={this.handleChange} inputValue={this.state.inputValue[0].name}/>
</div>
</div>
</div>
);
}
}
const FirstInput = (props) => (
<div>
<label>First Input</label>
<input className="form-control" onChange={props.handleChange} value={props.inputValue}/>
</div>
)
const SecondInput = ({inputValue, handleChange}) => (
<div>
<label>Second Input</label>
<input className="form-control" onChange={handleChange} value={inputValue}/>
</div>
)
ReactDOM.render(<App />, document.getElementById('root'));
Sorry, I forgot to mention that I want to maintain the array as an array of object. The goal is to have first input and second input be the same value. Meaning, changing one input will make the other input the same.
You don't have name attribute defined on your Input elements and hence the value doesn't change. Update your code to
class App extends React.Component {
state = { inputValue: [{ item: '', name: '' }, { item: '', name: '' }] }
handleChange = e => {
const newValue = [...this.state.inputValue];
newValue[0][e.target.name] = e.target.value;
this.setState({ inputValue: newValue });
}
render() {
return (
<div className='container jumbotron'>
<div className="row">
<div className="col">
<FirstInput handleChange={this.handleChange} inputValue={this.state.inputValue[0].name} />
</div>
<div className="col">
<SecondInput handleChange={this.handleChange} inputValue={this.state.inputValue[0].item} />
</div>
</div>
</div>
);
}
}
const FirstInput = (props) => (
<div>
<label>First Input</label>
<input className="form-control" name="name" onChange={props.handleChange} value={props.inputValue} />
</div>
)
const SecondInput = ({ inputValue, handleChange }) => (
<div>
<label>Second Input</label>
<input className="form-control" name="item" onChange={handleChange} value={inputValue} />
</div>
)
You are overwriting your state. inputValue: [{item:'', name:''}] is an array, and handleChange you try to assign string value.
Your code should look like this one:
class App extends React.Component {
state = {
firstInput: '',
secondInput: ''
}
handleChange = e => {
this.setState({
[e.target.name]: e.target.value;
});
}
render(){
return(
<div className='container jumbotron'>
<div className="row">
<div className="col">
<Input
label="First Input"
name="firstInput"
handleChange={this.handleChange}
inputValue={firstInput}/>
</div>
<div className="col">
<Input
label="First Input"
name="secondInput"
handleChange={this.handleChange}
inputValue={secondInput}/>
</div>
</div>
</div>
);
}
}
const Input = (props) => (
<div>
{props.label && <label>{props.label}</label>}
<input
className="form-control"
onChange={props.handleChange}
name={props.name}
value={props.inputValue}/>
</div>
)
ReactDOM.render(<App />, document.getElementById('root'));
class Form extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
const { Add, noteList } = this.props;
Add('this is title's value' , 'this is content's value');
}
render() {
const { handleSubmit, noteList: { list } } = this.props;
return (
<form onSubmit={handleSubmit(this.handleClick)}>
<div>
<Field className="title" name="title" component="input" type="text" />
</div>
<div>
<Field className="content" name="content" component="textarea" />
</div>
<div>
<button type="submit" onClick={(e) => { list ? this.handleClick : e.preventDefault(); }}>add</button>
</div>
</form>
);
}
}
when i click the button, i hope to get these two values into a function Add as two arguments to do something async, what should i do , help me please
The field values in handleClick function can can be obtained onSubmit as an object.
class Form extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick(values) {
const { Add, noteList } = this.props;
Add(values.title , values.content);
}
render() {
const { handleSubmit, noteList: { list } } = this.props;
return (
<form onSubmit={handleSubmit(this.handleClick)}>
<div>
<Field className="title" name="title" component="input" type="text" />
</div>
<div>
<Field className="content" name="content" component="textarea" />
</div>
<div>
<button type="submit" onClick={(e) => { list ? this.handleClick : e.preventDefault(); }}>add</button>
</div>
</form>
);
}
}
How to set defaultValue to input component?
<Field name={`${prize}.rank`} defaultValue={index} component={Input} type='text'/>
I tried like above but my fields are empty. I'm trying to create fieldArray (dynamic forms):
{fields.map((prize, index) =>
<div key={index} className="fieldArray-container relative border-bottom" style={{paddingTop: 35}}>
<small className="fieldArray-title marginBottom20">Prize {index + 1}
<button
type="button"
title="Remove prize"
className="btn btn-link absolute-link right"
onClick={() => fields.remove(index)}>Delete</button>
</small>
<div className="row">
<div className="col-xs-12 col-sm-6">
<Field name={`${prize}.rank`} defaultValue={index} component={Input} type='text'/>
<Field name={`${prize}.prizeId`} defaultValue={index} component={Input} type='text'/>
<Field
name={`${prize}.name`}
type="text"
component={Input}
label='Prize Name'/>
</div>
<div className="col-xs-12 col-sm-6">
<Field
name={`${prize}.url`}
type="text"
component={Input}
label="Prize URL"/>
</div>
<div className="col-xs-12">
<Field
name={`${prize}.description`}
type="text"
component={Input}
label="Prize Description" />
</div>
</div>
</div>
)}
On redux forms you can call initialize() with an object of values like so:
class MyForm extends Component {
componentWillMount () {
this.props.initialize({ name: 'your name' });
}
//if your data can be updated
componentWillReceiveProps (nextProps) {
if (/* nextProps changed in a way to reset default values */) {
this.props.destroy();
this.props.initialize({…});
}
}
render () {
return (
<form>
<Field name="name" component="…" />
</form>
);
}
}
export default reduxForm({})(MyForm);
This way you can update the default values over and over again, but if you just need to do it at the first time you can:
export default reduxForm({values: {…}})(MyForm);
This jsfiddle has an example
https://jsfiddle.net/bmv437/75rh036o/
const renderMembers = ({ fields }) => (
<div>
<h2>
Members
</h2>
<button onClick={() => fields.push({})}>
add
</button>
<br />
{fields.map((field, idx) => (
<div className="member" key={idx}>
First Name
<Field name={`${field}.firstName`} component="input" type="text" />
<br />
Last Name
<Field name={`${field}.lastName`} component="input" type="text" />
<br />
<button onClick={() => fields.remove(idx)}>
remove
</button>
<br />
</div>
))}
</div>
);
const Form = () => (
<FieldArray name="members" component={renderMembers} />
);
const MyForm = reduxForm({
form: "foo",
initialValues: {
members: [{
firstName: "myFirstName"
}]
}
})(Form);
this is my implementation using a HoC
import { Component } from 'react'
import {
change,
} from 'redux-form'
class ReduxFormInputContainer extends Component{
componentDidMount(){
const {
initialValue,
meta,
} = this.props
if(initialValue === undefined || meta.initial !== undefined || meta.dirty) return
const {
meta: { form, dispatch },
input: { name },
} = this.props
dispatch(change(form, name, initialValue))
}
render(){
const {
initialValue,
component: Compo,
...fieldProps
} = this.props
return <Compo {...fieldProps} />
}
}
function reduxFormInputContainer(component){
return function(props){
return <ReduxFormInputContainer {...props} component={component} />
}
}
export default reduxFormInputContainer
and then for exemple:
import reduxFormInputContainer from 'app/lib/reduxFormInputContainer'
InputNumericWidget = reduxFormInputContainer(InputNumericWidget)
class InputNumeric extends Component{
render(){
const props = this.props
return (
<Field component={InputNumericWidget} {...props} />
)
}
}