Button takes two clicks using boolean state hook - javascript

I'm fairly new to React. Here I've made a small form component for a project (with a bit of tailwind included). Above the form proper is a hidden alert box that will show on submission (green for success and red for fail). The handler that I have attached to the form shows the correct alert, however it takes two clicks. In the handler validateFormData() I'm resetting state (isError). I'm aware the useState hook is asynchronous, so my isError variable is not updating properly before I render my alert box (right?). I've tried passing callbacks to my setIsError functions (after looking online for solutions) but I could not resolve the issue. Am I even in the right ball park here? Or am I missing something?
function ContactInsert() {
const contactForm = useRef(null);
const alertBox = useRef(null);
const [isError, setIsError] = useState(false);
function showAlertBox() {
alertBox.current.classList.add("alert-show");
setTimeout(() => {
alertBox.current.classList.remove("alert-show");
}, 3000);
}
async function validateFormData() {
// unpack and validate data
const name = contactForm.current.querySelector("[name=name]").value.trim();
const email = contactForm.current.querySelector("[name=email]").value.trim();
const comment = contactForm.current.querySelector("[name=comment]").value.trim();
if (name.length > 0 && email.length > 0 && comment.length > 0) {
setIsError(false);
} else {
setIsError(true);
}
showAlertBox();
}
return (
<div className="flex flex-col">
<div className="flex justify-center my-2">
<h1>Drop me a message!</h1>
</div>
{isError ?
<div ref={alertBox} className="flex h-12 w-2/3 justify-center bg-[#ff462e] invisible">hello</div> :
<div ref={alertBox} className="flex h-12 w-2/3 justify-center bg-[#77ff6e] invisible">hello</div>
}
<form
className="flex flex-col items-center justify-center md:h-full"
method="POST"
name="contact"
id="contact"
type="submit"
ref={contactForm}
onSubmit={(e) => {
e.preventDefault();
validateFormData();
}}
>
<div className="flex flex-col justify-between w-2/3">
<label>name</label>
<input type="text" name="name"/>
</div>
<br/>
<div className="flex flex-col justify-between w-2/3">
<label>email</label>
<input type="text" name="email"/>
</div>
<br/>
<div className="flex flex-col justify-between w-2/3 h-40">
<label>comment</label>
<textarea className="h-full" name="comment"/>
</div>
<div className="flex w-2/3 justify-start my-4">
<button className="p-1" form="contact">Submit</button>
</div>
</form>
</div>
);
}

It is always a bad idea to manipulate DOM directly as React will have a hard time to match the rendered output and defeats the purpose of using react. Better to store this info in the state and let react handle it.
Also setState is asynchronous, and calling that will force the react component to rerender. We can use useEffect to let React know that component has to do something on next render.
function ContactInsert() {
const [isError, setIsError] = useState(false);
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [comment, setComment] = useState('');
const [displayAlert, setDisplayAlert] = useState(false);
const handleNameOnchangeHandler = (event) => {
setName(event.target.value);
};
const handleEmailOnchangeHandler = (event) => {
setName(event.target.value);
};
const handleCommentOnchangeHandler = (event) => {
setName(event.target.value);
};
const validateFormData = () => {
const hasError = !name || !email || !comment;
setIsError(hasError);
}
useEffect(() => {
if(isError) {
setDisplayAlert(true);
setTimeout(() => {
setDisplayAlert(false);
setIsError(false);
}, 3000);
}
}, [isError]);
return (
<div className="flex flex-col">
<div className="flex justify-center my-2">
<h1>Drop me a message!</h1>
</div>
{displayAlert ?
<div ref={alertBox} className="flex h-12 w-2/3 justify-center bg-[#ff462e] show-alert">hello</div> : null
}
<form
className="flex flex-col items-center justify-center md:h-full"
method="POST"
name="contact"
id="contact"
type="submit"
ref={contactForm}
onSubmit={(e) => {
e.preventDefault();
validateFormData();
}}
>
<div className="flex flex-col justify-between w-2/3">
<label>name</label>
<input type="text" name="name" onchange={handleNameOnchangeHandler}/>
</div>
<br/>
<div className="flex flex-col justify-between w-2/3">
<label>email</label>
<input type="text" name="email" onchange={handleEmailOnchangeHandler}/>
</div>
<br/>
<div className="flex flex-col justify-between w-2/3 h-40">
<label>comment</label>
<textarea className="h-full" name="comment" onchange={handleCommentOnchangeHandler}/>
</div>
<div className="flex w-2/3 justify-start my-4">
<button className="p-1" form="contact">Submit</button>
</div>
</form>
</div>
);
}
This is an example using useEffect. This can also be done without using it. You could also have a single onchange handler instead and set the value based on the target where the event was triggered.

