I am having issues trying to focus on a previous input once I delete out of the input that I am in. I have 6 inputs, all with a maxLength of 1. When I press the backspace key I would like to delete the value and move to the previous input. I have tried a variety of things but nothing seems to work how I need.
Here is my code
This is the Auth component which is passing props to verify (the page with the inputs)
const Auth = ({ sub }) => {
let params = useParams();
const navigate = useNavigate();
const [rec, setRec] = useState({
accept: false,
email: '',
phone: '',
pin: ['', '', '', '', '', '']
})
const onPaste = (event) => {
event.preventDefault()
const pasted = event.clipboardData.getData("text/plain")
setRec({ ...rec, pin: pasted.split("").slice(0, rec.pin.length) })
// target event last sibling
event.target.parentNode.lastChild.focus()
}
function update(event, index) {
event.preventDefault()
setRec({
...rec, pin: [
...rec.pin.slice(0, index),
event.target.value,
...rec.pin.slice(index + 1)
]
})
}
const handleFocus = (event) => {
if (event.target.nextSibling)
event.target.nextSibling.focus();
// if value is deleted, focus previous sibling
// if all siblings are empty, focus first sibling
if (event.target.value === '' && event.target.previousSibling === null)
event.target.parentNode.firstChild.focus()
}
const onKeyPress = (event) => {
// if backspace is clicked, go to previous input
if (event.key === 'Backspace' && event.target.value === '') {
event.target.previousSibling.focus()
}
}
const handleChange = (name, value) => {
if (name === 'mobile') name = 'phone'
const recState = {
...rec,
[name]: value,
}
setRec(recState)
}
const toggleAccept = () => {
if (!rec.accept) {
setRec({
...rec,
accept: true
});
} else {
setRec({
...rec,
accept: false
});
}
}
const handleSubmit = async () => {
const { accept, email, phone } = rec
if (params.method === 'email' && !email) return app.actions.setError('Enter an email')
else if (params.method !== 'email' && !phone) return app.actions.setError('Enter a phone number')
const send = params.method === 'email' ? { email } : { phone }
if (!accept) return app.actions.setError('Please accept the terms & conditions')
try {
await app.actions.setLoading(true)
// if there is already a user, just login, else create user first
await user.actions.login(send)
await app.actions.setLoading(false)
navigate(`/auth/verify/login/${params.method || 'phone'}`)
} catch (e) {
if (e.response && e.response.status === 404) {
// try join
await user.actions.join(send)
await app.actions.setLoading(false)
return navigate(`/auth/verify/join/${params.method || 'phone'}`)
}
await app.actions.setLoading(false)
await app.actions.setError(e)
}
}
const handleVerify = async ({ context, method }) => {
const { email, phone, pin } = rec
const joinPin = pin.join('');
if (method === 'email' && !email) return app.actions.setError('Enter an email')
else if (method !== 'email' && !phone) return app.actions.setError('Enter a phone number')
const send = method === 'email' ? { email } : { phone }
await app.actions.setError(null)
try {
await app.actions.setLoading(true)
if (context === 'login') {
await user.actions.loginVerify({
...send,
pin: joinPin,
})
} else {
await user.actions.verify({
...send,
pin: joinPin,
})
}
await app.actions.init()
await app.actions.setLoading(false)
navigate('/')
} catch (e) {
await app.actions.setLoading(false)
await app.actions.setError(e)
}
}
return (
<>
{sub ?
<Verify
context={params.context}
handleChange={handleChange}
handleFocus={handleFocus}
handleSubmit={() => handleVerify({ context: params.context, method: params.method })}
onKeyPress={onKeyPress}
method={params.method}
paste={onPaste}
update={update}
rec={rec} />
:
<Jogin
handleChange={handleChange}
handleSubmit={handleSubmit}
method={params.method}
toggleAccept={toggleAccept}
rec={rec} />
}
</>
)
}
export default Auth
and here is the verify component
import React from 'react'
import { onFormSubmit } from '../../utility/form'
import { Navigate, useNavigate } from 'react-router-dom'
import user from '../../model/user'
import app from '../../model/app'
const Verify = ({ handleSubmit, method, rec, paste, update, handleFocus, onKeyPress }) => {
const navigate = useNavigate();
const { email, phone } = rec
const send = method === 'email' ? { email: email } : { phone: phone }
const resendPin = async () => {
try {
await app.actions.setLoading(true)
// if there is already a user, just login, else create user first
await user.actions.login(send)
await app.actions.setLoading(false)
navigate(`/auth/verify/login/${method || 'phone'}`)
} catch (e) {
if (e.response && e.response.status === 404) {
// try join
await user.actions.join(send)
await app.actions.setLoading(false)
return navigate(`/auth/verify/join/${method || 'phone'}`)
}
await app.actions.setLoading(false)
await app.actions.setError(e)
}
}
const renderContent = () => {
const methodLabel = method === 'email' ? 'email address' : 'phone number'
return (
<>
<div id="back-grad" />
<div className="col-md-8">
<div className='verify-text-section'>
<p className="verify-text pb-5">
Enter the PIN number we just sent to your {methodLabel}.
</p>
</div>
<div className="flex">
{rec.pin.map((s, key) => (
<input key={key} className='code-input mr-2' value={s} maxLength='1' onPaste={(e) => paste(e)} onKeyDown={e => onKeyPress(e)} onInput={(e) => update(e, key)} onChange={(e) => handleFocus(e)} inputMode='numeric' autoFocus={key === 0} />
))}
</div>
<div className="verify-resend mt-3" onClick={resendPin}>Resend Code</div>
<div className="flex margin-set text-center">
<button className="btn btn-block text-white verify-btn">
<p className="verify-btn-text">VERIFY</p>
</button>
</div>
</div>
</>
)
}
return (
<>
{rec.email.length < 1 && rec.phone.length < 1 ? <Navigate to={'/auth'} />
:
<div className="d-flex align-items-center h-full">
<form className="container" onSubmit={e => onFormSubmit(e, handleSubmit)}>
<div className="row justify-content-center">
{renderContent()}
</div>
</form>
</div>
}
</>
)
}
export default Verify
any help is appreciated!
Related
I can't figure out how to sort tasks by the "created" value after filtering them in a React Firebase to-do list app I'm building. I've tried adding the following line to the database query but it's not working:
const q = query(collection(db, 'tasks'), where("uid", "==", user?.uid), orderBy("created"));
Here is the full code, minus the imports:
export default function Todo() {
const [name, setName] = useState("");
const [tasks, setTasks] = useState([]);
const [user, loading, error] = useAuthState(auth);
const [searchInput, setSearchInput] = useState("");
const [filteredTasks, setFilteredTasks] = useState([]);
//function for automatically retrieving items
useEffect(() => {
const q = query(collection(db, 'tasks'), where("uid", "==", user?.uid));
onSnapshot(q, (querySnapshot) => {
setTasks(querySnapshot.docs.map(doc => ({
id: doc.id,
data: doc.data()
})))
})
}, [])
//function for loading filtered tasks
useEffect(() => {
setFilteredTasks(tasks
.filter(task => task.data.name.match(searchInput))
);
}, [searchInput, tasks])
//function for getting the value of the main input
function handleChange(e) {
e.preventDefault();
setName(e.target.value);
console.log(name);
}
//function for getting the value of the search input
function handleSearchChange(e) {
e.preventDefault();
setSearchInput(e.target.value)
}
//function for adding items to firestore
const handleAdd = async (e) => {
e.preventDefault();
if (name === "") {
alert("Please enter some text");
clearInput();
return;
}
try {
await addDoc(collection(db, 'tasks'), {
name: name,
completed: false,
created: Timestamp.now(),
uid: user?.uid,
})
clearInput();
} catch (err) {
alert(err)
}
}
//function for adding strikethrough to an item
function handleClick(e) {
if (e.detail === 2) {
console.log("double click");
e.currentTarget.classList.toggle('double-clicked');
}
}
//function for updating an item
const handleUpdate = async (taskName, id) => {
let name = prompt("Please enter a new name", taskName);
if (name === null) {
return;
}
const taskDocRef = doc(db, 'tasks', id)
try {
await updateDoc(taskDocRef, {
name: name,
})
clearInput();
} catch (err) {
alert(err)
}
}
//function for deleting an item
const handleDelete = async (id) => {
console.log(id);
const taskDocRef = doc(db, 'tasks', id)
try {
await deleteDoc(taskDocRef)
clearInput();
} catch (err) {
alert(err)
}
}
//function for clearing and focusing the input
function clearInput() {
let input = document.querySelector("input");
input.value = '';
input.focus();
setName("");
}
return (
<div>
<div>
<div>
<h1>To Do List App</h1>
<p>Double click to mark an item off.</p>
</div>
<input
id="input"
type="text"
value={name}
onChange={handleChange}
autoFocus
/>
<button
className="add-button"
type="submit"
onClick={handleAdd}
>
<IoMdAddCircle />
</button>
</div>
<ol>
{filteredTasks.map(task => (
<li
className="task-list-items"
id={task.id}
key={task.id}
completed={task.data.completed}
onClick={handleClick}
>
{task.data.name}
<button
className="edit-button"
onClick={() => handleUpdate(task.data.name, task.id)}
>
<BsFillPencilFill />
</button>
<button
className="delete-button"
onClick={() => handleDelete(task.id)}
>
<BsFillTrashFill />
</button>
</li>
))}
</ol>
<div>
<h5>Search for an item by name</h5>
<input
id="search-bar"
type="text"
value={searchInput}
onChange={handleSearchChange}
/>
</div>
</div>
);
};
When I add new tasks they are appearing in the wrong order even if they are not filtered.
This is what the database looks like:
I figured out how to sort the tasks after filtering. Here is my updated code:
export default function Todo() {
const [name, setName] = useState("");
const [tasks, setTasks] = useState([]);
const [user, loading, error] = useAuthState(auth);
const [searchInput, setSearchInput] = useState("");
const [filteredTasks, setFilteredTasks] = useState([]);
//function for automatically retrieving items
useEffect(() => {
const q = query(collection(db, 'tasks'), where("uid", "==", user?.uid));
onSnapshot(q, (querySnapshot) => {
setTasks(querySnapshot.docs.map(doc => ({
id: doc.id,
data: doc.data()
})))
})
}, [])
//function for loading filtered tasks
useEffect(() => {
setFilteredTasks(tasks
.filter(task => task.data.name.match(searchInput))
);
}, [searchInput, tasks])
//function for getting the value of the main input
function handleChange(e) {
e.preventDefault();
setName(e.target.value);
console.log(name);
}
//function for getting the value of the search input
function handleSearchChange(e) {
e.preventDefault();
setSearchInput(e.target.value)
}
//function for adding items to firestore
const handleAdd = async (e) => {
e.preventDefault();
if (name === "") {
alert("Please enter some text");
clearInput();
return;
}
try {
await addDoc(collection(db, 'tasks'), {
name: name,
completed: false,
created: Timestamp.now(),
uid: user?.uid,
})
clearInput();
} catch (err) {
alert(err)
}
}
//function for adding strikethrough to an item
function handleClick(e) {
if (e.detail === 2) {
console.log("double click");
e.currentTarget.classList.toggle('double-clicked');
}
}
//function for updating an item
const handleUpdate = async (taskName, id) => {
let name = prompt("Please enter a new name", taskName);
if (name === null) {
return;
}
const taskDocRef = doc(db, 'tasks', id)
try {
await updateDoc(taskDocRef, {
name: name,
})
clearInput();
} catch (err) {
alert(err)
}
}
//function for deleting an item
const handleDelete = async (id) => {
console.log(id);
const taskDocRef = doc(db, 'tasks', id)
try {
await deleteDoc(taskDocRef)
clearInput();
} catch (err) {
alert(err)
}
}
//function for clearing and focusing the input
function clearInput() {
let input = document.querySelector("input");
input.value = '';
input.focus();
setName("");
}
//function for sorting tasks after they are filtered
function sortTasks(array) {
array.sort((a, b) => (a.data.created < b.data.created ? -1 : 1));
return array;
}
return (
<div>
<div>
<div>
<h1>To Do List App</h1>
<p>Double click to mark an item off.</p>
</div>
<input
id="input"
type="text"
value={name}
onChange={handleChange}
autoFocus
/>
<button
className="add-button"
type="submit"
onClick={handleAdd}
>
<IoMdAddCircle />
</button>
</div>
<ol>
{sortTasks(filteredTasks).map(task => (
<li
className="task-list-items"
id={task.id}
key={task.id}
completed={task.data.completed}
onClick={handleClick}
>
{task.data.name}
<button
className="edit-button"
onClick={() => handleUpdate(task.data.name, task.id)}
>
<BsFillPencilFill />
</button>
<button
className="delete-button"
onClick={() => handleDelete(task.id)}
>
<BsFillTrashFill />
</button>
</li>
))}
</ol>
<div>
<h5>Search for an item by name</h5>
<input
id="search-bar"
type="text"
value={searchInput}
onChange={handleSearchChange}
/>
</div>
</div>
);
};
My code keeps bringing torusPlugin is assigned a value but never used, LoggedInView is assigned a value but never used and I don't know where I went wrong. I have checked the code and the torusPlugin function was used, same with the loggedInView.
import { useEffect, useState } from "react";
import { Card, Form } from "react-bootstrap";
import { FaComment, FaRecycle, FaRetweet, FaThumbsUp } from "react-icons/fa";
import { Web3AuthCore } from "#web3auth/core";
import {
WALLET_ADAPTERS,
CHAIN_NAMESPACES,
SafeEventEmitterProvider,
} from "#web3auth/base";
import { OpenloginAdapter } from "#web3auth/openlogin-adapter";
import { TorusWalletConnectorPlugin } from "#web3auth/torus-wallet-connector-plugin";
import Twitter from "./twitter";
import RPC from "./evm";
import { APP_CONSTANTS } from "./constants";
import "./App.css";
import { ToastContainer, toast } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
const clientId = APP_CONSTANTS.CLIENT_ID; // get from https://dashboard.web3auth.io
function App() {
const [web3auth, setWeb3auth] = useState<Web3AuthCore | null>(null);
const [provider, setProvider] = useState<SafeEventEmitterProvider | null>(
null
);
const [tweets, setTweets] = useState<Array<any> | null>(null);
const [comment, setComment] = useState<string | "">("");
const [userName, setUserName] = useState<string | "">("");
const [profileImage, setProfileImage] = useState<string | "">("");
const [newTweetName, setNewTweetName] = useState<string | "">("");
const [newTweetDescription, setNewTweetDescription] = useState<string | "">(
""
);
const refreshTime = APP_CONSTANTS.REACT_APP_REFRESH_TIMER * 1000
const [torusPlugin, setTorusPlugin] =
useState<TorusWalletConnectorPlugin | null>(null);
useEffect(() => {
const init = async () => {
try {
const web3auth = new Web3AuthCore({
clientId,
chainConfig: {
chainNamespace: CHAIN_NAMESPACES.EIP155,
chainId: "0x13881",
rpcTarget: APP_CONSTANTS.RPC_TARGET, // This is the mainnet RPC we have added, please pass on your own endpoint while creating an app
},
});
const openloginAdapter = new OpenloginAdapter({
adapterSettings: {
clientId,
network: "testnet",
uxMode: "popup",
whiteLabel: {
name: "Twitter DApp",
logoLight: APP_CONSTANTS.APP_LOGO,
logoDark: APP_CONSTANTS.APP_LOGO,
defaultLanguage: "en",
dark: true, // whether to enable dark mode. defaultValue: false
},
loginConfig: {
// Add login configs corresponding to the providers on modal
// Twitter login
jwt: {
name: "Custom Auth Login",
verifier: APP_CONSTANTS.ADAPTER_TWITTER_CLIENT_VERIFIER, // Please create a verifier on the developer dashboard and pass the name here
typeOfLogin: "twitter", // Pass on the login provider of the verifier you've created
clientId: APP_CONSTANTS.ADAPTER_TWITTER_CLIENT_ID, // Pass on the clientId of the login provider here - Please note this differs from the Web3Auth ClientID. This is the JWT Client ID
},
// Add other login providers here
},
},
});
const torusPlugin = new TorusWalletConnectorPlugin({
torusWalletOpts: {},
walletInitOptions: {
whiteLabel: {
theme: { isDark: true, colors: { primary: "#ffffff" } },
logoDark:
"https://i.ibb.co/kDNCfC9/reshot-icon-wallet-9-H3-QMSDLFR.png",
logoLight:
"https://i.ibb.co/kDNCfC9/reshot-icon-wallet-9-H3-QMSDLFR.png",
},
useWalletConnect: true,
enableLogging: true,
},
});
await web3auth.addPlugin(torusPlugin);
setTorusPlugin(torusPlugin);
await web3auth.configureAdapter(openloginAdapter);
setWeb3auth(web3auth);
await web3auth.init();
if (web3auth.provider) {
await setProvider(web3auth.provider);
let user = await web3auth.getUserInfo();
console.log('user ', user)
if(user.name && user.name !== null && user.name !== " " && user.name !== "")
setUserName(user.name)
if(user.profileImage && user.profileImage !== null && user.profileImage !== " " && user.profileImage !== "")
setProfileImage(user.profileImage)
}
await fetchAllTweets();
//eslint-disable-next-line react-hooks/exhaustive-deps
} catch (error) {
console.error(error);
}
};
init();
}, []);
const logout = async () => {
if (!web3auth) {
console.log("web3auth not initialized yet");
return;
}
await web3auth.logout();
setProvider(null);
};
const login = async () => {
if (!web3auth) {
console.log("web3auth not initialized yet");
return;
}
const web3authProvider = await web3auth.connectTo(
WALLET_ADAPTERS.OPENLOGIN,
{
loginProvider: "jwt",
extraLoginOptions: {
domain: APP_CONSTANTS.AUTH0_DOMAIN, // Please append "https://" before your domain
verifierIdField: "sub",
},
}
);
setProvider(web3authProvider);
if(web3authProvider){
let user = await web3auth.getUserInfo();
if(user.name && user.name !== null && user.name !== " " && user.name !== "")
setUserName(user.name)
if(user.profileImage && user.profileImage !== null && user.profileImage !== " " && user.profileImage !== "")
setProfileImage(user.profileImage)
}
};
/*
const getAccounts = async () => {
if (!provider) {
console.log("provider not initialized yet");
return;
}
const rpc = new RPC(provider);
const userAccount = await rpc.getAccounts();
return userAccount;
};
*/
const refresh = (e: any) => {
e.preventDefault();
fetchAllTweets();
};
const fetchAllTweets = async () => {
console.log("fetchalltweetsrunning");
if (!provider) {
console.log("provider not initialized yet");
return;
}
const rpc = new RPC(provider);
try {
let fetchedTweets = await rpc.getAllTweets();
let tweets = [...fetchedTweets];
setTweets(tweets.reverse());
} catch (error) {
console.log("error in fetching tweets", error);
}
};
const upVote = async (tweetIndex: any) => {
if (!provider) {
console.log("provider not initialized yet");
return;
}
try {
const rpc = new RPC(provider);
await rpc.sendUpVoteTransaction(tweetIndex);
fetchAllTweets();
} catch (error) {
console.log("failed to execute upvote transaction", error);
}
};
const addNewTweet = (e: any) => {
e.preventDefault();
if (!provider) {
console.log("provider not initialized yet");
return;
}
try {
const rpc = new RPC(provider);
toast.success("Tweet added successfully", {
position: toast.POSITION.TOP_CENTER,
});
rpc.sendWriteTweetTransaction(newTweetName, newTweetDescription);
setTimeout(function () {
fetchAllTweets();
}, refreshTime);
fetchAllTweets();
} catch (error) {
toast.error("Something went wrong", {
position: toast.POSITION.TOP_LEFT,
});
console.log("failed to execute new tweet transaction", error);
}
};
const addComment = async (event: any, tweetIndex: any) => {
event.preventDefault();
if (!provider) {
console.log("provider not initialized yet");
return;
}
try {
const rpc = new RPC(provider);
toast.success("Comment added successfully - refresh after 30 sec", {
position: toast.POSITION.TOP_CENTER,
});
await rpc.sendAddCommentTransaction(tweetIndex, comment);
fetchAllTweets();
} catch (error) {
toast.error("Something went wrong", {
position: toast.POSITION.TOP_LEFT,
});
console.log("failed to execute add comment transaction", error);
}
};
// Event handlers
const handleCommentChange = async (event: any) => {
setComment(event.target.value);
};
const handleNewTweetNameChange = async (event: any) => {
setNewTweetName(event.target.value);
};
const handleNewTweetDescriptionChange = async (event: any) => {
setNewTweetDescription(event.target.value);
};
const loggedInView = (
<>
<button className="button" onClick={logout}>
Logout
</button>
<div>
<h1>New Tweet</h1>
<Card>
<Card.Body>
<Card.Title>What are you thinking? Tweet it out!</Card.Title>
<Card.Text></Card.Text>
<Form.Control
as="input"
onChange={handleNewTweetNameChange}
placeholder="Tweet Name"
/>
<br></br>
<br></br>
<Form.Control
as="textarea"
onChange={handleNewTweetDescriptionChange}
placeholder="Description"
/>
<br></br>
<FaRetweet onClick={addNewTweet} />
</Card.Body>
</Card>
</div>
<div>
<h1>
All Tweets <FaRecycle onClick={fetchAllTweets} />
</h1>
{(tweets || []).map((tweet: any, i) => (
<div key={i}>
<div>
<Card>
<Card.Body>
<Card.Title>
<FaThumbsUp onClick={(event) => upVote(i)} /> {tweet.name}
</Card.Title>
<p>Total Upvotes: {tweet.upvotes}</p>
<p>Tweeted by: {tweet.fromAddress}</p>
<Card.Text>{tweet.description}</Card.Text>
<div>
<h3>All Comments</h3>
{tweet.comments.map((comment: any, j: any) => (
<div key={j}>
Comment {j + 1}: {comment}
</div>
))}
<h3>New Comment</h3>
<span>
<Form.Control
as="input"
onChange={handleCommentChange}
placeholder="Your comment..."
/>
</span>
<span>
<FaComment onClick={(event) => addComment(event, i)} />
</span>
</div>
</Card.Body>
<a
href={
APP_CONSTANTS.OPENSEA_ASSETS_URL +
"/" +
APP_CONSTANTS.CONTRACT_ADDRESS +
"/" +
i
}
rel="opener"
>
Buy Now
</a>
</Card>
</div>
</div>
))}
</div>
<div></div>
<div id="console" style={{ whiteSpace: "pre-line" }}>
<p style={{ whiteSpace: "pre-line" }}></p>
</div>
</>
);
const unloggedInView = (
<>
<div className="login-account">
<button className="twitter-bg btn" onClick={login}>
<img src="images/twitter-white.png" alt=""></img>
Login to your Twitter account
</button>
</div>
</>
);
return (
<div className="grid">
{provider ? (
<Twitter
logoutButton={logout}
handleNewTweetDescriptionChange={handleNewTweetDescriptionChange}
handleNewTweetNameChange={handleNewTweetNameChange}
addNewTweet={addNewTweet}
fetchAllTweets={fetchAllTweets}
tweets={tweets}
upVote={upVote}
handleCommentChange={handleCommentChange}
addComment={addComment}
refresh={refresh}
username={userName}
profileimage={profileImage}
/>
) : (
unloggedInView
)}{" "}
<ToastContainer />
</div>
// <div className="grid">{provider
// ? loggedInView
// : unloggedInView}</div>
// {/* <div className="grid">{loggedInView}</div> */}
);
}
export default App;
enter image description here
this is a screenshot of my code terminal
You are actually not using torusPlugin (line 38 screenshot) anywhere. Inside your useEffect you created a new const torusPlugin and then you are calling that const instead of the [torusPlugin, ...] state you declared on line 38 of the screenshot. Try to differentiate your variables / constants names otherwise would be difficult for you to identify mistakes.
I have this Formik form for authentication which can switch from login (by default) to signup form using switch button. Using Formik and yup validation schema, the form first check the fields' values on change and before submitting. On submit, I am trying to have another value check from the server side and to show error messages on top of each field (at the same place as the client side error messages provided by Formik and Yup). When the 'submit' button is clicked and in case of a server error after validation, the state of my Redux 'authReducer' is updated with 'errors'' object inside 'data' property. Through local state, then I create a variable 'errorServer' which is an object created to store for each input (as key), the related server error's message (as value). Then, using this object, I'm trying to show the error message(s) on top of field(s) concerned (I tried both Formik methods 'setErrors' and 'setStatus' to do so). But, in the best case, this error message is only appearing after a second click on the 'submit' button and I don't know how to solve this problem... Can anyone provide me some help? Many thanks!
Server side
auth.controller.js
const { findUserPerUsername, findUserPerEmail} = require('../queries/users.queries');
const { createUser } = require('../queries/users.queries');
const { check, validationResult } = require('express-validator');
// Field value validation before signup
exports.validate = (method) => {
switch (method) {
case 'signup': {
return [
check('name')
.exists()
.withMessage('Enter your name.'),
check('username')
.exists()
.withMessage('Enter a username.')
.isAlphanumeric()
.withMessage('Username must contain only letters and numbers.')
.custom(value => {
return findUserPerUsername(value).then(user => {
if (user) {
return Promise.reject('Username already in use.');
}
})
}),
check('email')
.exists()
.withMessage('Enter an email address.')
.isEmail()
.withMessage('A valid email address is required.')
.custom(value => {
return findUserPerEmail(value).then(user => {
if (user) {
return Promise.reject('Email address already in use.');
}
})
}),
check('password')
.exists()
.withMessage('Enter a password.')
.custom(value => {
const pswRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[##$!%*?&-])[A-Za-z\d##$!%*?&-]{8,}$/;
if (pswRegex.test(value) === false) {
return Promise.reject('Password must contain 8 Characters, One Uppercase, One Lowercase, One Number and One Special Case Character')
} else {
return Promise.resolve();
}
})
]
}
case 'login': {
return [
check('email')
.exists()
.withMessage('Enter an email address.')
.isEmail()
.withMessage('A valid email address is required'),
check('password')
.exists()
.withMessage('Enter a password.')
.custom(value => {
const pswRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[##$!%*?&-])[A-Za-z\d##$!%*?&-]{8,}$/;
if (pswRegex.test(value) === false) {
return Promise.reject('Password must contain 8 Characters, One Uppercase, One Lowercase, One Number and One Special Case Character')
} else {
return Promise.resolve();
}
})
]
}
}
}
exports.signup = async (req, res, next) => {
try {
const body = req.body;
const errors = validationResult(req);
if (!errors.isEmpty()) {
res.status(400).send({
success: false,
errors: errors.array()
});
next();
} else {
const user = await createUser(body);
req.login(user);
res.status(200).send({
user: user,
success: true,
message: 'User successfully registered.'
})
next();
}
} catch(e) {
next(e);
}
}
exports.login = async (req, res, next) => {
try {
const errors = validationResult(req);
if (!errors.isEmpty()) {
res.status(400).send({
success: false,
errors: errors.array()
});
next();
} else {
const { email, password } = req.body;
const user = await findUserPerEmail(email);
if (user) {
const match = await user.comparePassword(password);
if (match) {
req.login(user);
res.status(200).send({
user: user,
success: true,
message: 'User successfully logged in.'
});
next();
} else {
res.status(401).send({
success: false,
errors: {
msg: 'Sorry, that password is not right. Please try again or reset your password.',
param: 'password',
location: 'body'
}
});
next();
}
} else {
res.status(401).send({
success: false,
errors: {
msg: 'Sorry, we could not find an account with that email. Please try again or sign up.',
param: 'email',
location: 'body'
}
});
next();
}
}
} catch(e) {
next(e);
}
}
exports.logout = (req, res, next) => {
req.logout();
res.status(200).send({
success: true,
message: "User successfully logged out."
})
next();
}
Client side
auth.config.js
import axios from 'axios';
const API_URL = 'http://localhost:3001/api/auth/'
const signup = ({ name, username, email, password }) => {
return axios.post(API_URL + 'signup', {
name,
username,
email,
password
});
};
const login = ({ email, password }) => {
return axios
.post(API_URL + 'login', {
email,
password
})
.then((res) => {
return res.data;
});
};
const logout = () => {
return axios.get(API_URL + 'logout').then((res) => {
return res.data;
});
};
const getCurrentUser = () => {
/* return JSON.parse(localStorage.getItem('user')); */
}
const AuthConfig = {
signup,
login,
logout,
getCurrentUser
}
export default AuthConfig;
Redux store:
actions.js
import AuthConfig from '../config/auth.config';
export const SIGNUP_SUBMIT = "signup submit";
export const SIGNUP_SUCCESS = "signup success";
export const SIGNUP_ERROR = "signup error";
export const LOGIN_SUBMIT = "login submit";
export const LOGIN_SUCCESS = "login success";
export const LOGIN_ERROR = "login error";
export const LOGOUT = "logout";
export const signup = (name, username, email, password) => (dispatch) => {
return AuthConfig.signup(name, username, email, password).then(
(response) => {
dispatch({
type: SIGNUP_SUCCESS,
payload: response.data
});
},
(error) => {
dispatch({
type: SIGNUP_ERROR,
error: error.response.data
});
}
);
};
export const login = (email, password) => (dispatch) => {
return AuthConfig.login(email, password).then(
(response) => {
dispatch({
type: LOGIN_SUCCESS,
payload: response.data
});
},
(error) => {
dispatch({
type: LOGIN_ERROR,
error: error.response.data
});
}
);
};
export const logout = () => (dispatch) => {
AuthConfig.logout().then(
(response) => {
dispatch({
type: LOGOUT,
payload: response.message
})
}
);
};
reducers.js
import {
SIGNUP_SUBMIT,
SIGNUP_SUCCESS,
SIGNUP_ERROR,
LOGIN_SUBMIT,
LOGIN_SUCCESS,
LOGIN_ERROR,
LOGOUT
} from "./actions";
const initialState = { isLoggedIn: false, data: {} }
export const authReducer = (state = initialState, action) => {
const { type, payload, error } = action;
switch (type) {
case SIGNUP_SUBMIT:
return {
...state,
isLoggedIn: false,
};
case SIGNUP_SUCCESS:
return {
...state,
isLoggedIn: true,
data: payload
};
case SIGNUP_ERROR:
return {
...state,
isLoggedIn: false,
data: error
};
case LOGIN_SUBMIT:
return {
...state,
isLoggedIn: false,
};
case LOGIN_SUCCESS:
return {
...state,
isLoggedIn: true,
data: payload
};
case LOGIN_ERROR:
return {
...state,
isLoggedIn: false,
data: error
};
case LOGOUT:
return {
...state,
isLoggedIn: false,
data: null
};
default: {
return state;
}
}
};
export default authReducer;
Form.js
import React, { useState, useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useNavigate } from 'react-router-dom';
import { Formik, useFormikContext } from 'formik';
import * as Yup from 'yup';
/* import { SignupSchema, LoginSchema /* YupSchema * } from './YupSchema'; */
import Input from './Input';
import { signup, login } from '../../store/actions';
import { Eye, EyeSlash, Lock, MedalStar, Personalcard, Sms, User } from 'iconsax-react';
import './Form.css';
const Logger = () => {
const formik = useFormikContext();
useEffect(() => {
console.group("Formik State");
console.log("status", formik.status);
console.log("values", formik.values);
console.log("errors", formik.errors);
console.log("touched", formik.touched);
console.log("isSubmitting", formik.isSubmitting);
console.log("isValidating", formik.isValidating);
console.log("submitCount", formik.submitCount);
console.groupEnd();
}, [
formik.status,
formik.values,
formik.errors,
formik.touched,
formik.isSubmitting,
formik.isValidating,
formik.submitCount
]);
return null;
};
const ServerValidation = () => {
const { values, /* submitForm, */ } = useFormikContext();
useEffect(() => {
if (values.username === 'JDope99' || values.email === 'jdoe#gmail.com') {
/* submitForm(); */
/* console.log('Username or email already in use.'); */
/* alert(JSON.stringify('Username or email already in use.')); */
}
}, [values]);
return null;
};
const Form = () => {
const authServerErrors = useSelector(state => state.authReducer.data.errors);
console.log(authServerErrors);
const [errorServer, setErrorServer] = useState('');
console.log(errorServer);
const [isSignupForm, setIsSignupForm] = useState(false);
const [showPassword, setShowPassword] = useState(false);
const handleShowPassword = () => setShowPassword(!showPassword);
useEffect(() => {
if (authServerErrors === undefined) {
return
} else {
console.log(authServerErrors);
if (Array.isArray(authServerErrors)) {
let fields = authServerErrors.map(el => el.param);
let msgs = authServerErrors.map(el => el.msg);
if (fields !== [] && msgs !== []) {
let errorObj;
fields.reduce(function(obj, key, index) {
obj[key] = msgs[index]
return errorObj = obj
}, {});
setErrorServer(errorObj);
};
} else {
let field = authServerErrors.param;
let msg = authServerErrors.msg;
let error = {};
error[field] = msg;
setErrorServer(error);
}
}
}, [authServerErrors]);
const switchForm = () => {
setIsSignupForm(!isSignupForm);
setShowPassword(false);
}
const initialValues = {
name: '',
username: '',
email: '',
password: ''
}
const dispatch = useDispatch();
const navigate = useNavigate(); // Will be used to redirect user in case of no server error to /Home
const YupSchema = Yup.object().shape({
name: Yup.string().when('isSignupForm', {
is: true,
then: Yup
.string()
.min(2, 'Too short')
.max(20, 'Too long')
.required('Required'),
otherwise: Yup.string().min(2, 'Too short').max(20, 'Too long')
}),
username: Yup.string().when('isSignupForm', {
is: true,
then: Yup
.string()
.min(2, 'Too short')
.max(20, 'Too long')
.required('Required'),
otherwise: Yup.string().min(2, 'Too short').max(20, 'Too long')
}),
email: Yup
.string()
.email('Valid email required')
.required('Required'),
password: Yup
.string()
.matches(
/^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!##\$%\^&\*])(?=.{8,})/,
"Must Contain 8 Characters, One Uppercase, One Lowercase, One Number and One Special Case Character"
)
.required('Required')
})
return (
<div className="container">
<Formik
initialValues={ initialValues }
validationSchema= { YupSchema }
onSubmit={async (values, actions) => {
try {
if (isSignupForm) {
await dispatch(signup(values));
/* errors.setErrors({
name: errorServer.name,
username: errorServer.username,
email: errorServer.email,
password: errorServer.password
})
console.log(errors); */
} else {
await dispatch(login(values));
}
actions.setStatus({
name: errorServer.name || '',
username: errorServer.username || '',
email: errorServer.email || '',
password: errorServer.password || ''
});
} catch (error) {
console.log(error);
}
}}
>
{({ handleChange, handleBlur, handleReset, handleSubmit, values, errors, status, isValid, dirty }) => (
<form
id="auth-form"
className="d-flex flex-column justify-content-start align-items-start py-3"
onChange={ handleChange }
onBlur={ handleBlur }
onReset={ handleReset }
onSubmit= { handleSubmit }
>
<Logger />
{ isSignupForm ? (
<>
<h2>Sign Up</h2>
<p className="text-lg">Welcome to our website! We hope you will enjoy being part of our community.</p>
<div className="input-box mb-3 w-100">
<Input
label="Name"
type="text"
name="name"
className="form-control"
helpText="Please enter your full name."
icon={<Personalcard size="20" color="#666664" variant="Bold" /* className="input-icon" */ />}
errors={errors.name}
value={values.name}
status={ status !== undefined ? status.name : ''}
/>
</div>
<div className="input-box mb-3 w-100">
<Input
label="Username"
type="text"
name="username"
className="form-control"
helpText="Must contain only letters and numbers"
icon={<User size="20" color="#666664" variant="Bold" /* className="input-icon" */ />}
errors={errors.username}
value={values.username}
status={ status !== undefined ? status.username : ''}
/>
</div>
</>
) : (
<>
<h2>Log In</h2>
<p className="text-lg">We are pleased to have you back.</p>
</>
)}
<div className="input-box mb-3 w-100">
<Input
label="Email"
type="text"
name="email"
className="form-control"
helpText="example#email.com"
icon={<Sms size="20" color="#666664" variant="Bold" /* className="input-icon" */ />}
errors={errors.email}
value={values.email}
status={ status !== undefined ? status.email : ''}
/>
</div>
<div className="input-box mb-3 w-100">
<Input
label="Password"
type={ showPassword ? "text" : "password"}
name="password"
/* className="form-control" */
helpText="Must Contain 8 Characters, One Uppercase, One Lowercase, One Number and One Special Case Character"
icon={<Lock size="20" color="#666664" variant="Bold" /* className="input-icon" */ />}
toggle={ showPassword ? <Eye size="20" color="#000000" className="toggle-icon" onClick={ handleShowPassword } /> : <EyeSlash size="20" color="#000000" className="toggle-icon" onClick={ handleShowPassword } /> }
errors={errors.password}
value={values.password}
status={ status !== undefined ? status.password : ''}
/>
</div>
{ isSignupForm ? (
<div className="mt-1">
<p>By clicking "Sign Up" you agree to our Customer Terms and Privacy Policy.</p>
</div>
) : null}
<div className="d-flex flex-row mt-1">
<button type="submit" className="btn btn-green me-3" disabled={isSignupForm ? !(isValid && dirty) : null }>
{ isSignupForm ? 'Sign Up' : 'Log In' }
</button>
<button type="reset" className="btn btn-outline-black">
Reset
</button>
</div>
<div className="mt-2">
{ isSignupForm ? (
<p>Already have an account? <button type="button" className="link" onClick={() => { switchForm(); handleReset() } }>Log In</button></p>
)
:
(
<p>New to InkIt? <button type="button" className="link" onClick={() => { switchForm(); handleReset() } }>Sign Up</button></p>
)
}
</div>
<ServerValidation />
</form>
)}
</Formik>
</div>
);
};
export default Form;
Input.js
import React, { useState } from 'react';
import { useField } from 'formik';
import { TickSquare } from 'iconsax-react';
const Input = ({ label, helpText, icon, toggle, errors, status, ...props}) => {
const [field, meta, helpers] = useField(props);
console.log({ field, meta, helpers });
// Show inline feedback if EITHER
// - the input is focused AND value is longer than 2 characters
// - or, the has been visited (touched === true)
const [didFocus, setDidFocus] = useState(false);
const handleFocus = () => setDidFocus(true);
const showInputFeedback = (!!didFocus && field.value.trim().length > 2) || meta.touched;
return (
<div className={`form-control px-3 py-2 ${showInputFeedback ? (meta.error ? 'invalid' : 'valid') : ""}`}>
<div className="label-box d-flex flex-row justify-content-between align-items-center">
<label htmlFor={props.name}>{label}</label>
{showInputFeedback ? (
<div className="feedback text-sm">
{ meta.error ? meta.error : status ? status : <TickSquare size="20" color="#06d6a0" variant="Bold" />}
</div>
)
: null}
</div>
<div className="d-flex flex-row flex-nowrap">
<span className="input-icon icon-left">
{icon}
</span>
<input className={ toggle ? "form-control input-password" : "form-control" } autoComplete="off" onFocus={ handleFocus } {...field} {...props} />
{toggle ? (
<span className="input-icon icon-right">
{toggle}
</span>
)
: null
}
</div>
<div className="help-text text-xs">
{helpText}
</div>
</div>
);
}
export default Input;
Chrome console.log:
enter image description here
After obtaining a data, I take a photo of the user from the users table with the user's ids, I can access all the data without any problem and I can see all of them with console.log. But it doesn't render.
How can i solve this problem ? Thank you.
export default function MentionInput(props) {
const [state, dispatch] = useContext(UsePeopleModuleContext);
const [decisionUserRole, setDecisionUserRole] = useContext(
UseDecisionUserRoleContext
);
const { isConsulted, isResponsible, isApprover, isCreator } =
decisionUserRole;
const { currentUser } = useAuth();
const { users, currentWorkspace, activeDecision, setActiveDecision } =
useWorkspace();
const [value, setValue] = useState([]);
const [loading, setLoading] = useState(true);
const userMentionData = users.map((myUser) => ({
id: myUser.userId,
value: myUser.userId,
displayName: `${myUser.displayName}`,
label: `${myUser.displayName}`,
}));
const propsData = props;
useEffect(async () => {
setLoading(true);
const people = await usersModule.getPeople(
currentWorkspace,
activeDecision.id
);
const datas = [];
if (propsData.field === 'consulted') {
people.consulted.map((consulted) => {
console.log('CONSULTED =>', consulted);
userdb.getUser(consulted.id).then((user) => {
const photoURL = user.photoURL;
datas.push({
id: consulted.id,
value: consulted.id,
displayName: `${user.displayName}`,
label: `${user.displayName}`,
photoURL: photoURL !== undefined ? photoURL : '',
});
});
});
console.log('DATAS =>', datas);
setValue(datas);
setLoading(false);
} else if (propsData.field === 'approver') {
const user = await userdb.getUser(people.approver.id);
const photoURL = user.photoURL;
setValue([{ ...people.approver, photoURL: photoURL }]);
setLoading(false);
} else if (propsData.field === 'responsible') {
//await userdb.getUser
const user = await userdb.getUser(people.responsible.id);
const photoURL = user.photoURL;
setValue([{ ...people.responsible, photoURL: photoURL }]);
setLoading(false);
}
}, []);
return (
!loading &&
(isCreator || !isResponsible ? (
<Box w="100%">
<Select
isMulti={
props.field === 'consulted'
? true
: props.field === 'approver'
? false
: props.field === 'responsible'
? false
: true
}
name=""
options={userMentionData}
placeholder="Assign people"
closeMenuOnSelect={false}
size="sm"
onChange={handleChange}
value={value}
/>
</Box>
) : (
<Box w="100%" ml="10px">
<Wrap>
{value?.map((item, index) =>
item !== undefined ? (
<WrapItem key={index}>
<Tag
size="md"
borderRadius="lg"
ml={index === 0 ? 0 : 2}
colorScheme="gray"
>
<Avatar
size="xs"
name={
item?.displayName !== undefined &&
item?.displayName.toLowerCase()
}
ml={-3}
mr={2}
src={item.photoURL}
/>
<TagLabel>{item?.displayName}</TagLabel>
</Tag>
</WrapItem>
) : null
)}
</Wrap>
</Box>
))
);
}
userdb.getUser: it works beautifully.
getUser: async (userId) => {
const q = query(doc(db, 'user', userId));
const getData = await getDoc(q);
if (getData.exists()) {
return getData.data();
}
},
As you can see from the screenshots, the data comes out fine, but the component does not render.
i update my setState function , the question solved.
if (propsData.field === 'consulted') {
const datas = [];
people.consulted.map((consulted) => {
userdb.getUser(consulted.id).then((user) => {
const photoURL = user.photoURL;
setValue((value) => [
...value,
{
id: consulted.id,
value: consulted.id,
displayName: `${user.displayName}`,
label: `${user.displayName}`,
photoURL: photoURL !== undefined ? photoURL : '',
},
]);
});
});
setLoading(false);
}
I cannot figure out how to add 'active' into the state of users.
For the sake of posting this here I hardcoded some users in the state, but they're supposed to be fetched from an API - and this doesn't come with 'active'. I need to be able to mark the checkboxes so that the specific user becomes active, also if active - it has to stay active when doing searches in the list through the text-input, so it doesn't reset. With what I wrote I am getting undefined for user.active. Any suggestions?
App.js
import './App.css';
import UserList from './components/UserList';
import './style/style.css';
function App() {
return (
<div className="App">
<UserList />
</div>
);
}
export default App;
UserList.js
import React, { useState, useEffect } from 'react';
function UserList() {
const [users, setUsers] = useState([
{
id: 681,
first_name: 'James',
last_name: 'Smith',
email: 'example1',
gender: 'm',
},
{
id: 3123,
first_name: 'Richard',
last_name: 'Greene',
email: 'example2',
gender: 'm',
},
{
id: 512,
first_name: 'Denise',
last_name: 'Bank',
email: 'example3',
gender: 'f',
},
{
id: 654,
first_name: 'Carl',
last_name: 'Ross',
email: 'example4',
gender: 'm',
},
]);
const [search, setSearch] = useState('');
const [filteredUsers, setFilteredUsers] = useState();
const [checkedUsers, setCheckedUsers] = useState('');
useEffect(() => {
const fetchUsers = async () => {
try {
const result = await fetch(users);
result.sort(function (a, b) {
if (a.last_name.toLowerCase() < b.last_name.toLowerCase()) return -1;
if (a.last_name.toLowerCase() > b.last_name.toLowerCase()) return 1;
else return;
});
setUsers(result);
} catch (error) {
console.log(error);
}
};
fetchUsers();
}, []);
useEffect(() => {
setFilteredUsers(
users.filter(
(user) =>
user.first_name.toLowerCase().includes(search.toLowerCase()) ||
user.last_name.toLowerCase().includes(search.toLowerCase())
)
);
}, [users, search]);
useEffect(() => {
setCheckedUsers(users.filter((user) => user.active));
}, [users]);
const changeChecked = (id) => {
users.forEach((user) => {
if (user.id === id) {
console.log(user);
if (user.active === '') {
user.active = true;
} else user.active = false;
}
});
setUsers(users);
console.log(checkedUsers);
};
return (
<div className="list-container">
<div className="search">
<input
type="text"
placeholder="Search for users"
onChange={(event) => setSearch(event.target.value)}
/>
</div>
{filteredUsers &&
filteredUsers.map((user) => (
<div
className="user-block"
key={user.id}
onClick={() => console.log(user.id, user.active)}>
<div className="details">
<h5>
{user.first_name} {user.last_name}
</h5>
<p>{user.email}</p>
</div>
<input
type="checkbox"
checked={user.active}
onClick={(event) => changeChecked(user.id)}
/>
</div>
))}
</div>
);
}
export default UserList;
A few things here:
I think you should map the user after the fetch to add the active with a default value, so it isn't undefined in any case:
useEffect(() => {
const fetchUsers = async () => {
try {
const request = await fetch(users);
const response = request.map(data => ({...data, active: true})).sort(function (a, b) {
if (a.last_name.toLowerCase() < b.last_name.toLowerCase()) return -1;
if (a.last_name.toLowerCase() > b.last_name.toLowerCase()) return 1;
else return;
});
setUsers(response);
} catch (error) {
console.log(error);
}
};
fetchUsers();
}, []);
Your filtered users is absolutely useless, you can do that with the user itself and lose the useEffect and the state variables entirely:
users.filter(user =>
user.first_name.toLowerCase().includes(search.toLowerCase()) ||
user.last_name.toLowerCase().includes(search.toLowerCase())
).map((user) => (
<div
className="user-block"
key={user.id}
onClick={() => console.log(user.id, user.active)}>
<div className="details">
<h5>
{user.first_name} {user.last_name}
</h5>
<p>{user.email}</p>
</div>
<input
type="checkbox"
checked={user.active}
onClick={(event) => changeChecked(user.id)}
/>
</div>
)
And last but not least, your changeChecked function is mutating the array. I would use .map as well instead of forEach:
const changeChecked = (id) => {
setUsers(
users.map(user => {
if (user.id === id) {
return {
...user,
active: !user.active
};
} else {
return user;
}
})
);
console.log(checkedUsers);
};