Reputation: 1751
I have a <Login>
component/form that uses <Formik>
to keep track of my forms state. There's a specific value props.values.userType
that I want to pass to a context provider so I can pass this prop down to my routing component.
My goal is to redirect users that aren't logged in as admin and if they are indeed an admin proceed to render the route as normal.
So I created an AuthContext
.
const AuthContext = React.createContext();
In my <Login>
component I have the <Formik>
component below. Where should I use AuthContext.Provider
and how should I pass values.props.userType
to that provider? Should props.values.userType
be initialized in state of the class component this <Formik>
component lives in ?
Or should I create an object store in state that keeps track of the userType
? Something like this
export const AuthContext = createContext({
user: null,
isAuthenticated: null
});
I have a codesandbox here.
class FormikLoginForm extends Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
const {} = this.state;
return (
<Formik
initialValues={{
username: "",
password: "",
userType: "",
}}
onSubmit={(values, { setSubmitting }) => {
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
setSubmitting(false);
}, 500);
}}
validationSchema={Yup.object().shape({
userType: Yup.string().required("User type is required"),
username: Yup.string().required(
"Required -- select a user type role"
),
password: Yup.string().required("Password is required"),
})}
>
{props => {
const {
values,
touched,
errors,
dirty,
isSubmitting,
handleChange,
handleBlur,
handleSubmit,
handleReset
} = props;
return (
<>
<Grid>
<Grid.Column>
<Header as="h2" color="teal" textAlign="center">
Log-in to your account
</Header>
<Form size="large" onSubmit={handleSubmit}>
<Segment stacked>
<RadioButtonGroup
id="userType"
label="User Type"
value={values.userType}
error={errors.userType}
touched={touched.userType}
>
Then in my index.js
file, where I render all my routes, I have my AdminRoute
that uses the logic I described above
const AdminRoute = props => {
const { userType, ...routeProps } = props;
if (userType !== "admin") return <Redirect to="/login" />;
return <Route {...routeProps} />;
};
const Routes = () => (
<Router>
<Switch>
<Route exact path="/" component={Home} />
<Route path="/login" component={FormikLoginForm} />
<Route exact path="/admin" component={AdminPage} />
/>
<Route path="/admin/change-password" component={ChangePassword} />
</Switch>
</Router>
);
Upvotes: 3
Views: 7989
Reputation: 991
As you are using React Router I would recommend following the example in their docs for authentication flow and create a PrivateRoute
component and use that in place of the regular Route
component.
Formik itself wouldn't be the solution to this, it's simply a way to make it easier to interact with forms and perform form validation. Letting the user pass their own type that's later used for authorization does not seem to be a good idea unless that's also somehow required for the authentication flow.
As for the userType
it seems to me it's something you should get as a claim from a successful login via an API endpoint, or from whatever login backend you are using. And yes, you could store that in your AuthContext and use it like so (assuming you use React 16.8+ in your project setup):
function AdminPrivateRoute({ component: Component, ...rest }) {
const auth = React.useContext(AuthContext);
return (
<Route
{...rest}
render={props =>
auth.isAuthenticated && auth.userType === 'admin' ? (
<Component {...props} />
) : (
<Redirect
to={{
pathname: "/login",
state: { from: props.location }
}}
/>
)
}
/>
);
}
The AuthContext.Provider
component should be used close to, if not at, the top of the component tree. In your code sample I'd say in the Routes
component. You probably also want to implement a way to interact with the context as it will need to be dynamic. Based on the React documentation it could look something like this:
// may want to pass initial auth state as a prop
function AuthProvider({ children }) {
const [state, setState] = React.useState({
isAuthenticated: false,
userType: null,
// other data related to auth/user
});
// may or may not have use for React.useMemo here
const value = {
...state,
// login() does not validate user credentials
// it simply sets isAuthenticated and userType
login: (user) => setState({ isAuthenticated: true, userType: user.type }),
// logout() only clears isAuthenticated, will also need to clear auth cookies
logout: () => setState({ isAuthenticated: false, userType: null }),
};
return (
<AuthContext.Provider value={value}>{children}</AuthContext.Provider>
);
}
Upvotes: 2