Vanilla Use Reducer with TypeScript
Tags: typescript, react
Published: 2021-06-16 20:11:22

Hi, I was recently doing a project for my portfolio, a React Project with a slightly complex state. Personally, I really love TypeScript and the Redux/Reducer patterns, because they give me a great Developer Experience with TypeScript and a lot of autocompletion available, quite useful in order to not get bit by a bug caused by a typo, and really convenient as I don't have to explore files constantly in order to know what are the properties that a certain object *should* have.

I always prefer to use TS over vanilla JS, so, it makes a lot of sense to try to make use of strict typing, even in a concept app. Also, while vanilla useReducer might not be what most projects use, I still want to share how I found it to be pretty useful in this context.

## The problem

My application is a Social Media-like platform, so a very important part is the use of a **user**, whose information lives in the global state, that I'm going to access constantly for making requests to the server and other stuff. In order to keep my Client-Side code tidy and predictable, I built a state into a `UserReducer` which is going to store the user info and make changes, this is then exposed in an almost global context in order to be accessible anywhere in the frontend.

So, my goals in typing the Reducer, are quite simple:
- Get better intellisense whenever I need to dispatch an action.
- Make quite simple the construction of an `Action Creator`.
- Having appropriate intellisense in the `action.payload` inside the reducer.

## The solution

### Typing the state

More often than not, the initial state isn't going to be a great example of how the *usual* state will look like, so it's pretty important to declare a State Type, in my particular use case, I'm going to propose the following typing:

```typescript
interface UserState {
    isLogged: boolean;
    userID: string | null;
    username: string | null;
}
```
I must type userID and username as string or null, as null it's my default state whenever I have no user. However, it's pretty important to type it as string, If I were to not define this interface, useReducer would infer my State type from the following initial state

```typescript
const initialState = {
    isLogged: false,
    userID: null,
    username: null,
}

// typeof initialState = {
// 	isLogged: boolean;
// 	userID: never;
// 	username: never;
// }

```
And would make the TS compiler complain whenever I try to set a valid `userID` as a `string`.


### Typing the actions

The first step in order for this to be successful, is to type adequately all the actions that the reducer might consume. This is pretty important, as using string literals (specifically in `action.type`) will help the TS to narrow down the needed payload. In the end, it's only a matter of making a union type between the individual interfaces.

```typescript

interface UserLoginAction {
    type: "LOGIN";
    payload: yourPayload;
}

interface UserLogoutAction {
    type: "LOGOUT";
}

type UserReducerAction = UserLoginAction | UserLogoutAction;

```

### Writing the reducer

The last part of making this cohesive, is to correctly type the reducer function


```typescript
const userReducer = (
	state = initialState, 
    // initialState must be explicitly typed as UserState, 
    // in order to state be correctly typed
	action: UserReducerAction
): UserState => {
	switch (action.type) {
        // We get nice intellisense in the case statement too, 
        // which shows us an error if we don't type the type correctly
		case "login":
        // Action gets typed as a UserLoginAction type, 
        // which helps us having the appropriate payload
			return {
				...state,
				isLogged: true,
				userID: action.payload.id,
				username: action.payload.username,
			};
		case "logout":
        // trying to use action.payload will result in an error
			return {
				...state,
				isLogged: false,
				userID: null,
				username: null,
			};
		default:
			return state;
	}
};


```


## Conclusion

And there it is, now we have a correctly typed useReducer pattern, which can be extended to, maybe creating Action Creators, or whatever you may like.