NextJS - Close Modal Component from Child inner Component - reactjs

I am using the Modal Component from React Bootstrap Everything works as expected, except from the fact that I cannot figure out how to auto close the Modal after a successful submit.
I know I am able to call the onHide fuction from the Modal component like this at button click:
<Button onClick={props.onHide}>Close</Button>
Is there a way to auto call this onHide function if, and only if there is a successful submit form the MailingListSendgrid component?
index.js
<ModalMailingList show={modalShow} onHide={() => setModalShow(false)} />
ModalMailingList.js
import Modal from "react-bootstrap/Modal";
import MailingListSendgrid from "#/components/MailingListSendgrid";
export default function ModalMailingList(props) {
return (
<Modal
{...props}
size="lg"
aria-labelledby="contained-modal-title-vcenter"
centered
className="special_modal" //Add class name here
>
<Modal.Header closeButton></Modal.Header>
<Modal.Body>
<MailingListSendgrid />
</Modal.Body>
</Modal>
);
}
MailingListSendgrid.js
.
.
.
.
const MailingListSendgrid = () => {
const [isError, setIsError] = useState(true);
const [shakeIt, setshakeIt] = useState(false);
const [mail, setMail] = useState("");
const [isLoading, setLoading] = useState(false);
const [message, setMessage] = useState(null);
const subscribe = () => {
const regEx = /[a-zA-Z0-9._%+-]+#[a-z0-9.-]+\.[a-z]{2,8}(.[a-z{2,8}])?/g;
setMail("");
if (!regEx.test(mail) && mail !== "") {
setIsError(true);
setshakeIt(true);
setMessage("Email is Not Valid");
setTimeout(() => {
setshakeIt(false);
}, 1000);
} else if (mail === "") {
setIsError(true);
setshakeIt(true);
setMessage("Email is Empty");
setTimeout(() => {
setshakeIt(false);
}, 1000);
} else {
setLoading(true);
axios
.put("api/MailingList", {
mail,
})
.then((result) => {
if (result.status === 200) {
setIsError(false);
setMessage(result.data.message);
setLoading(false);
}
})
.catch((err) => {
setIsError(true);
setMessage(err.data.message);
setLoading(false);
});
setMessage(null);
setshakeIt(false);
}
};
return (
.
.
.
<input
onChange={(e) => {
setMail(e.target.value);
}}
type="email"
className={`form-control required email w-auto text-center text-sm-start`}
placeholder={subscription.formPlaceholder}
value={mail}
autoComplete="email"
required
></input>
<button
type="submit"
name="subscribe"
onClick={subscribe}
className="input-group-text justify-content-center"
disabled={isLoading}
>
.
.
.
);
};
export default MailingListSendgrid;

I think that the best option here is to pass a function as a prop, thus still making the MailingListSendgrid reusable e.g.
<MailingListSendgrid onSubmit={()=> props.onHide()} />
And just use that in MailingListSendgrid if it was successfull.

Related

How to prevent a double submit

