Remove Readonly when it is clicked outside of the input in React - reactjs

I am trying to handle with read-only on my input component. So basically, I have an input component and as default, it comes with read-only. What I am trying to do is when it is clicked inside of the input field, read-only comes false and it is editable. But I want read-only true again, only when it is clicked to the outside of the input box.
So here is my component:
const InputComponent = () => {
const [disabled, setDisabled] = useState(true);
function handleClick() {
if(disabled == true) {
setDisabled(false);
}
else {
//TODO: click outside the set readonly
}
}
return (
<Form.Control type="number" readOnly={disabled} onClick={handleClick}/>
);
};
So my logic is quite simple when, disabled is true that means read-only is true so when it is clicked inside, disabled turns false and it is being editable. But I couldnt do the rest. So I dont know how to make disabled false again when it is clicked outside.
Thanks for your help. And I am open more idea.

You can use onBlur to do that:
<Form.Control onBlur={() => {setDisabled(true)}} />

Related

Show hide form fields in react using onFocus and onBlur

I'm working on a form that initially only shows one input field and when it is focused, it shows other inputs and the submit button.
I also want to hide all those extra fields if the form loses focus while they are empty. And this is the part that I'm not being able to implement.
This is my code: I use a controlled form and a state to handle focus.
const FoldableForm = () => {
const [formState, setFormState] = useState(defaultFormState);
const [hasFocus, setFocus] = useState(false);
const handleOnBlur = () => {
if (!formState.message.trim() && !formState.other_input.trim()) {
setFocus(false);
}
};
return (
<form
onFocus={() => setFocus(true)}
onBlur={handleOnBlur}
>
<textarea
name="message"
onChange={(e) => setFormState({ ...formState, message: e.target.value })}
/>
{hasFocus && (
<>
<input
type="text" name="other_input"
onChange={(e) => setFormState({ ...formState, message: e.target.other_input })}
/>
<button type="button">Post comment</button>
</>
)}
</form>
);
}
Currently, if I type something in the text area, setFocus(false) is never invoked, so it works as intended.
Otherwise, if I leave it empty and click on the other input field, the handleOnBlur function is called, it sets focus to false, so the form is 'minimized'.
This is expected because the blur event (from the textarea) is triggered before the focus event (from the new input field). So I tried to use setTimeout to check, after a fraction of a second if the focus event had already occurred.
To do so, I used a second state (shouldShow) that is updated in a setTimeout inside the handleOnBlue function.
setTimeout(() => {
if(!hasFocus) {
setShouldShow(false); // this should cause the form to minimize
}
}, 100);
However, according to the react lifecycle, the value of hasFocus that is passed to the setTimeout function is at the invocation time, not at execution. So setTimeout here is useless.
I also tried to use references, but I couldn't make it work.
In your case i think that the usage of the shouldShow state is redundant and you can also avoid using a timeout which may lead to bugs.
You can take advantage of the FocusEvent.relatedTarget attribute and prevent hiding the extra fields when blur from an input and focus to another happens simultaneously.
The handleOnBlur function should look like this:
const handleOnBlur = (e) => {
if (e.relatedTarget && e.relatedTarget.name === "other_input") return;
if (!formState.message.trim() && !formState.other_input.trim()) {
setFocus(false);
}
};
You can find a working example in this code sandbox.
The problem with this approach is that if you have multiple fields appearing you need to check if any of those is focused like below:
["other_input", "another_input"].includes(e.relatedTarget.name)
This behavior is because of closures in JavaScript. The value of hasFocus is not the value of the variable at the moment your callback inside setTimeout is executed. It's the value when the onBlur callback is executed.
One solution would be to use functional updates.
Define a state which holds both hasFocus and shouldShow inside:
const [state, setState] = useState({ hasFocus: false, shouldShow: false });
When you try to access the previous state using functional updates, you get the most recent value:
setTimeout(() => {
setState((state) => {
if (!state.hasFocus) {
return { ...state, shouldShow: false };
}
return state;
});
}, 100);
codesandbox
Another solution would be to debounce a function which sets the hasFocus state to false, which imo is way better.

How can I change multiple Checkboxes in React Hook Form Via state

