Antd's getFieldDecorator for other packages - javascript

I'd like to integrate react-mapbox-autocomplete with Antd's getFieldDecorator.
But it seems to Antd's getFieldDecorator only support for their own components.
Is there any way to use it for other packages like react-map-autocomplete?

Yes, it is possible to make getFieldDecorator to work with every custom component that is not an antd component.
As mentioned in the docs, you need to use the injected props from getFieldDecorator.
After wrapped by getFieldDecorator, value(or other property defined by valuePropName) onChange(or other property defined by trigger) props will be added to form controls.
As this is a very common question, here is some production code example:
Here we use a custom component SliderNumber with getFieldDecorator.
<Form.Item label={TOLERANCE.label}>
{getFieldDecorator(TOLERANCE.field)(<SliderNumber />)}
</Form.Item>
And within its implementation, we use the injected onChange and value from getFieldDecorator.
const SliderNumber = forwardRef(({ onChange, value: initial }, ref) => {
const [value, setValue] = useState(initial);
useEffect(() => {
onChange(value);
}, [onChange, value]);
return (
<FlexBox>
<FlexBox.Item span={SPAN}>
<Slider value={value} onChange={setValue} />
</FlexBox.Item>
<FlexBox.Item span={SPAN_REST}>
<InputNumber value={value} onChange={setValue} />
</FlexBox.Item>
</FlexBox>
);
});
Note that sometimes it is easier to just use ref without implementing the onChange like above.

Related

How to auto tab between Formik Field?

I have a component for 4 digit code of phone validation. By itself it works fine and looks good as well. The only issue I am facing - I can't autotab between numbers. I have to go to each input manually and write the number. Is it possible to do with Formik Field?
This is my piece of code:
<Formik
onSubmit={values =>
VerifyGarageFunc({ code: values.code.join(''), requestId: PhoneCodeData.data }, data.showModal)
}>
{({ values, handleChange, handleSubmit }) => (
<form onSubmit={handleSubmit}>
<FieldArray
name="code"
render={arrayHelpers => (
<div className={styles.inputWrapper}>
{values.code.map((item, index) => (
<div key={index}>
<Field
name={`code.${index}`}
type="text"
component={CustomInput}
onChange={handleChange}
value={values.code[index]}
/>
</div>
))}
</div>
)}
/>
<LoginActionButton onSubmit={handleSubmit} text={'Send'} />
<FieldArray />
</form>
)}
</Formik>
I tried https://www.npmjs.com/package/react-auto-tab but it works only with <input/>, for some reason it doesn't work at all with Formik Field.
P.S. I am using Next.js with React.js
You'll likely have more luck with a hook based solution. Install https://github.com/Romr1ch/react-pin-input-hook, which just does the logic without being opinionated about display.
Create a new component called PinInput and create a new field using the formik hook primitives.
Ive setup an example codesandbox: https://codesandbox.io/s/react-pin-input-hook-custom-input-1ze5dv?file=/src/App.js, but note this doesn't use your exact components as I don't have them. The below code should match closer your exact case.
import React from 'react'
import { useField } from 'formik'
import { usePinInput } from 'react-pin-input-hook'
export const PinInput = (props) => {
const [field, meta, helpers] = useField(props)
const { fields } = usePinInput({
values: field.value,
onChange: (values) => {
helpers.setValue(values)
},
})
return fields.map((fieldProps, index) =>
<CustomInput key={index} type="text" {...fieldProps} />
)
}
Then in your main file do this (by the way if you use Form component from Formik you dont need to do any of the onSubmit binding yourself, so I changed that along the way -- the button can just be "submit" type):
<Formik
onSubmit={values =>
VerifyGarageFunc({ code: values.code.join(''), requestId: PhoneCodeData.data }, data.showModal)
}>
<Form>
<PinInput name="code" />
<LoginActionButton type="submit" text={'Send'} />
</Form>
</Formik>
Note that this lib requires your CustomComponent to attach a ref to the underlying thing that needs focusing so you'll need to use forwardRef on that component and attach it to the underlying input. It also needs to support onBlur, onFocus, onChange and onKeyDown.

Material UI + React Form Hook + multiple checkboxes + default selected

