How to pass string only rather than the entire event in the onchange? - reactjs

Hi I am now working on a project. There were 2 component inside.
InputDialog & AuthenComponent.
I am now trying to keep track of the string inside the HTMLTextAreaElement.
My way of doing thing is working fine but one of the senior want a different way.
I will now show you the working version.
InputDialog Component
interface InputDialogContentProps {
onChange: (e: React.ChangeEvent<HTMLTextAreaElement>) => string;
}
export default function InputDialog(props: InputDialogContentProps) {
return (
<div className="inputDialogContent">
<DialogContent className="dialogContentPlacement">
<TextField
onChange={props.onChange}
fullWidth
/>
</DialogContent>
</div>
);
}
as you can see I am getting the onchange event from the props.
This is the AuthenComponent ( Function component )
<Dialog
open
onClose={hideDialogBox}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
className="TextPlacement"
>
<div className="AuthenticationDialog">
<InputDialogContent
inputLabel="Email Address"
inputType="email"
onChange={onInputChange}/>
</div>
</Dialog>
function onInputChange(e: React.ChangeEvent<HTMLTextAreaElement>) {
setEnableLoginButton(isValidEmail(e.target.value));
return e.target.value;
}
const isValidEmail = (email: string): boolean => {
const validateFormat = /^\w+([\.-]?\w+)*#\w+$/;
return email.match(validateFormat);
};
These component are working fine. But senior want me to pass the string only rather than passing the entire event. I have been searching google for too long and can't find a soluton.
He want something like this in inputDialog
onChange: (text: string) => void
Rather than
onChange: (e: React.ChangeEvent<HTMLTextAreaElement>) => string;

Extract the value from the element inside the child component's handler, and call the prop function with it:
interface InputDialogContentProps {
checkValidEmail: (text: string) => void
}
onChange={(e) => { props.checkValidEmail(e.currentTarget.value); }}
And in the parent component:
function checkValidEmail(text: string) {
setEnableLoginButton(isValidEmail(text));
}

You can try
<InputDialogContent
inputLabel="Email Address"
inputType="email"
onChange={(e) => onInputChange(e.target.value)}/>
then your onInputChange function
function onInputChange(email: string) {
setEnableLoginButton(isValidEmail(email));
return email;
}

Related

How to get floatValue automatically from react-number-format inside a antd Form.Item?

I'm looking for a way to antd Form get automatically a floatValue from react-number-format.
`<Form.Item
label="My label"
name="fator"
>
<NumericFormat
thousandSeparator="."
decimalSeparator=","
decimalScale={2}
prefix="R$"
fixedDecimalScale={true}
onValueChange={(values, sourceInfo) => {
setState({
...state,
fator: values.floatValue!,
});
}}
/>
</Form.Item>`
Some people used onValueChange to get floatValue, but I do not know if it is the only way or the best way to get that value. On that way, we have to check each field to assign the correct value.
In my opinion, it is not the best way. It is so manually work.
const submit = async (values: stateModel) => {
values.fator = state.fator;
...
...
await doRegister(values);
..
}
How the best way to solve the problem?
I tried using some replacement on values returned from form submit, but it is not the best way to solve the problem.
You can solve this issue by creating a custom Number Field, which takes value and onChange as props.
const CustomNumberField = ({ value, onChange }) => {
return (
<NumericFormat
value={value}
thousandSeparator='.'
decimalSeparator=','
decimalScale={2}
prefix='R$'
fixedDecimalScale={true}
onValueChange={(values, sourceInfo) => {
onChange(values.floatValue);
}}
/>
);
};
Now the main Form will look like this:
const App = () => {
return (
<Form
onFinish={(values) => {
console.log(values); // { factor: 12 }
}}
>
<Form.Item label='My label' name='fator'>
<CustomNumberField />
</Form.Item>
{/* OR */}
{/* <Form.Item name='field'>
{(control, meta, form) => {
return <CustomNumberField {...control} />;
}}
</Form.Item> */}
<Button htmlType='submit'>Submit</Button>
</Form>
);
};
Q: Where that value & onChange prop come from?
When you pass name prop to Form.Item, the field inside the Form.Item is now controlled by Form. You can either pass a ReactElement or a function. For ReactElement, it pass two props value & onChange. For callback function, it looks like this:
children?: React.ReactElement | ((control: ChildProps, meta: Meta, form: FormInstance<Values>) => React.ReactNode);
interface ChildProps {
[name: string]: any;
}
interface Meta {
touched: boolean;
validating: boolean;
errors: string[];
warnings: string[];
name: InternalNamePath;
}
type InternalNamePath = (string | number)[];

React hook form pass an array of values to child - some Regster and some not

