How to pass custom props to validate function in redux-form? - reactjs

I am trying to create a validation function that returns errors if there is a input error clientside or if there is an error returned from the server.
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { Form, submit, reduxForm, Field } from 'redux-form';
import Modal from '../../ui/modal';
import { ACCOUNT_REGISTER_MODAL_ID } from './constants';
import registerRequest from './actions';
import CField from '../../ui/form/field';
function validate(values, props) {
const errors = {};
console.log('-');
console.log(values);
console.log(props);
console.log('-');
if (!errors.description && (!values.description || values.description.trim() === '')) {
errors.description = 'Enter a Description';
}
if (!errors.username && (!values.username || values.username.trim() === '')) {
errors.username = 'Enter a Username';
}
return errors;
}
class RegisterModal extends Component {
static propTypes = {
handleSubmit: PropTypes.func,
fields: PropTypes.array,
register: PropTypes.shape({
requesting: PropTypes.bool,
successful: PropTypes.bool,
messages: PropTypes.array,
errors: PropTypes.array,
fieldErrors: PropTypes.array,
}),
dispatch: PropTypes.func,
};
onSubmit = (values) => {
console.log(this.props);
console.log(values);
}
getForm = () => {
this.props.dispatch(submit('register'));
}
render() {
const {
handleSubmit,
fields,
register: {
requesting,
successful,
messages,
errors,
fieldErrors,
},
} = this.props;
console.log(fieldErrors);
const required = value => value ? undefined : 'Required';
return (
<Modal
modalID={ACCOUNT_REGISTER_MODAL_ID}
header={'Connect Account'}
submitText={'Connect'}
onSubmitClick={this.getForm}
register={this.register}
>
<Form
className="ui form register"
onSubmit={handleSubmit(this.onSubmit)}
fieldErrors={fieldErrors}
>
<Field
name="description"
type="text"
component={CField}
label="Please give a recognizable name to this account"
required
placeholder="My main Account"
/>
<Field
name="username"
type="text"
component={CField}
label="Please enter your username"
required
placeholder="foobar2017"
/>
</Form>
</Modal>
);
}
}
const mapStateToProps = state => ({
register: state.RegisterModal,
});
const connected = connect(mapStateToProps, { registerRequest })(RegisterModal);
const formed = reduxForm({
form: 'register',
fields: ['description', 'username'],
validate
})(connected);
export default formed;
None of the values passed to the validate function seems to contain the "fieldErrors" prop I passed into the Form component. I need to be able to pass the prop into the validate function so I can access the responses from the server received via redux.
Is there a different way that I should be creating my validation function?

I came across this Scenario where I needed values from state and
validate the redux form data with them.
Solution that I implemented is to take Global variable in file and assign props to them eg:
let TicketList = [] // global variable
function () {
TicketList = this.props.tickets;
return (
<Field
validate={validationHandler}
/>
)
}
validationHandler(value){
if(TicketList.includes(value)){
return "error message"
}
}
This worked for me!!

Related

React Speech Recognition - inserting the text to the memory by updating the state

