Hire+Plus! For Employees Here's how I built it (Redux - Auth)
Tags: react, webdev, programming, tutorial
Published: 2022-08-03 04:46:00

**Purpose:** The candidate will be able to login, login with google, sign up, and logout.

I'm using Redux as my state management, more specifically Redux toolkit. A package for making working with redux much simpler than before. You can read more about the details of `Redux Toolkit` [here](https://redux-toolkit.js.org/introduction/getting-started). 

I added it to my project using npm
`npm install @reduxjs/toolkit @react-redux`

I followed the instructions on the docs (linked above) to set it up. 

I created an `app` folder where all my redux code will live, separating my reducers as features related to the project (auth, profile, job company).
![Redux folder structure](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xp6vijr4t0p6wiem1ror.png)

## Configure Redux toolkit
**store.ts** - configure redux and my reducers

```javascript
import { configureStore } from '@reduxjs/toolkit';
import authReducer from './features/auth/authSlice';
import profileReducer from './features/profile/profileSlice';
import jobReducer from './features/job/jobSlice';
import companyReducer from './features/company/companySlice';

export const store = configureStore({
  reducer: {
     auth: authReducer,
     profile: profileReducer,
     job: jobReducer,
     company: companyReducer,
  },
});

// Infer the `RootState` and `AppDispatch` types from the store itself
export type RootState = ReturnType<typeof store.getState>;

// Inferred type: {users: UsersState}
export type AppDispatch = typeof store.dispatch;
```
`RootState` and `AppDispatch` are Redux toolkit's version of using `useSelector` and `useDispatch`. 


**hooks.ts** - export redux toolkit's typed version of state and action hooks.

```javascript
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
import type { RootState, AppDispatch } from './store';

// Use throughout your app instead of plain `useDispatch` and `useSelector`
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
```
**index.tsx** - Pass store provider to the entire app

```javascript
import ReactDOM from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import 'tw-elements';
import './index.css';
import App from './App';
import { store } from './app/store';
import { Provider } from 'react-redux';

const root = ReactDOM.createRoot(document.getElementById('root')!);
root.render(
 <BrowserRouter>
   <Provider store={store}>
     <App />
   </Provider>
 </BrowserRouter>
);
```

---

## Types, Actions and Reducers: Auth state

### Types
**inside `app > features > auth > authTypes.ts`**
Data types for my login info and signup info.

```json
export type LoginFields = {
 email: string;
 password: string;
};
export type SignUpFields = {
 displayName: string;
 email: string;
 password: string;
};
``` 

---

### Actions
**inside `app > features > auth > authSlice.ts`**
setting the initial state for auth reducer, using `signInWithGoogle`, `signInWithEmailAndPassword`, `signUpUserEmailAndPassword`, and `signoutUser` funcs from DB.

`signInWithEmailAndPassword` and `signUpUserEmailAndPassword` both return stringifyed version of auth user. 

```javascript
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';

import {
 signInWithGooglePopup,
 signInEmailAndPassword,
 signUpEmailAndPassword,
 logoutUser,
} from '../../../utils/firebase/firebase.utils';
import { SignUpFields, LoginFields } from './authTypes';

interface authState {
 isSignedIn: boolean;
 currentUser: { uid: string; displayName: string };
 isLoading: boolean;
 signUpError: string;
 signInError: string;
 successMessage: string;
}
const initialState: authState = {
 isSignedIn: false,
 currentUser: { uid: '', displayName: '' },
 isLoading: false,
 signUpError: '',
 signInError: '',
 successMessage: '',
};

// ------- AUTH ACTIONS --------------------------------
export const signInWithGoogle = createAsyncThunk(
	'user/signInWithGoogle',
	async () => {
		await signInWithGooglePopup();
	}
);
export const signInWithEmailAndPassword = createAsyncThunk(
	'user/signInEmailAndPassword',
	async (formFields: LoginFields) => {
		const { user } = await signInEmailAndPassword(
			formFields.email,
			formFields.password
		);
		return JSON.stringify(user);
	}
);
export const signUpUserEmailAndPassword = createAsyncThunk(
	'user/signUpUserEmailAndPassword',
	async (formFields: SignUpFields) => {
		const user = await signUpEmailAndPassword(formFields);
		return JSON.stringify(user);
	}
);
export const signoutUser = createAsyncThunk('user/signoutUser', async () => {
	return await logoutUser();
});

``` 

`signInWithGoogle()` - calls sign in with google func

`signInWithEmailAndPassword()` - take args from the frontend, I stringify the user data before returning it, as data needs to be serialized first. 

`signUpUserEmailAndPassword()` - take args from frontend and pass on helper func, again I stringify the returning user. 

`signoutUser()` - calls logout helper func 

I will be calling these functions in the UI. 

Feel free to look into more detail about the `createAsyncThunk` and how it works on [docs](https://redux-toolkit.js.org/usage/usage-with-typescript#createasyncthunk).

---

### Reducers

I handled the response states and set the state accordingly.
On the `.fulfilled` response state for `signInWithEmailAndPassword` and `signUpUserEmailAndPassword` I get the data back, parse it and set it to state.
 
```javascript
const authSlice = createSlice({
	name: 'auth',
	initialState,
	reducers: {
		
		setSignedIn(state, action) {
			state.isSignedIn = action.payload.signedIn;
			state.currentUser = action.payload.currentUser;
		},
		setSignupError(state, action) {
			state.signUpError = action.payload;
		},
		resetError(state) {
			state.signInError = '';
		},
	},
	extraReducers: (builder) => {
		builder
			.addCase(signInWithGoogle.rejected, (_, action) => {
				console.log('something went wrong with google sign-in', action.error);
			})
			// ---------------------------------------- SIGN IN ACTIONS ---------------------------------
			.addCase(signInWithEmailAndPassword.pending, (state) => {
				state.isLoading = true;
			})
			.addCase(signInWithEmailAndPassword.fulfilled, (state, action) => {
				const { uid, displayName } = JSON.parse(action.payload);
				state.isLoading = false;
				state.currentUser = { uid, displayName };
			})
			.addCase(signInWithEmailAndPassword.rejected, (state) => {
				state.isLoading = false;
				state.signInError = 'User does not exist in the database';
			})
			// --------------------------------------- SIGN UP ACTIONS ---------------------------------
			.addCase(signUpUserEmailAndPassword.pending, (state) => {
				state.isLoading = true;
			})
			.addCase(signUpUserEmailAndPassword.fulfilled, (state, action) => {
				const { displayName, uid } = JSON.parse(action.payload);
				state.isLoading = false;
				state.currentUser = { uid, displayName };
			})
			.addCase(signUpUserEmailAndPassword.rejected, (state, { error }) => {
				state.isLoading = false;
				state.signUpError = error.code;
			})
			// --------------------------------------- SIGN OUT ACTIONS ---------------------------------
			.addCase(signoutUser.fulfilled, (state) => {
				state.isLoading = false;
				state.isSignedIn = false;
			});
	},
});


export const { resetError, setSignupError, setSignedIn } = authSlice.actions;
export default authSlice.reducer;

```

That's all for the auth/redux portion of the project, stay tuned!