you are using react completely wrong.
i highly recommend you to read react docs carefully and digging into the rendering concept.
the problems i saw in your code was :
the way you’re getting the input value is not good.
you should define a state (controlled component) or pass a ref to get the value (uncontrolled).
changing your css classes with a ref is not gonna work because it doesn’t trigger a rerender in your app (read useRef docs for more details)
you should define a new state and use that state for changing your className.

Related

Having little complication while using useLocation() [duplicate]

This question already has answers here:
How to pass data from a page to another page using react router
(5 answers)
Closed 27 days ago.
I do not now why the same code was working yesterday fine but today not working. I understand why the error happens, but have no idea how to fix it. I am trying to redirect the user to profile component using Navigate comp with a state pass. This works fine, but when a user manually clicks on profile I get the error because I am trying to access the state value that does not exist. Since the state value is defined after only redirecting. So is there any help to avoid or fix this error?
react-refresh-runtime.development.js:315 Uncaught TypeError:
Cannot read properties of null (reading 'mes')
Login.jsx
import { useState } from "react";
import { Link, Navigate, Outlet } from "react-router-dom";
import axios from "axios";
import { FaEye, FaEyeSlash } from "react-icons/fa";
function Login() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [inputType, setInputType] = useState("password");
const [icon, setIcon] = useState(<FaEye />);
const [success, setSuccess] = useState("");
const [result, setResult] = useState(false);
function handleToggle(e) {
if (inputType === "password") {
setInputType("text");
setIcon(FaEyeSlash);
} else {
setInputType("password");
setIcon(FaEye);
}
}
function handleSubmit(e) {
e.preventDefault();
const user = { email, password };
console.log(`email: ${email}, password: ${password}`);
axios
.post("http://localhost:5000/user/login", user)
.then((res) =>{
setResult(true);
setSuccess(`welcome ${res.data.user} you are successfully Logged in!`);
console.log(result);
}
)
.catch((err) => {
//console.log(`ERROR is ${err}`);
setResult(false);
console.log(result);
setSuccess("Incorrect password or email");
});
}
if(result){
console.log(result);
return <Navigate to="/profile" state={{mes: success }} />
}
return (
<form onSubmit={handleSubmit}>
<div className="text-center text-lg text-red-500 font-semibold">{success}</div>
<div className="h-auto w-5/12 border mx-auto rounded-2xl mt-3 ">
<div className="h-2 bg-indigo-400 rounded-t-2xl mb-5 "></div>
<div className="font-bold text-2xl text-center">Sign In</div>
<div className="px-16">
<div className="mt-5 ">
<label htmlFor="email" className="block font-semibold ">
Email address
</label>
<input
type="email"
className="border h-5 w-full px-3 py-5 rounded-md focus:outline-2 focus:outline-blue-600"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Enter email"
id="email"
required
/>
</div>
<div className="relative ">
<label
htmlFor="pass"
className="block font-semibold mt-5"
>
Password
</label>
<input
type={inputType}
className="border h-5 w-full px-3 py-5 rounded-md focus:outline-2 focus:outline-blue-600"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Enter password"
id="pass"
required
/>
<span className="absolute top-9 right-6" onClick={handleToggle}>
{icon}
</span>
</div>
<div className="">
<button
type="submit"
className="mt-5 text-white bg-blue-600 border h-10 w-full py-2 rounded-md"
>
Submit
</button>
</div>
<div className="flex justify-around">
<p className="mb-5 mt-3 text-left">
New here?
<Link to="/sign-up" className="text-blue-600">
Register
</Link>
</p>
<p className="mb-5 mt-3 text-right ">
Forgot
<Link to="/password-reset" className="text-blue-600">
password?
</Link>
</p>
</div>
</div>
</div>
</form>
);
}
export default Login;
Profile.jsx
import { useState } from "react";
import { useLocation } from "react-router-dom";
function Profile() {
const location = useLocation();
const msg = location.state.mes;
const [success, setSuccess] = useState(msg);
const [cancel, setCancel] = useState("X");
const [name, setName] = useState(
"h-10 flex justify-around items-center bg-green-200 text-black"
);
function handleClick() {
setSuccess("");
setCancel("");
setName("");
}
return (
<>
<div className={name}>
{success}
<button onClick={handleClick}>{cancel}</button>
</div>
profile
</>
);
}
export default Profile;
I have not tried anything, but I will try to see the docs on this.
Simply use Optional Chaining to check if location.state.mes exists. If location.state or location.state.mes doesn't exist then default to an empty string in the useState() call.
const msg = location.state?.mes; // msg will either be undefined or the message value
const [success, setSuccess] = useState(msg == undefined ? '' : msg);