I want to pass an array of strings to my Child Component which creates an input field managed by React Hook Form.
Sometimes, I want the React Hook Form to NOT register some fields in the array passed as the input fields are optional to the user. For example, address_line2 in the inputForm array is not always required to be completed on the form.
I use this Child Component in a number of places, so if I change it, is it possible to make the change or component optional or use an if statement? So, that i can continue to use the child component as is.
Here is the parent component:
const StopForm: React.FC<IProps> = ({ id, index, stop }) => {
const Router = useRouter();
const {
register,
handleSubmit,
unregister,
formState: { errors },
} = useForm<Stop>({
defaultValues: sampleStopData,
});
const inputFields = [
"address_line1",
"address_line2",
];
return (
<>
{inputFields.map((field, index) => (
<InputField
key={index}
val={field}
register={register}
defaultValue={
typeof stop === "object" &&
typeof stop[field as keyof Stop] !== "undefined"
? // ? stop[field as keyof Stop].toString()
String(stop[field as keyof Stop])
: ""
}
errors={errors}
classes="px-3 py-1.5 rounded placeholder:text-gray-800 lg:w-[25rem] lg:mx-auto bg-transparent border outline-none"
/>
))}
</>);
My Child Component that manages the react hook form part is this:
interface IProps {
val: string;
type?: string;
defaultValue: string;
register: UseFormRegister<any>;
errors: any;
classes: string;
}
const InputField: React.FC<IProps> = ({
defaultValue,
register,
errors,
val,
classes,
type = "text",
}) => {
return (
<>
<input
id={val}
type={type}
placeholder={val}
className={`${classes} ${
errors?.val ? "border-red-900" : "border-gray-400"
}`}
defaultValue={defaultValue}
{...register(val, {
required: { value: true, message: `${val} is required` },
})}
/>
{errors[val] && (
<small className="text-red-700">{errors[val].message}</small>
)}
</>
);
};
export default InputField;
Can the input fields also pass a value or somehow let the child component know to 'skip' that value and not register it?
Any assistance on how to do this - I have been going round and round on this one, wouuld be graciously received!
Thank you!
Karen

What type should I assign to a function that takes text from an input and writes it to a variable through a useState?

I have the following function in App.tsx:
const [myTxt, setMyTxt] = useState<string>('');
const getTxt: React.MouseEventHandler | React.KeyboardEventHandler = (e: any): void => {
e.preventDefault();
const target = e.target;
setMyTxt(target.city.value.trim());
};
It takes the value from the input form (component SearchPanel.tsx, created with MUI):
const SearchPanel: React.FC<SearchPanelProps> = ({ getTxt }) => {
<Box
component="form"
onSubmit={getTxt}
>
<TextField
size='small'
type="text"
name='city'
placeholder='Enter city'
/>
<Button
type="submit"
>
Press here to search
</Button>
</Box>
}
I create a props type in which I describe a function:
export type SearchPanelProps = {
getTxt: React.MouseEventHandler | React.KeyboardEventHandler
}
Then I pass the function as a props to another component:
<SearchPanel
getTxt={getTxt}
/>
And I see error: "No overload matches this call".
So I have questions, one dependent on the other.
What type should I assign to a function that takes text from an input and writes it to a variable through a useState, and how pass the function as a props?
What type should I assign to the event? I don't want use 'any', it's a bad practice.
Is there an easier way to take the value of a text field to pass to the function than mine?

How can I make a new interface in useState using Typescript?

I'm trying to duplicate an input using useState with interface. Every time I click on the + button, the interface has to duplicate in the state and thus duplicate my input.
This is the code I am trying to do.
interface newInputsInterface {
companyName?: string
cargo?: string
inicio?: string
termino?: string
}
const [newInputs, setNewInputs] = useState<newInputsInterface[]>([new newInputsInterface])
const onChangeInput = (
state: any,
setState: any,
propName: string,
e: any,
i: number
) => {
const arr = [...state]
const item = arr[i]
item[propName] = e.target.value
setState(arr)
}
return(
{InputsMap.map((card, i) => (
<div key={i}>
<div>
<Input
placeholder={card.placeholder1}
isLegalPerson={true}
title={card.title1}
onChange={(e) =>
onChangeInput(newInputs, setNewInputs, card.title1, e, i)
}
value={newInputs[i]}
/>
<Input
placeholder={card.placeholder2}
title={card.title2}
onChange={(e) =>
onChangeInput(newInputs, setNewInputs, card.title2, e, i)
}
value={newInputs[i]}
/>
</div>
<button
onClick={() => {
const arr = [...newInputs]
arr.push(new newInputsInterface)
setNewInputs(arr)
}}
>
+
</button>
</div>
))}
)
if you notice I'm trying to make a new interface inside useState so I can duplicate it, but it's giving this error:
'newInputsInterface' only references one type, but is currently being used as a value.
That's because you use your interface as a value here [new newInputsInterface]. The interface is used to declare types. So if you need a default value for newInputs it might look like this:
const defaultVaule = {}: newInputsInterface
const [newInputs, setNewInputs] = useState<newInputsInterface[]>([defaultVaule])
Not tested but you should get the idea.
See related question: Set types on useState React Hook with TypeScript.

Keeping state of variable mapped from props in functional react component after component redraw

Recently I started learning react and I decided to use in my project functional components instead of class-based. I am facing an issue with keeping state on one of my components.
This is generic form component that accepts array of elements in order to draw all of necessary fields in form. On submit it returns "model" with values coming from input fields.
Everything working fine until I added logic for conditionally enabling or disabling "Submit" button when not all required fields are set. This logic is fired either on component mount using useEffect hook or after every input in form input. After re-render of the component (e.g. conditions for enabling button are not met, so button becomes disabled), component function is fired again and my logic for creating new mutable object from passed props started again, so I am finished with empty object.
I did sort of workaround to make a reference of that mutated object outside of scope of component function, but i dont feel comfortable with it. I also dont want to use Redux for that simple sort of state.
Here is the code (I am using Type Script):
//component interfaces:
export enum FieldType {
Normal = "normal",
Password = "password",
Email = "email"
}
export interface FormField {
label: string;
displayLabel: string;
type: FieldType;
required: boolean;
}
export interface FormModel {
model: {
field: FormField;
value: string | null;
}[]
}
export interface IForm {
title: string;
labels: FormField[];
actionTitle: string;
onSubmit: (model: FormModel) => void;
}
let _formState: any = null;
export function Form(props: IForm) {
let mutableFormModel = props.labels.map((field) => { return { field: field, value: null as any } });
//_formState keeps reference outside of react function scope. After coponent redraw state inside this function is lost, but is still maintained outside
if (_formState) {
mutableFormModel = _formState;
} else {
_formState = mutableFormModel;
}
const [formModel, setFormModel] = useState(mutableFormModel);
const [buttonEnabled, setButtonEnabled] = useState(false);
function requiredFieldsCheck(formModel: any): boolean {
let allRequiredSet = true;
formModel.model.forEach((field: { field: { required: any; }; value: string | null; }) => {
if (field.field.required && (field.value === null || field.value === '')) {
allRequiredSet = false;
}
})
return allRequiredSet;
}
function handleChange(field: FormField, value: string) {
let elem = mutableFormModel.find(el => el.field.label === field.label);
if (elem) {
value !== '' ? elem.value = value as any : elem.value = null;
}
let submitEnabled = requiredFieldsCheck({ model: mutableFormModel });
setFormModel(mutableFormModel);
setButtonEnabled(submitEnabled);
}
useEffect(() => {
setButtonEnabled(requiredFieldsCheck({ model: mutableFormModel }));
}, [mutableFormModel]);
function onSubmit(event: { preventDefault: () => void; }) {
event.preventDefault();
props.onSubmit({ model: formModel })
}
return (
<FormStyle>
<div className="form-container">
<h2 className="form-header">{props.title}</h2>
<form className="form-content">
<div className="form-group">
{props.labels.map((field) => {
return (
<div className="form-field" key={field.label}>
<label>{field.displayLabel}</label>
{ field.type === FieldType.Password ?
<input type="password" onChange={(e) => handleChange(field, e.target.value)}></input> :
<input type="text" onChange={(e) => handleChange(field, e.target.value)}></input>
}
</div>
)
})}
</div>
</form>
{buttonEnabled ?
<button className={`form-action btn btn--active`} onClick={onSubmit}> {props.actionTitle} </button> :
<button disabled className={`form-action btn btn--disabled`} onClick={onSubmit}> {props.actionTitle} </button>}
</div>
</FormStyle >
);
}
So there is quite a lot going on with your state here.
Instead of using a state variable to check if your button should be disabled or not, you could just add something render-time, instead of calculating a local state everytime you type something in your form.
So you could try something like:
<button disabled={!requiredFieldsCheck({ model: formModel })}>Click me</button>
or if you want to make it a bit cleaner:
const buttonDisabled = !requiredFieldsCheck({model: formModel});
...
return <button disabled={buttonDisabled}>Click me</button>
If you want some kind of "caching" without bathering with useEffect and state, you can also try useMemo, which will only change your calculated value whenever your listeners (in your case the formModel) have changes.
const buttonDisabled = useMemo(() => {
return !requiredFieldsCheck({model: formModel});
}, [formModel]);
In order to keep value in that particular case, I've just used useRef hook. It can be used for any data, not only DOM related. But thanks for all inputs, I've learned a lot.

Resources