How to provide plugin for all forms in redux-form? - forms

Ok, so I wanted to deal with whitespaces at the beginning of the input in my register form and I have achieved this by providing plugin for redux-form reducer:
export default function(cookies, server) {
return combineReducers({
auth: auth(cookies, server),
reduxAsyncConnect,
alert,
programs,
exercises,
routing: routerReducer,
form: form.plugin(formPlugin),
profile,
spinner,
companies
});
}
The plugin is:
import {ltrim} from './ltrim';
const formPlugin = {
registerForm: (state, action) => {
switch(action.type) {
case '##redux-form/CHANGE':
if (action.meta.form === 'registerForm') {
return {
...state,
values: {
...state.values,
[action.meta.field]: ltrim(action.payload)
}
}
} else {
return state;
}
default:
return state;
}
}
}
export default formPlugin;
How can I get the same effect on all forms withouth hardcoding? Maybe I have to somehow edit redux-form CHANGE action to achieve this?

You could make a custom Field component that does this for you.
import { Field } from 'redux-form';
import {ltrim} from './ltrim';
const normalize = value => ltrim(value);
const TrimmedField = props => <Field {...props} normalize={normalize} />;
You could also give your custom normalize a callback argument if you need to do other normalization in some cases.
http://redux-form.com/6.6.3/docs/api/Field.md/#-normalize-value-previousvalue-allvalues-previousallvalues-nextvalue-optional-

Related

Register is not a function when passing as a prop?

I use react-hook-form for the first time. I was reading the docs and followed along. Likewise, I already laid out my components and styled them. Now I am trying to alert the data out after the form submits.
This is the ContactForm
import React, { useState } from 'react';
import * as S from './style';
import { PrimaryButton } from '#element/Button';
import TextInput from '#element/TextInput';
import { useForm } from 'react-hook-form';
export const ContactForm = () => {
const { register, handleSubmit } = useForm();
const [firstName, setFirstName] = useState('');
const onSubmit = (data) => {
alert(JSON.stringify(data));
};
return (
<S.ContactFormWrapper onSubmit={handleSubmit(onSubmit)}>
<TextInput
name={'firstName'}
label={'First Name'}
state={firstName}
setState={setFirstName}
placeholder={'John'}
type={'text'}
width={'48%'}
options={{
maxLength: '20',
minLength: '2',
required: true,
}}
register={register}
/>
<PrimaryButton type={'submit'} text={'Send Message'} />
</S.ContactFormWrapper onSubmit={handleSubmit(onSubmit)}>
)
}
This is my Custom created TextInput
import React, { useEffect, useState } from 'react';
import * as S from './style';
const TextInput = ({
name,
label,
placeholder,
state,
setState,
type,
width,
register,
options,
}) => {
const [isActive, setIsActive] = useState(false);
return (
<S.TextInputWrapper inputWidth={width}>
<S.Label htmlFor={name} isActive={isActive}>
{label}
</S.Label>
<S.Input
placeholder={placeholder}
type={type}
name={name}
id={name}
{...register(name, options)}
onChange={(event) => setState(event.target.value)}
onFocus={() => setIsActive(true)}
onBlur={() => setIsActive(false)}
/>
</S.TextInputWrapper>
);
};
export default TextInput;
Error Message
TypeError: register is not a function {...register(name, options)}
I was searching on StackOverflow there was a Post, but the Answer was confusing for me and the Questioner Code was much different than mine. Because I think the error occurred because I use styled-components, and it is nested deep. I am confused because I was reading the docs and followed along.
If I spread the Error says, register is not a function else if I not spread it then the error is ... spread is required.
Hopefully you can bring light to my confusion.
Kind regards
Kuku
The simplest solution is to take advantage of react hook form's context and use the useFormContext hook.
Input Component
import { useFormContext } from "react-hook-form";
const TextInput = ({ name, options }) => {
const { register } = useFormContext();
return (
<S.Input
name={name}
{...register(name, options)}
/>
</S.TextInputWrapper>
);
};
Remove the input register function from the parent form
export const ContactForm = () => {
...other functions
return <TextInput name={'firstName'} options={{maxLength: '20' }} />;
}
An even simpler solution is to let react-hook-form control the form values and use the useController hook or Controller component.
import { useController } from "react-hook-form";
const TextInput = ({ name, options }) => {
const { field } = useController({ name, rules: options });
return <S.Input name={name} {...field} />
};
You can also get the input states using the useContoller hook to reduce the number of events your using.
import { useController } from "react-hook-form";
const TextInput = ({ name, options }) => {
const {
field,
fieldState: { error, invalid, isDirty, isTouched }
} = useController({ name, rules: options });
};
useFormContext is a good solution explained by #Sean W
Here is another solution without useFormContext, you can use register as usual instead of passing it as a prop. You just have to forward the ref of your TextInput.
👉🏻 You can find an instance here: https://stackoverflow.com/a/68667226/4973076

