redux form update state and retrieve - reactjs

I have 2 redux form components, first one is LoginFormComponent which is a redux-form with form name 'submitValidation'
LoginFormComponent = reduxForm({
form: 'submitValidation'
})(LoginFormComponent)
This form component has input field like so:-
<Field name="userid" size="22" placeholder="Personal ID"
maxLength="22" component="input" type="text"/>
On form submit, I want to navigate to the second redux form component which is VerifyLoginBodyComponent and would like to show the submitted "userid" from the first component inside a div like so:-
<div className="tieringElement">
You are logging in as: <b>{userid}</b>
</div>
This is how I handle submit in the first form component
class LoginFormComponent extends Component {
constructor(props){
super(props);
this.submit=this.submit.bind(this);
}
submit(values) {
console.log(JSON.stringify(values));
this.context.router.push('/verify');
}
render(){
const { handleSubmit } = this.props
return(
<form onSubmit={handleSubmit(this.submit)} id="loginform">
<Field name="userid" size="22" placeholder="Personal ID" maxLength="22" component="input" type="text"/>
</form>
)
}
LoginFormComponent = reduxForm({
form: 'submitValidation'
})(LoginFormComponent)
export default LoginFormComponent;
This is my redux store:
const INITIAL_APP_STATE = {
form: {
submitValidation: {
userid: ''
}
}
};
How can I access the submitted userid from LoginFormComponent inside VerifyLoginBodyComponent?
I checked https://redux-form.com/6.5.0/examples/selectingFormValues/, Redux-form handleSubmit: How to access store state? and redux-form : How to display form values on another component, but still unable to display the userid in second form component.
Please provide any suggestions.

redux-form provides formValueSelector that you can use to create a selector that retrieves form values from the redux store. You can sue that in the mapStateToProps -function of your VerifyLoginBodyComponent.
Example:
const selector = formValueSelector('submitValidation')
const mapStateToProps = reducers => {
const signingInUserId = selector(reducers, 'userid')
return { signingInUserId }
}
This depends of course on the fact that the form values are not cleared from the store until after submitting succeeds.
Hope this helps!

Related

How to get children values on antd's form submit?

I made a custom component called TagsInput, and added it to a form. My problem is, when the form is submitted, the value for field tags is always undefined, because the Form.Item cannot retrieve the value from the TagsInput child. This is usually not a problem when children of Form.Item are antd components.
export default function NewProjectForm(props) {
return (
<Form layout="vertical" onFinish={props.onSubmit} id="newProjectForm">
<Form.Item label="Tags" name="tags">
<TagsInput />
</Form.Item>
</Form>
);
}
TagsInput is a class component that has its value within its state:
class TagsInput extends React.Component{
constructor(props){
super(props);
this.state = {
value: []
}
}
render(){
return(<div></div>)
}
}
I dont want to elevate the value field from TagsInput state to NewProjectForm because it is not a class component... I would be doing it just for the sake of solving this issue. I am asking this question because I think there is a cleaner way of solving this.
How can I make NewProjectForm retrieve the value of TagsInput from within its state?
Is there any function I can pass to TagsInput as a property, so that the form asks the component for a value when it is submitted?
In order to achive what you want, you need to handle the state of the form (and its components) at the parent component (NewProjectForm).
If you are willing to keep that component a function component, you can use React Hooks and define a state with useState like this:
const [tagValue, setTagValue] = useState([]);
While tagValue is the value you will get from the child TagsInput, and the setTagValue is the function that can set that value. You need to pass setTagValue as a function prop to TagsInput and use it on change. It will look something like this:
export default function NewProjectForm(props) {
const [tagValue, setTagValue] = useState([]);
return (
<Form layout="vertical" onFinish={props.onSubmit} id="newProjectForm">
<Form.Item label="Tags" name="tags">
<TagsInput setTagValue={(val)=>setTagValue(val)} />
</Form.Item>
</Form>
);
}
class TagsInput extends React.Component{
constructor(props){
super(props);
this.state = {
value: []
}
}
.........
.........
onChangeInput(val){
this.props.setTagValue(val);
}
.........
.........
render(){
return(<div></div>)
}
}
EDIT
After the clarification in the comments, you actually need to follow the steps of using Customized Form Controls, As described here.
You need to pass an object with value and onChange that changes the value. Then, the Form component should manage to get that value automatically.
So I guess it should look something like that:
const TagsInput= ({ value = {}, onChange }) => {
const triggerChange = newValue => {
if (onChange) {
onChange(newValue);
}
};
return(<div></div>)
};

Cannot pass data between components in React/Redux

I'm new to React and am trying to build an app which shuffles football players into two teams and am having difficulty with passing data from one component to another. I have redux and react-redux installed.
My problem is that once 10 names have been inputted (which I am calling numbersReached), the button to submit the addPlayers form should be disabled so no more players can be submitted.
I have tried passing a state value, numbersReached, into my AddPlayers component, but it is not being imported correctly - it is showing in console.log as undefined.
My code so far:
'initialState.js'
export const initialState = {
playersList: [],
shuffledList: [],
teamA: [],
teamB: [],
numbersReached: false
};
export default initialState;
'src\components\NumbersReached\index.js':
import { connect } from "react-redux";
import NumbersReached from "./NumbersReached";
const mapStateToProps = (state) => {
return {
...state,
numbersReached: state.playersList.length >= 10 // Once state.playersList.length>=10 numbersReached should = true. (10 is for five-a-side)
};
};
export default connect(mapStateToProps)(NumbersReached);
src\components\NumbersReached\NumbersReached.js:
import React from "react";
const NumbersReached = ({ numbersReached }) => (
<div>
{numbersReached ? "Numbers Reached" : null}
</div>
);
export default NumbersReached;
'src\components\AddPlayer\index.js':
import { connect } from "react-redux";
import AddPlayer from "./AddPlayer";
import { addPlayer } from "../../data/actions";
const mapStateToProps = (state) => {
return {
playerName: state.playerName,
numbersReached: state.numbersReached
};
};
const mapDispatchToProps = (dispatch) => {
return {
handleSubmit: (data) => dispatch(addPlayer(data)),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(AddPlayer);
'src\components\AddPlayer\AddPlayer.js'
import React, { Component } from 'react';
class AddPlayer extends Component {
constructor(props) {
super(props);
this.state = {
playerName: props.playerName,
numbersReached: props.numbersReached
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
};
handleChange(e) {
this.setState({ playerName: e.currentTarget.value });
}
handleSubmit(e) {
e.preventDefault();
this.props.handleSubmit({ ...this.state });
}
render() {
return (
<React.Fragment>
<form className="entry-form" onSubmit={this.handleSubmit}>
<input
placeholder="Enter a player's name"
className="player-input"
type="text"
onChange={this.handleChange}
/>
<button
type="submit"
className="player-submit"
disabled={this.state.numbersReached} // Does not disable the button as this.state.numbersReached is undefined
>
Add a player
</button>
</form>
</React.Fragment>
)
}
};
export default AddPlayer;
Help is very much appreciated!
The problem you're likely having is that you're not actually using the value of numbersReached in your Redux store. Instead, you're using a numbersReached variable inner to your AddPlayer component' state.
<button
type="submit"
className="player-submit"
disabled={this.state.numbersReached}
>
To use the numbersReached you defined in mapStateToProps, you should access to this.props.numbersReached instead of this.state.numbersReached. The props you inherit from Redux will automatically be updated when the action addPlayer is dispatched and the Redux store changes.
The value for your this.state.numbersReached, however, won't change unless you call setState anywhere in your component. Right now, you're only initializing that value on the constructor:
class AddPlayer extends Component {
constructor(props) {
super(props);
this.state = {
playerName: props.playerName,
numbersReached: props.numbersReached
};
};
}
When you do this, you're creating a new numbersReached variable in your component' state with the value that the numbersReached property in your Redux store had at the time your component is built. As it doesn't have value when the constructor runs, this.state.numbersReached is undefined.
Doing numbersReached: props.numbersReached does not suscribe you to all of the changes to that property; it just assigns numbersReached the value props.numbersReached initially had. That's why, no matter how many players you add, it'll keep been undefined even if players have been succesfully added to your store.
you are duplicating redux state at AddPlayer state. duplicate state is bad practice and should be avoided. secondly, they are different states, redux'state and react component's state. as it is this.state.numbersReached receives the first value on mounting but is not not updated anywehere after.
Also it is weird at your state playerName: props.playerName. first, the fact that you dont have at your redux a piece of state called playerName. Second, the most revelant, it's a component that create players and adds to your playersList. you should better initialize that state as an empty string.
btw, I refactored a little your component to reduce code. if you declare your functions as arrow functions you dont need to bind them at constructor. and you dont need to declare your state at constructor anymore. But if you want to keep the original for consistency, or personal preference go ahead, that's ok also :) .
after all that, your component would look something like:
import React, { Component } from 'react';
class AddPlayer extends Component {
state = {
playerName: ''
};
handleChange = (e) => {
this.setState({ playerName: e.currentTarget.value });
}
handleSubmit = (e) => {
e.preventDefault();
this.props.handleSubmit({ ...this.state });
}
render() {
return (
<React.Fragment>
<form className="entry-form" onSubmit={this.handleSubmit}>
<input
placeholder="Enter a player's name"
className="player-input"
type="text"
onChange={this.handleChange}
/>
<button
type="submit"
className="player-submit"
disabled={this.props.numbersReached}
>
Add a player
</button>
</form>
</React.Fragment>
)
}
};
export default AddPlayer;
a last note, you have numbersReached state, but at your mapStateToProps you set based on a custom function, not on your numbersReached state.
if you want to keep numbersReached state on your redux state, you should handle that logic state.playersList.length >= 10 at your reducers to update properly there, and not at your mapStateToProps. though you could remove from your redux state numbersReached altogether given it's derived from other pieces from your state.

Redux Forms - Ignore initialValues with no registered field

I have an object fetched from the server that contains lots of fields not relevant to the current form. I'd like to pass the whole object to initialValues on my form, but when I submit, I don't want the extra fields to carry through.
Here's a simple form:
const MyForm = ({ handleSubmit }) => (
<form onSubmit={handleSubmit}>
<Field name="name" component="input" placeholder="Name" />
<button type="submit">Submit</button>
</form>
);
export default reduxForm({
form: "foo",
onSubmit: values => {
console.log(values);
},
})(MyForm);
And in its parent component, it's rendered like so:
<MyForm initialValues={{ name: "bob", other: "thing" }} />
When I submit the form, I want values to look like:
{name: "bob"}
and not include the extra other field. Is this possible?
Since the extra fields are not visible to the end user, they shouldn't be part of the form's eventual patch request. I also don't want my parent component to have be coupled tightly to the form, so I don't want to filter the fields at that level.
We ended up solving this by wrapping the form with a higher-order component. You can then wrap the passed-in submission handler and perform some logic on the values returned by the form before they get sent to the submission handler:
export function specialForm(WrappedForm) {
return class extends Component {
submissionFilter(submitFunction) {
return function(values, dispatch, props) {
// Only let registered fields through.
if (props.registeredFields) {
values = _.pick(values, Object.keys(props.registeredFields));
}
return submitHandler(values, dispatch, props)
}
}
render() {
var submitHandler = this.props.onSubmit;
if (submitHandler) {
submitHandler = this.submissionFilter(submitHandler);
}
return <WrappedForm {...this.props} onSubmit={submitHandler} />;
}
};
}
The key to making this work is the fact that the form's props are passed as the 3rd argument to the submission handler, and the props have an attribute called registeredFields which are the fields in the form itself.

React: Get state of children component component in parent

I have this container where and is not placed in the same level. How can I get the state of the Form when I click on the button (which is placed on the parent) ?
I've created a demo to address my issue.
https://codesandbox.io/s/kmqw47p8x7
class App extends React.Component {
constructor(props) {
super(props);
}
save = () => {
alert("how to get state of Form?");
//fire api call
};
render() {
return (
<div>
<Form />
<button onClick={this.save}>save</button>
</div>
);
}
}
One thing I don't want to do is sync the state for onChange event, because within Form there might be another Form.
To access a child instance from parent, your need to know about ref:
First, add formRef at top your App class:
formRef = React.createRef();
Then in App render, pass ref prop to your Form tag:
<Form ref={this.formRef} />
Finaly, get state from child form:
save = () => {
alert("how to get state of Form?");
const form = this.formRef.current;
console.log(form.state)
};
Checkout demo here
ideally, your form submit action belongs to the Form component
You can put button inside your From component and pass a submit callback to the form.
class App extends React.Component {
constructor(props) {
super(props);
}
save = (data) => {
// data is passed by Form component
alert("how to get state of Form?");
//fire api call
};
render() {
return (
<div>
<Form onFormSubmit={this.save} />
</div>
);
}
}
you can write the code like this
https://codesandbox.io/s/23o469kyx0
As it was mentioned, a ref can be used to get stateful component instance and access the state, but this breaks encapsulation:
<Form ref={this.formRef}/>
A more preferable way is to refactor Form to handle this case, i.e. accept onChange callback prop that would be triggered on form state changes:
<Form onChange={this.onFormChange}/>
One thing I don't want to do is sync the state for onChange event, because within Form there might be another Form.
Forms will need to handle this any way; it would be impossible to reach nested form with a ref from a grandparent. This could be the case for lifting the state up.
E.g. in parent component:
state = {
formState: {}
};
onFormChange = (formState) => {
this.setState(state => ({
formState: { ...state.formState, ...formState }
}));
}
render() {
return (
<Form state={this.state.formState} onChange={this.onFormChange} />
);
}
In form component:
handleChange = e =>
this.props.onChange({
[e.target.name]: e.target.value
});
render() {
return (
<input
onChange={this.handleChange}
name="firstName"
value={this.props.state.firstName}
/>
);
}
Here is a demo.

Redux Form Field Array Validation

I am using the Redux Form module for all forms in the project. I want to create a validation based on the component props. There is a table with some fields on every row.
The table
The first field is a dropdown with all the products which come from the store. Every product has an available quantity and if the field quantity is more than the available quantity the Redux Form should return an error for the specific row. I can't do it in the validate function that is passed in the reduxForm method:
reduxForm({
validate,
form: 'theNameOfTheForm'
})
The reason why I can't do the table validation in the validate function is because it can't see the current props of the component (I didn't find a way how I can do that). I've decided to pass the validate function as a prop to the FieldArray component:
// some methods
validate(values) {
// some validation code
}
render() {
return (
<FieldArray validate={this.validate} />
)
}
From the validate method I can access the component props but whatever I return from this method an error is not received by the field prop in the component passed as a component prop to the <FieldArray />.
return 'Some error message';
return ['Some error message'];
return {
products: 'Some error message'
};
return {
products: ['Some error message']
};
<FieldArray validate={this.validate} component={FieldArrayComponent} />
const FieldArrayComponent = ({ field, meta }) => {};
How I can do the validation? Am I doing something wrong? Is there another way how I can do the validation?
You can pass the component props to the validate function when using HOC (higher order components) and do the validation in the main validate function (no need to create a method in the component and then pass it to the FieldArray component). Just export the component like this:
export default connect(
mapStateToProps,
mapDispatchToProps
)(
reduxForm({
validate,
form: 'ExampleForm'
})(ExampleForm)
);
The component props are passed in the validation function as a second parameter:
const validate = (values, props) => {
const errors = {};
return errors;
};

Resources