I am trying to build a form that accommodates multiple 'grouped' checkboxes using react-form-hook Material UI.
The checkboxes are created async from an HTTP Request.
I want to provide an array of the objects IDs as the default values:
defaultValues: { boat_ids: trip?.boats.map(boat => boat.id.toString()) || [] }
Also, when I select or deselect a checkbox, I want to add/remove the ID of the object to the values of react-hook-form.
ie. (boat_ids: [25, 29, 4])
How can I achieve that?
Here is a sample that I am trying to reproduce the issue.
Bonus point, validation of minimum selected checkboxes using Yup
boat_ids: Yup.array() .min(2, "")
I've been struggling with this as well, here is what worked for me.
Updated solution for react-hook-form v6, it can also be done without useState(sandbox link below):
import React, { useState } from "react";
import { useForm, Controller } from "react-hook-form";
import FormControlLabel from "#material-ui/core/FormControlLabel";
import Checkbox from "#material-ui/core/Checkbox";
export default function CheckboxesGroup() {
const defaultNames = ["bill", "Manos"];
const { control, handleSubmit } = useForm({
defaultValues: { names: defaultNames }
});
const [checkedValues, setCheckedValues] = useState(defaultNames);
function handleSelect(checkedName) {
const newNames = checkedValues?.includes(checkedName)
? checkedValues?.filter(name => name !== checkedName)
: [...(checkedValues ?? []), checkedName];
setCheckedValues(newNames);
return newNames;
}
return (
<form onSubmit={handleSubmit(data => console.log(data))}>
{["bill", "luo", "Manos", "user120242"].map(name => (
<FormControlLabel
control={
<Controller
name="names"
render={({ onChange: onCheckChange }) => {
return (
<Checkbox
checked={checkedValues.includes(name)}
onChange={() => onCheckChange(handleSelect(name))}
/>
);
}}
control={control}
/>
}
key={name}
label={name}
/>
))}
<button>Submit</button>
</form>
);
}
Codesandbox link: https://codesandbox.io/s/material-demo-54nvi?file=/demo.js
Another solution with default selected items done without useState:
https://codesandbox.io/s/material-demo-bzj4i?file=/demo.js
Breaking API changes made in 6.X:
validation option has been changed to use a resolver function wrapper and a different configuration property name
Note: Docs were just fixed for validationResolver->resolver, and code examples for validation in repo haven't been updated yet (still uses validationSchema for tests). It feels as if they aren't sure what they want to do with the code there, and it is in a state of limbo. I would avoid their Controller entirely until it settles down, or use Controller as a thin wrapper for your own form Controller HOC, which appears to be the direction they want to go in.
see official sandbox demo and the unexpected behavior of "false" value as a string of the Checkbox for reference
import { yupResolver } from "#hookform/resolvers";
const { register, handleSubmit, control, getValues, setValue } = useForm({
resolver: yupResolver(schema),
defaultValues: Object.fromEntries(
boats.map((boat, i) => [
`boat_ids[${i}]`,
preselectedBoats.some(p => p.id === boats[i].id)
])
)
});
Controller no longer handles Checkbox natively (type="checkbox"), or to better put it, handles values incorrectly. It does not detect boolean values for checkboxes, and tries to cast it to a string value. You have a few choices:
Don't use Controller. Use uncontrolled inputs
Use the new render prop to use a custom render function for your Checkbox and add a setValue hook
Use Controller like a form controller HOC and control all the inputs manually
Examples avoiding the use of Controller:
https://codesandbox.io/s/optimistic-paper-h39lq
https://codesandbox.io/s/silent-mountain-wdiov
Same as first original example but using yupResolver wrapper
Description for 5.X:
Here is a simplified example that doesn't require Controller. Uncontrolled is the recommendation in the docs. It is still recommended that you give each input its own name and transform/filter on the data to remove unchecked values, such as with yup and validatorSchema in the latter example, but for the purpose of your example, using the same name causes the values to be added to an array that fits your requirements.
https://codesandbox.io/s/practical-dijkstra-f1yox
Anyways, the problem is that your defaultValues doesn't match the structure of your checkboxes. It should be {[name]: boolean}, where names as generated is the literal string boat_ids[${boat.id}], until it passes through the uncontrolled form inputs which bunch up the values into one array. eg: form_input1[0] form_input1[1] emits form_input1 == [value1, value2]
https://codesandbox.io/s/determined-paper-qb0lf
Builds defaultValues: { "boat_ids[0]": false, "boat_ids[1]": true ... }
Controller expects boolean values for toggling checkbox values and as the default values it will feed to the checkboxes.
const { register, handleSubmit, control, getValues, setValue } = useForm({
validationSchema: schema,
defaultValues: Object.fromEntries(
preselectedBoats.map(boat => [`boat_ids[${boat.id}]`, true])
)
});
Schema used for the validationSchema, that verifies there are at least 2 chosen as well as transforms the data to the desired schema before sending it to onSubmit. It filters out false values, so you get an array of string ids:
const schema = Yup.object().shape({
boat_ids: Yup.array()
.transform(function(o, obj) {
return Object.keys(obj).filter(k => obj[k]);
})
.min(2, "")
});
Here is a working version:
import React from "react";
import { useForm, Controller } from "react-hook-form";
import FormControlLabel from "#material-ui/core/FormControlLabel";
import Checkbox from "#material-ui/core/Checkbox";
export default function CheckboxesGroup() {
const { control, handleSubmit } = useForm({
defaultValues: {
bill: "bill",
luo: ""
}
});
return (
<form onSubmit={handleSubmit(e => console.log(e))}>
{["bill", "luo"].map(name => (
<Controller
key={name}
name={name}
as={
<FormControlLabel
control={<Checkbox value={name} />}
label={name}
/>
}
valueName="checked"
type="checkbox"
onChange={([e]) => {
return e.target.checked ? e.target.value : "";
}}
control={control}
/>
))}
<button>Submit</button>
</form>
);
}
codesandbox link: https://codesandbox.io/s/material-demo-65rjy?file=/demo.js:0-932
However, I do not recommend doing so, because Checkbox in material UI probably should return checked (boolean) instead of (value).
Here's my solution, which is not using all the default components from Material UI cause at my interface each radio will have an icon and text, besides the default bullet point not be showed:
const COMPANY = "company";
const INDIVIDUAL = "individual";
const [scope, setScope] = useState(context.scope || COMPANY);
const handleChange = (event) => {
event.preventDefault();
setScope(event.target.value);
};
<Controller
as={
<FormControl component="fieldset">
<RadioGroup
aria-label="scope"
name="scope"
value={scope}
onChange={handleChange}
>
<FormLabel>
{/* Icon from MUI */}
<Business />
<Radio value={COMPANY} />
<Typography variant="body1">Company</Typography>
</FormLabel>
<FormLabel>
{/* Icon from MUI */}
<Personal />
<Radio value={INDIVIDUAL} />
<Typography variant="body1">Individual</Typography>
</FormLabel>
</RadioGroup>
</FormControl>
}
name="scope"
control={methods.control}
/>;
Observation: At this example I use React Hook Form without destruct:
const methods = useForm({...})
This is my solution with react hook form 7, the other solutions don't work with reset or setValue.
<Controller
name={"test"}
control={control}
render={({ field }) => (
<FormControl>
<FormLabel id={"test"}>{"label"}</FormLabel>
<FormGroup>
{items.map((item, index) => {
const value = Object.values(item);
return (
<FormControlLabel
key={index}
control={
<Checkbox
checked={field.value.includes(value[0])}
onChange={() =>
field.onChange(handleSelect(value[0],field.value))
}
size="small"
/>
}
label={value[1]}
/>
);
})}
</FormGroup>
</FormControl>
)}
/>
link to codesandbox: Mui multiple checkbox

