React Redux Form: form is submiting with old values - javascript

I have a FieldArray in Redux Form that I push objects inside this array and right after that I have a callback to trigger a function to make a POST request.
When I submit the form I get the old values, because the push() method of the Redux Form is an async dispach from redux.
// Parent component
<FieldArray
name="myObjects"
component={ChildComponent}
onSubmitObjects={this.onSubmit} />
// Function
onSubmit = async () => {
const { getFormValues } = this.props;
const data = {
myObjects: getFormValues.myObjects
}
try {
// const contact = await Service.updateUser(data);
} catch (e) {
console.log(e)
}
}
I need to submit the form with the new values added in the array right after the push method.
// Function inside ChildComponent
addNewObject = () => {
const { fields, onSubmitObjects} = this.props;
fields.push({
number: 1,
name: 'Foo',
});
if (onSubmitObjects) {
onSubmitObjects(); // cb() to trigger a function in the parent component
}
}
Is there a way to call the callback with the new values right after the push method?

You should use a form with redux-form handleSubmit to wrap your FieldArray. You can optionally pass your custom submit function (to make API requests, submit validation, etc.) to handleSubmit so it'd look like this <form onSubmit={handleSubmit(this.onSubmit)}> ...
See this example from redux-form official docs:
FieldArraysForm.js
import React from 'react'
import {Field, FieldArray, reduxForm} from 'redux-form'
import validate from './validate'
const renderField = ({input, label, type, meta: {touched, error}}) => (
<div>
<label>{label}</label>
<div>
<input {...input} type={type} placeholder={label} />
{touched && error && <span>{error}</span>}
</div>
</div>
)
const renderHobbies = ({fields, meta: {error}}) => (
<ul>
<li>
<button type="button" onClick={() => fields.push()}>Add Hobby</button>
</li>
{fields.map((hobby, index) => (
<li key={index}>
<button
type="button"
title="Remove Hobby"
onClick={() => fields.remove(index)}
/>
<Field
name={hobby}
type="text"
component={renderField}
label={`Hobby #${index + 1}`}
/>
</li>
))}
{error && <li className="error">{error}</li>}
</ul>
)
const renderMembers = ({fields, meta: {error, submitFailed}}) => (
<ul>
<li>
<button type="button" onClick={() => fields.push({})}>Add Member</button>
{submitFailed && error && <span>{error}</span>}
</li>
{fields.map((member, index) => (
<li key={index}>
<button
type="button"
title="Remove Member"
onClick={() => fields.remove(index)}
/>
<h4>Member #{index + 1}</h4>
<Field
name={`${member}.firstName`}
type="text"
component={renderField}
label="First Name"
/>
<Field
name={`${member}.lastName`}
type="text"
component={renderField}
label="Last Name"
/>
<FieldArray name={`${member}.hobbies`} component={renderHobbies} />
</li>
))}
</ul>
)
const FieldArraysForm = props => {
const {handleSubmit, pristine, reset, submitting} = props
return (
<form onSubmit={handleSubmit}>
<Field
name="clubName"
type="text"
component={renderField}
label="Club Name"
/>
<FieldArray name="members" component={renderMembers} />
<div>
<button type="submit" disabled={submitting}>Submit</button>
<button type="button" disabled={pristine || submitting} onClick={reset}>
Clear Values
</button>
</div>
</form>
)
}
export default reduxForm({
form: 'fieldArrays', // a unique identifier for this form
})(FieldArraysForm)

Related

React state change from within button's click handler is preventing form submission