Oky, so wasted like 12 hours already on this. And I really need help.
I created a filter using an array via Map that returns some checkbox components
Component:
import { useEffect, useState } from "react"
interface Checkbox {
id: string | undefined,
name: string,
reg: any,
value: any,
label: string,
required?: boolean,
allChecked: boolean
}
const Checkbox = ({ id, name, reg, value, label, allChecked }: Checkbox) => {
const [checked, setChecked] = useState(false);
useEffect(() => {
setChecked(allChecked);
}, [allChecked])
return (
<>
<label key={id}>
<input type="checkbox"
{...reg}
id={id}
value={value}
name={name}
checked={checked}
onClick={() => {
setChecked(!checked)
}}
onChange={
() => { }
}
/>
{label}
</label>
</>
)
}
export default Checkbox;
Map:
dataValues.sort()
?
dataValues.map((e: any, i: number) => {
let slug = e.replace(' ', '-').toLowerCase();
return (
<div key={JSON.stringify(e)} id={JSON.stringify(e)}
>
<Checkbox id={slug}
name={title as string}
label={e as string}
value={e}
reg={{ ...register(title as string) }}
allChecked={allChecked}
/>
</div>
)
})
:
null
}
State that is just above the Map:
const [allChecked, setAllChecked] = useState<boolean>(false);
When I try to change the state on the parent and check or uncheck all of the checkboxes, nothing happens.
(the form works without a problem if I manually click on the checkboxes, but I cannot do this as I have some sections with over 40 values)
Sample of array:
dataValues = [
"Blugi",
"Bluza",
"Body",
"Bratara",
"Camasa",
"Cardigan",
"Ceas",
"Cercel",
"Colier",
"Fusta",
"Geanta Cross-body",
"Hanorac",
"Jacheta",
"Palton",
"Pantaloni",
"Pulover",
"Rochie",
"Rucsac",
"Sacou",
"Salopeta",
"Set-Accesorii",
"Top",
"Trench",
"Tricou",
"Vesta"
]
allChecked never changes (at least in the code shown here).
Here's the timeline:
The parent passes down a boolean prop allChecked. That's supposed to tell us if all the checkboxes are checked or not.
useEffect in Checkbox runs on mount, and allChecked is false because that's its default. useEffect then sets checked to allChecked's value, false. Which it already is, because its default is also false.
useEffect then listens for a change in allChecked via [allChecked] that never comes.
Clicking on any checkbox just toggles the state of that checkbox. allChecked's setter, setAllChecked, is never passed to the child or called from the parent.
What's the solution?
Somewhere, setAllChecked(true) needs to happen. Maybe a single checkbox with a label "Check all"?
Then, allChecked in Checkbox needs to be able to control the checkbox inputs. One implementation could be:
checked={checked || allChecked}
I managed to solve this.
There were 2 main problems:
Checkboxes were not rendering again so react-hook-form would see the old value, no matter what
I couldn't press clear all multiple times, because I was sending the allChecked = = false info multiple times, and the state wasn't exactly changing.
What I did was force a render by integrating the allChecked state as an object
interface checkedState {
checked: boolean,
render: boolean
}
So, whenever I send the state, I send it something like:
{
checked: true,
render: !allChecked.render
}
meaning that the new object is always new, no matter if I send the same information.

Selecting created option on menu close/select blur using creatable component

Is there some way to instruct react-select to select an option on menu close or select blur, but only if it is the one created (not from default list)?
Context:
I have a list of e-mail addresses and want to allow user to select from the list or type new e-mail address and then hit Submit button. I do the select part with react-select's Creatable component and it works.
import CreatableSelect from 'react-select/creatable';
<CreatableSelect
options={options}
isMulti={true}
isSearchable={true}
name={'emailAddresses'}
hideSelectedOptions={true}
isValidNewOption={(inputValue) => validateEmail(inputValue)}
/>
But what happens to my users is that they type new e-mail address, do not understand they need to click the newly created option in dropdown menu and directly hit the Submit button of the form. Thus the menu closes because select's focus is stolen and form is submitted with no e-mail address selected.
I look for a way how can I select the created option before the menu is closed and the typed option disappears.
You can keep track of the inputValue and add the inputValue as a new option when the onMenuClose and onBlur callbacks are triggered.
Keep in mind that both onBlur and onMenuClose will fire if you click anywhere outside of the select area. onMenuClose can also fire alone without onBlur if you press Esc key so you will need to write additional logic to handle that extra edge case.
function MySelect() {
const [value, setValue] = React.useState([]);
const [inputValue, setInputValue] = React.useState("");
const isInputPreviouslyBlurred = React.useRef(false);
const createOptionFromInputValue = () => {
if (!inputValue) return;
setValue((v) => {
return [...(v ? v : []), { label: inputValue, value: inputValue }];
});
};
const onInputBlur = () => {
isInputPreviouslyBlurred.current = true;
createOptionFromInputValue();
};
const onMenuClose = () => {
if (!isInputPreviouslyBlurred.current) {
createOptionFromInputValue();
}
else {} // option's already been created from the input blur event. Skip.
isInputPreviouslyBlurred.current = false;
};
return (
<CreatableSelect
isMulti
value={value}
onChange={setValue}
inputValue={inputValue}
onInputChange={setInputValue}
options={options}
onMenuClose={onMenuClose}
onBlur={onInputBlur}
/>
);
}
Live Demo