I have the current code where with a button (OpenModal.jsx) the user open a modal where he can save data in a database, the problem that I have is that if the user is fast enough to click submit twice before the modal close he can save the same data twice (send a double submit).
What is the best way to prevent this?
OpenModal.jsx
const OpenModal = () => {
const [openModal, setOpenModal] = useState(false);
return (
<div className="container">
<button
className="openModalBtn"
onClick={() => {
setOpenModal(true);
}}
>
Set note
</button>
{openModal && <Modal closeModal={setOpenModal} />}
</div>
);
};
Modal.jsx
import { useState } from "react";
const Modal = ({ closeModal }) => {
const [data, setData] = useState({
note: "",
});
const submit = async (e) => {
e.preventDefault();
try {
const response = await axios.post(
`${process.env.REACT_APP_API_KEY}`,
{
note: data.note,
}
);
response.data.success ? closeModal(false) : null;
} catch (error) {
console.log(error);
}
};
const handle = (e) => {
const getData = { ...data };
getData[e.target.id] = e.target.value;
setData(getData);
};
return (
<div className="modal">
<div className="modal-content">
<form onSubmit={(e) => submit(e)}>
<div className="close-content">
<button
type="button"
className="btn-close"
onClick={() => {
closeModal(false);
}}
>
X
</button>
</div>
<div className="form-content">
<label>
Note:
<input
type="text"
required
onChange={(e) => handle(e)}
id="note"
/>
</label>
</div>
<div className="buttons-form">
<button
type="button"
className="btn-cancel"
onClick={() => {
closeModal(false);
}}
>
Cancel
</button>
<button className="btn-save" type="submit">
Save
</button>
</div>
</form>
</div>
</div>
);
};
Disable the button while the operation is processing. You can keep a disabled flag in state:
const [isDisabled, setIsDisabled] = useState(false);
And use it on the button:
<button className="btn-save" type="submit" disabled={isDisabled}>
Save
</button>
Then update that state as needed:
const submit = async (e) => {
setIsDisabled(true); // <--- here
e.preventDefault();
try {
const response = await axios.post(
`${process.env.REACT_APP_API_KEY}`,
{
note: data.note,
}
);
setIsDisabled(false); // <--- here
response.data.success ? closeModal(false) : null;
} catch (error) {
setIsDisabled(false); // <--- here
console.log(error);
}
};
For improved UX, you might even replace the button text with a spinner or some other indication that "something is processing" while it's disabled.
You could have an isLoading state that you set to true when the submit button is clicked and false when the request is completed. Then, you can either make the button disabled when that state is true or simply don't send the request in the submit function if the request is loading:
const [isLoading, setIsLoading] = useState(false);
const submit = async (e) => {
e.preventDefault();
if (isLoading)
return;
setIsLoading(true);
try {
const response = await axios.post(
`${process.env.REACT_APP_API_KEY}`, {
note: data.note,
}
);
response.data.success ? closeModal(false) : null;
} catch (error) {
console.log(error);
} finally {
setIsLoading(false);
}
};
You should introduce safety flag, to know when submitting started and when finished, and like that user will not be able to hit it twice because you can set constraints with flag. Do something like this:
const [data, setData] = useState({
note: "",
});
const [submitting, setSubmitting] = useState(false);
const submit = async (e) => {
e.preventDefault();
if(!submitting) {
setSubmitting(true);
try {
const response = await axios.post(
`${process.env.REACT_APP_API_KEY}`,
{
note: data.note,
}
);
response.data.success ? closeModal(false) : null;
} catch (error) {
console.log(error);
} finally {
setSubmitting(false);
}
}
};

Getting "doc is not a function" when writing to Firestore after creating a user