Consider the code below:
function Item({name, _key})
{
console.log('rendering Item')
const [updatingName, setUpdatingName] = useState(false);
const nameInputElement = useRef();
useEffect(() => {
if (updatingName) {
nameInputElement.current.focus();
}
}, [updatingName]);
function onUpdateClick() {
setUpdatingName(true);
}
function onCancelClick() {
setUpdatingName(false);
}
return (
<div>
<input ref={nameInputElement} type="text" defaultValue={name} name="name"
disabled={!updatingName} />
{!updatingName
? <>
<button key={1} type="button" onClick={onUpdateClick}>Update</button>
<button key={2} type="submit" name="delete" value={_key}>Remove</button>
</>
: <>
<button key={3} type="submit" name="update" onClick={(e) => {setUpdatingName(false)}}>Save</button>
<button key={4} type="button" onClick={onCancelClick}>Cancel</button>
</>}
</div>
)
}
function ItemList({title})
{
return <>
<h1>{title}</h1>
<form method="post" onSubmit={(e) => {console.log('submitting');e.preventDefault()}}>
<Item name={'small'} _key={0} />
</form>
</>
}
export default ItemList;
The problem that I am facing is the click of the Save button. When it's clicked, as you can see, I trigger a state change. But at the same time, I also want the button to cause the underlying <form>'s submission.
(To check whether the form is submitted, I've prevented its default submit mechanism and instead gone with a simple log.)
However, it seems to be the case that when the state change is performed from within the onClick handler of the Save button, it ceases to submit the form. If I remove the state change from within the handler, it then does submit the form.
Why is this happening?
Live CodeSandbox demo
When you call setUpdatingName(false) in save button's click handler, the button is removed from the DOM before submitting. You can add the logic for showing the buttons in ItemList, like below:
function ItemList({ title }) {
const [updatingName, setUpdatingName] = useState(false);
return (
<>
<h1>{title}</h1>
<form
method="post"
onSubmit={(e) => {
e.preventDefault();
setUpdatingName(false);
console.log("submitting");
}}
>
<Item
name={"small"}
_key={0}
updatingName={updatingName}
setUpdatingName={setUpdatingName}
/>
</form>
</>
);
}
export default ItemList;
function Item({ name, _key, updatingName, setUpdatingName }) {
console.log("rendering Item");
const nameInputElement = useRef();
useEffect(() => {
if (updatingName) {
nameInputElement.current.focus();
}
}, [updatingName]);
function onUpdateClick() {
setUpdatingName(true);
}
function onCancelClick() {
setUpdatingName(false);
}
return (
<div>
<input
ref={nameInputElement}
type="text"
defaultValue={name}
name="name"
disabled={!updatingName}
/>
{!updatingName ? (
<>
<button key={1} type="button" onClick={onUpdateClick}>
Update
</button>
<button key={2} type="submit" name="delete" value={_key}>
Remove
</button>
</>
) : (
<>
<button key={3} type="submit" name="update">
Save
</button>
<button key={4} type="button" onClick={onCancelClick}>
Cancel
</button>
</>
)}
</div>
);
}
Also, you could use useTransition to ask React to delay the state update, so the submission happens first:
function Item({ name, _key }) {
console.log("rendering Item");
const [isPending, startTransition] = useTransition();
const [updatingName, setUpdatingName] = useState(false);
const nameInputElement = useRef();
useEffect(() => {
if (updatingName) {
nameInputElement.current.focus();
}
}, [updatingName]);
function onUpdateClick() {
setUpdatingName(true);
}
function onCancelClick() {
setUpdatingName(false);
}
return (
<div>
<input
ref={nameInputElement}
type="text"
defaultValue={name}
name="name"
disabled={!updatingName}
/>
{!updatingName ? (
<>
<button key={1} type="button" onClick={onUpdateClick}>
Update
</button>
<button key={2} type="submit" name="delete" value={_key}>
Remove
</button>
</>
) : (
<>
<button
key={3}
type="submit"
name="update"
onClick={(e) => {
startTransition(() => setUpdatingName(false));
}}
>
Save
</button>
<button key={4} type="button" onClick={onCancelClick}>
Cancel
</button>
</>
)}
</div>
);
}

i want to disabled button if textbox null in react

return (
{jobstate.jobs.map((data,i) =>{
<form>
<input type="text" placeholder="Post a comment" onChange={(e) => jobcmthandler(e,data._id,i) } />
<button type="button" onClick={postcmt} >Send</button>
</form>
})}
)
I generate dynamic HTML using the map function and I want to disabled button if text null for induvial form and also how to get text value on button click in react js
I can't really see why you'd want to do this but here you go (example):
import React, { useState } from "react";
export default function App() {
return ["Name", "Age"].map((label) => <Form label={label} />);
}
function Form({ label }) {
const [readValue, writeValue] = useState("");
return (
<form>
<label>{label}</label>
<input
type="text"
placeholder="Post a comment"
onChange={(e) => writeValue(e.target.value)}
value={readValue}
/>
<button
type="button"
onClick={() => console.log("Submit")}
disabled={readValue === ""}
>
Send
</button>
</form>
);
}

React useState and map()

I'm trying to make an comment input from map,
but since I use the same useState all the input fields get changed.
How can I target a specific input?
return (
<div>
{posts.map(post => (
<div key={post.id}>
<img
src={`https://localhost:1111/api/posts/uploads/images/${post.content}`}
alt={`${post.id}`}
/>
<p>{post.description}</p>
<span>{post.likes ? post.likes : 0}</span>
<button onClick={() => like(post.id)}>Like</button>
<Link to={`/post/${post.id}`}>Edit</Link>
<button onClick={() => deletePost(post.id)}>Delete</button>
<form onSubmit={uploadComment}>
<input
type="text"
onChange={handleComment}
value={comment}
placeholder="Comment"
/>
</form>
</div>
))}
</div>
)
You have an own state per rendered post, which means that it is a use case for an own component:
function Post(post, deletePost) {
const [comment, setComment] = useState('');
const uploadComment = () => {}; // your code is missing
return (
<div key={post.id}>
<img
src={`https://localhost:1111/api/posts/uploads/images/${post.content}`}
alt={`${post.id}`}
/>
<p>{post.description}</p>
<span>{post.likes ? post.likes : 0}</span>
<button onClick={() => like(post.id)}>Like</button>
<Link to={`/post/${post.id}`}>Edit</Link>
<button onClick={() => deletePost(post.id)}>Delete</button>
<form onSubmit={uploadComment}>
<input
type="text"
onChange={e => setComment(e.target.value)}
value={comment}
placeholder="Comment"
/>
</form>
</div>
)
}
Then your render function would look like this:
return (
<div>
{posts.map(post => <Post post={post} deletePost={deletePost} />)}
</div>
)
Consider using react useState hook per input.

Field array with one field at initial

I am trying to use the concept of field array because in my app a user should be able to fill multiple value for same kind of field. I could do as per redux-form field array example, however, in an example there is no initial field we have to first click on add member and then only we can have the field to fill the value but i need one field initially and then add further field if user wants to.
I tried from the redux-form example but its not allowing me to input the value at all. Here is the demo
https://codesandbox.io/s/n47qr2pvzl
The format of that field value should be like this
general: {
general_information: {
members: [
{
name: ''
},
{
name: ''
}
]
}
}
Here is the code
const renderMembers = ({ fields, meta: { touched, error, submitFailed } }) => (
<ul>
<li>
<button type="button" onClick={() => fields.push({})}>
Add Member
</button>
{(touched || submitFailed) && error && <span>{error}</span>}
</li>
{fields.map((member, index) => (
<li key={index}>
<button
type="button"
title="Remove Member"
onClick={() => fields.remove(index)}
/>
<h4>Member #{index + 1}</h4>
<Field
name={`${member}.name`}
type="text"
component={renderField}
label="Name"
/>
</li>
))}
</ul>
);
const FieldArraysForm = props => {
const { handleSubmit, pristine, reset, submitting } = props;
return (
<form onSubmit={handleSubmit}>
<Field
name="general.general_information.clubName"
type="text"
component={renderField}
label="Club Name"
/>
<Field
name="general.general_information.members.name"
type="text"
component={renderField}
label="Name"
/>
<FieldArray
name="general.general_information.members"
component={renderMembers}
/>
<div>
<button type="submit" disabled={submitting}>
Submit
</button>
<button type="button" disabled={pristine || submitting} onClick={reset}>
Clear Values
</button>
</div>
</form>
);
};
According to Redux-Form, we can set the initial fields by specifying it like (in your case):
export default reduxForm({
form: "fieldArrays", // a unique identifier for this form
validate,
initialValues: {"general": {
"general_information": {
"members": [
{}
]
}
}
}
})(FieldArraysForm);
Here is the live demo
Hope it helps :)

Redux form defaultValue

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

Categories