DOM does not update on value change - reactjs

I am trying to set up a simple form using a function component using a next.js project:
const Form = () => {
let error = false
const handleNameSubmit = (e) => {
e.preventDefault()
const name = e.target.name.value.trim()
if(!! name.length) {
error = false
} else {
error = 'Please enter your name'
}
}
return (
<>
<form onSubmit={handleNameSubmit}>
<h1>I’d like to know how to address you,
please type in your name</h1>
<input type="text" name="name" placeholder="Your name"/>
{!!error && (<p>{error}</p>)}
<button type="submit">Next</button>
</form>
</>
)
}
I am doing some trivial validation i.e. checking if any value is entered in the name input and displaying an error, if the field is empty.
However, when I set the value of error on page load to be something, it shows in the DOM, but if I manipulate it later, the DOM does not update. I am new to next.js.

You have to use state variable to achieved your goal. Simply convert error in state variable and it will work.
const Form = () => {
const [error, setError] = useState(false);
const handleNameSubmit = (e) => {
e.preventDefault();
const name = e.target.name.value.trim();
setError(name.length ? false : 'Please enter your name');
}
return (
<>
<form onSubmit={handleNameSubmit}>
<h1>I’d like to know how to address you,
please type in your name</h1>
<input type="text" name="name" placeholder="Your name" />
<button type="submit">Next</button>
{error && <p>{error}</p>}
</form>
</>
)
}
I have created small demo for you.
https://stackblitz.com/edit/react-7kat5c
Hope this will help you!

Related

React how to disable submit button until form values are input