ReactJS state is modified with delay?

I'm programatically validating an email and password inputs for simple login, here is the function that call other function that validate the email.
handleLogin(event) {
this.validateEmail();
this.validatePassword();
if (this.state.emailValid === 'error' || this.state.passwordValid === 'error') {
alert('invalid form');
return;
};
const email = ReactDOM.findDOMNode(this.refs.email).value;
const password = ReactDOM.findDOMNode(this.refs.password).value;
const creds = { email: email, password: password }
this.props.onLoginClick(creds)
}
Notice that first than all I'm calling the validateEmail() function which modifies the store that indicates if the input is correct, here's the validateEmail() source code:
validateEmail() {
const email = ReactDOM.findDOMNode(this.refs.email).value;
let validEmail = /^.+([.%+-_]\w+)*#\w+([.-]\w+)*\.\w+([-.]\w+)*$/.test(email);
if (!validEmail) {
this.setState({
emailValid: 'error'
});
return;
}
this.setState({
emailValid: 'success'
});
}
But in the if statement the state.emailValid has not been yet updated, this is a delay in the state modifying, so the alert() is not displayed. How to get the updated state correctly?
Thanks
The thing to note here is that setState is asynchronous. It will not update the state until everything else that is synchronous in your handleLogin method has completed.
With React I like to use state as a single source of truth as often as I can. In the example above you have the html element as a source of truth and state. By changing your components to be controlled by the react state, you can validate your forms on each keystroke.
Forms and Controlled Components
Start by keeping the state of your input in state
class LoginForm extends React.Component {
constructor(props) {
super(props);
this.state = {
email: '',
emailValid: true,
};
// we bind the function in case we want to
// control text in child component
this.emailChange = this.handleEmailChange.bind(this);
}
emailChange(event) {
this.setState({email: event.target.value});
}
render() {
<textarea value={this.state.email} onChange={this.emailChange} />
}
}
Now whenever you type the state of your html input is handled in react. This will enable you to more easily check its validity. We can do this by adding another method to our class:
class LoginForm extends React.Component {
// ...all the stuff from above
validateEmail() {
let validEmail = /^.+([.%+-_]\w+)*#\w+([.-]\w+)*\.\w+([-.]\w+)*$/.test(email);
if (!validEmail) {
// Object.assign just ensures immutability
this.setState(Object.assign({}, this.state, {
emailValid: false
}))
} else {
// If using babel, this is ensure immutable also
this.setState({
...state,
emailValid: true
})
}
}
// or....
validateEmail() {
let validEmail = /^.+([.%+-_]\w+)*#\w+([.-]\w+)*\.\w+([-.]\w+)*$/.test(email);
this.setState({...state, emailValid: validEmail})
}
// ...render method
}
The validation will now occur on each keystroke. When you need to submit your form all you need to do is check the state if the data is valid and dont need to reference the dom. You can send the data from state.

How can I use props to auto-populate editable redux-form fields in React?

