How to create an event similar to onChange in React - javascript

I have a react native input component that takes a regex and emit a boolean if the regex matches.
I wanted to do the similar thing but return a string value in React Js but the problem is I don't totally understand how the react native input component is working.
Custom Input component
class CustomInput extends Component {
handleValidation(value) {
const {pattern} = this.props;
if (!pattern) {
return true;
}
// string pattern, one validation rule
if (typeof pattern === 'string') {
const condition = new RegExp(pattern, 'g');
return condition.test(value);
}
// array patterns, multiple validation rules
if (typeof pattern === 'object') {
const conditions = pattern.map(rule => new RegExp(rule, 'g'));
return conditions.map(condition => condition.test(value));
}
}
onChange(value) {
const {onChangeText, onValidation} = this.props;
const isValid = this.handleValidation(value);
onValidation && onValidation(isValid);
onChangeText && onChangeText(value);
}
render() {
const {pattern, onChangeText, children, style, ...props} = this.props;
return (
<Input
style={style}
onChangeText={value => this.onChange(value)}
{...props}
autoCapitalize="none">
{children}
</Input>
);
}
}
Usage
<CustomInput
value={pass} //state variable
onChangeText={onChangePass} //state variable setter
pattern={[regexes.password]}
onValidation={isValid => performValidation(isValid)}
//performValidatio method enables and disables a button according to isValid
/>
Now I want to make a component in ReactJs that has an Input and other elements the input will take a regex and and it will return the value of its input to the parent. It will be used in a form that will have many inputs and all inputs need to have and error msg and validations.

You can create custom component with controlled state. You need to set local state within the custom component. On change of value, you can parse and validate. after that set it to local state. You can leverage error either local of delegate to parent. Here below sample, i have delegated to parent on validation.
const Input = ({
onChangeText,
pattern,
onValidation,
defaultValue,
error,
...rest
}) => {
const [value, setValue] = useValue(defaultValue);
const onChange = ({ target: { value } }) => {
if (pattern) onValidation(pattern.test(value));
setValue(value);
onChangeText(value);
};
return (
<div className="wrapper">
<input value={value} onChange={onChange} {...rest} />
{error && <span>{error}</span>}
</div>
);
};

I have understood the logic if anyone wants to make an input like this they can use this code
I have used react-bootstrap for styling
The Custom Input
import React, { useState } from "react";
export default function CustomInput(props) {
const { error, inputProps, regex, className, onInput } = props;
const [showError, setShowError] = useState(false);
const [text, setText] = useState("");
const handleChange = (val) => {
setText(val);
if (regex.test(val)) {
setShowError(false);
} else {
setShowError(true);
}
onInput && onInput(val);
};
return (
<div className={className}>
<input
value={text}
className={showError ? "form-control border-danger" : "form-control"}
{...inputProps}
onChange={(e) => handleChange(e.target.value)}
/>
{showError && error ? (
<small className="text-danger">{error}</small>
) : null}
</div>
);
}
And use it in a parent component like this
const [temp, setTemp] = useState("");
<CustomInput
className="form-group col-md-3"
inputProps={{ placeholder: "test", maxLength: "50" }}
error="Required"
regex={regexes.url}
onInput={(val) => setTemp(val)}
/>
Somebody please confirm if this is a good approach

Related

React Native - react hook form - setValue not re-render Controller component