fetched data from firestore as initial value of useState it gives me udefined value

I want to set the fetched data from firestore as initial value of useState but it gives me undefined value because I want to update user profile and I don't know the user edits or updates which property because I want to keep the other properties of the user the same, only change the edited one.
I've tried this code, but it gives me this error:
Uncaught (in promise) FirebaseError: Function updateDoc() called with invalid data. Unsupported field value: undefined (found in field surname in document users/DQjpLaKYVgVuH9TeqNomIEyuMJB2)
import React, { useState, useEffect } from 'react';
import { useAuthState } from 'react-firebase-hooks/auth';
import { doc, onSnapshot, updateDoc } from "firebase/firestore";
import { auth, db } from '../../firebase';
export default function Form({ setEditForm }) {
const [user, setUser] = useState([]);
const [currentUser] = useAuthState(auth);
// fetching user information from firestore
useEffect(() => {
const getUser = async () => {
const docRef = await doc(db, 'users', currentUser.uid)
try {
await onSnapshot(docRef, (doc) => {
setUser({
...doc.data(), id: doc.id
})
})
} catch (e) {
console.log(e)
}
}
getUser()
}, [])
const [name, setName] = useState(user.firstName);
const [surname, setSurname] = useState(user.surname);
const [biography, setBiography] = useState(user.biography);
const [location, setLocation] = useState(user.location);
// updating user's profile
const updateProfile = async (e) => {
e.preventDefault();
const docRef = doc(db, 'users', currentUser.uid);
await updateDoc(docRef, {
firstName: name,
surname: surname,
biography: biography,
location: location
})
}
console.log(user)
return (
<form
onSubmit={updateProfile}
className="flex flex-col w-4/6 lg:w-3/6"
>
<div className="lg:flex lg:flex-row lg:justify-between lg:gap-6">
<div className="lg:flex lg:flex-col lg:w-1/2">
<h2 className="text-left text-[#4699C2] font-bold py-2">Name: </h2>
<div className="border border-gray-300 rounded-md">
<input
type="text"
placeholder={name}
value={name}
onChange={(e) => setName(e.target.value)}
className="w-full py-2 px-4 opacity-50 focus:opacity-100"
/>
</div>
</div>
<div className="lg:flex lg:flex-col lg:w-1/2">
<h2 className="text-left text-[#4699C2] font-bold py-2">Surname: </h2>
<div className="border border-gray-300 rounded-md">
<input
type="text"
placeholder={surname}
value={surname}
onChange={(e) => setSurname(e.target.value)}
className="opacity-50 px-4 focus:opacity-100 w-full py-2"
/>
</div>
</div>
</div>
<h2 className="text-left text-[#4699C2] font-bold py-2">Biograhpy: </h2>
<div className="border border-gray-300 rounded-md">
<textarea
onChange={(e) => setBiography(e.target.value)}
className="opacity-50 px-4 focus:opacity-100 w-full py-4"
>
{biography}
</textarea>
</div>
<h2 className="text-left text-[#4699C2] font-bold py-2">Location: </h2>
<div className="border border-gray-300 rounded-md">
<input
placeholder={location}
value={location}
onChange={(e) => setLocation(e.target.value)}
className="opacity-50 px-4 focus:opacity-100 w-full py-2"
/>
</div>
<div className="flex flex-row justify-center py-4">
<input
type="submit"
value="SAVE"
className="bg-[#4699C2] text-white fong-bold w-24 py-3 rounded-full mx-4 font-bold hover:bg-[#026FC2] hover:shadow-lg focus:bg-[#026FC2] focus:shadow-lg focus:outline-none focus:ring-0 active:bg-[#026FC2] active:shadow-lg transition duration-150 ease-in-out"
/>
<input
onClick={() => {
setEditForm(false);
}}
type="reset"
value="CANCEL"
className="bg-[#4699C2] cursor-pointer lg:bg-white hover:bg-[#026FC2] hover:text-white hover:shadow-lg focus:bg-[#026FC2] focus:shadow-lg focus:outline-none focus:ring-0 focus:text-white active:bg-[#026FC2] active:shadow-lg transition duration-150 ease-in-out text-white lg:text-[#4699C2] lg:border lg:border-[#4699C2] fong-bold w-24 py-3 rounded-full font-bold"
/>
</div>
</form>
);
}
Answering this as community wiki, As suggested by #yograjtandel, instead of storing the response in one single state, first declare all the states like name, biography, surname, etc... to null. then in useState set all the states ex. setName(doc.data().Name).