Clone a React component that implements forwardRef

Lets say I have a base component that uses forwardRef like so:
const BaseMessage = React.forwardRef((props, ref) => (
<div ref={ref}>
{props.icon}
<h2>{props.title}</h2>
<p>{props.message}</p>
</div>
)
Now I want to create a second component, ErrorMessage that is essentially a copy of the BaseMessage but with a predefined value for props.icon, such that the icon prop is not needed to be set. Otherwise, its an exact copy of BaseMessage.
<ErrorMessage title="Oops!" message="Something went wrong when submitting the form. Please try again." />
I don't want to have to do this, since it feels weird to have two layers of forwardRef going on here:
const ErrorMessage = React.forwardRef(({icon, ...props}, ref) => (
<BaseMessage ref={ref} icon={<svg></svg>} {...props} />
))
Is there a way I can make a clone/copy of BaseMessage without having to reimplement forwardRef for ErrorMessage as well? I know there are utils out there like withProps from recompose but I'd like to avoid using a library if I can.
Try cloneElememt
React.cloneElement(BaseMessage, { icon: '' })

How can I improve the react performance if I have many text inputs (Hooks) in a form?

The Problem
I have a form to send data through a api rest in React, the render and the writing on the form is very slow when I have about 80 text fields.
I'm using functional components with hooks to handle the input texts and Material-UI as UI framework.
In a first try, I had a currying function to handle the values:
setValue = (setter) => (e) => { setter(e.target.value) }
But the render process was really slow (because I was creating a function in every render), So I send the setter function as a prop, then it improves a little but not enough.
Actually the input response when I write a key in any input, it's about 500 ms.
What can I do to get a better performance?
The code was simplified for understanding purposes.
Sample code below:
const [input1, setInput1] = useState('')
const [input2, setInput2] = useState('')
const [input3, setInput3] = useState('')
.
.
.
const [input80, setInput80] = useState('')
// render the Inputs
<Input value={input1} setter={setInput1} text="Some label text" />
<Input value={input2} setter={setInput2} text="Some label text" />
<Input value={input3} setter={setInput3} text="Some label text" />
.
.
.
<Input value={input80} setter={setInput80} text="Some label text" />
My Input components:
const Input = ({
value, setter, text, type = 'text'
}) => {
const handleChange = (e) => {
const { value: inputValue } = e.target
setter(inputValue)
}
return (
<Grid>
<TextField
fullWidth
type={type}
label={text}
value={value}
onChange={handleChange}
multiline={multiline}
/>
</Grid>
)
}
All input values are must be in a component because I'm need to send them to a server with axios.
It looks like the Material-UI Input component is a bit heavy.
I have a sample codesandbox here where I initialised around 1000 inputs. Initially it lagged and crashed.
To begin with I added a memo to the Input component. This memoizes all the Input components, triggering a new render only if one of its props has changed.
For a start just add a memo to your input component.
import React, { memo } from 'react';
const Input = memo(({
value, setter, text, type = 'text'
}) => {
const handleChange = (e) => {
const { value: inputValue } = e.target
setter(inputValue)
}
return (
<Grid>
<TextField
fullWidth
type={type}
label={text}
value={value}
onChange={handleChange}
multiline={multiline}
/>
</Grid>
)
})
Note: If you have a custom setter like in your first case setValue = (setter) => (e) => { setter(e.target.value) }, you can wrap that in a useCallback to prevent multiple functions to be created for every render.

Material ui textfield ith redux form issue

i'm having an issue here which i can't handle
I have a textfield which i made compatible with redux form
like this:
const renderTextField = props => (
<TextField {...props} />
);
and i'm using it like this:
<Field
id="searchCif"
name="searchCif"
component={renderTextField}
floatingLabelText={SEARCHVIEW_HINT_CIF}
floatingLabelFixed={false}
value
/>
Then i writing this in my container:
import { reduxForm } from 'redux-form/immutable';
import { connect } from 'react-redux';
// import { injectIntl } from 'react-intl';
import SearchDefaultView from './views/searchDefaultView';
import { requestCustomerInfo } from './actions/customerActions';
export const mapDispatchToProps = dispatch => (
{
requestCustomerInfo: formData =>
dispatch(requestCustomerInfo(formData))
}
);
const SearchDefaultReduxForm = reduxForm({
form: 'customerInfo', // a unique identifier for this form
})(SearchDefaultView);
const SearchDefaultContainer = connect(
null,
mapDispatchToProps
)(SearchDefaultReduxForm);
export default SearchDefaultContainer;
But when i'm writing the value and submit my form the form has NO VALUES. what am i missing?
From the dicumentation i used this:
const renderTextField = ({
input,
label,
meta: { touched, error },
...custom
}) =>
<TextField
hintText={label}
floatingLabelText={label}
errorText={touched && error}
{...input}
{...custom}
/>
const SearchDefaultView = (props) => {
const { requestCustomerInfo, handleSubmit } = props;
return (
<form onSubmit={handleSubmit(requestCustomerInfo)}>
<Menu
autoWidth={false}
style={styleSearchMenu}
>
<Divider />
<Field
id="searchCif"
name="searchCif"
component={renderTextField}
floatingLabelText={SEARCHVIEW_HINT_CIF}
floatingLabelFixed={false}
/>
<br />
<Field
id="searchAFM"
name="searchAFM"
component={renderTextField}
floatingLabelText={SEARCHVIEW_HINT_AFM}
floatingLabelFixed={false}
/>
<br />
<RaisedButton
type="submit"
fullWidth
primary
label={SEARCHVIEW_SEARCH}
/>
</Menu>
</form>
);
};
But it is showing me an error in compilation at ...custom
When you want to use a custom field for Redux-Form, Redux-form gives you access to both props like onChange etc, but also other meta-data (like if the form has been touched or not). These different kinds of props are grouped depending on type. The interesting part for you is that all the attributes associated with a normal input element (like onChange, value, type) are grouped in props.input. So to pass those down to the <TextField /> component you can't use the spread operator (... ) on props directly. You must use it on props.input.
const renderTextField = props => (
<TextField {...props.input} />
);
You may also have to deal with the fact that the onChange method that <TextField /> expects doesn't necessarily have the same signature as the onChange method that Redux-form provides you. So you may have to do some manual work to make them work together, similar to what I've outlined in this post. You'd have to read up on the documentation of both the onChange of Redux-Form and Material-UI TextField respectively.
You may also be interested to know that for material-ui components, there actually already exists a library that has done that manual work for you: redux-form-material-ui.
I think you are not using the onChange prop of the component.
onChange: Callback function that is fired when the textfield's value changes.
You should dispatch the change and update the data in redux container.
http://www.material-ui.com/#/components/text-field

Categories

Resources