I'd like to keep the submit button in my form disabled until the values of the each input are at least one character, not including white space. I tried using trim() and it seems to work until I click submit.
Here is my Form component:
export function Form(props) {
const { form, inputChange, postQuiz } = props;
const onChange = () => {
inputChange()
}
const onSubmit = evt => {
evt.preventDefault()
const question_text_input = document.getElementById("newQuestion");
const question_text = question_text_input.value
const true_answer_text_input = document.getElementById("newTrueAnswer");
const true_answer_text = true_answer_text_input.value
const false_answer_text_input = document.getElementById("newFalseAnswer");
const false_answer_text = false_answer_text_input.value
postQuiz({ question_text, true_answer_text, false_answer_text })
}
return (
<form id="form" onSubmit={onSubmit}>
<h2>Create New Quiz</h2>
<input onChange={onChange} placeholder="Enter question" />
<input onChange={onChange} placeholder="Enter true answer" />
<input onChange={onChange} placeholder="Enter false answer" />
<button
id="submitNewQuizBtn"
disabled={
form.newFalseAnswer.trim().length >= 1
&& form.newTrueAnswer.trim().length >= 1
&& form.newQuestion.trim().length >= 1
? ""
: "disabled"
}
>
Submit new quiz
</button>
</form>
)
}
export default connect(st => st, actionCreators)(Form)
With the code above, the submit button stays disabled until I type at least one character in each input (doesn't count whitespace, like I wanted), but as soon as I click submit I get the error: Uncaught TypeError: Cannot read properties of undefined (reading 'trim').
I don't understand why that happens. Is using trim() on the form Object incorrect?
You can achieve that using two states in your component. One for input and another for the button.
const App = () => {
const [input, setInput] = useState('') // For input
const [isdisabled, setIsDisabled] = useState(false) // For button
// when input is changing this function will get called
const onChange = (e) => {
setInput((prevState) => (e.target.value))
if(e.target.value.trim().length < 1) { // Checking the length of the input
setIsDisabled(true) // Disabling the button if length is < 1
} else {
setIsDisabled(false)
}
}
const onSubmit = (e) => {
e.preventDefault()
// Code...
}
return (
<div className="App">
<form onSubmit={onSubmit}>
<input type='text' placeholder='email' value={input} onChange={onChange} />
<button id='button' type='submit' disabled={isdisabled}>Submit</button>
</form>
</div>
);
}
If you have multiple inputs change the onChange function and input state accordingly.

Unable to store data in useState

[Here is the view
So I have the following output.
I am unable to store the data from this form to the Array and an object.
Every time there is a change in children field there is another form that has to be stored.
I have to do this using and unique ID for each of them.
const CreateForm = () => {
const [isDisabled, setIsDisabled] = useState(false);
let [applicant, setApplicant] = useState([]);
const handleChildren = (e) => {
const udatedApplicant = { ...applicant };
udatedApplicant.childrens = e.target.value;
setApplicant({ ...udatedApplicant });
if (applicant.childrens!==0){
udatedApplicant.childrens= e.target.childrens=0;
}
};
const handleRadio = (e) => {
const udatedApplicant = { ...applicant };
udatedApplicant.maritalStatus = e.target.value;
setApplicant({ ...udatedApplicant });
};
// const finalForm = setApplicant(...applicant)
return (
<div>
<div>
<input name="firstName" placeholder="firstName" />
<input name="lastName" placeholder="lastName" />
<input type="email" name="Email" placeholder="Email" />
<div>
<label>
<input type="radio" name="Gender" value="Male" />
Male
</label>
I am able to handle all the onChanges fields but unable to store this using map in the useState applicant.
I tried it using the index, but after I store the first applicant, when I change the field married or children, I got a blank page.
Can someone help me handle this? I passed 2 hours on this and I'm still not able to store the data.
Im kinda new in React.
Any small tip would be appreciated.

How to do validation using useRef()

How do I validate input box value using useRef .
Initial validation is not required once user clicks on input box and comes out then it should validate if input box is empty it should show input box cannot be empty.
Codesandbox Link
code i tried. using onBlur
export default function App() {
const name = React.useRef("");
const nameBlurData = (name) => {
console.log("name", name);
};
return (
<div className="App">
<form>
<input
onBlur={() => nameBlurData(name.current.value)}
type="text"
ref={name}
placeholder="Enter First Name"
/>
// show error message here
</form>
</div>
);
}
You can use "useRef" to validate the value of an input field.
No need to use "useState".
Below code is a basic implementation of OP's question
You can replace the "console.log" with your alert component.
import { useRef } from "react";
const ComponentA = () => {
const emailRef = useRef(null);
const passwordRef = useRef(null);
const onBlurHandler = (refInput) => {
if (refInput.current?.value === "") {
console.log(`${refInput.current.name} is empty!`);
}
}
return (
<form>
<input ref={emailRef} onBlur={onBlurHandler.bind(this, emailRef)} />
<input ref={passwordRef} onBlur={onBlurHandler.bind(this, passwordRef)} />
<form/>
)
}
Link to "useRef"
Note: Not tested, code typed directly to SO's RTE
You can use a local state and conditionally render an error message like this:
const [isValid, setIsValid] = useState(true)
const nameBlurData = (name) => {
setIsValid(!!name);
};
return (
<div className="App">
<form>
<input
onBlur={() => nameBlurData(name.current.value)}
type="text"
ref={name}
placeholder="Enter First Name"
/>
{!isValid && <span> input must not be empty </span> }
</form>
Note that you don't really need a ref in this case, you can just use the event object like:
onBlur={(event) => nameBlurData(event.target.value)}
You need to use useState hook to update the value of the name property. Using ref is not ideal here.
Live demo https://stackblitz.com/edit/react-apqj86?devtoolsheight=33&file=src/App.js
import React, { useState } from 'react';
export default function App() {
const [name, setName] = useState('');
const [hasError, setError] = useState(false);
const nameBlurData = () => {
if (name.trim() === '') {
setError(true);
return;
}
setError(false);
};
return (
<div className="App">
<form>
<input
onBlur={nameBlurData}
type="text"
value={name}
onChange={e => setName(e.target.value)}
placeholder="Enter First Name"
/>
{hasError ? <p style={{ color: 'red' }}>Name is required</p> : null}
</form>
</div>
);
}

How to display a ref variable without using state in React?

was wondering if there is any way to directly display the value of a variable from ref without using state, all the examples deal with "alerting" and alert works just fine, I'm trying to figure out to way to display it immediately as well. So, I am simply trying to display the value from the "name" here. Apologies for the x variable naming.
I assume it's not friendly to the DOM.
Thank you.
const UncontrolledExample = () => {
const name = useRef();
let x = '';
const showValue = (e) => {
e.preventDefault();
alert(name.current.value);
x = name.current.value;
return x;
};
return (
<div>
<label>
<input type="text" ref={name}/>
</label>
<button onClick={showValue}>
Display value : {x}
</button>
</div>
)
}
In react, if you want the page to update, you must set state. Your tutorial seems to be showing you how to do uncontrolled components. If you want to keep the input as an uncontrolled component you can, but you still need a state for X. That would look like this:
const UncontrolledExample = () => {
const name = useRef();
const [x, setX] = useState('');
const showValue = (e) => {
e.preventDefault();
setX(name.current.value);
};
return (
<div>
<label>
<input type="text" ref={name}/>
</label>
<button onClick={showValue}>
Display value : {x}
</button>
</div>
)
}
Alternatively, you can turn the input into a controlled component. If you want the display value to only change when the button is pressed, you'll need two states:
const ControlledExample = () => {
const [inputValue, setInputValue] = useState('');
const [x, setX] = useState('');
const showValue = (e) => {
e.preventDefault();
setX(inputValue);
};
return (
<div>
<label>
<input type="text"
value={inputValue}
onChange={(e) => setInputValue(e.currentTarget.value)}
/>
</label>
<button onClick={showValue}>
Display value : {x}
</button>
</div>
)
}
If they should always change simultaneously (ie, without the button), you just need one state:
const ControlledExample = () => {
const [inputValue, setInputValue] = useState('');
return (
<div>
<label>
<input type="text"
value={inputValue}
onChange={(e) => setInputValue(e.currentTarget.value)}
/>
</label>
<p>Display value : {inputValue}</p>
</div>
)
}

How to find display error message from API in reactjs

I'm new to React.JS, creating an app for contacts. From API, the fields of contact got validated, if same name or phone number exists then it will show error message in API. My query is how to show the error message in UI while entering the same name or phone number. Do I need to fetch from Contact API? If yes, I could fetch the API of contacts in DIdmount but don't know how to show the error? Can any one help me in this?
Create a state variable for error with the initial value set to "" or null. Make the api call, the server (assuming you are also building the server) should check to see if the name and phone number already exist. If they already exist the server should return an error. In your react app catch the error after your API call and assign it to your state variable for error. Here is an example for the client using hooks.
export default function RegistrationForm(props) {
const [error, setError] = useState(null)
const errorDiv = error
? <div className="error">
<i class="material-icons error-icon">error_outline</i>
{error}
</div>
: '';
const handleSubmit = e => {
e.preventDefault();
setError(null);
const { full_name, user_name, password } = e.target;
AuthApiService.postUser({
full_name: full_name.value,
user_name: user_name.value,
password: password.value
})
.then(user => {
full_name.value = '';
user_name.value = '';
password.value = '';
props.onRegistrationSuccess();
})
.catch(res => {
setError(res.error);
})
};
return(
<form className='RegistrationForm'
onSubmit={handleSubmit}
>
<div className='full_name'>
<label htmlFor='RegistrationForm__full_name'>
Full name
</label>
<Input
name='full_name'
type='text'
required
id='RegistrationForm__full_name'>
</Input>
</div>
<div className='user_name'>
<label htmlFor='RegistrationForm__user_name'>
User name
</label>
<Input
name='user_name'
type='text'
required
id='RegistrationForm__user_name'>
</Input>
</div>
<div className='password'>
<label htmlFor='RegistrationForm__password'>
Password
</label>
<Input
name='password'
type='password'
required
id='RegistrationForm__password'
>
</Input>
</div>
<div className='confirm-password'>
<label htmlFor="LoginForm__confirm-password">
Retype Password
</label>
<Input
name='confirm-password'
type="password"
required
id="LoginForm__confirm-password">
</Input>
</div>
{errorDiv}
<Button type='submit' variant='contained' color='default'>
Register
</Button>
</form>
)
}
Yes if you want exact error message from api response than you can get this by
.then(responseJson=> { console.log(responseJson.response.data.message) })
Have an API to check if the name is available or not, and hit the API when the user changes something in the input.
import React, { useState } from "react";
import ReactDOM from "react-dom";
let delayTimer;
function App() {
const [name, setName] = useState("");
const [nameAvailable, setNameAvailable] = useState(null);
async function checkNameAvailable() {
try {
const response = await APIUtility.isNameAvailable(name);
const { data = null } = response;
if (data) {
setNameAvailable(data); // data may be true or false
}
} catch (error) {
setNameAvailable(null);
}
}
function handleNameChange(e) {
setName(e.target.value);
if (name.length > 0) {
if (delayTimer) clearTimeout(delayTimer);
delayTimer = setTimeout(function() {
checkNameAvailable();
}, 1000); // Will do the ajax stuff after 1000 ms, or 1 s
}
}
return (
<form>
<label htmlFor="invite_code">Enter your name:</label>
<input
value={name}
onChange={handleNameChange}
type="text"
name="inviteCode"
id="invite_code"
autoComplete="off"
/>
{nameAvailable && <span>Name already taken</span>}
</form>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Resources