Uncaught TypeError: DroppedItem.map is not a function ReactJS

I get the error "Uncaught TypeError: DroppedItem.map is not a function ReactJS" when trying to show a floating window on my page after opening a case. I think it has something to do with renders (https://jsramblings.com/are-you-logging-the-state-immediately-after-updating-it-heres-why-that-doesnt-work/) but I got no idea how to fix it. Please help. I use Axios to load arrays from my backend server. The "className" is from TailWind.
import {useEffect, useState, useContext} from "react"
import {useLocation} from 'react-router-dom'
import Axios from "axios";
import { AppContext } from "/src/App";
export default function CaseDetail(){
const [ContentList, setContentList] = useState([])
const [Case, setCase] = useState([])
const [ShowDrop, setShowDrop] = useState(false)
const [DroppedItem, setDroppedItem] = useState([])
const { token } = useContext(AppContext);
const { setToken } = useContext(AppContext);
const { userID } = useContext(AppContext);
const DroppedItemFunc = (array) => {
setDroppedItem(array)
}
const DropWindow = () => {
setShowDrop(!ShowDrop)
}
const MilSpecList = ContentList.filter(function(driver){
return driver.RarityName === "Mil-Spec"
})
const openCase = (CasePrice) => {
const Price = parseInt(CasePrice)
if (token >= Price){
setToken(token-Price)
const randomNumero = Math.random()* 100;
if(randomNumero <= 70)
{
const millength = MilSpecList.length
const randomItemM = Math.floor(Math.random() * (millength - 0)+ 0)
console.log(MilSpecList[randomItemM].SkinName)
console.log(MilSpecList[randomItemM].IDItem)
submitItem(MilSpecList[randomItemM].IDItem)
setDroppedItem(MilSpecList[randomItemM])
setShowDrop(!ShowDrop)
}
}else{console.log("No funds")}
}
return(
<div className="flex justify-center align-bottom">
{ShowDrop && <div className=" absolute">
{DroppedItem.map(item => (
<div className=" border-2 border-slate-500 w-[25rem] h-[30rem] p-1.5 px-2 m-2 box-content rounded-cool bg-slate-700 ">
<button
className=' border-2 border-green-700 bg-green-400 hover:bg-green-600 text-green-900 hover:text-rose-50 w-[100%] h-10 rounded-cool '
onClick={()=>{DropWindow()}}
>
Close
</button>
</div>
))}
</div>
}
<div className="bg-slate-800 rounded-cool text-white divide-y-2 w-3/5 p-3">
{Case.map(item => (
<h1 key={item.CaseName} className="text-4xl font-bold text-slate-200 py-2" >
{item.CaseName} 💼
</h1>
))}
{Case.map(item => (
<div key={item.CaseName} className="flex justify-center p-3 mt-2">
<div className=" w-[50%] p-1.5 px-2 m-2 box-content rounded-cool bg-slate-700 "
>
<div className="flex justify-center">
<img className="" src={`src/images/${item.CaseImage}`}/>
</div>
<h1 className="hover:text-orange-300">
{item.CaseName}
</h1>
<h1 className="hover:text-orange-300 basis-1/2">
{item.CasePrice} Tokens
</h1>
<div className=" flex justify-center">
<button className=" cursor-pointer text-xl text-slate-900 w-40 h-10 hover:text-orange-300 hover:bg-slate-600 hover:border-2 hover:border-white-200 bg-orange-300 rounded-cool px-2 py-0.25 transition-all"
onClick={() => {openCase(item.CasePrice)}}
>
OPEN
</button>
</div>
</div>
</div>
))}
</div>
</div>
)
}
your error is here: setDroppedItem(MilSpecList[randomItemM]) - you cannot modify the data type of your state from an array, to some other value, as the method 'map' no longer becomes available. Try to make sure you do not modify data structures, I find that prop-types (when in reactJS), typescript (when in reactTS), and eslint (in general) help me a lot to make sure I don't accidentally mutate my structure type on accident. Try also console logging the value and type of MilSpecList[randomItemM] to confirm the type is indeed an array, as well as anywhere you might be running setDroppedItem

The placeholder is not showing up at first instance because of i put value={newTodo} and i know that in first instance "newTodo" is empty. How to fix?

The "newTodo" state variable is empty in the first instance when i load the page, that's why the placeholder is not displaying, it act as if there is zero value in newTodo variable so placeholder is not showing up in the first instance, but after i enter a text then placeholder shows up.
import React, {useState} from 'react'
const ToDo = () => {
const [todo, setTodo] = useState([]);
const [newTodo, setNewTodo] = useState(" ");
let globalID = 0;
const handleSubmit = (e) => {
e.preventDefault();
setNewTodo("");
setTodo((oldTodo) => {
return [...oldTodo, newTodo]
});
}
const handleInput = (e) => {
setNewTodo(e.target.value);
}
return (
<>
<h1 className="header">Building To Do App</h1>
<section className='flex justify-center mt-8'>
<div className='border border-indigo-800 w-1/2 flex flex-col text-center h-96'>
<h2 className=' h-16 flex justify-center items-center bg-pink-600 text-white' >To-Do List</h2>
<form onSubmit={handleSubmit} className="mt-6">
<input placeholder='Add a Item' type="text" onChange={handleInput} name="todo" id="todo" value={newTodo} className='w-1/2 h-12 rounded-sm border-b-4 border-indigo-500 text-xl bg-gray-300 focus:outline-none text-black' />
<button className='bg-pink-600 rounded-full w-12 h-12 mx-4 hover:bg-green-700'>+</button>
</form>
<ol className='todoList'>
{todo.map((items) => {
return <li key={globalID++} >{items}</li>
} )}
</ol>
</div>
</section>
</>
)
}
export default ToDo
You set the default value newTodo value " " that's issue !
const [newTodo, setNewTodo] = useState();
Remove " " from useState(); solve the issue !

React: trigger onChange on checkbox input when changing checked state programmatically?

I have a ref to an <input type="checkbox"/> element, and when I programmatically set checked=false on the element, the element's onChange callback does not get called.
I tried using ref.dispatchEvent(new Event('input')) and ref.dispatchEvent(new Event('change')) and neither caused the React onChange callback to get executed.
All the questions and answers I could find on StackOverflow about this have to do with <input type="text"/> elements, none dealing with changing the checked property programmatically on an <input type="checkbox"/> element and its onChange handler not being invoked.
Here's a CodePen that demonstrates the issue:
https://codepen.io/dossy/pen/QWKVNzZ/left/?editors=0011
You can check and uncheck the checkbox, and the <div>Checked!</div> will appear and disappear as expected. However, clicking the <button>Reset</button> will uncheck the checkbox if it's checked, but since the input's onChange handler isn't being executed, the div isn't being hidden as it should be.
...
Yes, I know that I could do this as a Controlled Component but that's not the point: I have a use case where using refs is required so I must implement this as an Uncontrolled Component, and getting the onChange handler to execute when the DOM element changes is the problem I need to solve.
Thanks!
Here is the working code. working link https://codesandbox.io/s/blissful-wozniak-cc1sn?file=/src/Test.js:0-1753
import React, { useEffect, useRef } from "react";
export function Test() {
const ref_input = useRef(null);
const ref_text = useRef(null);
useEffect(() => {
ref_input.current.addEventListener("change", function (event) {
alert(event.target.checked);
});
}, []);
function triggerEvent(element, eventName) {
var event = document.createEvent("HTMLEvents");
event.initEvent(eventName, false, true);
element.dispatchEvent(event);
}
return (
<div className="h-screen flex bg-white text-gray-900 justify-center items-center">
<div className="flex items-start w-64">
<div className="flex items-center gap-4">
<button
className="inline-flex items-center px-4 py-2 border border-gray-500 rounded-md"
onClick={() => {
ref_input.current.checked = false;
triggerEvent(ref_input.current, "change");
//ref_input.dispatchEvent(new Event("input"));
//ref_input.current.dispatchEvent(new Event("onChange"));
}}
>
Reset
</button>
<div>Checkbox:</div>
<input
ref={ref_input}
type="checkbox"
className="h-4 w-4 border-gray-300 rounded"
// onChange={(e) => {
// console.log("onChange called", e.target.checked);
// e.target.checked
// ? ref_text.current.classList.remove("hidden")
// : ref_text.current.classList.add("hidden");
// }}
/>
<div ref={ref_text} className="hidden">
Checked!
</div>
</div>
</div>
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
It's better to do things the "react way".
That means, instead of manipulating dom elements with imperative code (if you're using refs, you're using imperative code), do it declaratively with state/props:
function App() {
const [checked,setChecked] = React.useState(false);
return (
<div className="h-screen flex bg-white text-gray-900 justify-center items-center">
<div className="flex items-start w-64">
<div className="flex items-center gap-4">
<button
className="inline-flex items-center px-4 py-2 border border-gray-500 rounded-md"
onClick={() => setChecked(false)}
>
Reset
</button>
<div>Checkbox:</div>
<input
type="checkbox"
checked={checked}
onChange={() => setChecked(!checked)}
className="h-4 w-4 border-gray-300 rounded"
/>
<div className={checked ? '' : 'hidden'}>
Checked!
</div>
</div>
</div>
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
Here's a link to the updated pen: https://codepen.io/zuze-lab/pen/QWKVEbY?editors=0011
EDIT: I want to be really clear, ref's aren't bad, not at all. Lots of react libraries expose refs because imperative code makes sense for those libraries APIs. When using refs to add event listeners, or imperatively manipulate elements, you're doing things wrong and you need to back up.

Categories