Error on OnBlur after using useReduce in React - javascript

I can't figure it out, why am I getting the error 'TypeError: can't access property "trim", action.val is undefined' after I tried to set the PassDispach on ValidateEmailHandler (which is used to trigger the unblur of the pass input) ? It seems to work on validateEmailHandler. I have tried setting the initial value of PassDefaultState to a string and this also throws me an error
const emailReducer = (state, action) => {
if (action.type === "USER_INPUT") {
return { value: action.val, isValid: action.val.includes("#") };
}
if (action.type === "INPUT_BLUR") {
return { value: state.value, isValid: state.value.includes("#") };
}
return { value: "", isValid: false };
};
const passReducer = (state, action) => {
if ((action.type = "PASS_INPUT")) {
return {
...state,
value: action.val,
isValid: action.val.trim().length > 6,
};
}
if ((action.type = "PASS_BLUR")) {
//this is where the error happens
return {
...state,
};
}
return { value: "", isValid: false };
};
const PassDefaultState = {
value: "",
isValid: null,
};
const MailDefaultState = {
value: "",
isValid: null,
};
const Login = (props) => {
// ! Email STATE ----------------------
const [emailState, dispatchEmail] = useReducer(emailReducer, MailDefaultState);
// ! PASSWORD STATE ----------------------
const [passState, passDispach] = useReducer(passReducer, PassDefaultState);
const { isValid: passIsValid } = passState;
const { isValid: mailIsValid } = emailState;
useEffect(() => {
const identifier = setTimeout(() => {
console.log("Checking form validity!");
setFormIsValid(mailIsValid && passIsValid);
}, 500);
return () => {
console.log("CLEANUP");
clearTimeout(identifier);
};
}, [passIsValid, mailIsValid]);
const emailChangeHandler = (event) => {
dispatchEmail({ type: "USER_INPUT", val: event.target.value });
};
const passwordChangeHandler = (event) => {
passDispach({ type: "PASS_INPUT", val: event.target.value });
setFormIsValid(emailState.isValid && passState.isValid);
};
const validateEmailHandler = () => {
dispatchEmail({ type: "INPUT_BLUR" });
};
const validatePasswordhandler = () => {
passDispach({ type: "PASS_BLUR" });
};
const submitHandler = (event) => {
event.preventDefault();
props.onLogin(emailState.value, passState.value);
};
return (
<Card className={classes.login}>
<form onSubmit={submitHandler}>
<div
className={`${classes.control} ${
emailState.isValid === false ? classes.invalid : ""
}`}
>
<label htmlFor="email">E-Mail</label>
<input
type="email"
id="email"
value={emailState.value}
onChange={emailChangeHandler}
onBlur={validateEmailHandler}
/>
</div>
<div
className={`${classes.control} ${
passState.isValid === false ? classes.invalid : ""
}`}
>
<label htmlFor="password">Password</label>
<input
type="password"
id="password"
value={passState.value}
onChange={passwordChangeHandler}
onBlur={validatePasswordhandler}
/>
</div>
<div className={classes.actions}>
<Button type="submit" className={classes.btn} disabled={!formIsValid}>
Login
</Button>
</div>
</form>
</Card>
);
};
export default Login;