I need to save the username in my Firestore Database while creating the user. I'm using Firebase (v9) with React. The code is Below.
A user is created but in the Firestore Database user is not added.
What is the way to use setDoc inside createUserWithEmailAndPassword
Can someone help me with the code?
import Box from "#mui/material/Box";
import OutlinedInput from "#mui/material/OutlinedInput";
import Button from "#mui/material/Button";
import Alert from "#mui/material/Alert";
import { Link, Outlet } from "react-router-dom";
import {
collection,
query,
onSnapshot,
setDoc,
serverTimestamp,
doc,
} from "firebase/firestore";
import { createUserWithEmailAndPassword } from "firebase/auth";
import { db, auth } from "../../../firebase";
import IGLogo from "../../../images/instagram-logo.png";
import "./SignUpForm.scss";
function SignUpForm() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [username, setUsername] = useState("");
const [successalert, setSuccessAlert] = useState(undefined);
const [failalert, setFailAlert] = useState(undefined);
//const [user, setUser] = useState(undefined);
useEffect(() => {
const timeId = setTimeout(() => {
// After 3 seconds set the show value to false
setSuccessAlert(undefined);
}, 3000);
return () => {
clearTimeout(timeId);
};
});
useEffect(() => {
const timeId = setTimeout(() => {
// After 3 seconds set the show value to false
setFailAlert(undefined);
}, 3000);
return () => {
clearTimeout(timeId);
};
});
useEffect(() => {
const timeId = setTimeout(() => {
// After 3 seconds set the show value to false
setFailAlert(undefined);
}, 3000);
return () => {
clearTimeout(timeId);
};
});
const instagramSignUp = (event) => {
event.preventDefault();
const q = query(collection(db, "users"));
onSnapshot(q, (querySnapshot) => {
querySnapshot.docs.forEach((doc) => {
if (doc.id === username) {
setFailAlert({ type: "userexist" });
} else {
createUserWithEmailAndPassword(auth, email, password)
.then((userCreated) => {
setDoc(doc(db, "users", username), {
timestamp: serverTimestamp(),
})
.then(() => {
setSuccessAlert({ type: "success" });
console.log("user created in collection");
})
.catch((error) => {
console.log(error.message);
});
})
.catch((error) => {
console.log(
"createUserWithEmailAndPassword = " +
error.message
);
});
}
});
});
};
return (
<>
<div className="component__signupalerts">
{successalert?.type === "success" && (
<Alert variant="filled" severity="success">
Account Created Successfully. Please check your Email
for Verification.
</Alert>
)}
{failalert?.type === "userexist" && (
<Alert variant="filled" severity="error">
Username already taken
</Alert>
)}
</div>
<div className="component__signupform">
<img src={IGLogo} alt="" />
<Box component="form" className="component__signupform--box">
<OutlinedInput
className="component__loginform--input"
type="text"
placeholder="Username"
value={username}
onChange={(e) => setUsername(e.target.value)}
/>
<OutlinedInput
className="component__signupform--input"
type="email"
placeholder="Email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<OutlinedInput
className="component__signupform--input"
type="password"
placeholder="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<Button
className="component__signupform--button"
type="submit"
variant="contained"
onClick={instagramSignUp}
>
Sign Up
</Button>
<Link to="/" className="component__signupform--button">
Sign In
</Link>
</Box>
</div>
<Outlet />
</>
);
}
export default SignUpForm;
Console.log "doc is not a function"
The problem is that you have two definitions of doc in your code:
First you import { ...., doc, ... } from "firebase/firestore";.
Then you also define it in querySnapshot.docs.forEach((doc) => {.
The doc in that second line hides the one that you imported.
The easiest fix is to name the variable of forEach something else:
onSnapshot(q, (querySnapshot) => {
querySnapshot.docs.forEach((docSnapshot) => {
if (docSnapshot.id === username) {
...

While on click on the span, how can we get the correct span name by using useRef()

I am getting the nominee_name as the last span name even after clicking on the right span element. How can I get the correct span name from here <span className="countname" key={data.nomineename} ref={nominee_name} onClick={handleClick}>{data.nomineename}</span>.
The above span is iterated based on the data received.
import React, { useRef, useEffect, useState } from "react";
import Axios from "axios";
const Dashboard = props => {
const [nominationCount, setNominationCount] = useState([]);
const [nameText, setNameText] = useState("");
let nominee_name = useRef(null);
const isMounted = useRef(false);
useEffect(() => {
isMounted.current = true;
return () => isMounted.current = false;
}, []);
useEffect(() => {
const fetchData = async () => {
try {
const res = await Axios.get('http://localhost:8000/service/nominationcount');
if (isMounted.current) {
setNominationCount(res.data);
console.log("Nomination count data from server :" + res.data);
}
} catch (e) {
console.log(e);
}
}
fetchData();
}, []);
const handleClick = () => {
setNameText(nominee_name.current.outerText);
}
return (
<div className="space_1 tile">
<h3>Nominations Count</h3>
<div className="grid-container">
{
!nominationCount.length && (<div className="nonominationdata">No nominations count to display !</div>)
}
{
nominationCount.map(data => (
<div key={data.id}>
<div onClick={() => {setOpen(!open); }} className="count badge" >
<span className="badgenumber" value={data.count} key={data.count}>{data.EmailCount}</span>
<span className="countname" key={data.nomineename} ref={nominee_name} onClick={handleClick}>{data.nomineename}</span>
</div>
</div>
))
}
</div>
</div>
<Modal
open={open}
onClose={() => {
setOpen(false);
}}
className={classes.modal}>
<form className={classes.form}>
<label className={classes.label}>Confirm winner {nameText}</label>
<input className={classes.submit} type="submit" value="Confirm" />
</form>
</Modal>
)
}
not sure you want to use ref here.
Just pass the name into your click handler:
(
<span className="countname" key={data.nomineename}
onClick={()=>setNameText(data.nomineename)}>{data.nomineename}</span>
)

All other input data get clear when I i hit handleSubmit

Hi i am working on a React application where there are four options.when a user select an option corresponding input element will be added to the wrapper.In the following code add operation works fine but remove operation is not working properly ,it is not removing the corresponding element.Another problem the values on the inputs fields not present when the component re-renders.so experts guide me how i can acheive removing the corresponding row when the remove button is clicked and the input values should not be reset when the component re-renders.
But when I submit the input it will appear my data perfectly and when i restart the page and just click into edit and hit submit with the defaultValue it just clear all the data and send back to my backend with undefined value like this: [ undefined, undefined, undefined, undefined ]
Here is my full component:
const Agreement = (props) => {
const { agreement, editable, teamData, teamId, fetchTeamData } = props;
const [editing, setEditing] = useState(false);
const [title, setTitle] = useState("");
const [description, setDescription] = useState("");
const [showErrors, setShowErrors] = useState(false);
const [errorsArr, setErrorsArr] = useState();
const initialFormState = {
rule_0: teamData.rules.rule_0,
rule_1: teamData.rules.rule_1,
rule_2: teamData.rules.rule_2,
rule_3: teamData.rules.rule_3,
creator: teamData.User.public_user_id,
};
const [updateTeamData, setUpdateTeamData] = useState(initialFormState);
const [inputs, setInputs] = useState(teamData.rules);
const handleChange = (event) => {
const { name, value } = event.target;
// Update state
setUpdateTeamData((prevState) => ({
...prevState,
[name]: value,
}));
};
// Add more input
const addInputs = () => {
setInputs([...inputs, { name: `rule_${inputs.length + 1}` }]);
};
// handle click event of the Remove button
const removeInputs = (index) => {
const list = [...inputs];
list.splice(index, 1);
setInputs(list);
};
const clearInput = (dataName) => {
setUpdateTeamData((prevState) => {
delete prevState[dataName];
return {
...prevState,
};
});
};
const handleSubmit = async (event) => {
event.preventDefault();
setEditing(false);
// Send update request
const res = await axios.put(`/api/v1/teams/team/${teamId}`, updateTeamData);
// If no validation errors were found
// Validation errors don't throw errors, it returns an array to display.
if (res.data.validationErrors === undefined) {
// Clear any errors
setErrorsArr([]);
// Hide the errors component
setShowErrors(false);
// Call update profiles on parent
fetchTeamData();
} else {
// Set errors
setErrorsArr(res.data.validationErrors.errors);
// Show the errors component
setShowErrors(true);
}
};
const handleCancel = () => {
setEditing(false);
};
useEffect(() => {
if (agreement === "default") {
setTitle(defaultTitle);
setInputs(teamData.rules);
} else {
setTitle(agreement.title ?? "");
}
}, [agreement, teamData]);
console.log("teamData.rules", teamData.rules);
console.log("inputs", inputs);
return (
<div className="team-agreement-container">
{!editing && (
<>
<h4 className="team-agreement-rules-title">{title}</h4>
{editable && (
<div className="team-agreement-rules">
<EditOutlined
className="team-agreement-rules-edit-icon"
onClick={() => setEditing(true)}
/>
</div>
)}
<p className="team-agreement-rules-description">{description}</p>
{teamData.rules.map((rule, index) => (
<div className="team-agreement-rule-item" key={`rule-${index}`}>
{rule ? (
<div>
<h4 className="team-agreement-rule-item-title">
{`Rule #${index + 1}`}
</h4>
<p className="team-agreement-rule-item-description">
- {rule}
</p>
</div>
) : (
""
)}
</div>
))}
</>
)}
{/* Edit rules form */}
{editing && (
<div className="team-agreement-form">
{showErrors && <ModalErrorHandler errorsArr={errorsArr} />}
<h1>Rules</h1>
{inputs.map((data, idx) => {
return (
<div className="agreement-form-grid" key={`${data}-${idx}`}>
<button
type="button"
className="agreement-remove-button"
onClick={() => {
removeInputs(idx);
clearInput(`rule_${idx}`);
}}
>
<Remove />
</button>
<input
name={`rule_${idx}`}
onChange={handleChange}
value={teamData.rules[idx]}
/>
</div>
);
})}
{inputs.length < 4 && (
<div className="team-agreement-add-rule">
<button type="submit" onClick={addInputs}>
<Add />
</button>
</div>
)}
<div className="div-button">
<button className="save-button" onClick={handleSubmit}>
Save
</button>
<button className="cancel-button" onClick={handleCancel}>
Cancel
</button>
</div>
</div>
)}
</div>
);
};
export default Agreement;
How can I fix this error?
My thought is the problem is around [inputs, setInputs]
Try this
<input
//..
onChange={(event) => handleChange(event.target.value)}
//..
/>
then in your "handleChange" function
const handleChange = (event) => {
const { name, value } = event;
//....
};

How to use "ref" to refer an element in React Stateless Component

I was trying to implement focus for the Submit button with Ref. I wanted to omit refering elements by ID.
import React, { useRef } from 'react'
import PropTypes from 'prop-types'
export const LabelComponent = () => {
const createButton = enableCreateButton()
? <button ref={(input) => { this.createLabelBtn = input }} >Submit</button>
: <button disabled ref={(input) => { this.createLabelBtn = input }} >Submit</button>
const createLabelBtn = useRef();
const focusCreateBtn = (e) => {
if ((e.key === 'Enter') && (newLabel.name !== '')) {
this.createLabelBtn.focus();
}
};
return (
<div className='create-label-container'>
<input type='text'
onKeyDown={(e) => { focusCreateBtn(e) }}
/>
{createButton}
</div>
)
}
It gives following error.
Uncaught TypeError: Cannot set property 'createLabelBtn' of undefined
Uncaught TypeError: Cannot set property 'createLabelBtn' of undefined
What could be the issue here.?
Functional components are instanceless, therefore, no this to bind anything to or call upon. Set the ref prop on the button as so ref={createLabelBtn}, and to set the focus you need to access createLabelBtn.current to get at the current value of the ref.
export const LabelComponent = ({ enableCreateButton }) => {
const createLabelBtn = useRef(null);
const focusCreateBtn = e => {
if (e.key === "Enter") {
createLabelBtn.current.focus();
}
};
return (
<div className="create-label-container">
<input type="text" onKeyDown={focusCreateBtn} />
<button
// upon being focused upon, console log proof
onFocus={() => console.log("Submit Focused!")}
disabled={!enableCreateButton}
ref={createLabelBtn}
>
Submit
</button>
</div>
);
};
Try this
import React, { useRef, useState } from "react";
const LabelComponent = () => {
const [name, setName] = useState("");
const createButton = true ? (
<button
ref={input => {
createLabelBtn.current = input;
}}
>
Submit
</button>
) : (
<button
disabled
ref={input => {
createLabelBtn.current = input;
}}
>
Submit
</button>
);
const createLabelBtn = useRef();
const focusCreateBtn = e => {
if (e.key === "Enter" && name !== "") {
createLabelBtn.current.focus();
}
};
return (
<div className="create-label-container">
<input
type="text"`enter code here`
value={name}
onChange={e => {
setName(e.target.value);
}}
onKeyDown={e => {
focusCreateBtn(e);
}}
/>
{createButton}
</div>
);
};
export default LabelComponent;

Resources