React-Admin custom login page and React Hook - reactjs

I'm using React-Admin 3.14 and I would like to have a custom login page. When I use the one below with the useLogin hook, I have a hook related error which I can't figure out.
import React from 'react';
import { useLogin,useNotify } from 'react-admin';
import Button from '#material-ui/core/Button'
const LoginPage = () => {
const HandleClick = () => {
console.log('Clicked login ...');
const login = useLogin;
const notify = useNotify;
login({ username:'john', password:'doe' }).catch(() => notify("Invalid email or password"));
}
return(
<Button variant="contained" onClick={HandleClick}>Login</Button>
);
}
export default LoginPage;
UseLogin is a callback to the login method of the Reac-Admin 3.14 authProvider (https://marmelab.com/react-admin/doc/3.14/Authentication.html#uselogin-hook).
The error that I get is:
Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
You might have mismatching versions of React and the renderer (such as React DOM)
You might be breaking the Rules of Hooks
You might have more than one copy of React in the same app
See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.
I think I must be breacking Rules of Hooks somewhere ... but which one?
Thanks for your help.
C

Your useLogin and useNotify not executed
Change const login = useLogin to const login = useLogin()
Change const notify = useNotify to const notify = useNotify()
import React from 'react';
import { useLogin,useNotify } from 'react-admin';
import Button from '#material-ui/core/Button'
const LoginPage = () => {
const login = useLogin();
const notify = useNotify();
const HandleClick = () => {
console.log('Clicked login ...');
login({ username:'john', password:'doe' }).catch(() => notify("Invalid email or password"));
}
return(
<Button variant="contained" onClick={HandleClick}>Login</Button>
);
}
export default LoginPage;

Related

Next js getting double result

I am trying to build a web application using next js. Router Push is working fine, it's redirect to login page, but after redirect in login page i am getting double notification. where the problem? i want to show only one notification. please help me.
(1) dashboard Page
import { useRouter } from "next/router";
import { useContext } from "react";
import { toast } from "react-toastify";
import AppContext from "../../components/AppContext";
import DashLayout from "../../layouts/DashLayout";
function DashMain() {
const router = useRouter();
const server = useContext(AppContext);
if (!server.auth) {
router.push("/auth/login");
toast.success("Please login to access dashboard");
}
return (
<>
{server.auth ?
<h2>Welcome to Dashboard</h2>
:
<h1>Please login to access dashboard</h1>
}
</>
);
}
export default DashMain;
(2) Login Page
import { useContext, useState } from "react";
import Head from "next/head";
import { useRouter } from "next/router";
import Link from "next/link";
import Image from "next/image";
import AppContext from "../../components/AppContext";
import { toast } from "react-toastify";
import MainLayout from "../../layouts/MainLayout";
function Login() {
const server = useContext(AppContext);
const router = useRouter();
if (server.auth) {
router.push("/");
toast.success("You are already logged In");
}
//states
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [errors, setErrors] = useState("");
//Login Submit
const submitForm = (e) => {
e.preventDefault();
server.login({ email, password, setErrors });
}
if (server.userLoding) {
return (
<div id="mainmodal">
<div className="modalconent">
<span className='loader'></span>
</div>
</div>
);
}
return (
<>
.......
</>
);
}
export default Login;
Login.getLayout = function getLayout(page) {
return <MainLayout>{page}</MainLayout>;
};
Same way, when router redirect login page to home page i am getting double notification. please give me proper solution.
If you are using React 18 with StrictMode, then in development only a double-render is purposely occurring. This section of the documentation highlights this feature:
https://reactjs.org/blog/2022/03/29/react-v18.html#new-strict-mode-behaviors
Essentially, it's helping you deal with a bad side effect:
if (server.auth) {
router.push("/");
toast.success("You are already logged In");
}
Since server is a context and auth is set, when React 18 performs its intentional double-render, your component triggers this twice. Because when your login page unmounts and remounts, context.auth is preserved.
A better way to handle to handle this situation would be to use a single hook the redirect logic where you detect location.pathname. For example:
useRedirects.ts
import { useEffect } from 'react';
import { useLocation, useRouter } from 'react-router-dom';
import toast from 'toast';
const useRedirects = () => {
const location = useLocation();
const { pathname } = location;
const router = useRouter();
// when pathname changes, do stuff
useEffect(() => {
// if on dashboard and not authed, force login page
if (pathname === '/dashboard' && !server.auth) {
router.push("/auth/login");
toast.success("Please login to access dashboard");
}
// if on login page and authed, go to dashboard
if (pathname === '/login' && server.auth) {
router.push("/");
toast.success("You are already logged In");
}
}, [pathname]);
return null;
};
Include this hook in your app.js file component:
import useRedirects from './useRedirects';
const App = () => {
useRedirect();
// ...
// return ...
};
export default App;
Now you can remove this from your dashboard page:
if (!server.auth) {
router.push("/auth/login");
toast.success("Please login to access dashboard");
}
And remove this from your login page:
if (server.auth) {
router.push("/");
toast.success("You are already logged In");
}
Essentially, the double render happens on purpose in React 18 to help you identify bad code (improperly created side effects). It doesn't happen in production and should help identify issues.

React Context API current

Okay...what is happening here cause I don't undrestand? I have an react context api where I store the current user data. In the login function I console log the currentUser and everything looks fine, but then when I write the currentUser in chrome dev tools, it appears undefined. In localStorage I also have the data. What is happening? What am I doing wrong? Can someone, please help me.
Here is my code:
authContext.js
import { createContext, useState, useEffect } from "react";
import axios from "axios";
export const AuthContext = createContext();
export const AuthContextProvider = ({ children }) => {
const [currentUser, setCurrentUser] = useState(
JSON.parse(localStorage.getItem("user")) || null
);
const login = async (inputs) => {
try {
const res = await axios.post("/login", inputs);
setCurrentUser(res.data);
console.log("res.data: ", res.data); //returns data
console.log("currentUser ", currentUser); //returns data
} catch (error) {
console.log(error);
}
};
const logout = async () => {
localStorage.clear();
setCurrentUser(null);
};
useEffect(() => {
localStorage.setItem("user", JSON.stringify(currentUser));
}, [currentUser]);
return (
<AuthContext.Provider value={{ currentUser, login, logout }}>
{children}
</AuthContext.Provider>
);
};
index.js
import React from "react";
import ReactDOM from "react-dom/client";
import { AuthContextProvider } from "./ccontext/authContext";
import App from "./App";
import "./index.css";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
<AuthContextProvider>
<App />
</AuthContextProvider>
</React.StrictMode>
);
Login.jsx
/*...*/
const LoginForm = () => {
const [error, setError] = useState(null);
const navigate = useNavigate();
const { login } = useContext(AuthContext);
const handleFormSubmit = (values, actions) => {
try {
login(values);
actions.resetForm();
navigate("/");
console.log("logged in");
} catch (err) {
setError(err.response.data);
}
};
/*...*/
Updating state is best seen like an asynchronous operation. You cannot set state in a function / effect and expect it to immediately be updated, on the spot. Well, it technically is, but you won't see the updates in your "already-running" function.
I am pretty sure that if you extract your log in the component root it will display the appropriate value after the login function finishes executing and the component properly sets the new state.
If you do need the value in the function you should directly use res.data.
A deeper dive:
Whenever your login function runs it forms a closure around your current values (including currentUser which is undefined at the moment).
When you update the currentUser in the login function you basically inform react that you need to update that value. It will handle this in the background, preparing the state for the next render, but your login function will keep running with whatever values it started with. Your "new" state values will not be available until you run the function again. This is because the already-running function "closed over" old values, so it can only reference those.
As a side note, if you use a ref for instance you would not have this problem. Why? Because refs do not participate in the react lifecycle. When you modify a ref it changes on the spot. You will have the updated value precisely on the next line. State does not work like that, it is coupled to the component lifecycle, so it will update on the next render.

Stub a React hook using cypress

I'm trying to get my head around the way that cypress does things and I come from mostly a PHP background so I'm a bit confused.
I have a very simple React component
import React from 'react';
import LoginButton from '../atom/LoginButton';
import LogoutButton from '../atom/LogoutButton';
import { useAuth0 } from '#auth0/auth0-react';
const AuthenticationButton = () => {
const { isAuthenticated } = useAuth0();
return isAuthenticated ? <LogoutButton /> : <LoginButton />;
};
export default AuthenticationButton;
and I am trying to write a test to ensure that the login/logout button appears
import {mount} from '#cypress/react18'
import AuthenticationButton from './AuthenticationButton'
import { useAuth0 } from '#auth0/auth0-react';
it('should display login if not authenticated', () => {
mount(<AuthenticationButton/>)
cy.get('button').contains('Log in');
});
it('should display logout if authenticated', () => {
cy.stub(useAuth0, 'isAuthenticated').returns(true);
mount(<AuthenticationButton/>);
cy.get('button').contains('Log out');
});
I currently get the error Cannot stub non-existent own property isAuthenticated. I think because isAuthenticated is a property and not a function. Is there a way I can mock the property to be true?

I want to set preauthorizeApiKey in swagger-ui-react application auto authorized

I have below code where I have set preauthorizeApiKey and it's working fine and calls to APIs is also working. APIs need header "Authorization: Bearer xxxxxxxxxxx". I had key stored in react store and reading using getToken().
import React from 'react';
import SwaggerUI from 'swagger-ui-react';
import swaggerSpec from '../../swagger.json';
import { getToken } from '../../api/utils'
export const complete=function(swaggerUi)
{
let token = getToken();
swaggerUi.preauthorizeApiKey('bearerAuth', token.Token);
}
const ApiDocs = () => {
return <SwaggerUI spec={swaggerSpec} onComplete={(swaggerUi) => complete(swaggerUi)} />
};
export default ApiDocs;
Below is my route configuration:
<Route path="/api-docs" component={ApiDocs} />
I don't need to click on Authorize button on swagger UI screen and it is auto Authorized. Just wanted to share for any comment/suggestion/improvement.
The onComplete props should be a function. Please see the document here.
import React from "react";
import SwaggerUI from "swagger-ui-react";
import swaggerSpec from "../../swagger.json";
import { getToken } from "../../api/utils";
export const complete = function (swaggerUi) {
const token = getToken();
swaggerUi.preauthorizeApiKey("bearerAuth", token.Token);
};
const ApiDocs = () => {
return <SwaggerUI spec={swaggerSpec} onComplete={complete} />;
};
export default ApiDocs;

React: A service returning a ui component (like toast)?

Requirement: Show toast on bottom-right corner of the screen on success/error/warning/info.
I can create a toast component and place it on any component where I want to show toasts, but this requires me to put Toast component on every component where I intend to show toasts. Alternatively I can place it on the root component and somehow manage show/hide (maintain state).
What I am wondering is having something similar to following
export class NotificationService {
public notify = ({message, notificationType, timeout=5, autoClose=true, icon=''}: Notification) => {
let show: boolean = true;
let onClose = () => {//do something};
if(autoClose) {
//set timeout
}
return show ? <Toast {...{message, notificationType, onClose, icon}} /> : </>;
}
}
And call this service where ever I need to show toasts.
Would this be the correct way to achieve the required functionality?
You can use AppContext to manage the state of your toast and a hook to trigger it whenever you want.
ToastContext:
import React, { createContext, useContext, useState } from 'react';
export const ToastContext = createContext();
export const useToastState = () => {
return useContext(ToastContext);
};
export default ({ children }) => {
const [toastState, setToastState] = useState(false);
const toastContext = { toastState, setToastState };
return <ToastContext.Provider value={toastContext}>{children}</ToastContext.Provider>;
};
App:
<ToastProvider>
<App/>
<Toast show={toastState}/>
</ToastProvider>
Then anywhere within your app you can do:
import {useToastState} from 'toastContext'
const {toastState, setToastState} = useToastState();
setToastState(!toastState);

Resources