if ((action.type = "PASS_INPUT")) {
Here you need to use === instead of =, the code you wrote is trying to assign a "PASS_INPUT" to action.type while you should chec the value

Related

MUI Tree select - Set Value programatically

Hi Im using this package, All working fine but I can't select a value by default programatically which is user selected already using same component .
https://codesandbox.io/s/github/mikepricedev/mui-tree-select
Developer saying use this doc https://mikepricedev.github.io/mui-tree-select/interfaces/TreeSelectProps.html#value we can set, But I cant understand. Pls help me fix it.
Here is a working example of how to use a default value. I have changed this source to have an initial value programatically. value: "2:0"
import React, { useCallback, useState } from "react";
import TreeSelect, { BranchNode, defaultInput } from "mui-tree-select";
const generateOptions = (parentBranch, randomAsync = true) => {
const depth = parentBranch
? Number.parseInt(parentBranch.valueOf().label.split(":")[0]) + 1
: 0;
const options = [];
for (let i = 0, len = Math.ceil(Math.random() * 10); i < len; i++) {
const option = `${depth}:${i}`;
options.push(new BranchNode({ label: option }, parentBranch), option);
}
return randomAsync && Math.random() > 0.5
? new Promise((resolve) => {
setTimeout(() => {
resolve(options);
}, Math.ceil(Math.random() * 1000));
})
: options;
};
const getOptionLabel = (option) =>
option instanceof BranchNode ? option.valueOf().label : option.toString();
const defaultBranch = BranchNode.createBranchNode([
{ label: "0:5" },
{ label: "1:2" }
]);
const Sample = () => {
const [state, setState] = useState({
single: {
value: "2:0",
options: generateOptions(defaultBranch, false),
loading: false,
branch: defaultBranch
},
multiple: {
value: [],
options: generateOptions(null, false),
loading: false,
branch: null
}
});
return (
<div style={{ width: 350, padding: 16 }}>
<TreeSelect
branch={state.single.branch}
onBranchChange={(_, branch) => {
const options = generateOptions(branch);
if (options instanceof Promise) {
setState((state) => ({
...state,
single: {
...state.single,
branch,
loading: true
}
}));
options.then((options) => {
setState((state) => ({
...state,
single: {
...state.single,
options,
loading: false
}
}));
});
} else {
setState((state) => ({
...state,
single: {
...state.single,
branch,
options,
loading: false
}
}));
}
}}
options={state.single.options}
loading={state.single.loading}
getOptionLabel={getOptionLabel}
renderInput={useCallback(
(params) =>
defaultInput({
...params,
variant: "outlined",
label: "Single"
}),
[]
)}
value={state.single.value}
onChange={useCallback(
(_, value) => {
setState((state) => ({
...state,
single: {
...state.single,
value
}
}));
},
[setState]
)}
/>
<div style={{ height: "16px" }} />
<TreeSelect
onBranchChange={(_, branchOption) => {
const options = generateOptions(branchOption);
if (options instanceof Promise) {
setState((state) => ({
...state,
multiple: {
...state.multiple,
loading: true
}
}));
options.then((options) => {
setState((state) => ({
...state,
multiple: {
...state.multiple,
options,
loading: false
}
}));
});
} else {
setState((state) => ({
...state,
multiple: {
...state.multiple,
options,
loading: false
}
}));
}
}}
options={state.multiple.options}
loading={state.multiple.loading}
getOptionLabel={getOptionLabel}
freeSolo
multiple
renderInput={useCallback(
(params) =>
defaultInput({
...params,
variant: "outlined",
label: "Multiple"
}),
[]
)}
/>
</div>
);
};
export default Sample;
Hi I have got solution, That you have to pass particular object with
const [value, setValue] = useState(null);
useEffect(() => {
if (defaultIndustries !== null) {
setValue(() => new Node(defaultIndustries));
}
}, [defaultIndustries]);
And
<TreeSelect
getChildren={(node) =>
syncOrAsync(
node
? node.getChildren()
: industryMultiTree.map((country) => new Node(country)),
runAsync
)
}
getOptionDisabled={(option) => {
var _a;
return (
option.isBranch() &&
!((_a = option.getChildren()) === null || _a === void 0
? void 0
: _a.length)
);
}}
getParent={(node) => syncOrAsync(node.getParent(), runAsync)}
isBranch={(node) => syncOrAsync(node.isBranch(), runAsync)}
isOptionEqualToValue={(option, value) => {
return option instanceof FreeSoloNode ? false : option.isEqual(value);
}}
value={value}
renderInput={(params) => (
<TextField
{...params}
label={label}
error={error}
helperText={helperText}
/>
)}
sx={{ mb: 3 }}
onChange={(_, value) => {
onChangeSelect(value);
}}
/>

React render [object Object] when using useReducer instead of JSX

So I was learning my React course today on Udemy and came across an error where the react app renders [object Object] instead of the JSX (which is supposed to be an empty input box), also, since the input box has onChange method, The input value is also unable to change. Here is the image of the application and also the code given below:-
import React, { useState, useEffect, useReducer } from "react";
import Card from "../UI/Card/Card";
import classes from "./Login.module.css";
import Button from "../UI/Button/Button";
const emailReducer = (state, action) => {
if(action.type === 'USER_INPUT'){
return {value: action.value, isValid: action.value.includes('#')};
}
if(action.type === 'INPUT_BLUR'){
return {value: state.value, isValid: state.value.includes('#')};
}
return { value: '', isValid: false };
};
const Login = (props) => {
// const [enteredEmail, setEnteredEmail] = useState("");
// const [emailIsValid, setEmailIsValid] = useState();
const [enteredPassword, setEnteredPassword] = useState("");
const [passwordIsValid, setPasswordIsValid] = useState();
const [formIsValid, setFormIsValid] = useState(false);
const [emailState, dispatchEmail] = useReducer(emailReducer, {
value: '',
isValid: false
});
useEffect(() => {
console.log("EFFECT RUNNING");
return () => {
console.log("EFFECT CLEANUP");
};
}, []);
const emailChangeHandler = (event) => {
dispatchEmail({type: 'USER_INPUT', value: event.target.value});
setFormIsValid(
emailState.isValid && enteredPassword.trim().length > 6
);
};
const passwordChangeHandler = (event) => {
setEnteredPassword(event.target.value);
setFormIsValid(
emailState.isValid && event.target.value.trim().length > 6
);
};
const validateEmailHandler = () => {
dispatchEmail({type: 'INPUT_BLUR'});
};
const validatePasswordHandler = () => {
setPasswordIsValid(enteredPassword.trim().length > 6);
};
const submitHandler = (event) => {
event.preventDefault();
props.onLogin(emailState.value, enteredPassword);
};
return (
<Card className={classes.login}>
<form onSubmit={submitHandler}>
<div
className={`${classes.control} ${
emailState.isValid === false ? classes.invalid : ""
}`}
>
<label htmlFor="email">E-Mail</label>
<input
id="email"
value={emailState}
onChange={emailChangeHandler}
onBlur={validateEmailHandler}
/>
</div>
<div
className={`${classes.control} ${
passwordIsValid === false ? classes.invalid : ""
}`}
>
<label htmlFor="password">Password</label>
<input
type="password"
id="password"
value={enteredPassword}
onChange={passwordChangeHandler}
onBlur={validatePasswordHandler}
/>
</div>
<div className={classes.actions}>
<Button type="submit" className={classes.btn} disabled={!formIsValid}>
Login
</Button>
</div>
</form>
</Card>
);
};
export default Login;
Your emailState is an object with the following shape:
{ value: string, isValid: boolean }
So in your input field you need to use the value attribute:
<input
id="email"
value={emailState.value}
onChange={emailChangeHandler}
onBlur={validateEmailHandler}
/>
You are passing the entire emailState object to the input when you likely want the nested value property. You can pass emailState.value to the input's value prop, or destructure the state beforehand and pass value directly.
const [{ isValid, value }, dispatchEmail] = useReducer(emailReducer, {
value: '',
isValid: false
});
...
<input
id="email"
value={value}
onChange={emailChangeHandler}
onBlur={validateEmailHandler}
/>

How to create two reusable invoking custom hook?

I created some logic that is reusable code anywhere. I have an input.jsx component for handling input elements, and use-form.js custom hook for managing all of the stuff and I have a basic-form component for creating form logic. Inside of basic-form component, I'm calling the custom hook many times. That is tedious and quite repetitive* so I wanna create two reusable invoking custom hooks in the input.jsx component
input.component.jsx
const Input = (props) => {
return (
<div className={`form-control ${props.error && 'invalid'}`}>
<label htmlFor={props.id}>{props.label}</label>
<input
type={props.type}
id={props.id}
value={props.value}
onChange={props.changeHandler}
onBlur={props.blurHandler}
/>
{props.error && <p className="error-text">{props.label} must be entered!</p>}
</div>
);
};
use-input.js
import { useReducer } from 'react';
const inputReducer = (state, action) => {
switch (action.type) {
case 'INPUT':
return { value: action.value, isTouched: state.isTouched };
case 'BLUR':
return { isTouched: true, value: state.value };
case 'RESET':
return { value: '', isTouched: false };
}
return state;
};
export const useInput = (validateValue) => {
const [state, dispatch] = useReducer(inputReducer, {
value: '',
isTouched: false,
});
const valueIsValid = validateValue(state.value);
const error = !valueIsValid && state.isTouched;
const valueChangeHandler = (e) => {
dispatch({ type: 'INPUT', value: e.target.value });
};
const inputBlurHandler = () => {
dispatch({ type: 'BLUR' });
};
const reset = () => {
dispatch({ type: 'RESET' });
};
return {
value: state.value,
isValid: valueIsValid,
error,
valueChangeHandler,
inputBlurHandler,
reset,
};
};
basic-form.component.jsx
import { useInput } from '../hooks/use-input';
import Input from './input.component';
const BasicForm = () => {
const {
value: name,
isValid: nameIsValid,
error: nameHasError,
valueChangeHandler: nameChangeHandler,
inputBlurHandler: nameBlurHandler,
reset: nameReset,
} = useInput((value) => value.trim() !== '');
const {
value: surname,
isValid: surnameIsValid,
error: surnameHasError,
valueChangeHandler: surnameChangeHandler,
inputBlurHandler: surnameBlurHandler,
reset: surnameReset,
} = useInput((value) => value.trim() !== '');
const {
value: email,
isValid: emailIsValid,
error: emailHasError,
valueChangeHandler: emailChangeHandler,
inputBlurHandler: emailBlurHandler,
reset: emailReset,
} = useInput((value) => /^\S+#\S+\.\S+$/.test(value));
let formIsValid = false;
if (nameIsValid && surnameIsValid && emailIsValid) {
formIsValid = true;
}
const formSubmitHandler = (e) => {
e.preventDefault();
nameReset();
surnameReset();
emailReset();
};
return (
<form onSubmit={formSubmitHandler}>
<div className="control-group">
<Input
id="name"
label="First Name"
type="text"
error={nameHasError}
value={name}
changeHandler={nameChangeHandler}
blurHandler={nameBlurHandler}
/>
<Input
id="surname"
label="Last Name"
type="text"
error={surnameHasError}
value={surname}
changeHandler={surnameChangeHandler}
blurHandler={surnameBlurHandler}
/>
<Input
id="email"
label="Email"
type="email"
error={emailHasError}
value={email}
changeHandler={emailChangeHandler}
blurHandler={emailBlurHandler}
/>
</div>
<div className="form-actions">
<button disabled={!formIsValid}>Submit</button>
</div>
</form>
);
};
export default BasicForm;
The problem with that is we must return two values from input.jsx to basic-form.jsx. The first value shows the property isValid or not. Second, a function for reseting every individual input element. Also, we must receive a function that validates the logic from the parent to the children components. How can we solve this problem? I think we should use useRef, useImperativeHandle and forwardRef but how?
Please vote up for more people to see.

how to use clearInteval let timer clear it self in ReactJS?

I am new to react, I am trying to write a react component, component has several features.
user can input a random number, then number will be displayed in the
page too.
implement a button with text value 'start', once click the button,
the number value displayed will reduce one every 1second and the
text value will become 'stop'.
continue click button, minus one will stop and text value of button
will become back to 'start'.
when number subtracted down to 0 will automatically stop itself.
I have implemented first three features. but I am not sure how do I start the last one. should I set another clearInteval? based on if statement when timer counts down 0?
code is here:
var myTimer;
class App extends Component {
constructor(props) {
super(props);
this.state = {
details: [{ id: 1, number: "" }],
type: false
};
this.handleClick = this.handleClick.bind(this);
}
changeNumber = (e, target) => {
this.setState({
details: this.state.details.map(detail => {
if (detail.id === target.id) {
detail.number = e.target.value;
}
return detail;
})
});
};
handleClick = () => {
this.setState(prevState => ({
type: !prevState.type
}));
if (this.state.type === false) {
myTimer = setInterval(
() =>
this.setState({
details: this.state.details.map(detail => {
if (detail.id) {
detail.number = parseInt(detail.number) - 1;
}
return detail;
})
}),
1000
);
}
if (this.state.type === true) {
clearInterval(myTimer);
}
};
render() {
return (
<div>
{this.state.details.map(detail => {
return (
<div key={detail.id}>
Number:{detail.number}
<input
type="number"
onChange={e => this.changeNumber(e, detail)}
value={detail.number}
/>
<input
type="button"
onClick={() => this.handleClick()}
value={this.state.type ? "stop" : "start"}
/>
</div>
);
})}
</div>
);
}
}
export default App;
just add
if (detail.number === 0) {
clearInterval(myTimer);
}
in
handleClick = () => {
this.setState(prevState => ({
type: !prevState.type
}));
if (this.state.type === false) {
myTimer = setInterval(
() =>
this.setState({
details: this.state.details.map(detail => {
if (detail.id) {
detail.number = parseInt(detail.number) - 1;
if (detail.number === 0) {
clearInterval(myTimer);
}
}
return detail;
})
}),
1000
);
}
if (this.state.type === true) {
clearInterval(myTimer);
}
};
Here You have this solution on Hooks :)
const Test2 = () => {
const [on, setOn] = useState(false)
const initialDetails = [{ id: 1, number: "" }]
const [details, setDetails] = useState(initialDetails)
const changeNumber = (e, target) => {
setDetails({ details: details.map(detail => { if (detail.id === target.id) { detail.number = e.target.value; } return detail; }) });
if (this.state.details.number === 0) { setOn(false) }
};
const handleClick = () => {
if (on === false) {myTimer = setInterval(() =>
setDetails({details: details.map(detail => {if (detail.id) {detail.number = parseInt(detail.number) - 1; if (detail.number === 0) {clearInterval(myTimer);} }
return detail;})}),1000);}
if (on === true) { clearInterval(myTimer); }
};
return (
<div>
{details.map(detail => {
return (
<div key={detail.id}>
Number:{detail.number}
<input
type="number"
onChange={e => changeNumber(e, detail)}
value={detail.number}
/>
<input
type="button"
onClick={() => handleClick()}
value={on ? "stop" : "start"}
/>
</div>
);
})}
</div>
)
}

form validation on change blur and submit of form and fields

// App.js
import React, { Component } from 'react';
import './App.css';
import fields from './fields'
import CustomInputType from './custominputtype'
class App extends Component {
state = {
formData: {},
fieldErrorStatus: {},
submitErrorStatus: false
}
handleChange = (e) => {
// adding the new on change value to the corresponding field name
const { name, value } = e.target;
const tempObj = { ...this.state.formData };
tempObj[name] = value;
this.setState({ formData: tempObj });
// adding the error status for the corresponding field name
let tempErrorStatus = { ...this.state.fieldErrorStatus }
tempErrorStatus[name] = false;
this.setState({ fieldErrorStatus: tempErrorStatus })
};
handleSubmit = (e) => {
let formValues = this.state.formData;
if (Object.keys(formValues).length === 0) {
this.setState({ submitErrorStatus: true })
}
else {
let tempErrorStatus = {};
this.setState({ submitErrorStatus: false });
Object.keys(formValues).forEach(key => {
if (formValues[key]) {
tempErrorStatus[key] = false;
}
})
this.setState(prevState => {
return {
fieldErrorStatus: { ...prevState.fieldErrorStatus, tempErrorStatus }
}
})
}
e.preventDefault();
}
render() {
return (
<div className="form">
<form
onSubmit={this.handleSubmit}
onChange={(e) => this.handleChange(e)}
>
<div className="inputs-collection">
{
fields[0].attributes.map((field, i) => {
return (
<CustomInputType
attributes={field}
key={i}
value={this.state.formData[i]}
obj={this.state.formData}
errorStatus={this.state.fieldErrorStatus}
displayError={this.state.submitErrorStatus}
/>
)
})
}
</div>
<div className="button-container">
<button className="submit-button" type="submit">Submit Details</button>
</div>
</form>
</div>
)
}
}
export default App;
// CustomInputType
import React , {Component} from 'react'
class CustomInputType extends Component {
render(){
const {
attributes: {
id,
name,
dataType,
} = {},
displayError,
obj,
errorStatus
} = this.props;
return (
<div className="input-container">
<label htmlFor={id}>
{name}
</label>
<input
type={dataType}
name={name || ''}
value={obj[name]}
id={id}
/>
{
displayError || Boolean(errorStatus[name]) ?
<span>{`Error on ${name}`}</span> : null
}
</div>
)
}
}
export default CustomInputType
// fields
let fields = [
{
"id": "1",
"name": "Form 1",
"type": "Dynamic Form",
"attributes": [
{
"name": "First Name",
"dataType": "String",
"id": 101,
},
{
"name": "Surname",
"dataType": "String",
"id": 102,
},
{
"name": "Phone Number",
"dataType": "Number",
"id": 103,
},
{
"name": "Roll Number",
"dataType": "Number",
"id": 104,
}
]
}
];
export default fields;
i have a parent component , where i am reading a json file locally and rendering the fields, basically i have a child component which is a custom input type component.
in my child component there is an prop called error it is a boolean value. So if it is true it will show a red box around the field. The cases i need to show the red box are onChange , onBlur and submit.
for sumbit i am using submitErrorStatus variable in state, and for handleChange and onBlur using fieldErrorStatus. So when the user directly submit without any fields entering redbox should come, once he types each field or blur the redbox should go.
i have done the below but some where it is confusing.
Parent Component
state = {
formData : {},
fieldErrorStatus : {},
submitErrorStatus : false
}
handleChange = (e) => {
// adding the new on change value to the corresponding field name
const { name, value} = e.target;
const tempObj = {...this.state.formData};
tempObj[name] = value;
this.setState({ formData:tempObj });
// adding the error status for the corresponding field name
let tempErrorStatus = {...this.state.fieldErrorStatus}
tempErrorStatus[name] = false;
this.setState({fieldErrorStatus:tempErrorStatus})
};
handleSubmit = (e) => {
let formValues = this.state.formData;
if(Object.keys(formValues).length === 0){
this.setState({submitErrorStatus: true})
}
else{
let tempErrorStatus = {};
this.setState({submitErrorStatus: false});
Object.keys(formValues).forEach(key => {
if(formValues[key]){
tempErrorStatus[key] = false;
}
})
this.setState(prevState => {
return {
fieldErrorStatus: {...prevState.fieldErrorStatus, tempErrorStatus}
}
})
}
e.preventDefault();
}
render(){
<div className = "form">
<form
onSubmit = {this.handleSubmit}
onChange = {(e) => this.handleChange(e)}
>
<div className = "inputs-collection">
{
fields.map((field, i) => {
return (
<InputTypes
attributes = {field}
key = {i}
value = {this.state.formData[i]}
obj = {this.state.formData}
errorStatus = {this.state.fieldErrorStatus}
displayError = {this.state.submitErrorStatus}
/>
)
})
}
</div>
<div className = "button-container">
<button className = "submit-button" type = "submit">Submit Details</button>
</div>
</form>
</div>
}
Child Component
render(){
const {
attributes : {
id,
name,
dataType,
rules,
} = {},
displayError,
obj,
errorStatus
} = this.props;
return(
<div className="input-container">
<Input
type = {dataType}
id = {id.toString()}
name = {name || ''}
value = {obj[name]}
error={displayError || errorStatus[name] ? false : true} />
</div>
)
}
So I made you some basic components which you can use as a reference. I've implemented the onChange and onBlur method. I also made an easily accessible error message but I didn't create the onSubmit functions as you just have to map the array while comparing for empty inputs.
Here is my code:
Container:
import React, { Component } from 'react';
import Field from './Field';
export default class Container extends Component {
state = {
// Create fields
fields: [
{ key: "0", errorMessage: 'Error message for field: 0', isValid: true },
{ key: "1", errorMessage: 'Error message for field: 1', isValid: true },
{ key: "2", errorMessage: 'Error message for field: 2', isValid: true },
{ key: "3", errorMessage: 'Error message for field: 3', isValid: true },
{ key: "4", errorMessage: 'Error message for field: 4', isValid: true }
]
}
render() {
return this.state.fields.map((field, i) => {
return <Field
key={field.key}
isValid={field.isValid}
onChange={this.onInputChange}
index={i}
/>
});
}
onInputChange = (index, event) => {
let newState = this.state;
if(event.target.value === '') {
// Set field invalid
newState.fields[index].isValid = false;
// In this case log but you could do other stuff with the message
console.log(this.state.fields[index].errorMessage);
} else {
// Set field valid
newState.fields[index].isValid = true;
}
this.setState(newState);
}
}
Input:
import React, { Component } from 'react';
export default class Field extends Component {
render() {
// Get props
const {isValid, onChange, index} = this.props;
return <input
type="text"
// Check the input
onInput={event => onChange(index, event)}
onBlur={event => onChange(index, event)}
// If invalid make the background red
style={{backgroundColor: isValid ? 'white' : 'red'}}
/>
}
}
I hope it helps =)

Categories