There is a similar question but I can't comment on it so I opening a new one.
I am new to React and try to implement React SpeechRecognition component for my app. The text should be in an input box. the code for it (from react doc [https://www.npmjs.com/package/react-speech-recognition][1] - with span tag instead of an input):
import React, { PropTypes, Component } from 'react'
import SpeechRecognition from 'react-speech-recognition'
const propTypes = {
// Props injected by SpeechRecognition
transcript: PropTypes.string,
resetTranscript: PropTypes.func,
browserSupportsSpeechRecognition: PropTypes.bool
}
class Dictaphone extends Component {
render() {
const { transcript, resetTranscript, browserSupportsSpeechRecognition } = this.props
if (!browserSupportsSpeechRecognition) {
return null
}
return (
<div>
<button onClick={resetTranscript}>Reset</button>
<span>{transcript}</span>
</div>
)
}
}
Dictaphone.propTypes = propTypes
export default SpeechRecognition(Dictaphone)
Now I try to update a state of text (a string) by the transcript (the words that have been already recognized) but I can't make it.
from an earlier question, someone suggested this:
<input
type="text"
value={transcript}
onChange={event => this.onInputChange(event.target.value)}
/>
now when I speak, I do see the words in the input box,
so the final code should be :
import React, { Component } from "react";
import PropTypes from "prop-types";
import SpeechRecognition from "react-speech-recognition";
const propTypes = {
// Props injected by SpeechRecognition
transcript: PropTypes.string,
resetTranscript: PropTypes.func,
browserSupportsSpeechRecognition: PropTypes.bool
};
class Dictaphone extends Component {
constructor() {
super();
this.state = {
text: '',
events: []
}
}
onInputChange = (event) => {
console.log (event.target.value);
this.setState( {text: event.target.value} );
}
render() {
const { transcript, resetTranscript, browserSupportsSpeechRecognition } = this.props;
if (!browserSupportsSpeechRecognition) {
return null
}
return (
<div>
<button onClick={resetTranscript}>Reset</button>
<input
className='bg-light-blue'
type="text"
value={transcript}
onChange={event => this.onInputChange(event.target.value)}
/>
</div>
)
}
}
Dictaphone.propTypes = propTypes;
export default SpeechRecognition(Dictaphone);
but when I console.log(event.target.value) which is text - I see nothing so I'm doing something wrong.
Note that if I just write in the render func:
render() {
const { transcript, resetTranscript, browserSupportsSpeechRecognition } = this.props;
var x = transcript;
console.log('x is ',x);
console.log('x length is: ',x.length);
.....
it does console the transcript (x) but it's not what I want - I need to save it in text by updating the state.
any suggestion?
If you need to store the transcript prop in your state you should do something like this.
componentDidUpdate(prevProps){
if(prevProps.transcript !== this.props.transcript){
this.setState({
text: this.props.transcript
});
}
}
In your render method use this.state.text to show in the input value.
Also in your constructor do
this.state = {
text: props.transcript
}

Testing simple Redux-Form with Enzyme (where is value??)

I have the most simple redux-form connected to redux example :
import * as React from 'react';
import { Field, reduxForm } from 'redux-form';
import { connect } from 'react-redux';
class TestForm extends React.PureComponent {
render() {
return (
<form>
<Field component={Input} name={'testField'}/>
</form>
);
}
}
class Input extends React.PureComponent {
render(): React.Node {
let { input } = this.props;
// input = {name: 'testField', value: 'test value'};
return (
<input name={input.name} value={input.value} type='text' onChange={()=>1}/>
);
}
}
const mapStateToProps = ({ testForm }) => {
return {
initialValues: testForm,
};
};
export const TestFormWithReduxForm = reduxForm({ form: 'test form'})(TestForm);
export default connect(mapStateToProps)(TestFormWithReduxForm);
Note the following :
I have my own custom Input (called Input)
I am connecting to reduxForm, and then connecting to redux.
The initial values that are passed in should have 'name' and 'value'.
I have the following test (Jest+Enzyme)
import React from 'react';
import { Provider } from 'react-redux';
import Enzyme, { mount } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import configureStore from 'redux-mock-store';
import TestForm from './TestForm';
Enzyme.configure({ adapter: new Adapter() });
describe('Redux Form Test', () => {
let wrapper;
let mockStoreData = {
testForm: {
testField: 'test value',
},
};
const mockStore = configureStore();
const store = mockStore(mockStoreData);
beforeEach(() => {
wrapper = mount(
<Provider store={store}>
<TestForm />
</Provider>
);
});
it('Check Value', () => {
let inputs = wrapper.find('input');
expect(inputs.at(0).prop('name')).toBe('testField'); //OK!!!
expect(inputs.at(0).prop('value')).toBe('test value'); //empty string!!
});
});
The Jest test passes in a 'testForm' object with 'testField' (and its value) into the the store.
As expected, the name on the first input is 'testField', however the 'value' is empty (ie. an empty string).
This is not expected, because if I were to render the component in a normal page then 'test value' would appear.
So something seems to be broken here. I am not sure if it has something to do with redux-form or enzyme, but the redux-form Field object seems to be intercepting the properties that are passed into the Input object.
I am starting to question whether it is even possible to test redux form.
I'm not sure why you'd want nor need to test Redux Form's functionality, as it's already been tested by the creators/maintainers. However, redux-mock-store appears to be made for unit tests only (where you'll mock middlewares and call store.dispatch(actionType) and expect the action to be called). It doesn't handle reducer side effects nor track changes in state.
In the case above, you'll only need to do a unit test on your custom Input component, because that's the only component that's different from what would be considered a standard redux form.
That said... for an integration test, you'll need to use a real store that contains the redux-form's reducer and your field state.
Working example: https://codesandbox.io/s/zl4p5w26xm (I've included a Form integration test and an Input unit test -- as you'll notice, the Input unit test covers most of your testing needs)
containers/Form/Form.js
import React, { Component } from "react";
import { Form, Field, reduxForm } from "redux-form";
import { connect } from "react-redux";
import Input from "../../components/Input/input";
const isRequired = value => (!value ? "Required" : undefined);
class SimpleForm extends Component {
handleFormSubmit = formProps => {
alert(JSON.stringify(formProps, null, 4));
};
render = () => (
<div className="form-container">
<h1 className="title">Text Field</h1>
<hr />
<Form onSubmit={this.props.handleSubmit(this.handleFormSubmit)}>
<Field
className="uk-input"
name="testField"
component={Input}
type="text"
validate={[isRequired]}
/>
<button
type="submit"
className="uk-button uk-button-primary uk-button-large submit"
disabled={this.props.submitting}
>
Submit
</button>
<button
type="button"
className="uk-button uk-button-default uk-button-large reset"
disabled={this.props.pristine || this.props.submitting}
onClick={this.props.reset}
style={{ float: "right" }}
>
Clear
</button>
</Form>
</div>
);
}
export default connect(({ field }) => ({
initialValues: { [field.name]: field.value }
}))(
reduxForm({
form: "SimpleForm"
})(SimpleForm)
);
containers/Form/__test__/Form.js
import React from "react";
import { Provider } from "react-redux";
import { mount } from "enzyme";
import SimpleForm from "../Form";
import store from "../../../store/store";
const wrapper = mount(
<Provider store={store}>
<SimpleForm />
</Provider>
);
describe("Redux Form Test", () => {
it("renders without errors", () => {
expect(wrapper.find(".form-container")).toHaveLength(1);
});
it("fills the input with a default value", () => {
expect(wrapper.find("input").prop("name")).toBe("testField");
expect(wrapper.find("input").prop("value")).toBe("Test Value");
});
it("updates input value when changed", () => {
const event = { target: { value: "Test" } };
wrapper.find("input").simulate("change", event);
expect(wrapper.find("input").prop("value")).toBe("Test");
});
it("resets the input value to defaults when the Clear button has been clicked", () => {
wrapper.find("button.reset").simulate("click");
expect(wrapper.find("input").prop("value")).toBe("Test Value");
});
});
stores/stores.js (for simplicity, I lumped reducers and store into one file)
import { createStore, combineReducers } from "redux";
import { reducer as formReducer } from "redux-form";
const initialValues = {
name: "testField",
value: "Test Value"
};
const fieldReducer = (state = initialValues, { type, payload }) => {
switch (type) {
default:
return state;
}
};
const reducer = combineReducers({
field: fieldReducer,
form: formReducer
});
export default createStore(reducer);
Note: Aside from using initialValues, buried in the documentation, there are three other ways to update field values: Utilizing redux-form's reducer.plugin and dispatching an action to update the form, or by using this.props.intialize({ testField: "Test Value" }); with enableReinitialize: true and keepDirtyOnReinitialize: true,, or by using this.props.change("SimpleForm", { testField: "Test Value" });. Important to note because sometimes mapStateToProps is asynchronous.

Accept multiple input field in redux?

I'm learning redux and can able to update single input field but when there's more than 1 input field can't able to update state of both input field!
Here's my code please check it.
My main index.js:
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import {FormReducer} from "./reducers/name";
import { createStore } from "redux";
import { Provider } from "react-redux";
const store = createStore(FormReducer);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
My presentational and input field component:
import React, { Component } from "react";
import { connect } from "react-redux";
import { bindActionCreators } from "redux";
import * as actions from "./actions";
class App extends Component {
inputData = event => {
this.props.actions.addToDo({ name: event.target.value });
};
submitData = event => {
console.log(this.props.name);
// console.log(this.props.mobile);
event.preventDefault();
}
render() {
return (
<div>
<form onSubmit={this.submitData}>
FirstName:
<input
type="text"
name={this.props.name}
onChange={this.inputData}
/>
<input
type="text"
name={this.props.mobile}
onChange={this.inputData2}
/>
<button type="submit">Submit</button>
</form>
{this.props.name}
{this.props.mobile}
</div>
);
}
}
const mapStateToProps = state => ({
mobile: state.mobile,
name: state.name
});
const mapDispatchToProps = dispatch => {
return {
actions: bindActionCreators(actions, dispatch)
};
};
export default connect(mapStateToProps, mapDispatchToProps)(App);
In my presentational component I want to make input acceptance such like array like [event.target.name]:event.target.value or something like this.
But when I try to do this with redux it's not working properly. Please check my code and give your input!
My reducer function:
let init = {
name: ''
}
export const FormReducer = (state = init, action) => {
switch (action.type) {
case "ADD_NAME":
return {...state, name:action.name, mobile:action.mobile}
default:
return state;
}
};
My action function which takes action on new input:
let init = {
name: ''
}
export const FormReducer = (state = init, action) => {
switch (action.type) {
case "ADD_NAME":
return {...state, name:action.name, mobile:action.mobile}
default:
return state;
}
};
And also as you can see I want to print both name and mobile both but it's only printing on which I'm working. Please give code so that I can able to print both simultaneously.
Thanks in advance!
It should be like this.
Use ecma6#computed property name [event.target.name]
inputData = event => {
this.props.actions.addToDo({ [event.target.name]: event.target.value });
};
EDIT :
in reducers,add this .
let init = {
name: { key : "name", value : "" },
mobile: { key: "mobile", value: "" },
};
reducer :
case "ADD_NAME":
console.log(action,state);
return {
...state,
name: {
...state.name, value: action.name||state.name.value
},
mobile: {
...state.mobile,
value: action.mobile || state.mobile.value
}
};
If you checked in render,both input field need name attribute which is not initialised and coming blank via props mapped in mapStateToProps.
So,you are not getting name attribute in event handler.
Need to initilise it.I assumed mobile and name.
1rst make state and save field inputs there like this.
state = {
username: "",
password: ""
}
where updateValue will save values from inputs to state
updateValue = (e) => {
this.setState({ [e.target.name]: e.target.value });
}
<input type="text" name="username" value={this.state.username} onChange={(e) => this.updateValue(e)} />
<input type="password" name="password" value={this.state.password} onChange={(e) => this.updateValue(e)} />
then you can collect data from state and pass it to your function, for example like this login form.
submitData = event => {
const user = {
username: this.state.username,
password: this.state.password
}
// now pass this `user` to your action
event.preventDefault();
}

Redux form: change other field if validation success

I'm using React Redux, Redux-form and reselect library (https://github.com/reactjs/reselect).
I have a component with two Fields: quota and amount. I want to update the amount field based on quota field. For the calculation of amount I'm using a selector with reselect.
I want to update the amount field only if is valid quota.
I have tried without success onChange field props because it's executed before the selector.
The best solution I've found is to use onChange reduxForm() property because it's executed after the selector but I can't run after validation.
This is my code:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { makeModalData } from '../selectors/Modal';
import { change, Field, reduxForm, formValueSelector } from 'redux-form';
const validate = values => {
const errors = {};
if (values.quota <= 1) {
errors.quota = "The quota must be greater than 1";
} else if (values.quota > 50) {
errors.quota = "The quota must be less than 50";
}
return errors;
}
const updateForm = (values, dispatch, props, previousValues) => {
if (props.valid) { // this not work because it shows the previous state of validation
if (values.quota !== previousValues.quota) {
let amount = props.modal_data.amount; // data with reselect
dispatch(change('modal', 'amount', amount));
}
}
}
class Modal extends Component {
constructor(props) {
super(props);
}
renderField = ({ input, label, type, meta: { touched, error } }) => (
<div>
<input {...input} type={type} placeholder={label} />
{touched && error && <span>{error}</span>}
</div>
)
// this not work because is executed before selector
/*
updateAmount(event) {
let amount = this.props.modal_data.amount;
this.props.dispatch(change('modal', 'amount', amount));
}*/
render() {
return (
<label>Quota</label>
<Field
name="quota"
component={this.renderField}
type="number"
label="Quota"
/*onChange={this.updateAmount.bind(this)} */
/>
<label>Amount</label>
<Field
name="amount"
component={this.renderField}
type="number"
label="Amount"
/>
)
}
}
const makeMapStateToProps = () => {
const modal_data = makeModalData(); // selector
const mapStateToProps = state => {
let my_modal_data = modal_data(state, state.modal)
return {
initialValues: {
quota: state.modal.quota,
amount: state.modal.amount,
},
}
}
return mapStateToProps;
}
Modal = reduxForm({
form: 'modal',
enableReinitialize: true,
touchOnChange: true,
validate,
onChange: updateForm
},makeMapStateToProps)(Modal);
Modal = connect(
makeMapStateToProps,
mapDispatchToProps
)(Modal);
export default Modal;
Here's a working CodeSandbox.
The answer is to use formValueSelector and update in componentWillReceiveProps (or componentDidUpdate). Here is the version of the working Modal.js. Notice that the quota validation is reused both in the validation function and also in the calculation.
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { Field, reduxForm, formValueSelector } from 'redux-form'
const validateQuota = quota => {
if (quota <= 1) {
return 'The quota must be greater than 1'
} else if (quota > 50) {
return 'The quota must be less than 50'
}
}
const validate = values => {
const errors = {}
errors.quota = validateQuota(values.quota)
return errors
}
/**
* Arbitrary function that takes quota and calcuates amount.
* For the purposes of this demo, I'm assuming that we're a
* mobile phone company and are charging $19.95 per 5 GB of quota.
*/
const calculateAmount = quota => Math.ceil(quota / 5) * 19.95
class Modal extends Component {
renderField = ({ input, label, type, meta: { touched, error } }) => (
<div>
<input {...input} type={type} placeholder={label} />
{touched && error && <span>{error}</span>}
</div>
)
componentWillReceiveProps(nextProps) {
const { change, quota } = nextProps
if (this.props.quota !== nextProps.quota && !validateQuota(quota)) {
// quota value is valid
change('amount', calculateAmount(quota))
}
}
render() {
return (
<div>
<label>Quota</label>
<Field
name="quota"
component={this.renderField}
type="number"
label="Quota"
/>
<label>Amount</label>
<Field
name="amount"
component={this.renderField}
type="number"
label="Amount"
/>
</div>
)
}
}
const valueSelector = formValueSelector('modal') // <-- form name
const makeMapStateToProps = () => {
const mapStateToProps = state => {
return {
quota: valueSelector(state, 'quota'),
initialValues: {
// Not implementing the modal reducer...
// quota: state.modal.quota,
// amount: state.modal.amount
}
}
}
return mapStateToProps
}
Modal = reduxForm(
{
form: 'modal',
enableReinitialize: true,
validate
},
makeMapStateToProps
)(Modal)
const mapDispatchToProps = undefined // not included in StackOverflow snippet
Modal = connect(makeMapStateToProps, mapDispatchToProps)(Modal)
export default Modal

Redux form does not reset

i have a component which based upon props renders a form with different components.
class Feedback extends Component {
submitMyForm(data) {
const { onSubmit, reset } = this.props;
reset();
return onSubmit(data);
//
// do other success stuff
}
render() {
const { handleSubmit } = this.props;
let component;
if(this.props.data.feedbackType == "likert")
component = Likert;
else if(this.props.data.feedbackType == "single choice")
component = SingleChoice;
else if(this.props.data.feedbackType == "multiple choice")
component = MultipleChoice;
return (
<div>
<h1>Feedback zu Aufgabe {this.props.id}</h1>
<form onSubmit={handleSubmit(this.submitMyForm.bind(this))}>
<Field
name="feedback"
component={component}
heading={this.props.data.description}
items={this.props.data.options}
required={true}
/>
<button type="submit">Submit</button>
</form>
</div>
);
}
}
// Decorate the form component
Feedback = reduxForm({
form: 'feedback', // a unique name for this form,
validate,
enableReinitialize:true
})(Feedback);
function validate(formProps) {
const errors = {};
if (!formProps.feedback) {
errors.feedback = 'please select an option';
}
return errors;
}
export default Feedback;
import React, { PropTypes } from 'react';
const SingleChoice = ({ input, disabled, heading, required, className, items, name, meta: { touched, error } }) => (
<fieldset className={`form__field ${className || ''}`}>
<legend className="form__label">
{heading}{required ? (<span>*</span>) : null}
{ (touched && error) ? (
<span className="form__error"> {error}</span>
) : null }
</legend>
<div>
{ items.map((item, i) => (
<div className="form__segmented-control width-1/2#small" key={ i }>
<input
{...input}
name={ name }
type="radio"
value={ item.value }
disabled={ disabled }
className="segmented-control__input u-option-bg-current"
id={ `${name}-${item.value}` }
/>
<label className="segmented-control__label u-adjacent-current" htmlFor={ `${name}-${item.value}` }>
{item.label}
</label>
</div>
))
}
</div>
</fieldset>
);
SingleChoice.propTypes = {
input: PropTypes.object.isRequired,
name: PropTypes.string.isRequired,
className: PropTypes.string,
items: PropTypes.arrayOf(PropTypes.shape({
label: PropTypes.string.isRequired,
value: PropTypes.any.isRequired,
})).isRequired,
heading: PropTypes.string,
meta: PropTypes.object,
required: PropTypes.bool,
disabled: PropTypes.bool,
};
export default SingleChoice;
The first time the form renders everything is fine. All radio buttons are unchecked and if i try to submit it i get an validation error as intended. But when my Feeback component receives new props and the form is updated. The old values still remain selected when the form component for the new props is the same as the one for the old props.
When the form component for the new props is different all values are not selected as intended, but i can submit the form without selecting anything, which should be prevented by validation.
I hope you got any suggestions, i am totally out of ideas at this point.
I searched for hours trying to find a resolution to this problem. The best way I could fix it was by using the plugin() API to teach the redux-form reducer to respond to the action dispatched when your submission succeeds. Exactly like the first step here How can I clear my form after my submission succeeds?
const reducers = {
// ... your other reducers here ...
form: formReducer.plugin({
nameOfTheForm: (state, action) => { // <- 'nameOfTheForm' is name of form
switch(action.type) {
case ACCOUNT_SAVE_SUCCESS:
const values = undefined;
const fields = {
fields: {
input_field_name: {
visited: false,
touched: false
}
// repeat for each input field
}
};
const newState = { ...state, values, fields };
return newState;
default:
return state;
}
}
})
}
You will have to change a couple things in your component.
onSubmit(values) {
this.props.postForm(values, () => {});
}
render(){
const { handleSubmit } = this.props;
}
return (
<form onSubmit={handleSubmit(this.onSubmit.bind(this))}></form>
)
In your actions file:
export function postForm(values, callback) {
const request = axios.get(`${ROOT_URL}`, config).then(() => callback());
return {
type: ACCOUNT_SAVE_SUCCESS,
payload: request
};
}
The best way I found:
import 'initialize'...
import {initialize} from 'redux-form';
and then when you call the action, call another action right after passing an empty object to the 'initialize' function...
yourFunction = (data) => {
this.props.dispatch(yourAction(data))
.then(
result => {
if (result) {
this.props.dispatch(initialize('nameOfTheForm', {}));
}
}
);
when my Feeback component receives new props and the form is updated, the old values still remain selected when the form component for the new props is the same as the one for the old props.
This is because the values for the feedback form are stored in your Redux store.
You should implement componentWillReceiveProps and test whether your form should be reset or not.
class Feedback extends Component {
componentWillReceiveProps ( nextProps ) {
if ( nextProps.blabla !== this.props.blabla ) {
// oh cool, props changed, let's reset the form
// checkout more available form props at http://redux-form.com/6.4.3/docs/api/ReduxForm.md/
this.props.reset();
}
}
render () {
// your normal rendering function
}
}

Resources