I have a navbar with the usual login and sign-up buttons. When I click the relevant link, a modal with the form should pop-up allowing the user to login or sign-up. At the moment, neither of the pop-ups seem to work. I have been following React-Bootstrap Multiple Modal and have been using the chosen answer to try and implement the modals.
The handle functions are in my app.jsx:
import React, {useEffect, useState} from 'react'
import {useRouter} from 'next/router'
import {SessionProvider, useSession} from 'next-auth/react'
import {SSRProvider} from 'react-bootstrap'
import 'application.css';
import Navigation from "../components/Navigation";
import Login from "../components/Login";
import SideNav from "../components/SideNav";
import SignUp from "../components/SignUp";
function MyApp({Component, pageProps}) {
let id = '';
const [show, setShow] = useState(null)
function handleClose() {
setShow({show: id});
}
function handleShow(id) {
setShow({show: id});
}
const [showSideBar, setShowSideBar] = useState(false)
const toggleSideMenu = () => setShowSideBar((prev) => !prev);
return (
<SessionProvider session={pageProps.session}>
<SSRProvider>
<SideNav showSideBar={showSideBar} />
<Navigation
handleShow={handleShow}
handleClose={handleClose}
toggleSideMenu={toggleSideMenu}
/>
<Login
show={show}
handleShow={handleShow}
handleClose={handleClose}
/>
<SignUp
show={show}
handleShow={handleShow}
handleClose={handleClose}
/>
{Component.auth
? <Auth><Component {...pageProps} /></Auth>
: <Component {...pageProps} />
}
</SSRProvider>
</SessionProvider>
)
}
function Auth({children}) {
// #ts-ignore
const [session, loading] = useSession()
const isUser = !!session?.user
const router = useRouter()
useEffect(() => {
if (loading) return // Do nothing while loading
if (!isUser) router.push('/login')
// If not authenticated, force log in
}, [isUser, loading])
if (isUser) {
return children
}
// Session is being fetched, or no user.
// If no user, useEffect() will redirect.
return <div>Loading...</div>
}
export default MyApp
I pass the show, handleshow, handleclose to each component. In the navigation the buttons look like this:
<Nav.Link href="#" onClick={() => handleShow('login')}><FontAwesomeIcon icon={solid('sign-in-alt')}/><span> Login</span></Nav.Link>
<Nav.Link href="#" onClick={() => handleShow('signup')}><FontAwesomeIcon icon={solid('user-plus')}/><span> Sign up</span></Nav.Link>
And then finally, my Login.jsx with a modal:
import {getCsrfToken, signIn, useSession} from "next-auth/react";
import Router from "next/router";
import { Modal, CloseButton, Form } from 'react-bootstrap';
import Link from "next/link";
import { useState } from "react";
function Login({csrfToken, show, handleClose, handleShow, props}) {
const [error, setError] = useState(false);
//setShow(prev => !prev);
const handleSubmit = async (e) => {
e.preventDefault();
const res = await signIn('credentials', {
redirect: false,
email: e.target.email1.value,
password: e.target.password1.value,
callbackUrl: `/dashboard`,
});
if (res?.error) {
setError(true);
} else {
Router.push('/dashboard');
}
}
return (
<Modal show={show === "login"} onHide={handleClose} fade={false}>
<Modal.Header className={"modal-dark bg-dark"}>
<Modal.Title><h1>Log In</h1></Modal.Title>
<CloseButton variant="white" onClick={handleClose}/>
</Modal.Header>
<Modal.Body className={"modal-dark bg-dark"}>
<form noValidate onSubmit={(e) => handleSubmit(e)}>
<input name="csrfToken" type="hidden" defaultValue={csrfToken}/>
<Form.Group className="mt-3">
<Form.Control
id="email-address1"
name="email1"
type="email"
autoComplete="email"
required
className=""
placeholder="Email address"
/>
</Form.Group>
<Form.Group className="mt-3">
<Form.Control
id="password1"
name="password1"
type="password"
autoComplete="current-password"
required
placeholder="Password"
/>
</Form.Group>
<button type={"submit"} className={"btn orange-button mt-3"}>Login</button>
<div>
or <Link href='/signup'>sign up</Link>
</div>
{error && <div className="bg-red-300 p-2 text-white rounded">Wrong email or password</div>}
</form>
</Modal.Body>
<Modal.Footer className={"modal-dark bg-dark"}>
<button onClick={handleClose}>
Close
</button>
</Modal.Footer>
</Modal>
);
}
export default Login;
In the developer tools looking at the React components tab, it is sending the correct information.
The props all seem to be there, the modal just doesn't seem to pop-up. Thanks
Related
I have a simple react app with an update profile page, using firebase for auth, and when I try to change the password, I'm getting this error:TypeError: updatePassword is not a function.
Here is my UpdateProfile.js
import React, { useRef, useState } from "react";
import { Form, Button, Card, Alert } from "react-bootstrap";
import { getAuth } from "firebase/auth";
import { Link, useNavigate } from "react-router-dom";
export default function UpdateProfile() {
const emailRef = useRef();
const passwordRef = useRef();
const passwordConfirmRef = useRef();
const { currentUser, updateEmail, updatePassword } = getAuth();
const [error, setError] = useState("");
const [loading, setLoading] = useState(false);
const navigate = useNavigate();
function handleSubmit(e) {
e.preventDefault();
if (passwordRef.current.value !== passwordConfirmRef.current.value) {
return setError("Passwords do not match");
}
const promises = [];
setLoading(true);
setError("");
if (emailRef.current.value !== currentUser.email) {
promises.push(updateEmail(emailRef.current.value));
}
if (passwordConfirmRef.current.value) {
promises.push(updatePassword(passwordRef.current.value));
}
Promise.all(promises)
.then(() => {
navigate("/");
})
.catch(() => {
setError("Failed to update account");
})
.finally(() => {
setLoading(false);
});
}
return (
<>
<Card>
<Card.Body>
<h2 className="text-center mb-4">Update Profile</h2>
{error && <Alert variant="danger">{error}</Alert>}
<Form onSubmit={handleSubmit}>
<Form.Group id="email">
<Form.Label>Email</Form.Label>
<Form.Control
type="email"
ref={emailRef}
required
defaultValue={currentUser.email}
/>
</Form.Group>
<Form.Group id="password" data-testid="pword">
<Form.Label>Password</Form.Label>
<Form.Control
type="password"
ref={passwordRef}
placeholder="Leave blank to keep the same"
/>
</Form.Group>
<Form.Group id="password-confirm">
<Form.Label>Password Confirmation</Form.Label>
<Form.Control
type="password"
ref={passwordConfirmRef}
placeholder="Leave blank to keep the same"
/>
</Form.Group>
<div className="w-100 mt-2">
<Button disabled={loading} className="w-100" type="submit">
Update
</Button>
</div>
</Form>
</Card.Body>
</Card>
<div className="w-100 text-center mt-2">
<Link to="/">Cancel</Link>
</div>
</>
);
}
the Error points to line 30, which is here in stars:
if (emailRef.current.value !== currentUser.email) {
promises.push(updateEmail(emailRef.current.value));
}
if (passwordConfirmRef.current.value) {
**promises.push(updatePassword(passwordRef.current.value));**
}
Can anyone see what I have wrong? Thanks!
The updateEmail and updatePassword functions can be imported from Firebase Auth SDK and not the Auth instance. Try updating your import statements to:
import { updateEmail, updatePassword } from "firebase/auth"
import React, { useState } from "react";
import { Link } from "react-router-dom";
import { useDispatch, useSelector } from "react-redux";
import Message from "../../components/Message/Message";
import Loader from "../../components/Loader/Loader";
import { login } from "../../actions/userActions";
export default function Login({ history }) {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const dispatch = useDispatch();
const userLogin = useSelector((state) => state.userLogin);
const { loading, error, userInfo } = userLogin;
const submitHandler = (e) => {
e.preventDefault();
dispatch(login(email, password));
if (userInfo) {
if (userInfo.isAdmin) {
history.push("/admin/dashboard");
} else {
history.push("/");
}
}
};
return (
<div className="login-wrapper">
<div className="container py-5">
<div className="login-form p-5 rounded-2">
<h2 className="pb-3 text-center">Sign In</h2>
{error && <Message className="alert alert-danger">{error}</Message>}
{loading && <Loader />}
<form onSubmit={submitHandler}>
<div className="mb-3">
<label for="email" className="form-label">
Email address
</label>
<input
type="email"
value={email}
className="form-control"
id="email"
onChange={(e) => setEmail(e.target.value)}
/>
</div>
<div class="mb-3">
<label for="password" className="form-label">
Password
</label>
<input
type="password"
className="form-control"
value={password}
id="password"
onChange={(e) => setPassword(e.target.value)}
/>
</div>
<div className="d-grid ">
<button className="btn btn-submit p-2" type="submit">
CONTINUE
</button>
</div>
<div className="d-flex justify-content-between py-3">
<p className="">
New User?
<Link to="/register" className="ms-1">
Signup
</Link>
</p>
Forgot your password
</div>
</form>
</div>
</div>
</div>
);
}
After complete successful login and if a user is an admin I want to show /admin/dashboard in url. But it still showing the /login URL. Or user is not an admin I want to show the home page. it's working fine for a normal user. But for the admin URL is not update.
Note: After submitting the login form I store userInfo in localstorage. Then I get localstorage data for checking if user admin or not.
How can I solve this problem?
You need to redirect on your call back action in Login.
Example :
if (userInfo) {
if (userInfo.isAdmin) {
history.push("/admin/dashboard");
} else {
history.push("/");
}
}
In submitHandler After dispatch(login(email, password)); they not wait for the action complate and userInfo. isAdmin is still not available so It's not redirect to the /admin/dashboard page.
You can use the history object in your action and do that.
import React from 'react';
import { Router } from 'react-router-dom';
import { createBrowserHistory } from 'history';
export const history = createBrowserHistory();
export const App: React.FC = () => {
return (
<Router history={history}>
<Routes /> // Your all routes
</Router>
);
};
Now you can use history on your action.
import { history } from './app';
const login = () => {
// do API stuff
history.push('/somewhere');
history.push({
pathname: '/somewhere',
search: '?some=search-string',
hash: '#howdy',
state: {
[userDefined]: true
}
});
};
Or you can listen to the userInfo in useEffect hooks and do that.
useEffect(() => {
if (userInfo.isAdmin) {
history.push("/admin/dashboard");
} else {
history.push("/");
}
}, [userInfo?.isAdmin]);
I have not been able to use the useEffect function to not get the following warning:
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates >a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a >useEffect cleanup function.
here's my code
import React, { useEffect, useRef, useState } from 'react';
import { Card, Button, Form, Alert } from 'react-bootstrap';
import { useAuth } from '../context/AuthContext';
import { Link, useHistory } from "react-router-dom";
export default function Login() {
const emailRef = useRef();
const passwordRef = useRef();
const { login } = useAuth();
const [error, setError] = useState('');
const [loading, setLoading] = useState(false);
const history = useHistory();
async function handleSubmit(e) {
e.preventDefault();
try {
setError('')
setLoading(true)
await login(emailRef.current.value, passwordRef.current.value)
history.push('/')
} catch {
setError('Failed to Log In')
}
setLoading(false)
}
return (
<>
<Card>
<Card.Body>
<h2 className='text-center mb-4'>
Log In
</h2>
{error && <Alert variant='danger'>{error}</Alert>}
<Form onSubmit={handleSubmit}>
<Form.Group id='email'>
<Form.Label>
Email
</Form.Label>
<Form.Control type='email' ref={emailRef} required />
</Form.Group>
<Form.Group id='password'>
<Form.Label>
Password
</Form.Label>
<Form.Control type='password' ref={passwordRef} required />
</Form.Group>
<Button disabled={loading} className='w-100' type='submit'>
Log In
</Button>
</Form>
<div className='w-100 text-center mt-3'>
<Link to='/forgot-password'>Forgot Password?</Link>
</div>
</Card.Body>
</Card>
<div className='w-100 text-center mt-2'>
Need an account? <Link to='/signup'>Sign Up</Link>
</div>
</>
);
}
I have tried several things including:
import React, { useEffect, useRef, useState } from 'react';
import { Card, Button, Form, Alert } from 'react-bootstrap';
import { useAuth } from '../context/AuthContext';
import { Link, useHistory } from "react-router-dom";
export default function Login() {
const emailRef = useRef();
const passwordRef = useRef();
const { login } = useAuth();
const [error, setError] = useState('');
const [loading, setLoading] = useState(false);
const history = useHistory();
useEffect(() => {
async function handleSubmit(e) {
e.preventDefault();
try {
setError('')
setLoading(true)
await login(emailRef.current.value, passwordRef.current.value)
history.push('/')
} catch {
setError('Failed to Log In')
}
setLoading(false)
}
handleSubmit();
return handleSubmit;
}, [])
return (
<>
<Card>
<Card.Body>
<h2 className='text-center mb-4'>
Log In
</h2>
{error && <Alert variant='danger'>{error}</Alert>}
<Form onSubmit={handleSubmit}>
<Form.Group id='email'>
<Form.Label>
Email
</Form.Label>
<Form.Control type='email' ref={emailRef} required />
</Form.Group>
<Form.Group id='password'>
<Form.Label>
Password
</Form.Label>
<Form.Control type='password' ref={passwordRef} required />
</Form.Group>
<Button disabled={loading} className='w-100' type='submit'>
Log In
</Button>
</Form>
<div className='w-100 text-center mt-3'>
<Link to='/forgot-password'>Forgot Password?</Link>
</div>
</Card.Body>
</Card>
<div className='w-100 text-center mt-2'>
Need an account? <Link to='/signup'>Sign Up</Link>
</div>
</>
);
}
Chrome also shows me a warning a bout a password leak every time I Login or Log out I´m not sure if it is because of this problem.
Please help
Your useEffect ( create one more ) should look like this :
let mounted = true;
useEffect(()=>{
return function cleanup(){
mounted = false
}
})
Now check for the mounted value while updating a state, especially the loading one
I'm wondering if anyone else has had success editing text inside a Bootstrap React Modal and could share some wisdom. I'm using a Bootstrap React Modal to display data once the user clicks on the task title. The data is displaying beautifully, but the user cannot edit the data. The cursor changes to an editing cursor, but no amount of keyboard pounding gets an edit. I tried adding a Bootstrap React Form as you can see below. When I click on the "Save Changes" button, I get my toastify error.
import React, { useState } from "react";
import Button from "react-bootstrap/Button";
import Form from "react-bootstrap/Form";
import Modal from "react-bootstrap/Modal";
import taskAPI from "../../utils/taskAPI";
import { ToastContainer, toast } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
import "./style.css";
function TaskModal({ onHide, onTaskUpdated, task }) {
const [title, setTitle] = useState("");
const [textBody, setTextBody] = useState("");
const [isPending, setIsPending] = useState(false);
const notify = () => toast.warn("You cannot save an empty task!");
const updateTask=(event)=> {
event.preventDefault();
setIsPending(true);
taskAPI
.updateTask({ title, textBody })
.then((response) => {
response.setTitle("");
response.setTextBody("");
})
.then((response) => {
onTaskUpdated();
setIsPending(false);
})
.catch((error) => {
console.log(error);
setIsPending(false);
notify();
});
}
return (
<>
<Modal show={true} onHide={onHide}>
<Modal.Header closeButton>
<Modal.Title>
<Form>
<Form.Group controlId="TaskTitleText">
<Form.Control
className="TaskTitleText"
as="textarea"
size="lg"
rows="1"
value={task.title}
onChange={(event)=> setTitle(event.target.value)}
/>
</Form.Group>
</Form>
</Modal.Title>
</Modal.Header>
<Modal.Body>
<Form>
<Form.Group controlId="TaskBodyText">
<Form.Control
as="textarea"
multiple
value={task.textBody}
onChange={(event)=> setTextBody(event.target.value)}
></Form.Control>
</Form.Group>
</Form>
</Modal.Body>
<Modal.Footer>
<Button
variant="primary"
value={isPending ? "Saving..." : "Submit"}
onClick={updateTask}
>
Save Changes
<ToastContainer />
</Button>
<Button variant="secondary"
value={isPending ? "Saving..." : "Submit"}
onClick={onHide}>
Close
</Button>
</Modal.Footer>
</Modal>
</>
);
}
export default TaskModal;
Update your code to below, I have checked it and update the solutions with you.
It will perfectly works everytime.
import React, { useState } from "react";
import Button from "react-bootstrap/Button";
import Form from "react-bootstrap/Form";
import Modal from "react-bootstrap/Modal";
import taskAPI from "../../utils/taskAPI";
import { ToastContainer, toast } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
import "./style.css";
function TaskModal({ onHide, onTaskUpdated, task }) {
const [title, setTitle] = useState(task.title || "");
const [textBody, setTextBody] = useState(task.textBody || "");
const [isPending, setIsPending] = useState(false);
const notify = () => toast.warn("You cannot save an empty task!");
const updateTask=(event)=> {
event.preventDefault();
setIsPending(true);
taskAPI
.updateTask({ title, textBody })
.then((response) => {
response.setTitle("");
response.setTextBody("");
})
.then((response) => {
onTaskUpdated();
setIsPending(false);
})
.catch((error) => {
console.log(error);
setIsPending(false);
notify();
});
}
return (
<>
<Modal show={true} onHide={onHide}>
<Modal.Header closeButton>
<Modal.Title>
<Form>
<Form.Group controlId="TaskTitleText">
<Form.Control
className="TaskTitleText"
as="textarea"
size="lg"
rows="1"
value={title}
onChange={(event)=> setTitle(event.target.value)}
/>
</Form.Group>
</Form>
</Modal.Title>
</Modal.Header>
<Modal.Body>
<Form>
<Form.Group controlId="TaskBodyText">
<Form.Control
as="textarea"
multiple
value={textBody}
onChange={(event)=> setTextBody(event.target.value)}
></Form.Control>
</Form.Group>
</Form>
</Modal.Body>
<Modal.Footer>
<Button
variant="primary"
value={isPending ? "Saving..." : "Submit"}
onClick={updateTask}
>
Save Changes
<ToastContainer />
</Button>
<Button variant="secondary"
value={isPending ? "Saving..." : "Submit"}
onClick={onHide}>
Close
</Button>
</Modal.Footer>
</Modal>
</>
);
}
export default TaskModal;
Note that you are setting the value of your Form.Control to task.textBody and not textBody. task.Textbody is a prop that is passed into your element from the parent element, while taskBody is the local state (from useState("")) that you are actually updating in your onChange function.
You probably want to manage the state in a parent component, in which case you can pass down an onChange method that this component can call whenever the text changes.
Every time I submit a form using axios, history.push updates the path in the browser but the component is not rendered. However, when I use BrowserRouter it works fine but apparently it doest support history.
App.js
import React from 'react';
import {Router, Route, Switch, BrowserRouter, withRouter} from 'react-router-dom';
import {history} from "./helpers";
import Layout from './containers/Layout'
import AllProductsList from "./containers/AllProducts";
import ProductAnalytics from "./containers/ProductAnalytics";
function App() {
return (
<>
<Router history={history}>
<Layout>
<Switch>
<Route path="/sell/allproducts" component={withRouter(AllProductsList)} />
<Route path="/sell/analytics/:product_id" component={withRouter(ProductAnalytics)} />
</Switch>
</Layout>
</Router>
</>
);
}
export default App;
Layout.js
import React from "react";
import Sidebar2 from "../components/sidebar2";
import Navbar2 from "../components/navbar2";
import Footer from "../components/Footer";
import {withRouter} from 'react-router-dom';
const Layout = ({children}) => {
return(
<div id="wrapper">
<Navbar2 />
<Sidebar2 />
<div className="content-page">
<div className="content">
{children}
</div>
</div>
<Footer />
</div>
);
};
export default withRouter(Layout);
Allproducts.js
const ProductCreateModal = (props) => {
const [loading, setLoading] = useState(false)
const [error, setError] = useState(null);
const {
buttonLabel='Add New Product',
className
} = props;
const [modal, setModal] = useState(false);
const toggle = () => setModal(!modal);
const [productName, setProductName] = useState(null)
const [productPrice, setProductPrice] = useState(null)
const [productImage, setProductImage] = useState(null)
function handleSubmit(e) {
e.preventDefault();
setLoading(true);
console.log(productName)
console.log(productPrice)
console.log(productImage)
const formData = new FormData()
formData.append("name", productName)
formData.append("price", productPrice)
if(productImage) formData.append("image", productImage)
formData.append("status", "ACTIVE")
console.log(formData)
axios
.post(api.product.create, formData, {
headers: {
"Content-Type": "multipart/form-data"
}
})
.then(res => {
console.log(res)
setLoading(false);
history.push('/sell/allproducts')
})
.catch(err => {
console.log(err)
setLoading(false);
setError(err.message || err)
})
}
return(
<>
<Button color="success" className="waves-effect waves-light mb-3" onClick={toggle}><i className="mdi mdi-cloud-upload mr-1"/>{buttonLabel}</Button>
<Modal isOpen={modal} toggle={toggle} className={className}>
<ModalHeader toggle={toggle}>Add New Product</ModalHeader>
<ModalBody>
<Form onSubmit={handleSubmit}>
<FormGroup>
<Label for="exampleEmail">Name</Label>
<Input
type="text"
name="name"
id="exampleEmail"
placeholder="Product name"
value={productName}
onChange={e => setProductName(e.target.value)}
/>
</FormGroup>
<FormGroup>
<Label for="examplePassword">Price</Label>
<Input
type="number"
min="1"
step="any"
name="Price"
id="examplePassword"
placeholder="Product Price"
value={productPrice}
onChange={e => setProductPrice(e.target.value)}
/>
</FormGroup>
<FormGroup>
<Label for="exampleText">Description</Label>
<Input
type="textarea"
name="Description"
id="exampleText"
/>
</FormGroup>
<FormGroup>
<Label for="exampleFile">Image</Label>
<Input
type="file"
name="file"
id="exampleFile"
onChange={e => setProductImage(e.target.files[0])}
/>
<FormText color="muted">
This is some placeholder block-level help text for the above input.
It's a bit lighter and easily wraps to a new line.
</FormText>
</FormGroup>
<Button color='success' block>Submit</Button>
</Form>
</ModalBody>
</Modal>
</>
)
};
helpers/history.js
import {createBrowserHistory} from "history";
export const history = createBrowserHistory();
helpers/index.js
export * from "./history";
How do I get this working with Router and history.push?