Following this example, you can see how to handle input changes, form submissions, api requests, parent to child data sharing (through component input props) and state persistence using local storage (through a 3rd party library use-persisted-state
).
Code for this example can be found below:
This example contains an auth module, within which we have an auth component, login page, login form component and auth module.
This provides context (data/state/functions) to components (or pages) within the auth module.
Create auth context function and export it.
export const AuthContext = () => {
...
}
Create a store for auth state (hasAuth
), a way to set it (setAuth
) and set it's default state to false.
const [hasAuth, setAuth] = AuthState(false);
Define an object to return from the context function. This contains the hasAuth
state, a login
function and a logout
function.
return {
hasAuth,
login: ...,
logout: ...
};
The login
function calls another function (processLogin
) with the auth
object which was passed to the login
function. It then waits for a response and sets the auth state to the response value (loginValue
).
login: async auth => {
// Attempt to login
const loginValue = await processLogin(auth);
// Update auth state based on API response
setAuth(loginValue);
// Return login response
return loginValue;
}
The logout
function simply sets the hasAuth
state on AuthContext
to false
.
logout: () => setAuth(false)
The auth.context.ts
file also contains another exported function processLogin
. This accepts an auth
argument and returns a callback to an API request handler using the axios
library.
export async function processLogin(auth) {
...
}
Create a POST
request to the login endpoint, sending the auth
object which was provided to the processLogin
function, handle and return the response.
return axios.post("https://reqres.in/api/login", auth).then(
res => {
// If we got a user token
return !!res["data"]["token"];
},
error => {
console.log("error", error);
return false;
}
);
This is the outer template, which is included in the main app component. The location of this page is definitely not ideal - I couldn't decide if it should live in modules/auth
or modules/login
.
Create the login page function component.
const LoginPage: React.FC = () => {
...
};
Call the AuthContext
function and make it accessible within this component.
const authContext = AuthContext();
Render the component. If the user is logged in, AuthComponent
will be rendered. If the user is not logged in LoginFormComponent
will be rendered.
return (
<div>
{/* If user is logged in */}
{authContext.hasAuth ? (
<AuthComponent authContext={authContext} />
) : (
<LoginFormComponent authContext={authContext} />
)}
</div>
);
This component displays the current login state and a logout button. To be honest, this could probably be called LogoutComponent
.
Create the functional component, with a props argument.
const AuthComponent = (props: any) => {
...
};
Get auth context from the component's input props.
const authContext = props.authContext;
Create a store for a button element, if logged in - set it to a logout button.
// Store for logout button element
let button;
// if user has auth
if (authContext.hasAuth) {
// Button to show logout + process logout on click
button = <button onClick={authContext.logout}>Logout</button>;
}
Render the component, showing the login state and logout button (if set).
return (
<div>
<h2>State: {authContext.hasAuth ? "Logged in" : "Logged out"}</h2>
{button}
</div>
);
This is the login form function component, it renders the login form and handles input change and form submission events.
Create the login form function component, with and argument for input props. Access auth context from the component's props.
function LoginForm(props: any) {
// Get authContext from props
const authContext = props.authContext;
...
}
Create a component state to store the state of the login form.
// Create form state on component
const [form, setForm] = React.useState({
// Successful login credentials
email: "[email protected]",
password: "cityslicka",
error: ""
});
Handle login form submission - call the login function with the form state's email and password fields, then wait for the promise to resolve. If authContext.login
returns false
an error will be set on the login form's state.
/** Handles login form submission */
const handleSubmit = event => {
event.preventDefault();
authContext
.login({
email: form.email,
password: form.password
})
.then(response => {
// If login failed
if (!response) {
// Update error on form state
setForm({
...form,
error: "Login failed, please try again"
});
}
});
};
When the email or password fields change, update the form state on the component with the new value of the changed field.
/** Handles email field changes */
const handleEmailChange = event => {
// Updates form state
setForm({
...form,
email: event.target.value
});
};
/** Handles password field changes */
const handlePasswordChange = event => {
// Updates form state
setForm({
...form,
password: event.target.value
});
};