React JS - disable a button if the input field is empty? - reactjs

React Code:
import { useState } from "react";
export default function IndexPage() {
const [text, setText] = useState("");
const autoComplete = (e) => {
setText({ value: e.target.value });
};
return (
<form>
<input type="text" placeholder="search here" onChange={autoComplete} />
<button type="submit" disabled={!text}> find </button>
</form>
);
}
SandBox Link: https://codesandbox.io/s/practical-panini-slpll
Problem:
when Page Loads, the find button is disabled at first because the input field is empty which is good. But,
Now, when I start typing something. then delete them all with Backspace key. the find button is not disabling again.
I want:
I just want to have my find button disabled if the input field is empty
Where I'm doing wrong?

It's a common mistake coming from Class Components, state setter in hooks does not merge state value as in classes.
You initialized your state as a string (useState('')) and then assigned it as an object
const autoComplete = (e) => {
setText({ value: e.target.value });
};
// State is an object { value: 'some text' }
Fix your set state call:
const autoComplete = (e) => {
setText(e.target.value);
};
// State is a string 'some text'
Then to be more verbose you want to check if the string is empty:
<button disabled={text === ''}>find</button>

disabled={!text.value}
Should do the trick.
With this function
const autoComplete = (e) => {
setText({ value: e.target.value });
};
you are writing your input in text.value, not in text.

use this code for check text
<button type="submit" disabled={text==''}> find </button>

Related

React: why the input value is not displayed in the list after clicking on the "add"-button?

I'm trying out React and trying to make a simple component. Input and "Add" button.
I want get a list of values after filling in the input and clicking on the button. I can see that the state is getting filled, but I don't understand why the list is not being rerender.
Here is my code https://jsfiddle.net/3hkm2qnL/14/
`
const InputWithAddBtn = props => {
const [ value, setValue ] = React.useState('');
return (
<div>
<input type="text" value={value} onChange={e => setValue(e.target.value)} />
<button onClick={() => props.add(value)}>+</button>
</div>
);
};
`
The problem is in the add() function, which by pushing onto the original array does not signal to the component to rerender.
const add = (value) => {
initValue.push(value)
console.log(initValue)
setValue(initValue)
}
One possible solution:
const add = (value) => {
const newValues = [...initValue, value]
console.log(newValues)
setValue(newValues)
}
That will trigger correctly the component to rerender.
For more info see https://stackoverflow.com/a/67354136/21048989
Cheers

Adding validation to simple React + Typescript application

I have kind of an easy problem, but I'm stuck because I do not know TypeScript well (and I need to get to know it very quickly).
I have to add simple validation on submit which will check if a value is not empty.
I have the simplest React form:
type FormValues = {
title: string;
};
function App() {
const [values, handleChange] = useFormState<FormValues>({
title: ""
});
const handleSubmit = (evt: FormEvent) => {
evt.preventDefault();
console.log("SUBMITTED");
};
return (
<div>
<form onSubmit={handleSubmit}>
<input
id="title"
name="title"
onChange={handleChange}
type="text"
value={values.title}
/>
<button type="submit">submit</button>
</form>
</div>
);
}
Custom hook to handle form:
import { useCallback, useReducer } from "react";
type InputChangeEvent = {
target: {
name: string;
value: string;
};
};
function useFormState<T extends Record<string, string>>(initialValue: T) {
const [state, dispatch] = useReducer(
(prevState: T, { name, value }: InputChangeEvent["target"]) => ({
...prevState,
[name]: value
}),
initialValue
);
const handleDispatch = useCallback(
(evt: InputChangeEvent) => {
dispatch({
name: evt.target.name,
value: evt.target.value
});
},
[dispatch]
);
return [state, handleDispatch] as const;
}
export default useFormState;
Now I'd like to add simple validation on submit and (because of TS) I have no idea how. I've thought about three options:
Put the validation logic to the handleSubmit method.
Put the validation logic inside custom useFormState hook.
Create another custom hook just to manage validation only.
I tried to handle the first (and I think the worst) solution in this CodeSandbox example, but as I said the TS types are stronger than me and the code does not work.
Would anyone be so kind and help me with both cases (pick the most correct solution and then, run the code with TS)?
A common approach would be to just store the form error value in a React useState variable and render it as necessary.
const [error, setError] = React.useState('');
// ...
const handleSubmit = (evt: FormEvent) => {
evt.preventDefault();
// This might be the wrong way to check the input value
// I haven't worked directly with form submit events in a hot minute
if (!evt.target.value) {
setError('Title is required');
}
};
// ...
<input
id="title"
name="title"
onChange={handleChange}
type="text"
value={values.title}
aria-describedby="title-error"
required
aria-invalid={!!error}
/>
{error && <strong id="title-error" role="alert">{error}</strong>}
Notice that the aria-describedby, required, aria-invalid and role attributes are important to enforce semantic relationships between the input and its error, announce the error to screen readers when it appears, and designate the input as "required".
If you had multiple inputs in the form, you can make your error value into an object that can store a separate error for each field of your form:
const [errors, setErrors] = React.useState({});
// ...
setErrors(oldErrors => ({...oldErrors, title: "Title is required"}));
// ...
{errors.title && <strong id="title-error" role="alert">{errors.title}</strong>}
Another common pattern is to clear an input error when it is modified or "touched" to allow the form to be resubmitted:
onChange={(e) => {
setError(''); // or setErrors(({title: _, ...restErrors}) => restErrors);
handleChange(e);
}}
Note that all of this error handling logic can be rolled into your custom hooks for form/input handling in general, but does not have to be.

