I am using React Query to make API calls.
I have an OTP Generation API in which I am making a POST API call to generate an OTP as a response from the API I receive the status of OTP deliverance.
/* eslint-disable no-unused-vars */
import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { TextField } from '../Input/TextField';
import { CustomButton } from '../Button/CustomButton';
import { MOBILE_NUMBER } from '../Common/Placeholder';
import { getOtpData } from '../../hooks/getOtp.hook';
export function MobileNumber() {
const navigate = useNavigate();
const [mobileno, setMobileNo] = useState('');
const [isTermsAgree, setisTermsAgree] = useState(false);
const [isDisplayLoader, setDisplayLoader] = useState(false);
const [isDisplayError, setDisplayError] = useState(false);
const { mutate, isError, isSuccess, isLoading, isIdle, data } =
getOtpData();
// Onchnage event for input mobile number
const handleNumberChange = (
e: React.ChangeEvent<HTMLInputElement>,
) => {
setMobileNo(e.target.value);
};
// Onchnage event for Checkbox
const TermsAgreeChange = () => {
setisTermsAgree((current) => !current);
};
// onClick Event Confirm Btn //Generate OTP API call Goes Here
const getOtp = () => {
mutate(mobileno);
if (isSuccess) {
if (data?.data.otpSent) {
console.log('Sent - true');
navigate('/phone-otp-confirmation', {
state: { phoneNumber: mobileno },
});
}
if (data?.data.maxOtpRetriesExceeded) {
setDisplayError(true);
}
}
if (isError) {
console.log('error');
}
};
return (
<div className="bg-grey-800 h-1/2 mt-40 flex flex-col justify-evenly font-Manrope ">
<section>
<div className=" flex-col flex items-center md:items-baseline md:pl-36 ">
<p className=" text-3xl "> Enter Mobile Number </p>
</div>
<div>
<p className="text-l flex-col flex items-center mt-1 md:items-baseline md:pl-36 ">
<span className=" text-gray-400 text-center ">
Enter Mobile Number used for instant login
</span>
</p>
</div>
<div className="flex-col flex items-center md:items-baseline md:pl-36 mt-5">
<div className=" flex items-center sm:justify-start sm:px-0 ">
<div>
<div className=" flex w-18 px-3 justify-center items-center bg-transparent rounded-bl-lg rounded-tl-lg border text-2xl md:text-3xl border-gray-700 h-12 md:h-16 focus:outline-none focus:bg-transparent">
<span>+91</span>
</div>
</div>
<div>
<TextField
width="w-48"
height="h-12"
padding="px-5"
placeholder={MOBILE_NUMBER}
maxlen={10}
other="rounded-br-lg rounded-tr-lg px-5 md:w-72 md:h-16"
type="text"
onChangeFunction={handleNumberChange}
val={mobileno}
error={false}
/>
</div>
</div>
</div>
</section>
<div className=" flex-col flex mt-16 items-center md:items-baseline md:pl-36 md:mt-5 ">
<div className="flex items-center w-72">
<TextField
width="w-7"
height="h-7"
type="checkbox"
other="form-checkbox"
onChangeFunction={TermsAgreeChange}
/>
<p className="ml-3 text-sm md:text-base tracking-wide text-gray-400 font-extralight">
I have read the OneCard{' '}
<a
href="http://"
className="underline text-sm md:text-base text-gray-400"
>
Terms and Conditions & Privacy Policy
</a>{' '}
</p>
</div>
<div className="mt-8 ">
<CustomButton
clickEvent={getOtp}
btntext="Get OTP"
isbuttonactive={mobileno.length === 10 && isTermsAgree}
/>
</div>
{/* <h2>Loader</h2>
<h2>Error</h2> */}
</div>
</div>
);
}
OTP Generation hook
import { useMutation } from 'react-query';
import axios from 'axios';
import { WEB } from '../constants/constants';
interface IGetOTPResult {
otpSent: boolean;
maxOtpRetriesExceeded: boolean;
}
const getOTP = async (mobileNumber: string) => {
const response = await axios.post<IGetOTPResult>(
`${process.env.REACT_APP_URL}/`,
{
mobile: mobileNumber
},
{
headers: {
Authorization: '',
'Content-Type': 'application/json',
},
},
);
return response;
};
export const getOtpData = () => {
return useMutation(getOTP);
};
PROBLEM : As soon as I make this API call through the frontend as I click the button, it goes into isIdle state to be true.
Only the second time, I click the button, isSuccess becomes true.
However, bot the times the API call is made and I receive a 200 response!
I want to ensure my API call never enters isIdle state.
Plus, there is no significant information given about isIDle in any of react-queries documentation.
How do I go about this?
This is not how state in react works. when you call mutate, react-query updates state in your component, and on the next render cycle, it will be available. It is the same concept as setState, you can't really do:
function MyComponent() {
const [foo, setFoo] = React.useState('foo')
return <button onClick={() => {
setFoo('something')
console.log(foo) // 🚨 this will still log "foo", not "something"
}}>click</button>
}
if you want to get access directly to the response, you have to either:
use the provided callbacks of mutate:
mutate(
mobileno,
{
onSuccess: (response) => {
// handle success here
},
onError: (error) => {
// handle error here
}
)
use mutateAsync and await:
try {
const response = await mutateAsync(mobileno)
// handle success here
} catch(error) {
// handle error here
}
side question: how can a hook be called getOtpData ? It has to start with use...
Related
*is it possible to integrate in my ready checkout paypal code to add monthly payment function?
I have developed a function that is responsible for receiving a one-time payment, but I need to add a function that will be responsible for charging a monthly fixed amount from a person who wants to make a monetary contribution
*
import React, { useEffect } from "react";
import {
PayPalScriptProvider,
PayPalButtons,
usePayPalScriptReducer,
} from "#paypal/react-paypal-js";
import imgLogo from "../../../images/FUMlogo.png";
import imgColageDesk from "../../../images/Donate/colageDesk.png";
import DonateBTN from "../../UI/Button/DonateBTN/DonateBTN";
import DonateOther from "../../UI/Button/DonateBTN/DonateOther";
import { useDispatch, useSelector } from "react-redux";
import {
setAmountMoney,
setFlowAmountMoney,
} from "../../../Store/Reducers/inputMoneyAmount";
export default function Donate() {
const amountDonate = useSelector((state) => state.inputMoneyAmount.amount);
const flowAmountDonate = useSelector(
(state) => state.inputMoneyAmount.flowAmount
);
const dispatch = useDispatch();
const amount50 = () => {
dispatch(setFlowAmountMoney(false));
dispatch(setAmountMoney(50));
};
const amount100 = () => {
dispatch(setFlowAmountMoney(false));
dispatch(setAmountMoney(100));
};
const amount200 = () => {
dispatch(setFlowAmountMoney(false));
dispatch(setAmountMoney(200));
};
// This values are the props in the UI
const amount = amountDonate ? amountDonate : flowAmountDonate + "";
const currency = "USD";
const style = { layout: "vertical", color: "blue" };
// Custom component to wrap the PayPalButtons and handle currency changes
const ButtonWrapper = ({ currency, showSpinner }) => {
// usePayPalScriptReducer can be use only inside children of PayPalScriptProviders
// This is the main reason to wrap the PayPalButtons in a new component
const [{ options, isPending }, dispatch] = usePayPalScriptReducer();
useEffect(() => {
dispatch({
type: "resetOptions",
value: {
...options,
currency: currency,
},
});
}, [currency, showSpinner]);
return (
<>
{showSpinner && isPending && <div className="spinner" />}
<PayPalButtons
style={style}
disabled={false}
forceReRender={[amount, currency, style]}
fundingSource={undefined}
createOrder={(data, actions) => {
return actions.order
.create({
purchase_units: [
{
amount: {
currency_code: currency,
value: amount,
},
},
],
})
.then((orderId) => {
// Your code here after create the order
return orderId;
});
}}
onApprove={function (data, actions) {
return actions.order.capture().then(function () {
// Your code here after capture the order
});
}}
/>
</>
);
};
// bg-[#010321]
return (
<main className="flex flex-col items-center w-full h-screen sm:h-auto text-white bg-[#1b1d33] pt-[8vh] sm:pt-[9vw] lg:pt-[7vw] xl:pt-[5vw] relative ">
<section className=" flex flex-col items-center justify-center px-[3vw] ">
<img
className=" w-full sm:w-[70vw] md:w-[60vw] lg:w-[40vw] xl:w-[30vw]"
src={imgColageDesk}
alt="colage"
/>
<img
className="w-[10vw] lg:w-[8vw] xl:w-[5vw] h-[10vw] lg:h-[8vw] xl:h-[5vw] mt-10"
src={imgLogo}
alt="logo"
/>
<h3 className="text-xs sm:text-sm md:text-base lg:text-lg mt-5">
Donate to
</h3>
<h1 className="uppercase mt-3 text-sm sm:text-base md:text-lg lg:text-xl font-bold ">
forward ukraine ministries
</h1>
<p className="w-full sm:w-[70vw] md:w-[60vw] lg:w-[40vw] xl:w-[30vw] text-center text-xs sm:text-sm md:text-base lg:text-lg mt-2">
A humanitarian catastrophe is unfolding in Ukraine as attacks of war
put millions of lives at risk. Support Military & Refugee
</p>
<div className="w-[80vw] sm:w-[50vw] md:w-[30vw] flex justify-around mt-14 mb-4 sm:mt-15 text-black">
<DonateBTN setAmount={amount50} amount="$50" />
<DonateBTN setAmount={amount100} amount="$100" />
<DonateBTN setAmount={amount200} amount="$200" />
<DonateOther name="Other" />
</div>
<input
value={flowAmountDonate}
onChange={(e) => dispatch(setFlowAmountMoney(e.target.value))}
onKeyDown={(e) =>
["ArrowUp", "ArrowDown", "e", "E", "+", "-", ".", ","].includes(
e.key
) && e.preventDefault()
}
className={`${
flowAmountDonate
? "block font-bold w-[80%] sm:w-3/4 md:w-[85%] h-[7vw] sm:h-[6vw] md:h-[4vw] mb-4 sm:mb-8 text-black rounded-lg bg-gradient-to-r from-blue-500 to-yellow-500 focus:from-pink-500 focus:to-yellow-500 hover:from-pink-500 hover:to-yellow-500"
: "hidden"
}`}
type="number"
name=""
/>
<PayPalScriptProvider
options={{
"client-id": "test",
components: "buttons",
currency: "USD",
}}
>
<ButtonWrapper currency={currency} showSpinner={false} />
</PayPalScriptProvider>
</section>
</main>
);
}
Recurring payments use a separate process.
For react-paypal-js in particular you can see an example of a Subscription checkout here. You can create subscription plans in your live PayPal account here (or for a sandbox business account here, or via API here). The client-id that a subscription plan is created for must correspond to the client-id that the JS SDK is loaded with for checkout. If it does not correspond, the error will be RESOURCE_NOT_FOUND.
I'm learning vuex at the moment and wondering if there is a way to pass a different endpoint url in my vuex action? I'm building a simple movie app using a movies API and have numerous buttons that will call the API and post specific categories. What I would like to do is dispatch the same function from vuex but specify which endpoint to use rather than create the same function with just a different endpoint in vuex.
Vuex:
import axios from 'axios'
const topTwentyEndpoint =
'https://api.themoviedb.org/3/discover/movie?sort_by=popularity.desc&api_key='
const theatresEndpoint =
'https://api.themoviedb.org/3/discover/movie?primary_release_date.gte=2014-09-15&primary_release_date.lte=2014-10-22&api_key='
export const state = () => ({
posts: [],
})
export const mutations = {
setMovies(state, posts) {
state.posts = posts
},
}
export const getters = {}
export const actions = {
getMovies({ commit }) {
axios.get(topTwentyEndpoint).then((response) => {
commit('setMovies', response.data.results)
})
},
}
movies component:
<template>
<form
id="formGetMovies"
class="flex flex-col items-start w-3/4 pt-5 get-movies"
>
<div class="flex buttons__row">
<button
class="py-3 mt-2 mr-5 text-white bg-red-700 rounded-md hover:bg-red-800 px-7 btn btn__submit btn-disabled"
:class="{ disabled: moviesShown }"
:disabled="moviesShown"
#click.prevent="submitForm"
>
Top 20
</button>
<button
class="py-3 mt-2 text-white bg-red-700 rounded-md hover:bg-red-800 px-7 btn btn__submit btn-disabled"
:class="{ disabled: moviesShown }"
:disabled="moviesShown"
#click.prevent="submitForm"
>
In Theatres Now
</button>
</div>
</form>
</template>
<script>
export default {
name: 'MoviesForm',
data() {
return {
moviesShown: false,
}
},
methods: {
submitForm() {
this.$store.dispatch('movies/getMovies')
this.moviesShown = true
},
},
}
</script>
I am a beginner in web development and am developing at a rapid pace. This may be a question that can be found in the research.
What I want to do
I am trying to use Next.js to retrieve information from an external API and display it on the screen.
The API we want to call is this one.
https://en.wiki.zks.org/interact-with-zkswap/restful-api#get-account-balance
My goal is to display the balance of tokens in this Response on the screen.
What I did
Specifically, when I press the button in the header, I want to get the address from Metamask and at the same time get the ETH balance in zkSync.
To implement this, I wrote the following code
import { useState } from "react";
import { Contract, utils, ethers } from 'ethers'
import ConnectMetamaskButton from '../components/ConnectMetamaskButton';
import zkswapABI from '../../zkswap.ABI.json'
import * as zksync from "zksync"
export default function ConnectWallet(props) {
const [Accounts, setAccounts] = useState("Connect Metamask");
const ethersProvider = ethers.getDefaultProvider("ropsten");
var MNEMONIC = process.env.MNEMONIC;
const ethWallet = ethers.Wallet.fromMnemonic(MNEMONIC).connect(ethersProvider);
console.log("Accounts = ", Accounts);
async function ConnectMetamask() {
try {
const newAccounts = await ethereum.request({
method: 'eth_requestAccounts',
})
let accounts = newAccounts;
setAccounts(accounts[0]);
console.log("Accounts = ", Accounts);
} catch (error) {
console.error(error);
}
getBalases();
};
async function getBalases() {
const address = Accounts;
console.log("address = ", address);
const zkSyncUrl = "https://api.zks.app/v2/3/account/0x2D1Ac1CA744da293c5AcceAe25BE8DCd71168241/balances";
const response = await fetch(zkSyncUrl);
const data = await response.json();
console.log("data = ", data);
};
async function withdrawETH() {
const syncProvider = await zksync.getDefaultProvider("ropsten");
const syncWallet = await zksync.Wallet.fromEthSigner(ethWallet, syncProvider);
const withdraw =
await
syncWallet.withdrawFromSyncToEthereum({
ethAddress: ethWallet.address,
token: "ETH",
amount: ethers.utils.parseEther("0.001"),
});
}
async function depositETH() {
let ABI = zkswapABI;
let ZKSwapContract = '0x010254cd670aCbb632A1c23a26Abe570Ab2Bc467'
const contract = new Contract(ZKSwapContract, ABI, ethWallet)
const tx = await contract.depositETH(ethWallet.address, {
value: utils.parseEther('0.1')
})
return tx
}
async function zkSyncToZKSwap() {
event.preventDefault();
console.log("amount = ", event.target.amount.value);
withdrawETH();
depositETH();
}
return (
<div>
<header className="text-gray-100 bg-gray-900 body-font shadow w-full">
<div className="container mx-auto flex flex-wrap p-5 flex-col md:flex-row items-center">
<nav className="flex lg:w-2/5 flex-wrap items-center text-base md:ml-auto">
<a className="mr-5 hover:text-gray-900 cursor-pointer border-b border-transparent hover:border-indigo-600"></a>
</nav>
<a
className="flex order-first lg:order-none lg:w-1/5 title-font font-medium items-center lg:items-center lg:justify-center mb-4 md:mb-0">
<span className="ml-3 text-xl">L2 DEX HUB</span>
</a>
<div className="lg:w-2/5 inline-flex lg:justify-end ml-5 lg:ml-0">
<ConnectMetamaskButton Accounts={Accounts} onClick={() => { ConnectMetamask(); }}></ConnectMetamaskButton>
</div>
</div>
</header>
<div>
<section className="h-screen w-4/5 max-w-5xl mx-auto flex items-center justify-center flex-col">
<form onSubmit={zkSyncToZKSwap}>
<div className="flex flex-col rounded-lg overflow-hidden sm:flex-row">
<label htmlFor="amount"></label>
<input className="py-3 px-4 bg-gray-200 text-gray-800 border-gray-300 border-2 outline-none placeholder-gray-500 focus:bg-gray-100" id="amount" type="text" amount="amount" placeholder="Amount" />
<button className="py-3 px-4 bg-gray-700 text-gray-100 font-semibold uppercase hover:bg-gray-600" type="submit" >Exchange</button>
</div>
</form>
</section>
</div>
</div>
);
};
withdrawETH() and depositETH() are example of actually using mnemonics to create a wallet object and perform a transaction.
In this implementation, we have already confirmed that the money transfer actually takes place when the Exchange button is pressed!
Problem
We have succeeded in getting the data in console.log("data = ", data);
After this, how can I specify the token and get only the balance?
{
"success": true,
"data": {
"balances": {
"tokens": [
{
"id": 0,
"amount": "2.84",
"value": "8226.013602874849956332"
}
],
"pairs": []
},
"asset": {
"tokens": "8226.013602874849956332",
"pairs": "0",
"total": "8226.013602874849956332"
}
}
}
Priblem(optional)
Here, I am writing the address directly in the API URL, but I want to enter the accounts in useState and have it change dynamically, how do I rewrite it?
I am trying to use Howler.js in Reactjs using typescript.
I can able to play the sound but it does not pause or stop. Here is my code.
This a component where I am passing all the audio details using props.
I did console.log() to check, is it going in else part and it goes and print the console.
Please help me in this
import React, { useState } from 'react';
import Button from 'components/button/button';
import PlayIcon from 'assets/icons/play.svg';
import PauseIcon from 'assets/icons/pause.svg';
import AudioWave from 'assets/icons/sound-wave.svg';
import { Howl, Howler } from 'howler';
interface Props {
name?: string,
audio?: any,
loop?: boolean,
autoplay?: boolean
}
const Audio = (props: Props) => {
const { name, audio, loop, autoplay } = props;
const [isPlaying, setIsPlaying] = useState(false);
const [audioId, setAudioId] = useState(0);
const sound = new Howl({
src: [audio],
autoplay: autoplay,
loop: loop,
volume: 1,
onend: function () {
setIsPlaying(false);
},
});
Howler.volume(0.5);
const playAudio = () => {
let Id: any;
if (!isPlaying) {
Id = sound.play();
setAudioId(Id);
setIsPlaying(true);
console.log('THS')
} else {
sound.stop(audioId);
console.log('THATAT', audioId)
}
console.log(sound)
}
return (
<div className="flex flex-col items-center justify-center rounded shadow-md w-full">
{console.log(isPlaying, audioId)}
<div className="grid grid-cols-12 w-full">
<div className="col-span-6 p-2">
<p className="text-left">
{name}
</p>
</div>
<div className="col-span-6 p-2">
<p className="text-right text-light-gray">
{sound ? `${Duration.toTimeFormat(sound._duration * 1000)}s` : '0:00s'}
</p>
</div>
</div>
<div className="grid grid-cols-12 w-full items-center justify-center">
<div className="grid col-span-2 w-full p-2">
<img
className="w-full cursor"
onClick={() => playAudio()}
src={isPlaying ? PauseIcon : PlayIcon}
alt="PlayPauseIcon"
/>
</div>
<div className="grid col-span-10 p-2">
<img className="w-full" alt="Audio Wave" src={AudioWave} />
</div>
</div>
</div>
)
}
export default Audio;
I'm not too familiar with Howler, but it seems it's not 'react-friendly' - more suited for a non-SPA environment. However, I figure if you try it like this, you might get the results you're looking for. The main issue with your code is that every time you change something via useState, the component will re-render. When it re-renders, any variables that are not stored via useState, will have their values re-initialized. Therefore, in your code from the question, the sound variable gets reset on every render making it impossible to control the element. In the code below I shifted that into a useEffect function, which is similar to componentDidMount. It will only run on the first render; thus, it prevents you from having several copies of the sound object.
I haven't tested this code, but I think in general it targets the main issue you're having.
const Audio = (props: Props) => {
const { name, audio, loop, autoplay } = props;
const [isPlaying, setIsPlaying] = useState(false);
const [audioId, setAudioId] = useState(0);
const [sound, setSound] = useState(null);
useEffect(() => {
const s = new Howl({
src: [audio],
autoplay: autoplay,
loop: loop,
volume: 0.5,
onplay: function (id) {
setAudioId(id);
setIsPlaying(true);
},
onpause: function (id) {
setIsPlaying(false);
},
onend: function () {
setIsPlaying(false);
},
onloaderror: function(id, error) {
console.log(`a load error has occured on id ${id}`);
console.error(error);
},
onplayerror: function(id, error) {
console.log(`a play error has occured on id ${id}`);
console.error(error);
}
});
setSound(s);
});
const toggleAudioState = () => {
if (sound !== null) {
if (!isPlaying) {
sound.play();
setIsPlaying(true);
} else {
sound.stop(audioId);
}
}
}
return (
<div className="flex flex-col items-center justify-center rounded shadow-md w-full">
<div className="grid grid-cols-12 w-full">
<div className="col-span-6 p-2">
<p className="text-left">
{name}
</p>
</div>
<div className="col-span-6 p-2">
<p className="text-right text-light-gray">
{sound ? `${Duration.toTimeFormat(sound._duration * 1000)}s` : '0:00s'}
</p>
</div>
</div>
<div className="grid grid-cols-12 w-full items-center justify-center">
<div className="grid col-span-2 w-full p-2">
<img
className="w-full cursor"
onClick={() => toggleAudioState()}
src={isPlaying ? PauseIcon : PlayIcon}
alt="PlayPauseIcon"
/>
</div>
<div className="grid col-span-10 p-2">
<img className="w-full" alt="Audio Wave" src={AudioWave} />
</div>
</div>
</div>
)
}
I got the solution for that : -
Here it should be define global. But we can't do it in react, because we are receiving all the data through props.
const sound = new Howl({ } );
If we define this able the function then it will work -
const sound = new Howl({
src: ['some_music_link.mp3'],
autoplay: true,
loop: true,
volume: 1,
});
const Audio = (props :Props) => {
...
}
In order to solve this problem someone created a new npm package for that called Router Howler
So, if you are trying to use howler.js in react then use React Howler
I have a login in react,
import React, { Fragment, useState } from 'react';
import { useHistory } from "react-router-dom";
import { useDispatch } from 'react-redux';
import { Link } from 'react-router-dom';
import { auth } from '../actions';
export const Login = () => {
let history = useHistory();
const dispatch = useDispatch();
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState({email: '', password: ''});
const validEmailRegex = RegExp(/^(([^<>()\[\]\.,;:\s#\"]+(\.[^<>()\[\]\.,;:\s#\"]+)*)|.
(\".+\"))#(([^<>()[\]\.,;:\s#\"]+\.)+[^<>()[\]\.,;:\s#\"]{2,})$/i);
const handleChange = e => {
e.persist();
const { name, value } = e.target;
let validationError = error;
switch(name) {
case 'email':
setEmail(value);
validationError.email = validEmailRegex.test(value) ? '' : 'Email is not valid';
break;
case 'password':
setPassword(value)
validationError.password = value.length < 8 ? 'Password must be 8 characters
long!': '';
break;
case 'submit':
validationError.email = email.length < 1 ? 'Email is required' : '';
validationError.password = password.length < 1 ? 'Password is required' : '';
default:
break;
};
setError(validationError);
console.log('in change', error)
};
const validateForm = (errors) => {
let valid = true;
Object.values(errors).forEach(
// if we have an error string set valid to false
(val) => val.length > 0 && (valid = false)
);
return valid;
};
const validate = () => {
console.log('email,password', email, password);
let validationError = error;
if(!email){
validationError.email = 'Email is required';
}
if(!password){
validationError.password = 'Password is required';
}
setError(validationError);
console.log('in validate',error)
};
const onSubmit = e => {
e.preventDefault();
validate();
console.log('error on submit', error);
if(validateForm(error)) {
dispatch(auth(email, password, true));
history.replace('/home');
}else{
console.error('Invalid Form', error)
}
};
return (
<Fragment>
<div className="w-full max-w-sm container mt-20 mx-auto">
<form onSubmit={onSubmit}>
<div className="w-full mb-5">
<label className="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2" htmlFor="email">
Email
</label>
<input className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:text-gray-600" value={email} name='email' onChange={(e) => handleChange(e)} type="text" placeholder="Email" />
{ error && <span style={{color: "red"}}>{error['email']}</span>}
</div>
<div className="w-full mb-5">
<label className="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2" htmlFor="password">
Password
</label>
<input className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:text-gray-600" value={password} name='password' onChange={(e) => handleChange(e)} type="password" placeholder="Password" />
{ error && <span style={{color: "red"}}>{error['password']}</span>}
</div>
<div className="flex items-center justify-between">
<button className="mt-5 bg-green-400 w-full hover:bg-green-500 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline">
Login
</button>
</div>
<div className="text-center mt-4 text-gray-500"><Link to='/'>Cancel</Link></div>
</form>
</div>
</Fragment>
)
}
I have added field validation in onchange and in submit, the onchange validation is working correctly and showing erros. Validation on submit is also wokring fine, but still the component is not showing error when I try submit without any change in fields.
I am new to react, I dont know if this is the correct way to do that. Thanks in advance.
I put your code in a code sandbox and it seems to work just fine. Note that i removed className properties and commented stuff that is not necessary to test your issue like redux imports.
Since I removed the className properties this could be an CSS issue, where your error span is actually rendered but not visible (check out the dev tools of your browser to see if the span is really not there).
Also I would recommend you to use a library if you use a lot of forms, since state handling + validation could get quite complicated and there are plenty solutions out there.
I wrote my own library - react-fluent-form - feel free to check that out.
EDIT
The issue here is that when you update the error object using setError you always pass the same object reference:
// this is not doing a copy
// validationError will have the same reference as error
let validationError = error;
// ...
// following line will not trigger a rerender
setError(validationError);
Since error and validationError have the same reference, react will asume no change has happened, thus it will cause bail out of a state update. If you work with complex types (like objects or arrays) in state you always need to create a new reference instead of adapting the previous one:
// this is an actual copy using the spread operator
// validationError will have different reference than error
let validationError = {...error};
// ...
// triggers rerender as expected
setError(validationError);
EDIT 2
I added a return value for validate to use the updated validationError object when calling validateForm.
const validate = () => {
//..
let validationError = { ...error };
// ...
return validationError;
};
const onSubmit = e => {
// ...
const validationError = validate();
if (validateForm(validationError)) {
//...
}
};
See the updated code sandbox.