Trigger change-event inside react-component not working - javascript

I have a simple react-component which contains of a form. In this form the user has an search-box where to find users.
In order to have a valid form, there must be at least 3 users but not more than 6.
Therefore I've added a hidden field (hidden by surrounding display:none; div) which contains the number of already added users.
This one will show the error-message in case the current number is invalid.
The following code is simplified but shows the main issue I have.
In my render-call I have:
render():JSX.Element {
return (
<form>
<ValidatableInput
input={<Input type="number" value={this.state.users.length.toString()} min={3} max={6} getRef={(el: HTMLInputElement) => this.testElement = el} onChange={() => alert("changed...")} />}
minValueMessage={"Currently there are " + this.state.users.length + " users. A minimum of 3 is needed"}
hidden={true} />
<UserSearch onUserSelected={this.handleUserSelected} />
// ...
</form>
);
}
whereas UserSearch is the component to search for users. This is mainly an autocomplete-input which triggers the handleUserSelected:
private handleUserSelected = (selectedElement: IUser) : void => {
// get a copy of the current state
const newState = { ...this.state };
// add the user
newState.users.push(selectedElement);
// thats what I've tried so far
this.testElement.dispatchEvent(new Event("change"));
this.testElement.dispatchEvent(new Event("change", {bubbles: true}));
this.testElement.onchange(new Event("change"));
this.testElement.onchange(new Event("change"), {bubbles: true});
this.setState(newState, () => // here I want to do the triggering as the input has now the new count as value set)
}
However, the console does not show changed at all.
How can I call/trigger the change-event manually when something other changed on the component?
This is the ValidatableInput-component:
export class ValidatableInput extends React.Component<IValidatableInputProps, IValidatableInputState> {
render(): JSX.Element {
const { labelText, input, requiredMessage, maxLengthMessage, minValueMessage, hidden } = this.props;
input.props.onInvalid = this.handleInvalid;
input.props.onChange = this.handleChange;
if (hidden) {
// set height to 0
}
return (
<FormGroup row >
<Label for={input.props.name} md={3}>{!hidden && labelText}</Label>
<Col md={9}>
<div>
{input}
{this.state.validityState.valueMissing && <div className={classNames("invalid-feedback")}>{requiredMessage || "This field is required"}</div>}
{this.state.validityState.tooLong && <div className={classNames("invalid-feedback")}>{maxLengthMessage || "The field shoud not be longer than " + input.props.maxLength}</div>}
{this.state.validityState.rangeOverflow && <div className={classNames("invalid-feedback")}>{maxLengthMessage || "There are too many items. Maximum allowed: " + input.props.max}</div>}
{this.state.validityState.rangeUnderflow && <div className={classNames("invalid-feedback")}>{minValueMessage || "There are too less items. Minimum needed: " + input.props.min}</div>}
</div>
</Col>
</FormGroup>
);
}
private handleInvalid = (ev: React.FormEvent<HTMLInputElement>): any => {
const input = ev.target as HTMLInputElement;
this.setState({ validityState: input.validity });
}
private handleChange = (ev: React.FormEvent<HTMLInputElement>): any => {
const input = ev.target as HTMLInputElement;
this.setState({ validityState: input.validity });
}
}

Maybe keep it simpler and remove the refs and eventHandling boilerplate.
Your business logic relies on this.state.users.length, right?
So use it on your favor:
handleUserSelected = (selectedElement: IUser) : void => {
this.setState({
users: [
...this.state.users,
selectedElement,
],
})
}
render() {
const { users } = this.state
const isInvalid = users.length < 3 || users.length > 6
return (
<form>
{isInvalid && <span>Your invalid message</span>}
<UserSearch ... />
</form>
)
}

Related

Reactjs Form Validations-for input type number the text field in constantly showing single value even after giving backspace from the keyboard

Unable to Clear the value which is entering in the input field even after clicking backspace it is not clearing. one value is remaining constant in the input field
import * as React from "react";
import { Button, Form } from "react-bootstrap";
function Adminform() {
const [docId, setdocId] = React.useState("");
const errorHandle = (name, value) => {
const errors = {}
if (name === "docID") {
if (value === '') {
errors.docID = "Doctor ID Required"
}
else {
setdocId(value)
}
}
setError(errors)
}
return (
<div className="center">
<div className="select">
<h2>Register Your Appointment</h2>
<Form method="POST">
<div>
<label htmlFor="docID">Enter Hospital Name:</label>
<input required type="text" id="docID" name="docID"
onKeyPress={(event) => {
if (!/[0-9]/.test(event.key)) {
event.preventDefault();
}
}}
value={docId} onChange={(e) => errorHandle(e.target.name, e.target.value)}
placeholder="Doctor ID" />
<p style={{ color: "red" }}>{error.docID}</p>
</div>
</Form>
</div>
</div>
)
}
export default Adminform
Before Entering the values
Entered the Random value
Even after clicking backspace one value remaining constant in the text field
You are not completely handling the input value. Since you apply the check that if value === '', then just set the error. Just console the value in errorHandle function, you will see you will get the empty string, but since you are not setting the '' in state, it input value is not updated and remains the last value. You can do this:
const errorHandle = (name, value) => {
setdocId(value);
const errors = {};
if (name === 'docID') {
if (value === '') {
// Set the error
} else {
setdocId(value);
}
}
};
Don't add check for empty value. Just update the state. You can check if value is empty then set the error.
It worked fine by changing the code as below
const errorHandle = (name, value) => {
const errors = {}
if (name === "docID") {
if (value === '') {
setdocId(value) errors.docID = "Doctor ID Required"
} else {
setdocId(value) } }
setError(errors)
}

React : trying to pass state to reset custom input child, why is it not updating?

I made a custom file input in my app. It's working like a charm but when the file is uploaded, the custom file input is still showing the name of the file, which is a problem.
I tried to pass a state from parent component in order to reset the name displayed but, for some reason, the child prop does not update with the parent state and I don't know why.
Here's the custom file input :
export default function CustomInput({
disabler,
setUpperLevelFile,
previousName,
typeOfFiles,
lastInput,
reset,
}) {
const [fileUpload, setFileUpload] = useState(null);
useEffect(() => {
if (lastInput) {
setUpperLevelFile && setUpperLevelFile(fileUpload, lastInput);
} else {
setUpperLevelFile && setUpperLevelFile(fileUpload);
}
}, [fileUpload]);
useEffect(() => {
reset && setFileUpload(null);
console.log("custom input use effect : ", reset);
}, [reset]);
return (
<label className="customInputLabel">
<CustomButton
buttonInnerText="browse"
/>
<p>
{(fileUpload &&
`${fileUpload.name}, (${sumParser(fileUpload.size)})`) ||
(previousName && previousName) ||
"chose a file"}
</p>
<input
type="file"
name="realInput"
className="innerFileInput"
accept={typeOfFiles && typeOfFiles}
disabled={disabler && !disabler}
style={{ display: "none" }}
onChange={(e) => {
setFileUpload(e.target.files[0]);
}}
/>
</label>
);
}
And here is some of the parent code :
export default function ImportFiles(props){
...
const [resetInputs, setResetInputs] = useState(false);
const returningInputs = () => {
let stockInputs = [];
for (let i = 0; i < filesCounter; i++) {
stockInputs.push(
<CustomInput
key={`custom input ${i}`}
setUpperLevelFile={handlingInputChange}
lastInput={i === filesCounter - 1}
reset={resetInputs}
/>
);
}
setFilesInputs(stockInputs);
};
const handlingPostingFiles = () => {
postingFiles(uploadFiles, setUploadStatus);
setResetInputs(true);
};
useEffect(() => {
console.log("edit packages use effect : ", resetInputs);
}, [resetInputs]);
...
return(
...
{filesInputs}
...
)
The console.log in parent component shows that the state is updated but the one in CustomInput doesn't trigger after first render. So it's not updated.
After realizing the process - there should be a clean 🧼 🧽 phase:
so add this line after submitting form:
setFileUpload(null)
in the function handlingPostingFiles or postingFiles.
There is also option to hide this section with that condition: !resetInputs:
<p>
{((fileUpload && !resetInputs) &&
`${fileUpload.name}, (${sumParser(fileUpload.size)})`) ||
(previousName && previousName) ||
"chose a file"}
</p>

Why mapping filtered array of objects is not rendered using react?

I am new to programming. I want to display the list of filtered users in a dropdown.
Below is what I am trying to do,
when user types '#' in the input field I get the string after '#' and use that string to filter the one that matches from user_list.
But this doesn't show up in the dropdown.
Say, user types '#', but it doesn't show the dropdown with user list.
Could someone help me fix this? thanks.
class UserMention extends react.PureComponent {
constructor(props) {
super(props);
this.state = {
text='',
user_mention=false,
};
this.user='';
}
get_user = s => s.includes('#') && s.substr(s.lastIndexOf('#')
+ 1).split(' ')[0];
user_list = [
{name: 'user1'},
{name: 'first_user'},
{name: 'second_user'},
];
handle_input_change = (event) => {
let is_user_mention;
if (event.target.value.endsWith('#')) {
is_user_mention = true;
} else {
is_user_mention = false;
}
this.setState({
is_user_mention: is_user_mention,
[event.target.name]: event.target.value,
});
this.user = this.get_user(event.targe.value);
}
render = () => {
const user_mention_name = get_user(this.state.text);
console.log("filtered_users", filtered_users);
return {
<input
required
name="text"
value={this.state.text}
onChange={this.handle_input_change}
type="text"/>
{this.user &&
<div>
{this.user_list.filter(user =>
user.name.indexOf(this.user)
!== -1).map((user,index) => (
<div key={index}>{user.name}
</div>
))
}
</div>
}
);};}
It seems like this.state.user_mention is always false. In handle_input_change you set is_user_mention, but in render you check user_mention. Could this possibly be the issue?
Since user_mention is false, the expression will short-circuit and not render the list.

Manage focusing multiple TextInputs in react-native (more than 10+)

In my React Native application, there will be many TextInputs, and when the user presses the return button, I want react to focus on the next TextInput. Previous stackoverflow threads recommend using refs, but only have examples for 2 TextInputs. What would be the most efficient way to create lets say 30+ refs and handle focusing?
React Native 0.59 w/ regular old js.
I'm using this plugin
https://github.com/gcanti/tcomb-form-native
It allow me to pass inputs as Array, Thus it can be easy manipulate the array and set the focus dynamically.
You can do the following, First install the library
npm install tcomb-form-native
Then import the library and set your input
var t = require('tcomb-form-native');
var Person = t.struct({
name: t.String, // a required string
surname: t.maybe(t.String), // an optional string
age: t.Number, // a required number
rememberMe: t.Boolean // a boolean
});
Now you can customize fields options through an array. Note I just add a key called "next" this will allow me to loop through the array and focus on the next input dynamically in componentDidMount
let options = {
fields: {
name: {
returnKeyType: "next",
next: "surname"
},
surname: {
returnKeyType: "next",
next: "age"
},
age: {
returnKeyType: "next",
next: "rememberMe"
},
rememberMe: {
returnKeyType: "done",
next: "rememberMe"
}
}
};
Now here is the magic, We just need to loop on the array and dynamically add the focus function for each input
Object.keys(options.fields).forEach(function(key) {
if ("next" in options.fields[key]) {
if (key == options.fields[key]["next"]) {
//set the blur in case the input is the last input
options.fields[key].onSubmitEditing = () => {
self.refs["form"].getComponent(key).refs.input.blur();
};
}else{
//set the next focus option
options.fields[key].onSubmitEditing = () => {
self.refs["form"].getComponent(options.fields[key]["next"]).refs.input.focus();
};
}
}
})
self.setState({
options: options
});
and then in the render section you should be able to pass fields and options
<Form
ref="form"
type={Person}
options={this.state.options}
value={this.state.value}
/>
This should work dynamically, without worry about how much inputs do you have.
Hopefully this answer your question!
// i have a form handler class which does it this is its code.
import React, { Component } from "react";
import { getKeyboardType, isInputValid, isSecureTextEntry } from "./Utils";
class Form extends Component {
childReferences = [];
childDetails = [];
/* go through each input and check validation based on its type */
checkValidation = () => {
let isValid = true;
for (let i = 0; i < this.childReferences.length; i++) {
if (
this.childReferences[i].getValue &&
!isInputValid(
this.childReferences[i].getValue(),
this.childDetails[i].type
)
) {
this.childReferences[i].setError &&
this.childReferences[i].setError(true);
isValid = false;
}
}
return isValid;
};
/* collecting user entered values from all inputs */
getValues = () => {
let data = {};
this.childReferences.forEach((item, index) => {
data[this.childDetails[index].identifier] = item.getValue();
});
return data;
};
onSubmitForm = () => {
return this.checkValidation() ? this.getValues() : undefined;
};
refCollector = ref => this.childReferences.push(ref);
collectChildDetails = childProps =>
this.childDetails.push({
identifier: childProps.identifier,
type: childProps.type
});
/* handling onSubmit of each input when user moves to next input from keyboard */
onSubmitEditing = index => ev => {
if (
index < this.childReferences.length - 1 &&
this.childReferences[index + 1].setFocus
) {
this.childReferences[index + 1].setFocus();
}
};
render() {
const wrappedChildrens = [];
React.Children.map(this.props.children, (child, index) => {
if (!child) {
return;
}
/* holding details of input in an array for later user */
this.collectChildDetails(child.props);
/* cloning children and injecting some new props on them */
wrappedChildrens.push(
React.cloneElement(child, {
key: child.props.identifier || `${child.props.type}_${index}`,
ref: this.refCollector,
onSubmitEditing: this.onSubmitEditing(index),
returnKeyType:
index < this.props.children.length - 1 ? "next" : "done",
keyboardType: getKeyboardType(child.props),
secureTextEntry: isSecureTextEntry(child.props)
})
);
});
return wrappedChildrens;
}
}
export default Form;
// this is how its used
<Form ref={ref => (this.formHandler = ref)}>
<MaterialInput
label="Emial address"
error="Enter a valid email"
rightImage={Images.forwardArrow}
type={INPUT_TYPES.EMAIL}
identifier="email"
blurOnSubmit={false}
/>
<MaterialInput
label="Password"
error="Password length must be greater then six"
rightImage={Images.forwardArrow}
type={INPUT_TYPES.PASSWORD}
identifier="password"
/>
</Form>

Hiding An Element While User Input Doesn't Exist

My goal is to hide one of my divs or all my p tags until user input actually exists. You can see my attempt below which included a method to change the value of my div state to true or false and whether it's true or false, adjust the display to block or none whether or not the user has inputted anything.
I understand that it would be simple to apply this to a button of some sort but my goal here is to allow React to re-render the div or p elements once the user has typed something in.
My vision was to measure the user input's length, and if it was greater than 0, show my div or p tags.
Within my render section of my code, you'll see a div with three p tags inside. I want those p tags, or even the entire div (if it's easier) to not show until the user starts typing something within the input box.
import React from "react";
class UserInput extends React.Component {
constructor(props) {
super(props);
this.state = {
value: "",
showElements: false
};
}
handleChange = event => {
this.setState({ value: event.target.value });
};
badRobot = () => {
const newInput = this.state.value;
let badInput = "BLA"
.repeat(newInput.length / 3 + 1)
.substring(0, newInput.length);
return badInput;
};
hideElements = () => {
const userValueLength = this.state.value;
if (userValueLength.length !== 0) {
console.log("it worked");
this.setState({ showElements: true });
}
};
render() {
return (
<div>
<form>
<label>
<p>Say Anything</p>
<input
type="text"
value={this.state.value}
onChange={this.handleChange}
/>
</label>
</form>
<div style={{ display: this.state.showElements ? "block" : "none" }}>
<h3>Good Robot</h3>
<p>I hear you saying {this.state.value}. Is that correct?</p>
<h3>Bad Robot</h3>
<p>I hear you saying {this.badRobot()}. Is that correct?</p>
<h3>Kanyebot 5000</h3>
<p>I'm gonna let you finish but Beyonce is {this.state.value}.</p>
</div>
</div>
);
}
}
export default UserInput;
Checking if the value string differs from the empty string sounds like a good condition for showing the div.
Instead of keeping a boolean in state you could check the value directly in the render method.
class UserInput extends React.Component {
state = {
value: ""
};
handleChange = event => {
this.setState({ value: event.target.value });
};
render() {
const { value } = this.state;
const showDiv = value !== "";
const badInput = "BLA"
.repeat(value.length / 3 + 1)
.substring(0, value.length);
return (
<div>
<form>
<label>
<p>Say Anything</p>
<input
type="text"
value={value}
onChange={this.handleChange}
/>
</label>
</form>
<div style={{ display: showDiv ? "block" : "none" }}>
<h3>Good Robot</h3>
<p>I hear you saying {value}. Is that correct?</p>
<h3>Bad Robot</h3>
<p>I hear you saying {badInput}. Is that correct?</p>
<h3>Kanyebot 5000</h3>
<p>I'm gonna let you finish but Beyonce is {value}.</p>
</div>
</div>
);
}
}
ReactDOM.render(<UserInput />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
You can do conditional rending.
class UserInput extends React.Component {
constructor(props) {
super(props);
this.state = {
value: '',
showElements: false
};
}
handleChange = (event) => {
const value = event.target.value;
const showElements = value.length > 0 ? true: false;
this.setState({showElements, value});
}
badRobot = () => {
const newInput = this.state.value;
let badInput = 'BLA'.repeat(newInput.length / 3 + 1).substring(0, newInput.length)
return badInput
}
hideElements = () => {
const userValueLength = this.state.value
if (userValueLength.length !== 0) {
console.log("it worked");
this.setState({showElements: true})
}
}
render(){
return(
<div>
<form>
<label>
<p>Say Anything</p>
<input type="text" value={this.state.value} onChange={this.handleChange} />
</label>
</form>
{
this.state.showElements ?
(
<div>
<h3>Good Robot</h3>
<p>I hear you saying {this.state.value}. Is that correct?</p>
<h3>Bad Robot</h3>
<p>I hear you saying {this.badRobot()}. Is that correct?</p>
<h3>Kanyebot 5000</h3>
<p>I'm gonna let you finish but Beyonce is {this.state.value}.</p>
</div>
): null
}
</div>
)
}
}

Categories

Resources