I'm new to React so I've tried to show as much code as possible here to hopefully figure this out! Basically I just want to fill form fields with properties from an object that I fetched from another API. The object is stored in the autoFill reducer. For example, I would like to fill an input with autoFill.volumeInfo.title, where the user can change the value before submitting if they want.
I used mapDispatchtoProps from the autoFill action creator, but this.props.autoFill is still appearing as undefined in the FillForm component. I'm also confused about how to then use props again to submit the form. Thanks!
My reducer:
import { AUTO_FILL } from '../actions/index';
export default function(state = null, action) {
switch(action.type) {
case AUTO_FILL:
return action.payload;
}
return state;
}
Action creator:
export const AUTO_FILL = 'AUTO_FILL';
export function autoFill(data) {
return {
type: AUTO_FILL,
payload: data
}
}
Calling the autoFill action creator:
class SelectBook extends Component {
render() {
return (
....
<button
className="btn btn-primary"
onClick={() => this.props.autoFill(this.props.result)}>
Next
</button>
);
}
}
....
function mapDispatchToProps(dispatch) {
return bindActionCreators({ autoFill }, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(SelectBook);
And here is the actual Form where the issues lie:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { reduxForm } from 'redux-form';
import { createBook } from '../actions/index;
class FillForm extends Component {
constructor(props) {
super(props);
this.state = {value: ''};
this.handleChange = this.handleChange.bind(this);
}
onSubmit(props) {
this.props.createBook(props)
}
handleChange(event) {
this.setState({value: event.target.value});
}
render() {
const { fields: { title }, handleSubmit } = this.props;
return (
<form {...initialValues} onSubmit={handleSubmit(this.onSubmit.bind(this))}>
<input type="text" className="form-control" name="title" {...title} />
<button type="submit">Submit</button>
</form>
)
}
export default reduxForm({
form: 'AutoForm',
fields: ['title']
},
state => ({
initialValues: {
title: state.autoFill.volumeInfo.title
}
}), {createBook})(FillForm)
I think you're mixing up connect and reduxForm decorators in the actual form component. Currently your code looks like this (annotations added by me):
export default reduxForm({
// redux form options
form: 'AutoForm',
fields: ['title']
},
// is this supposed to be mapStateToProps?
state => ({
initialValues: {
title: state.autoFill.volumeInfo.title
}
}),
/// and is this mapDispatchToProps?
{createBook})(FillForm)
If this is the case, then the fix should be as simple as using the connect decorator as it should be (I also recommend separating this connect props to their own variables to minimize confusions like this):
const mapStateToProps = state => ({
initialValues: {
title: state.autoFill.volumeInfo.title
}
})
const mapDispatchToProps = { createBook }
export default connect(mapStateToProps, mapDispatchToProps)(
reduxForm({ form: 'AutoForm', fields: ['title'] })(FillForm)
)
Hope this helps!

How to use node-simple-schema reactively?

Given that there is not much examples about this, I am following the docs as best as I can, but the validation is not reactive.
I declare a schema :
import { Tracker } from 'meteor/tracker';
import SimpleSchema from 'simpl-schema';
export const modelSchema = new SimpleSchema({
foo: {
type: String,
custom() {
setTimeout(() => {
this.addValidationErrors([{ name: 'foo', type: 'notUnique' }]);
}, 100); // simulate async
return false;
}
}
}, {
tracker: Tracker
});
then I use this schema in my component :
export default class InventoryItemForm extends TrackerReact(Component) {
constructor(props) {
super(props);
this.validation = modelSchema.newContext();
this.state = {
isValid: this.validation.isValid()
};
}
...
render() {
...
const errors = this.validation._validationErrors;
return (
...
)
}
}
So, whenever I try to validate foo, the asynchronous' custom function is called, and the proper addValidationErrors function is called, but the component is never re-rendered when this.validation.isValid() is supposed to be false.
What am I missing?
There are actually two errors in your code. Firstly this.addValidationErrors cannot be used asynchronously inside custom validation, as it does not refer to the correct validation context. Secondly, TrackerReact only registers reactive data sources (such as .isValid) inside the render function, so it's not sufficient to only access _validationErrors in it. Thus to get it working you need to use a named validation context, and call isValid in the render function (or some other function called by it) like this:
in the validation
custom() {
setTimeout(() => {
modelSchema.namedContext().addValidationErrors([
{ name: 'foo', type: 'notUnique' }
]);
}, 100);
}
the component
export default class InventoryItemForm extends TrackerReact(Component) {
constructor(props) {
super(props);
this.validation = modelSchema.namedContext();
}
render() {
let errors = [];
if (!this.validation.isValid()) {
errors = this.validation._validationErrors;
}
return (
...
)
}
}
See more about asynchronous validation here.

Handling tab for lists in Draft.js

I have a wrapper around the Editor provided by Draft.js, and I would like to get the tab/shift-tab keys working like they should for the UL and OL. I have the following methods defined:
_onChange(editorState) {
this.setState({editorState});
if (this.props.onChange) {
this.props.onChange(
new CustomEvent('chimpeditor_update',
{
detail: stateToHTML(editorState.getCurrentContent())
})
);
}
}
_onTab(event) {
console.log('onTab');
this._onChange(RichUtils.onTab(event, this.state.editorState, 6));
}
Here I have a method, _onTab, which is connected to the Editor.onTab, where I call RichUtil.onTab(), which I assume returns the updated EditorState, which I then pass to a generic method that updates the EditorState and calls some callbacks. But, when I hit tab or shift-tab, nothing happens at all.
So this came up while implementing with React Hooks, and a google search had this answer as the #2 result.
I believe the code OP has is correct, and I was seeing "nothing happening" as well. The problem turned out to be not including the Draft.css styles.
import 'draft-js/dist/Draft.css'
import { Editor, RichUtils, getDefaultKeyBinding } from 'draft-js'
handleEditorChange = editorState => this.setState({ editorState })
handleKeyBindings = e => {
const { editorState } = this.state
if (e.keyCode === 9) {
const newEditorState = RichUtils.onTab(e, editorState, 6 /* maxDepth */)
if (newEditorState !== editorState) {
this.handleEditorChange(newEditorState)
}
return
}
return getDefaultKeyBinding(e)
}
render() {
return <Editor onTab={this.handleKeyBindings} />
}
The following example will inject \t into the current location, and update the state accordingly.
function custKeyBindingFn(event) {
if (event.keyCode === 9) {
let newContentState = Modifier.replaceText(
editorState.getCurrentContent(),
editorState.getSelection(),
'\t'
);
setEditorState(EditorState.push(editorState, newContentState, 'insert-characters'));
event.preventDefault(); // For good measure. (?)
return null;
}
return getDefaultKeyBinding(event);
}