can anyone help me how to use react strapi auth with email with user persist in session thruough context
https://github.com/strapi/community-content/issues/770
We are going to look to write an article on the topic, but here is an example you can reference now from one of my personal projects. It is not done yet, but you can see an example of how I am implementing basic login and user registration.
GitHubLink Frontend
GitHubLink Backend
Here is also a video around logging in with Strapi and React I made
Create a login form with React
I will show quick code examples below for reference but you can pull my code, and run it to learn more about my example.
My Home Page
Home.jsx
GitHub Home.jsx
This is where I display the SigninForm or SignupForm based on what the user chooses.
import { useState, useContext } from "react";
import { GlobalContextState } from '../context/globalContext';
import { Navigate } from 'react-router-dom';
import SigninForm from "../components/SigninForm/SigninForm";
import SignupForm from "../components/SignupForm/SignupForm";
import Page from "../styled/base/Page/Page";
import Heading from "../components/Heading/Heading";
import Grid from "../styled/layout/Grid/Grid";
import GridItem from "../styled/layout/Grid/GridItem";
import Benefits from '../components/Benefits/Benefits';
export default function Home() {
const [selection, setSelection] = useState("signin");
const { loggedIn } = useContext(GlobalContextState);
if (loggedIn) return <Navigate to="/dashboard/teams" />;
return (
<Page className="bg-gray-800">
<main className="h-full flex justify-center content-center">
<Grid>
<GridItem>
<Heading
heading="Welcome to Teams/we make it easy"
subheading="Manage your teams, members and projects all in one place."
/>
</GridItem>
<GridItem>
{selection === "signin" ? (
<SigninForm setSelection={setSelection} />
) : (
<SignupForm setSelection={setSelection} />
)}
</GridItem>
</Grid>
</main>
<Benefits />
</Page>
);
}
My Singin Form
Signin.jsx
GitHub Singin Form
import { useState, useContext, useEffect } from "react";
import { GlobalContextDispatch } from "../../context/globalContext";
import { isRegexValid, checkURLRegex } from "../../helpers/isRegexValid";
import useFetchMutation from "../../hooks/useFetchMutation";
import {
FormWrapper,
FormContainer,
FormImage,
FormBox,
FormHeading,
FormError,
} from "../../styled/styles/form";
import Button from "../../styled/base/Button/Button";
import ButtonLink from "../../styled/base/ButtonLink/ButtonLink";
import Input from "../../styled/base/Input/Input";
import ErrorMessage from "../ErrorMessage/ErrorMessage";
import MockUser from '../MockUser/MockUser';
import { baseUrl } from '../../config';
const INITIAL_FORM_DATA = {
identifier: "",
password: "",
};
const INITIAL_FORM_ERRORS = {
identifier: false,
password: false,
};
const loginUrl = `${baseUrl}/api/auth/local`;
export default function Login({ setSelection }) {
const dispatch = useContext(GlobalContextDispatch);
const [login, { loading, error, data }] = useFetchMutation(loginUrl);
const [formData, setFormData] = useState(INITIAL_FORM_DATA);
const [formError, setFormError] = useState(INITIAL_FORM_ERRORS);
useEffect(() => {
if (data) {
const { jwt, user } = data;
dispatch({ type: "LOGIN", payload: { jwt, user } });
}
}, [data, dispatch]);
function handleInputChange(event) {
const { name, value } = event.target;
setFormData({ ...formData, [name]: value });
}
function validateidentifier(identifier) {
if (!isRegexValid(identifier, checkURLRegex)) {
setFormError((prevState) => ({ ...prevState, identifier: true }));
return true;
} else {
setFormError((prevState) => ({ ...prevState, identifier: false }));
return false;
}
}
function validatePassword(password) {
if (password.length <= 6) {
setFormError((prevState) => ({ ...prevState, password: true }));
return true;
} else {
setFormError((prevState) => ({ ...prevState, password: false }));
return false;
}
}
function formValidation(formData) {
let hasError = false;
hasError = validateidentifier(formData.identifier) ? true : false;
hasError = validatePassword(formData.password) ? true : false;
return hasError;
}
async function hadleFormSubmit(event) {
event.preventDefault();
const hasErrors = formValidation(formData);
if (!hasErrors) {
const loginPayload = {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(formData),
};
login(loginPayload);
}
}
return (
<FormWrapper>
<FormBox className="mt-8">
<FormContainer>
<form onSubmit={hadleFormSubmit}>
<fieldset className="space-y-6"disabled={loading}>
<FormBox>
<FormImage
src="https://tailwindui.com/img/logos/workflow-mark-indigo-600.svg"
alt="Workflow"
/>
<FormHeading>Sign in</FormHeading>
</FormBox>
<Input
id="identifier"
name="identifier"
type="identifier"
label="Email Address"
autoComplete="off"
placeholder="Enter your email"
onChange={handleInputChange}
onBlur={(e) => validateidentifier(e.target.value)}
value={formData.identifier}
error={
formError.identifier && "Please provide a valid email"
}
/>
<Input
id="password"
name="password"
type="password"
label="Password"
autoComplete="new-password"
placeholder="Enter your password"
onBlur={(e) => validatePassword(e.target.value)}
value={formData.password}
error={
formError.password && "Password must be at least 6 characters"
}
onChange={handleInputChange}
/>
<MockUser />
<Button className="mt-6 mb-6" type="submit">
{loading ? "Loading..." : "Sign in"}
</Button>
<div className="flex items-center justify-between">
<div className="flex items-center">
<span className="ml-2 block text-sm text-gray-900">
Don't have an account?
</span>
</div>
<ButtonLink onClick={() => setSelection("signup")}>
Sign Up
</ButtonLink>
</div>
</fieldset>
</form>
{error && (
<FormError>
<ErrorMessage message={error.message} />
</FormError>
)}
</FormContainer>
</FormBox>
</FormWrapper>
);
}
We are saving our data to context via
import { GlobalContextDispatch } from "../../context/globalContext";
useEffect(() => {
if (data) {
const { jwt, user } = data;
dispatch({ type: "LOGIN", payload: { jwt, user } });
}
}, [data, dispatch]);
Here is how my context looks like
context/globalContext.js
import { createContext, useEffect } from "react";
import { useImmerReducer } from "use-immer";
import { useLocalStorage } from "../hooks/useLocalStorage";
import { useFetchTeams } from "./contextHooks/useFetchTeams";
import { useFetchTeamUsers } from "./contextHooks/useFetchTeamUsers";
export const GlobalContextState = createContext();
export const GlobalContextDispatch = createContext();
export const GlobalContextMethods = createContext();
const initialState = {
loggedIn: false,
user: null,
token: null,
teams: [],
teamUsers: {},
};
const reducer = (draft, action) => {
switch (action.type) {
case "LOGIN":
draft.loggedIn = true
draft.user = action.payload.user;
draft.token = action.payload.jwt;
return;
case "LOGOUT":
draft.loggedIn = false;
return;
case "GET_TEAMS": {
draft.teams = action.payload;
return;
}
case "GET_TEAM_USERS": {
draft.teamUsers = {
...draft.teamUsers,
[action.payload.teamID]: action.payload.users,
};
return;
}
default:
return draft;
}
};
const GlobalContextProvider = ({ children }) => {
const [data, setData] = useLocalStorage("teams-app-data", initialState);
const [state, dispatch] = useImmerReducer(reducer, data);
useEffect(() => {
if (data.loggedIn === false) localStorage.removeItem("teams-app-data");
setData(state);
}, [state, setData, data]);
const fetchTeams = useFetchTeams(state, dispatch);
const fetchTeamUsers = useFetchTeamUsers(state, dispatch);
return (
<GlobalContextState.Provider value={state}>
<GlobalContextDispatch.Provider value={dispatch}>
<GlobalContextMethods.Provider value={{ fetchTeams, fetchTeamUsers }}>
{children}
</GlobalContextMethods.Provider>
</GlobalContextDispatch.Provider>
</GlobalContextState.Provider>
);
};
export default GlobalContextProvider;
My SignIUp Form
SignupFotrm.jsx
GitHub SignupForm
import { useState, useContext, useEffect } from "react";
import { baseUrl } from '../../config';
import {
GlobalContextDispatch,
} from "../../context/globalContext";
import useFetchMutation from '../../hooks/useFetchMutation';
import { isRegexValid, checkURLRegex } from "../../helpers/isRegexValid";
import {
FormWrapper,
FormContainer,
FormImage,
FormBox,
FormHeading,
FormError,
} from "../../styled/styles/form";
import Button from "../../styled/base/Button/Button";
import ButtonLink from "../../styled/base/ButtonLink/ButtonLink";
import Input from "../../styled/base/Input/Input";
const registerUrl = `${baseUrl}/api/auth/local/registerUser`;
const INITIAL_FORM_DATA = {
firstName: "",
lastName: "",
email: "",
password: "",
};
const INITIAL_FORM_ERRORS = {
firstName: false,
lastName: false,
email: false,
password: false,
};
export default function SignupForm({ setSelection }) {
const dispatch = useContext(GlobalContextDispatch);
const [registerUser, { data, loading, error} ] = useFetchMutation(registerUrl)
const [formData, setFormData] = useState(INITIAL_FORM_DATA);
const [formError, setFormError] = useState(INITIAL_FORM_ERRORS);
useEffect(() => {
if (data) {
const { jwt, user } = data;
dispatch({ type: "LOGIN", payload: { jwt, user } });
}
}, [data, dispatch]);
function handleInputChange(event) {
const { name, value } = event.target;
setFormData({ ...formData, [name]: value });
}
function validateGeneric(text, name) {
if (text.length === 0) {
setFormError((prevState) => ({ ...prevState, [name]: true }));
return true;
} else {
setFormError((prevState) => ({ ...prevState, [name]: false }));
return false;
}
}
function validateEmail(email) {
if (!isRegexValid(email, checkURLRegex)) {
setFormError((prevState) => ({ ...prevState, email: true }));
return true;
} else {
setFormError((prevState) => ({ ...prevState, email: false }));
return false;
}
}
function validatePassword(password) {
if (password.length <= 6) {
setFormError((prevState) => ({ ...prevState, password: true }));
return true;
} else {
setFormError((prevState) => ({ ...prevState, password: false }));
return false;
}
}
function formValidation(formData) {
let hasError = false;
hasError = validateGeneric(formData.firstName, "firstName") ? true : false;
hasError = validateGeneric(formData.lastName, "lastName") ? true : false;
hasError = validateEmail(formData.email) ? true : false;
hasError = validatePassword(formData.password) ? true : false;
return hasError;
}
function hadleFormSubmit(event) {
event.preventDefault();
const hasErrors = formValidation(formData);
if (!hasErrors) {
const registerPayload = {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(formData),
};
registerUser(registerPayload)
}
}
if (error) return <h1>OOPPS!</h1>
return (
<FormWrapper>
<FormBox className="mt-8">
<FormContainer>
<form onSubmit={hadleFormSubmit}>
<fieldset className="space-y-6" disabled={loading}>
<FormBox>
<FormImage
src="https://tailwindui.com/img/logos/workflow-mark-indigo-600.svg"
alt="Workflow"
/>
<FormHeading>Sign Up</FormHeading>
</FormBox>
<Input
id="firstName"
name="firstName"
type="text"
label="First Name"
placeholder="Enter your first name"
onChange={handleInputChange}
onBlur={(e) => validateGeneric(e.target.value, "firstName")}
value={formData.firstName}
error={formError.firstName && "Please provide a first name"}
/>
<Input
id="lastName"
name="lastName"
type="text"
label="Last Name"
placeholder="Enter your last name"
onChange={handleInputChange}
onBlur={(e) => validateGeneric(e.target.value, "lastName")}
value={formData.lastName}
error={formError.lastName && "Please provide a last name"}
/>
<Input
id="email"
name="email"
type="email"
autoComplete="off"
label="Email Address"
placeholder="Enter your email"
onChange={handleInputChange}
onBlur={(e) => validateEmail(e.target.value)}
value={formData.email}
error={formError.email && "Please provide a valid email"}
/>
<Input
id="password"
name="password"
type="password"
label="Password"
autoComplete="new-password"
placeholder="Enter your password"
onBlur={(e) => validatePassword(e.target.value)}
value={formData.password}
error={
formError.password && "Password must be at least 6 characters"
}
onChange={handleInputChange}
/>
<Button className="mt-6 mb-6" type="submit">
Register
</Button>
<div className="flex items-center justify-between">
<div className="flex items-center">
<span className="ml-2 block text-sm text-gray-900">
Have an account?
</span>
</div>
<ButtonLink onClick={() => setSelection("signin")}>
Sign In
</ButtonLink>
</div>
</fieldset>
</form>
<FormError></FormError>
</FormContainer>
</FormBox>
</FormWrapper>
);
}
You can use Strapi docs to see how user sign in and registration is handled here
Here is also a video around logging in with Strapi and React I made
Create a login form with React
We are looking for an author for WFC to make a tutorial, but in the mean time I hope this example helps you.