I want to update the text whatever users types in the input field and then join that text with another text (i.e ".com" in this example). So somehow I managed to join the extension with the user's input text but when the input field is empty the extension is still showing. Can someone help me to remove that extension text when the input field is empty?
Here's my code
import React, { useState } from "react";
import Check from "./Check";
export default function App() {
const [inputValue, setInputValue] = useState("");
const [inputExtension, setInputExtension] = useState("");
const handleChange = (e) => {
setInputValue(e.target.value);
if (setInputValue == inputValue) {
setInputExtension(inputExtension);
}
if (inputValue == inputValue) {
setInputExtension(".com");
}
};
return (
<>
<h1 className="text-[2.1rem] font-semibold text-black">
Find Your Perfect Domain
</h1>
<form>
<label
for="default-search"
class="mb-2 text-sm font-medium text-gray-900 sr-only dark:text-gray-300"
>
Search
</label>
<div class="relative">
<input
type="search"
id="in"
value={inputValue}
onChange={handleChange}
class="block p-4 pl-10 w-full text-sm text-gray-900 bg-gray-50 rounded-lg border border-gray-300 focus:ring-blue-500 focus:border-blue-500"
placeholder="Search for domains..."
/>
</div>
</form>
<Check domain={inputValue} extension={inputExtension} />
<Check domain={inputValue} extension={inputExtension} />
</>
);
}
Here you have the code
if (inputValue == inputValue) {
setInputExtension(".com");
}
Change this to
if(inputValue.length > 0) {
setInputExtension(".com");
}
If you are passing both inputValue and inputExtension as props to the Check component then it should be trivial to just check the inputValue length, i.e. use the fact that empty strings are falsey, and conditionally append the extension value.
Example:
const value = ({
inputValue,
inputExtension = ".com"
}) => inputValue + (inputValue ? inputExtension : "");
console.log({ value: value({ inputValue: "" }) }); // ""
console.log({ value: value({ inputValue: "someValue" })}); // "someValue.com"
Code:
const Check = ({ inputValue = "", inputExtension = "" }) => {
const value = inputValue + (inputValue ? inputExtension : "");
...
};
Related
whenever i try. to change an input it's show the 2 component changes of input at the same time how i show only the changed one
input.tsx
export const Input: React.FC<InputProps> = ({ type, placeholder, classNameProp, value, onChange, forwardRef, isReadOnly = false, isRequired = true, errorText }) => {
console.log(forwardRef?.current);
return (
<>
<input type={type} placeholder={placeholder} className={`p-2 mb-4 rounded-sm border-b-2 border-gray-300 focus:outline-none focus:border-orange-500 ${classNameProp}`} value={value} onChange={onChange} disabled={isReadOnly} required={isRequired} ref={forwardRef} />
</>
);
};
formSurvey.tsx
export const SurveyForm: React.FC<SurveyFormProps> = () => {
const titleRef = React.useRef<HTMLInputElement>(null);
const subjectRef = React.useRef<HTMLInputElement>(null);
const [surveyTitle, setSurveyTitle] = React.useState("");
const [surveySubject, setSurveySubject] = React.useState("");
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
console.log(surveyTitle);
let state: any = {
title: surveyTitle,
subject: surveySubject,
};
};
return (
<div className="relative">
<form className="max-w-3xl mx-auto bg-white rounded-xl p-5 mb-6" onSubmit={handleSubmit}>
<Input type="text" placeholder="Survey Title" classNameProp={`w-full ${titleRef.current?.value.length === 0 ? "border-red-500" : ""}`} value={surveyTitle} onChange={React.useCallback(setter(setSurveyTitle), [])} forwardRef={titleRef} errorText="Please fill the title field" />
<Input type="text" placeholder="Subject Line" classNameProp={`w-full ${subjectRef.current?.value.length === 0 ? "border-red-500" : ""}`} value={surveySubject} onChange={React.useCallback(setter(setSurveySubject), [])} forwardRef={subjectRef} errorText="Please fill the subject field" />
<div className="form-footer flex justify-center space-x-10">
<button className="bg-orange-500 text-white px-4 py-2 rounded-3xl font-semibold" type="submit">
Submit
</button>
</div>
</form>
</div>
);
};
setter.ts
type Set<T> = React.Dispatch<React.SetStateAction<T>>;
type ChangeEvent<E> = React.ChangeEvent<E>;
type Input = HTMLInputElement | HTMLTextAreaElement;
export function setter<T extends number | string | Date, E extends Input = HTMLInputElement>(setX: Set<T>) {
return (e: ChangeEvent<E>) => {
setX(e.target.value as T);
};
}
try to find the best way to implement reusable component in react js
you can see the code here also here
Since you use state, whenever you call setState, each children re-renders. If the components are not so big, most of the time re-renders don't matter and everything is fast enough (optimized)
But if you want to optimize this, there are techniques you can use to avoid unnecessary renders.
The quickest solution for you would be to use React.memo to wrap your inputs.
const Input = React.memo(...your component body)
Or you can use uncontrolled inputs.
Or some library that does this for you, like react-hook-form
Okay so breaking down my code into chunks I have the following in HTML:
<input
type="text"
name="email"
id="email"
autoComplete="email"
onChange={(e) => {validateField(e.target)}}
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
/>
<FormFieldError
Field='email'
FormFieldErrors={formFieldErrors}
/>
The validateField function looks like this:
const validateField = (field) => {
// console.log('validatefield', field.id, field.value)
switch(field.id) {
case 'email':
const pattern = /[a-zA-Z0-9]+[\.]?([a-zA-Z0-9]+)?[\#][a-z]{3,9}[\.][a-z]{2,5}/g
const result = pattern.test(field.value)
if (result !== true) {
setFormFieldErrors({...formFieldErrors, email: 'Please enter a valid email address'})
console.log('Please enter a valid email address')
} else {
setFormFieldErrors({...formFieldErrors, email: null})
}
break
}
}
The FormFieldError function looks like this:
function FormFieldError({ Field, FormFieldErrors }) {
let message = FormFieldErrors[Field] ? FormFieldErrors[Field] : null
return (
<>
<div className="text-red-500 text-xs text-bold">{(message)}</div>
</>
)
}
Now, when in the onSubmit of the form I got to the following function:
const submitNewRegistration = async (event) => {
event.preventDefault()
Array.prototype.forEach.call(event.target.elements, (element) => {
validateField(element);
})
let errors = Object.keys(formFieldErrors).some(key => key !== null)
console.log(errors)
}
I am declaring formFieldErrors in my state like this:
const [formFieldErrors, setFormFieldErrors] = useState({})
When I am changing a field the onChange function works perfectly for each input, and if the input is wrong then the message below the input shows up thanks to the formFieldErrors displays the message underneath the input. However, when I call validateInput from my submitNewRegistration function, setFormFieldErrors doesnt seem to be called. In fact, even if I put setFormFieldErrors in to submitNewRegistration it doesnt seem to change the state of formFieldErrors. So when I am trying to validate the form just before submitting it I do not get any errors.
Can anyone explain to me why its working with the onChange method and now when I call it from submitNewRegistration ??
It's not that setFormFieldErrors isn't called, the issue is you are trying to rely on the state value before the state has been updated. When you call setState the state doesn't update immediately. This is because setState is asynchronous, essentially a promise which resolves upon the next render of your component.
So to solve this, you need to strip your validation logic outside of the state update. In other words:
const validateField = (field) => {
// console.log('validatefield', field.id, field.value)
switch(field.id) {
case 'email':
const pattern = /[a-zA-Z0-9]+[\.]?([a-zA-Z0-9]+)?[\#][a-z]{3,9}[\.][a-z]{2,5}/g
const result = pattern.test(field.value)
if (result !== true) {
return {email: 'Please enter a valid email address'}
console.log('Please enter a valid email address')
} else {
return {email: null}
}
break
}
Then in your html:
<input
type="text"
name="email"
id="email"
autoComplete="email"
onChange={(e) => {
setFormFieldErrors({...formFieldErrors, ...validateField(e.target)})
}
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
/>
<FormFieldError
Field='email'
FormFieldErrors={formFieldErrors}
/>
And finally in the submit:
const submitNewRegistration = async (event) => {
event.preventDefault()
let formErrors = formFieldErrors;;
Array.prototype.forEach.call(event.target.elements, (element) => {
formErrors = {...formErrors, ...validateField(element)};
})
setFormFieldErrors(formErrors)
let errors = Object.keys(formErrors).some(key => key !== null)
console.log(errors)
}
i have a scenario where i have to maintain the state of multiple checkboxes dynamically.
but when it is in on state on clicking again it doesn't changes to off state.
the number of checkboxes depenmds on a constant number say num-=10
const handleAmountChange = ev => {
const idx = ev.target.id;
const val = ev.target.value;
setEmiAmount(prev => {
const nd = [...prev];
nd[idx].emiAmount = (val);
values.push(nd[idx].emiAmount)
return nd;
});
};
useEffect(() => setEmiAmount(
(numberOfInstallments && numberOfInstallments > 0)
? ([...Array(+numberOfInstallments).keys()].map(
id => ({ id, emiAmount: '' })
)) : null
), [numberOfInstallments]);
console.log("emiAmount", emiAmount)
emiAmount && emiAmount.map((emi, idx) => {
values.push(emi.emiAmount)
})
// ui
emiAmount.map(
({ id, loan }) => (
<div className="relative px-2 mt-2 w-9">
<label
className="block uppercase text-blueGray-600 text-xs font-bold mb-2"
htmlFor="grid-password"
>
</label>
<input
onChange={handleAmountChange}
key={id} id={id}
type="checkbox" role="switch"
required={true}
/>
</div>
So the problem I am facing is this. Here I have created PaymentForm with Stripe. So when I am not entering the input value of CardHolder name, and when I press the Purchase button it should display the <h1>Please enter your cardholder name</h1> but it is not doing it. I just want to create a test of Cardholder name, I know the documentation of Stripe is showing real approaches. Where could the error be located?
Payment form
import React,{useContext, useEffect, useState} from 'react'
import {CardElement, useStripe, useElements } from"#stripe/react-stripe-js"
import { CartContext } from '../../context/cart'
import { useHistory, Link } from "react-router-dom";
const PaymentForm = () => {
const { total} = useContext(CartContext)
const {cart, cartItems}= useContext(CartContext)
const history = useHistory()
const {clearCart} = useContext(CartContext)
const [nameError, setNameError]=useState(null)
const [name, setName] = React.useState("");
const [succeeded, setSucceeded] = useState(false);
const [error, setError] = useState(null);
const [processing, setProcessing] = useState('');
const [disabled, setDisabled] = useState(true);
const [clientSecret, setClientSecret] = useState('');
const stripe = useStripe();
const elements = useElements();
useEffect(() => {
// Create PaymentIntent as soon as the page loads
window
.fetch("http://localhost:5000/create-payment-intent", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({items: [{cart}]})
})
.then(res => {
return res.json();
})
.then(data => {
setClientSecret(data.clientSecret);
});
}, [cart]);
const cardStyle = {
style: {
base: {
color: "#32325d",
fontFamily: 'Arial, sans-serif',
fontSmoothing: "antialiased",
fontSize: "16px",
"::placeholder": {
color: "#32325d"
}
},
invalid: {
color: "#fa755a",
iconColor: "#fa755a"
}
}
};
const handleChange = async (event) => {
setDisabled(event.empty);
setError(event.error ? event.error.message : "");
};
const handleChangeInput = async (event) => {
setDisabled(event.empty);
setNameError(event.nameError ? event.nameError.message : "");
setName(event.target.value)
};
const handleSubmit = async ev => {
ev.preventDefault();
setProcessing(true);
const payload = await stripe.confirmCardPayment(clientSecret, {
payment_method: {
card: elements.getElement(CardElement)
}
});
if (payload.error) {
setError(`Payment failed ${payload.error.message}`);
setProcessing(false);
} else {
setNameError(null)
setError(null);
setProcessing(false);
setSucceeded(true)
clearCart()
}
};
useEffect(()=>{
setTimeout(() => {
if(succeeded){
history.push('/')
}
}, 3000);
},[history, succeeded])
console.log(name)
return (
<form id="payment-form" onSubmit={handleSubmit}>
<h2>Checkout</h2>
<div className='payment__cont'>
<label>Cardholder Name </label>
<input
placeholder='Please enter your Cardholder name'
type="text"
id="name"
value={name}
onChange={handleChangeInput}
/>
</div>
<div className="stripe-input">
<label htmlFor="card-element">
Credit or Debit Card
</label>
<p className="stripe-info">
Test using this credit card : <span>4242 4242 4242 4242</span>
<br />
enter any 5 digits for the zip code
<br />
enter any 3 digits for the CVC
</p>
</div>
<CardElement id="card-element" options={cardStyle} onChange={handleChange} />
<div className='review__order'>
<h2>Review Order</h2>
<h4>Total amount to pay ${total}</h4>
<h4>Total amount of items {cartItems}</h4>
<button
className='purchase__button'
disabled={processing || disabled || succeeded}
id="submit"
>
<span id="button-text">
{processing ? (
<div className="spinner" id="spinner"></div>
) : (
"Complete purchase"
)}
</span>
</button>
<button className='edit__button'onClick={()=> {history.push('/cart')}}>Edit Items</button>
</div>
{error && (
<div className="card-error" role="alert">
{error}
</div>
)}
{nameError && (
<div className="card-error" role="alert">
<h1>Please enter yor card holder name</h1>
</div>
)}
<p className={succeeded ? "result-message" : "result-message hidden"}>
Payment succeeded
{''}
<h1>Redirecting you yo the home</h1>
</p>
</form>
);
}
export default PaymentForm
You still need to validate your name input yourself. Stripe doesn't do that for you
Your handleChangeInput handler only fires when you write to your name input, and you're treating the event as if it's fired from a Stripe component, but it's not, so try this:
// Validates name input only
const handleChangeInput = async (event) => {
const { value } = event.target;
// Disable when value is empty
setDisabled(!value);
// Set the error if value is empty
setNameError(value ? "" : "Please enter a name");
// Update form value
setName(value)
};
I'm not sure what you want to do is possible with Stripe elements as cardholder name isn't actually part of it. You'll need to handle it yourself.
An alternative way of ensuring the name field is entered prior to the card element being pressed would be to force input into cardholder name field before displaying/enabling the Stripe Card Element. This way you can be more certain the the name has been entered (and then you can do what you like with it) before the card element is pressed. You could do this a lot of ways, but for example in your render:
{name.length >= 10 ? ( //Check length of name
<CardElement id="card-element" options={cardStyle} onChange={handleChange} />
) : (
<span>Please enter your cardholder name.</span> // do nothing if check not passed
)}
This is a really simple example but it checks the length of name and then if greater than or equals ten characters makes the card element visible. You could instead use your handleChangeInput to set a boolean state (true or false) on button press or something; it would be better to make this more robust.
edit: some clarifications.
When i'm trying to type in the input element the state dosen't update. So i can't type anything in the input. I get no error code either. In the handleChange function the text variable is undefined when i log it to the console. But the value variable updates every single letter i type. But not as a whole sentence, the letters just overwrites them self.
import React, { useState } from "react";
function AddTodo(props) {
const initialFromState = { text: "", isComplete: null, id: null };
const [todo, setTodo] = useState(initialFromState);
const handleSubmit = e => {
e.preventDefault();
if (!todo.text) return;
props.AddTodo(todo);
setTodo(initialFromState);
console.log(todo);
};
const handleChange = event => {
const { text, value } = event.target;
setTodo({ ...todo, [text]: value });
};
return (
<div className="container mx-auto text-center">
<form onSubmit={handleSubmit} className="rounded flex">
<input
className="shadow appearance-none border rounded w-full py-2 mr-4 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
type="text"
name="text"
value={todo.text}
onChange={handleChange}
autoComplete="off"
autoFocus={true}
placeholder="Eg. Buy apples"
/>
<button
type="submit"
className="bg-blue-500 hover:bg-blue-700 text-white font-bold px-4 rounded focus:outline-none focus:shadow-outline"
>
Add
</button>
</form>
</div>
);
}
export default AddTodo;
I expect that what i'm typing to the input is stored in the state and that i can see what i'm typing. Next challenge is to see if the todo actually is stored and showed among the other todos. :) One step at the time.
I think you have wrong prop names in your handleChange, text should be name:
const handleChange = event => {
const { name, value } = event.target;
setTodo({ ...todo, [name]: value });
};
Its supposed to be name, you do not have text attribute to target it.
name='text' attribute given use that.
const { name, value } = event.target;
setTodo({ ...todo, [name]: value });