How to set ErrorMessage on TextField dynamically on Button onClick method

I need to bind ErrorMessage to textfield only when user press button. In this there are nice examples how to use errormessage but the problem is that I don't know how to make append errorMeesage after user click
<TextField id='titleField' value={titleValue} required={true} label={escape(this.props.description)} onGetErrorMessage={this._getErrorMessage} validateOnLoad={false} />
And this is a call of a button:
private _buttonOnClickHandler() {
let textvalue = document.getElementById('titleField')["value"];
if(textvalue === "")
{
//call onGetErrorMessage or something that will set ErrorMeesage and input will be red
}
return false;
}
Thank you
The easiest way I can think of to accomplish this is by predicating the onGetErrorMessage on a state check, which tracks whether the button has been clicked.
<TextField
id='titleField'
value={titleValue}
required={true}
label={escape(this.props.description)}
// Only allow showing error message, after determining that user has clicked the button
onGetErrorMessage={this.state.buttonHasBeenClicked ? this._getErrorMessage : undefined}
validateOnLoad={false}
/>
Then in your button click handler, simply set that state value:
private _buttonOnClickHandler() {
this.setState({ buttonHasBeenClicked: true });
return false;
}
As long as you instantiate buttonHasBeenClicked to be false, then this method will meet the requirement that (a) before the user clicks the button, no error messages are shown by the TextField, and (b) after the user clicks the button, error messages start to be shown. You retain the ability to use _getErrorMessage(value) to customize the error message based upon the current value in the TextField.
You need to set state for displaying/hiding error messages based on user input, Check below code
import React, { Component } from 'react';
class App extends Component {
state = {
isErrorMessageHidden: true
}
clickHandler = () => {
let textValue = document.getElementById('titleField').value;
if(textValue === '')
this.setState({isErrorMessageHidden: false});
else
this.setState({isErrorMessageHidden: true});
}
render() {
return (
<div>
<button onClick={this.clickHandler}>Check</button>
<input type='text' id='titleField' />
<p
style={{'color':'red'}}
hidden={this.state.isErrorMessageHidden}
>Please fill the form</p>
</div>
);
}
}
export default App;
I hope this helps.
Yet another approach would be to handle all the validations via state values.
The control definition goes like this
<TextField placeholder="Enter a venue location" required onChange={this._onVenueChange} {...this.state.errorData.isValidVenue ? null : { errorMessage: strings.RequiredFieldErrorMessage }}></TextField>
On the Onchange event, you validate the control's value and set the error state value as below,
private _onVenueChange = (event: React.FormEvent<HTMLTextAreaElement | HTMLInputElement>, newValue?: string): void => {
this.setState(prevState => ({
errorData: {
...prevState.errorData,
isValidVenue: newValue && newValue.length > 0,
isValidForm: this.state.errorData.isValidForm && newValue && newValue.length > 0
}
}));
this.setState(prevState => ({
formData: {
...prevState.formData,
venue: newValue
}
}));
}
In the above example, I am using two objects within states, one for capturing the values and other for capturing whether the field is error-ed or not.
Hope this helps..
Cheers!!!

Programmatically close react-select menu

React-select default behavior is to have the menu pop open when the input value is empty. I want to modify this behavior so that when the input is empty, whether before a user has typed anything or the user has backspaced to get to the empty state, the menu will be closed.
I could not find any prop that enables this behavior, so I thought to do it programmatically, by calling some function that closes the menu in onInputChange. Something like:
onInputChange={(inputValue) => {
this.setState({
inputValue,
});
this.selectRef.closeMenu();
}}
I tried using blur() on the Select ref but it just blurred the input without closing the menu, definitely not the behavior I'm looking for.
Is there a prop or function that's exposed that can fulfill my needs?
You can set the menuIsOpen prop onInputChange like this:
handleInputChange = input => {
this.setState({ open: !!input });
}
<Select
onInputChange={this.handleInputChange}
menuIsOpen={this.state.open}
/>
I kept the open state separately
const [open, setOpen] = useState(false);
<Select
menuIsOpen={open}
onMenuOpen={() => setOpen(true)}
onMenuClose={() => setOpen(false)}
/>
and set false when I want to close it. In this case, like this
onInputChange={(text) => {
if (text === '') {
setOpen(false);
}
}}

Resources