Re-render problem
I have two components:
MainForm
CFNumericInput
The values coming from CFNumericInput are correct, but setValue won't render the old one.
Do I have to use an useEffect?
MainForm
const { control, watch, setValue, register } = useFormContext();
return (
<CFNumericInput
name="number1"
control={control}
setValueOnChange={(nextValue: string, oldValue: string) => {
let nextValidValue = checkNumericType(nextValue, "f6.2");
if (nextValidValue !== "") setValue("number1", nextValidValue);
else if (oldValue) setValue("number1", oldValue);
}}
/>;
)
CFNumericInput
export const CFNumericInput: React.FC<any> = ({
name,
control,
setValueOnChange,
}) => {
return control ? (
<Controller
name={name}
control={control}
render={({ field }) => {
return (
<NumericInput
{...field} // onChange, onBlur, value, name, ref
title={title}
onChange={(e) => {
field.onChange(e);
setValueOnChange && setValueOnChange(e, field.value);
}}
/>
);
}}
/>
) : (
<></>
);
};
Working but heavy solution
This solution it's working, but it's really heavy.
const [number1] = watch(["number1"]);
const [old, setOld] = useState("");
useEffect(() => {
let nextValidValue = checkNumericType(number1, "f6.2");
if (nextValidValue !== "") {
setValue("number1", nextValidValue);
setOld(nextValidValue);
} else if (old) setValue("number1", old);
}, [number1]);
Isn't possible that you call checkNumericType on onSubmit?
Also you can try on looking to use Yup (take a look at: https://react-hook-form.com/advanced-usage/#CustomHookwithResolver)
let me know what have you tried

React: How to get a group value of components?

I have a input component that does validation. In a reduced form it looks like this:
const InputField = ({validation, name}) => {
const [value, setValue] = useState("");
const [error, setError] = useState(false);
const handleChange = (e) => {
let errors = 0;
if (validation.includes("required") && val === "") {
errors++;
}
if (errors > 0) {
setError(true);
} else {
setError(false);
}
setValue(e.target.value)
}
return (
<input
type="text"
value={value}
onChange={handleChange}
name={name}
/>
);
};
If I am using this component multiple times in a parent like
const Parnet = () => {
// 🔥🔥🔥 help required here
const groupedErrorValueBool = true;
return (
<>
<InputField name="Name" validation="required" />
<InputField name="Email" validation="required" />
<InputField name="Birthday" validation="required" />
<button disabled={groupedErrorValueBool}>Submit</button>
</>
);
How can I get a grouped value of errors? Like: I want to disable a submit button, if any field has any error.
Ideally I know which component has an error, so that I can print meaningful comments.
Implementation
You could do it like this:
const InputField = ({validation, onErrorChanged}) => {
const [value, setValue] = React.useState("test");
const [error, setError] = React.useState(false);
const handleChange = (e) => {
let errors = 0;
if (validation.includes("required") && e.target.value === "") {
errors++;
}
if (errors > 0 && !error) {
// we have error(s) and the error state is set to false
setError(true);
onErrorChanged(true);
} else if (errors === 0 && error){
// we don't have any error but the error state is set to true
setError(false);
onErrorChanged(false);
}
setValue(e.target.value)
}
return (
<input
type="text"
value={value}
onChange={handleChange}
/>
);
};
const InputGroup = () => {
const [errors, setErrors] = React.useState(0);
function errorChanged(hasError){
if(hasError) setErrors(errors + 1);
else setErrors(errors - 1);
}
return (
<React.Fragment>
<InputField validation="required" onErrorChanged={errorChanged}/>
<InputField validation="required" onErrorChanged={errorChanged}/>
<InputField validation="required" onErrorChanged={errorChanged}/>
<button disabled={errors === 0 ? false: true}>Submit</button>
</React.Fragment>
);
}
ReactDOM.render(<InputGroup/>, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Explanation
Child component
You need to pass a callback function to the children that is called whenever the error state changes on a child element i. e.
when we have found an error in the input and error state is currently set to false
when we have no error(s) in the input and error state is currently set to true
Parent component
In the parent component we need a state errors which is a counter that counts the number of child component that have errors. Additionally we need to define the above mentioned callback function such that it increments the counter whenever a child component reports it has an error and decrements the counter when a child component reports it has switched to state no error.
If the counter equals zero we know we don't have any error in any of our child components, if it's not null we know how many child components have errors.
The groupedErrorValueBool state would need to be in your parent component then, then you give the state handler to the children
const InputField = ({ validation, setGroupErrorBool }) => {
const [value, setValue] = useState("");
const [error, setError] = useState(false);
const handleChange = (e) => {
let errors = 0;
if (validation.includes("required") && val === "") {
errors++;
}
if (errors > 0) {
setError(true);
setGroupErrorBool(true);
} else {
setError(false);
}
setValue(e.target.value);
};
return <input type="text" value={value} onChange={handleChange} />;
};
const Parent = () => {
const [groupErrorBool, setGroupErrorBool] = useState(false);
return (
<>
<InputField validation="required" setGroupErrorBool={setGroupErrorBool} />
<InputField validation="required" setGroupErrorBool={setGroupErrorBool} />
<button disabled={groupErrorBool}>Submit</button>
</>
);
};
Form stuff is admittedly can be very difficult. That's why I use "Formik" for my React forms. Formik I believe will use a parent/overarching form control element with rendered props technique to expose form state handlers, which could also be something you can try yourself

React custom input passing props to parent functional component while using custom hooks

Am new to react and i have a custom input where am handling the value and input handler via a custom hook but would like to get the value and the input handler to the parent component using the custom input but am stuck on how to achieve this.
I have written the following code.
On the custom hook
import {useReducer} from "react";
const INITAL_STATE = {value:'',valid:false,pristine:true, error:''}
const REDUCER_ACTIONS = { input:"INPUT", blur:"BLUR"}
const reducer = (state,action)=>{
if (action.type === REDUCER_ACTIONS.input){
return {...state, value: action.value}
}
if (action.type === REDUCER_ACTIONS.blur){
return {...state, pristine: false}
}
return INITAL_STATE;
}
const useForm = () => {
const [inputState, dispatch] = useReducer(reducer,INITAL_STATE)
const onBlurHandler = (event) => {
dispatch({type:REDUCER_ACTIONS.blur});
}
const onInputHandler = (event) => {
dispatch({type:REDUCER_ACTIONS.input,value:event.target.value})
}
return {
...inputState,
onBlurHandler,
onInputHandler
}
};
export default useForm;
And for my custom input i have
import useForm from "../../hooks/use-form";
const CustomInput = (props) => {
const {value, onInputHandler, onBlurHandler} = useForm(); //uses custom hook
return <>
<label htmlFor={props.id}>{props.label}</label>
<input value={value} onBlur={onBlurHandler} onInput={onInputHandler}
{...props} />
</>
}
export default CustomInput;
The above custom input has the onInput and onBlur pointing to the custom hooks since i want to reuse the functionality on other input types like select and date pickers without having to duplicate them.
On my parent component am simply calling the Custom input like
function App() {
return (
<div className="container">
<CustomInput onInputHandler={} label="First name"/>
</div>
);
}
export default App;
I would like to pass the onInputHandler and value as a props back to the parent component from the custom input but am stuck on how to do this. How do i proceed?
When you say you need to pass value, I guess you wanted to pass the initial value of the input to CustomInput. To achieve that you can pass another prop.
App.js pass initialValue to CustomInput
<CustomInput
initialValue={"abc"}
label="First name"
/>
In CustomInput pass initialValue prop to useForm hook as an argument.
const { value, onInputHandler, onBlurHandler } = useForm(props.initialValue);
Set the initialValue as the value in initial state in useForm.
const useForm = (initialValue) => {
const [inputState, dispatch] = useReducer(reducer, {
...INITAL_STATE,
value: initialValue
});
...
...
}
To pass the onInputHandler as a prop you can check if onInputHandler is available as a prop and call it along with onInputHandler coming from useForm.
In App.js defines another function that accepts event as an argument.
export default function App() {
const onInputHandler = (e) => {
console.log(e);
};
return (
<div className="App">
<CustomInput
...
onInputHandler={onInputHandler}
label="First name"
/>
</div>
);
}
In CustomInput change the onInput handler like below. You can change the logic as per your needs (I called onInputHandler in useForm and prop).
<input
value={value}
onBlur={onBlurHandler}
onInput={(e) => {
props.onInputHandler && props.onInputHandler(e);
onInputHandler(e);
}}
{...props}
/>
My approach to this will be to simply call the onInputHandler() from hooks and onInputHandler() from the props received from Parent and send the e.target.value as a prop to these functions.
const CustomInput = (props) => {
const { value, onInputHandler, onBlurHandler } = useForm(); //uses custom hook
console.log(value);
const handleInputChange = (e: any) => {
onInputHandler(e);
props.onInputHandler(e.target.value);
};
return (
<>
<label htmlFor={props.id}>{props.label}</label>
<input
value={value}
onBlur={onBlurHandler}
onInput={(e) => {
handleInputChange(e);
}}
{...props}
/>
</>
);
};
export default CustomInput;
And in the parent component we can receive them as props returned from that function and use it according to our requirement.
function App() {
return (
<div className="container">
<CustomInput
label="name"
onInputHandler={(value: string) => console.log("App",value)}
/>
</div>
);
}
export default App;
sandbox link : https://codesandbox.io/s/nifty-lamport-2czb8?file=/src/App.tsx:228-339

I use React hooks and React memo to prevent my functional component from re-rendering ,but not work?

I use React.memo to control re-render, but my component still re-rendered.
my code like this:
in my Template Component:
const Template = (props: Props) => {
const { propsValue, data } = props
const [value, setValue] = useState(propsValue)
const handleChange = (value) => {
setValue(value)
props.onChange(value)
}
return (
<div>
{info.type === 'input' && <Input value={value} onChange={(event, val) => handleChange(val) onBlur={(event) => handleBlur()} />
{info.type === 'image' && <Uploader multiple value={value} uploadUrl={data.uploadUrl} onChange={(val) => handleChange(val)} />
{info.type === 'select' && <Select onChange={(val) => handleChange(val)} />
</div>
)
}
const areEqual = (prevProps, nextProps) => {
if (JSON.stringify(prevProps) !== JSON.stringify(nextProps)) {
return false
}
return true
}
export default React.memo(EditTemplate, areEqual)
in my Uploader Component:
const Uploader = props => {
const [value, setValue] = useState(props.value)
let { uploadUrl, multiple } = props
const handleChange = ({ file, fileList }) => {
if (file.status === 'done') {
setValue(fileList)
props.onChange(fileList)
} else {
setValue(fileList)
}
}
return (
<div>
<Upload fileList={value} multiple={multiple} action={uploadUrl} onChange={handleChange} />
</div>
)
}
const areEqual = (prevProps, nextProps) => {
if (JSON.stringify(prevProps) !== JSON.stringify(nextProps)) {
return false
}
return true
}
export default React.memo(Uploader, areEqual)
when I change value in Select Component, the areEqual seems like not work, the address of all images in Upload Component will reload. why...?
the performance like this:
how can I do?
The rerender might be because of the internal state change(setValue(value)). React.memo doesn't prevent rerender caused by a state change.
React.memo only checks for prop changes. If your function component
wrapped in React.memo has a useState or useContext Hook in its
implementation, it will still rerender when state or context change.
Docs
You're passing the onChange param to Uploader like onChange={(val) => handleChange(val)} so this effectively creates new function on each render and probably React.memo gives false positive because of this. Also in Uploader you have props.onChange(fileList) and fileList might also be the reason if it's a different Array instance everytime.
In addition to #Ramesh Reddy's answer, you're calling setValue upon Select changes too, that could also be the reason, since you're using the new value as prop to Uploader.
If this is not the reason, you can add a codesandbox sample with reproduction of the issue.
thanks all very much~I find that the type of my value in Uploader is array, so when Select change ,props.onChange will be lead to change the value, so Uploader will reload.
I add useEffect in Uploader Component like this : useEffect(() => { setValue(formatValue(props.value)) }, [JSON.stringify(props.value)]) ,and then the images will not reload...

How to set value through patch event to any field from custom input in sanity.io?

I want to make patch event from custom component and set a value to another field in document, but couldn’t find documentation about patch events.
there are only example without field specification:
PatchEvent.from(set(value))
Does anybody knows how to specify field name?
This opportunity contains in documentation, but without examples
https://www.sanity.io/docs/custom-input-widgets#patch-format-0b8645cc9559
I couldn't get PatchEvent.from to work at all for other fields inside a custom input component but useDocumentOperation combined with withDocument from part:#sanity/form-builder
This is a rough working example using a custom component:
import React from "react";
import FormField from "part:#sanity/components/formfields/default";
import { withDocument } from "part:#sanity/form-builder";
import { useDocumentOperation } from "#sanity/react-hooks";
import PatchEvent, { set, unset } from "part:#sanity/form-builder/patch-event";
// tried .from(value, ["slug"]) and a million variations to upate the slug but to no avail
const createPatchFrom = (value) => {
return PatchEvent.from(value === "" ? unset() : set(value));
};
const ref = React.createRef();
const RefInput = React.forwardRef((props, ref) => {
const { onChange, document } = props;
// drafts. cause an error so remove
const {patch} = useDocumentOperation(
document._id.replace("drafts.", ""), document._type)
const setValue = (value) => {
patch.execute([{set: {slug: value.toLowerCase().replace(/\s+/g, "-")}}])
onChange(createPatchFrom(value));
// OR call patch this way
patch.execute([{set: {title: value}}])
};
return (
<input
value={document.title}
ref={ref}
onChange={(e) => setValue(e.target.value)}
/>
);
});
class CustomInput extends React.Component {
// this._input is called in HOC
focus = () => {
ref.current.focus();
};
render() {
const { title } = this.props.type;
return (
<FormField label={title}>
<RefInput ref={ref} {...this.props} />
</FormField>
);
}
}
export default withDocument(CustomInput);

Categories