Validating a child input type file with react-hook-form and Yup

I'm creating a form with a file upload with help of react-hook-form and Yup. I am trying to use the register method in my child component. When passing register as a prop (destructured in curly braces) the validation and submiting doesn't work. You can always submit the form and the submitted file object is empty.
Here's a sandbox link.
There are several of problems with your code.
1- register method returns an object with these properties:
{
onChange: function(){},
onBlur:function{},
ref: function(){}
}
when you define your input like this:
<input
{...register('photo')}
...
onChange={(event) => /*something*/}
/>
actually you are overrding the onChange method which has returned from register method and react-hook-form couldn't recognize the field change event. The solution to have your own onChange alongside with react-hook-form's onChange could be something like this:
const MyComp = ()=> {
const {onChange, ...registerParams} = register('photo');
...
return (
...
<input
{...params}
...
onChange={(event) => {
// do whatever you want
onChange(event)
}}
/>
);
}
2- When you delete the photo, you are just updating your local state, and you don't update photo field, so react-hook-form doesn't realize any change in your local state.
the problems in your ImageOne component could be solved by change it like this:
function ImageOne({ register, errors }) {
const [selectedImage, setSelectedImage] = useState(null);
const { onChange, ...params } = register("photo");
return (
...
<Button
className="delete"
onClick={() => {
setSelectedImage(null);
//Make react-hook-form aware of changing photo state
onChange({ target: { name: "photo", value: [] } });
}}
>
...
<input
//name="photo"
{...params}
type="file"
accept="image/*"
id="single"
onChange={(event) => {
setSelectedImage(event.target.files[0]);
onChange(event); // calling onChange returned from register
}}
/>
...
);
}
3- Since your input type is file so, the value of your photo field has length property that you can use it to handle your validation like this:
const schema = yup.object().shape({
photo: yup
.mixed()
.test("required", "photo is required", value => value.length > 0)
.test("fileSize", "File Size is too large", (value) => {
return value.length && value[0].size <= 5242880;
})
.test("fileType", "Unsupported File Format", (value) =>{
return value.length && ["image/jpeg", "image/png", "image/jpg"].includes(value[0].type)
}
)
});
Here is the full edited version of your file.

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.

Custom Hook for Radio Button with semantic-ui-react

I've created a custom hook to use with a form I've built using semantic-ui-react. The code for the hook looks like this (taken from here )
import React, { useState } from 'react'
const useForm = callback => {
const [inputs, setInputs] = useState({})
const handleSubmit = event => {
if (event) {
event.preventDefault()
}
}
const handleInputChange = event => {
event.persist()
setInputs(inputs => ({
...inputs,
[event.target.name]: event.target.value,
}))
}
return {
handleSubmit,
handleInputChange,
inputs,
}
}
export default useForm
It works perfectly well with all of my text based inputs at the moment, but I've added some radio buttons like this:
<Form.Group inline>
<label>Number of Hours</label>
<Form.Radio
label="<3 Hours"
name="hours"
onChange={handleInputChange}
value="1"
checked={inputs.hours === 1}
/>
<Form.Radio
label="3+ Hours"
name="hours"
onChange={handleInputChange}
value="2"
checked={inputs.hours === 2}
/>
</Form.Group>
But the hook doesn't work properly. I've done some digging and it looks like it's because the onChange (I've tried onClick too) seems to fire on the label in semantic-ui-react, so the event doesn't contain the proper target name or value. The only workaround I can think of is to write some custom handler that creates a fake event that looks for the hidden radio input :before the label, but it seems like there should be a cleaner way.
Updated Workaround
I created a custom handler for radios as a temporary workaround and also adjusted the checked part to put quotes around the value. It works, but if anyone knows a better way, please share.
This is the custom handler.
const radioHandleInputChange = e => {
let { value, name } = e.target.previousSibling
e.target.name = name
e.target.value = value
handleInputChange(e)
}

Resources