Best Redux Architecture
Tags: react, nextjs, typescript, redux
Published: 2021-08-11 23:22:46

##Intoduction
I want to warn people, which probably will remark about architecture, I absently appreciate your opinion, so if u find some remarks, just tell in comments, thanks.
Stack: `React, NextJs, Typescript, Redux`.

The ideology of this post isn't to write app, its about how powerful is [redux](https://redux.js.org/) with [typescript](https://www.typescriptlang.org/) in [react](https://reactjs.org/) of course, and we will use [nextjs](https://nextjs.org/) to write some example api requests. 

####So lets get started
First step is so simple
```npm
npx create-next-app --typescript
```

So then we installing npm dependency

```npm 
npm i redux react-redux redux-thunk reselect
```
Also you can  delete all usless files.

At first, add folder `store` in root folder and there create a file `index.tsx`, consequently folder `modules` and in this folder we creating another file `index.ts`, also here another  folder with name `App`. 

So store folder should look like that
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cubnnjh7glh3yvj568fa.png)
After that, move to `store/modules/App` and creating base module structure:
`index.ts, action.ts,  enums.ts, hooks.ts, reducers.ts selectors.ts, types.ts`
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nh8mfpco4sbo3ys7hpz3.png)

1. `enum.ts`(for every new action u need new property in [enum]https://www.typescriptlang.org/docs/handbook/enums.html)

```react
export enum TypeNames {
  HANDLE_CHANGE_EXAMPLE_STATUS = 'HANDLE_CHANGE_EXAMPLE_STATUS' 
}
```

2.Then to make magic we need to install dev dependency -[utility-types] (https://www.npmjs.com/package/utility-types)
`types.ts` - the imortant part
```react 
import { $Values } from 'utility-types';
import { TypeNames } from './enums';
```
Just import `TypeNames` and `$Values`

```react 
export type AppInitialStateType = {
  isThisArchitecturePerfect: boolean;
};
```
Describes which type have AppState

```react 
export type PayloadTypes = {
  [TypeNames.HANDLE_CHANGE_EXAMPLE_STATUS]: {
    isThisArchitecturePerfect: boolean;
  };
};
```

```react 
export type ActionsValueTypes = {
  toChangeStatusOfExample: {
    type: typeof TypeNames.HANDLE_CHANGE_EXAMPLE_STATUS;
    payload: PayloadTypes[TypeNames.HANDLE_CHANGE_EXAMPLE_STATUS];
  };
};
```
That's the code we need to tell our reducers which type of different actions we have.
specification* `toChangeStatusOfExample` can have just a random name, but I also give the identical name as (action function, but its a little bit soon)

```react 
export type AppActionTypes = $Values<ActionsValueTypes>
```
In this step we need to make typescript magic, we will see soon, what magic I am telling.

So in result our `types.ts` file should look like that
```react
import { $Values } from 'utility-types';
import { TypeNames } from './enums';

export type PayloadTypes = {
  [TypeNames.HANDLE_CHANGE_STATUS_OF_EXAMPLE]: {
    isThisArchitecturePerfect: boolean;
  };
};

export type ActionsValueTypes = {
  toChangeStatusOfExample: {
    type: typeof TypeNames.HANDLE_CHANGE_STATUS_OF_EXAMPLE;
    payload: PayloadTypes[TypeNames.HANDLE_CHANGE_STATUS_OF_EXAMPLE];
  };
};
export type AppActionTypes = $Values<ActionsValueTypes>;

export type AppInitialStateType = {
  isThisArchitecturePerfect: boolean;
};
```
You can presume that it is so bulky and over-coding, but if you appreciate your time its will give you the opportunity to save a lot of time in the future.

3.So next move to file `reducers.ts`
```react 
import { TypeNames } from './enums';
import { AppActionTypes, AppInitialStateType } from './types';
```
As always at first we import modules.

```react 
const initialState: AppInitialStateType = {};
```
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/phwdzfcovd9grqun9cz7.png)

Remarkably, as you see, it a typescript magic, because we have given to `initialState` the  type `AppInitialStateType` where was describes that's const should have property `isThisArchitecturePerfect`, `isThisArchitecturePerfect`, 
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zdpf78htzkmc0dlxqcte.png)
so when we will started to write something, we will again see the typescript magic.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2jis3v2sns03nm4ao0h4.png)

Consequently, when we will start to write something, we will again see the typescript magic.

```react
export const appReducer = (state = initialState, action: AppActionTypes): AppInitialStateType => {
  switch (action.type) {
    default:
      return state;
  }
}; 
```
Pro temporary nothing special, just basic redux reducer with switch construction. 

4. In `index.ts` we just exporting our `appReducer` with `default` construction.
```react
import { appReducer as app } from './reducers';
export default app;
```

At least right now we should have something like that
```typescript
//enum.ts**

export enum TypeNames {
  HANDLE_CHANGE_STATUS_OF_EXAMPLE = 'HANDLE_CHANGE_STATUS_OF_EXAMPLE',
}

//types.ts**

import { $Values } from 'utility-types';
import { TypeNames } from './enums';

export type PayloadTypes = {
  [TypeNames.HANDLE_CHANGE_STATUS_OF_EXAMPLE]: {
    isThisArchitecturePerfect: boolean;
  };
};

export type ActionsValueTypes = {
  toChangeStatusOfExample: {
    type: typeof TypeNames.HANDLE_CHANGE_STATUS_OF_EXAMPLE;
    payload: PayloadTypes[TypeNames.HANDLE_CHANGE_STATUS_OF_EXAMPLE];
  };
};
export type AppActionTypes = $Values<ActionsValueTypes>;

export type AppInitialStateType = {
  isThisArchitecturePerfect: boolean;
}

//reducers.ts

import { TypeNames } from './enums';
import { AppActionTypes, AppInitialStateType } from './types';

const initialState: AppInitialStateType = {
  isThisArchitecturePerfect: true,
};
export const appReducer = (state = initialState, action: AppActionTypes): AppInitialStateType => {
  switch (action.type) {
    default:
      return state;
  }
}; 

//index.ts
import { appReducer as app } from './reducers';
export default app;

 ```

So if yes, my congradulation, but what not all, then in `store/modules/index.ts` 
```react 
export { default as app } from './App';
```
This is a feature of es6 js.

And then we should connect it in `store/index.ts` by coding this : 
```react
import { createStore, combineReducers, applyMiddleware, compose } from 'redux';
import thunkMiddleware from 'redux-thunk';
import * as reducers from './modules';

const combinedRedusers = combineReducers({ ...reducers });
const configureStore = createStore(combinecRedusers, compose(applyMiddleware(thunkMiddleware)));

export default configureStore;
```

`* as reducers` will import all reducers which you import in prev step, for sure we applying `thunkMiddleware` to async code. And exporting store of course.

After this, we need connect store to our `pages/_app.tsx` file, so we can do that by:
1. Creating in`layouts` folder `StoreLayout`, here create `index.tsx` which have `<Provider store={store}>{children}</Provider>`, I get sm like that:
```react
import { FC } from 'react';
import { Provider as ReduxProvider } from 'react-redux';
import store from './../../store';

const StoreLayout: FC = ({ children }) => {
  return <ReduxProvider store={store}>{children}</ReduxProvider>;
};

export default StoreLayout;
```
2.The main feature of `layouts` its that firstly we creating `layouts/index.tsx` file with this code:
```react 
import { FC } from 'react';

export const ComposeLayouts: FC<{ layouts: any[] }> = ({ layouts, children }) => {
  if (!layouts?.length) return children;

  return layouts.reverse().reduce((acc: any, Layout: any) => <Layout>{acc}</Layout>, children);
};
``` 
The main idea isn't to have the nesting of your `Providers` because at least you will have a lot of different `Providers`. We can make it so simple with[reduce()](https://dev.to/pas8/reduce-is-awesome-2aio). 
And finally  in `pages/_app.tsx` we need change default next code to our 
```react 
import type { AppProps } from 'next/app';
import StoreLayout from '../layouts/StoreLayout';
import { ComposeLayouts } from '../layouts/index';

const _App = ({ Component, pageProps }: AppProps) => {
  const layouts = [StoreLayout];

  return (
    <ComposeLayouts layouts={layouts}>
      <Component {...pageProps} />
    </ComposeLayouts>
  );
};
export default _App;
```

Of course, we want that our state isn't be static, so to do that we need to move to `store/modules/App/action.ts` and write simple action function, like that:

```react 
import { TypeNames } from './enums';
import { AppActionTypes, PayloadTypes } from './types';

export const toChangeThemePropertyies = (
  payload: PayloadTypes[TypeNames.HANDLE_CHANGE_STATUS_OF_EXAMPLE]
): AppActionTypes => ({
  type: TypeNames.HANDLE_CHANGE_STATUS_OF_EXAMPLE,
  payload
});

```
The important thing is to give `payload(param of function)` the correct type, so because we have [enum](https://www.typescriptlang.org/docs/handbook/enums.html) TypeNames we cannot make mistakes with type naming. And the most impressive is that when we writing that this action should return `AppActionTypes`(its type with all actions type), and then writing in function `type: TypeNames.HANDLE_CHANGE_STATUS_OF_EXAMPLE`, payload will automatically be found. We will see the example soon.

Also having the opportunity, open `store/modules/App/selectors.ts`, there we use library [reselect](https://www.npmjs.com/package/reselect) to have access to our state, main idea that if store chaging, and we using some value from store, component will rerender without `reselect` so, its so powerfull. But until we start creating reducers we need to have `RootStoreType` and I like to creating a new global folder `models` and here also create file `types.ts` 
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rawjy5iwwq5hy50c6x8h.png) and here:
```react 
import { AppInitialStateType } from '../store/modules/App/types';
export type RootStoreType = { app: AppInitialStateType };
```
In this code, we should describe `RootStoreType` with all `reducers`. Now back to `store/modules/App/selectors.ts`

As always:
```react 
import { RootStoreType } from '../../../models/types';
import { createSelector } from 'reselect';
```
Then good practice its starts naming your selector with `get 
+ someName`,like that:
`export const getIsThisArchitecturePerfect= createSelector()`
Also,`createSelector` have 2 params:
* Array with functions (in our case)`(state:RootStoreType) =>state.app.isThisArchitecturePerfect `
* Function which takes in param (return values of prev Arr) and returning value which u need,
Result code:
```react
import { RootStoreType } from '../../../models/types';
import { createSelector } from 'reselect';

export const getIsThisArchitecturePerfect= createSelector(
  [(state: RootStoreType) => state.app.isThisArchitecturePerfect],
  isThisArchitecturePerfect => isThisArchitecturePerfect
);
```
Finally, we can test does our logic work,to do that move to `pages/index.tsx;` and write this code:
```react

import { useSelector } from 'react-redux';
import { getIsThisArchitecturePerfect } from '../store/modules/App/selectors';

const Index = () => {
  const isThisArchitecturePerfect = useSelector(getIsThisArchitecturePerfect);
  console.log(isThisArchitecturePerfect);
  return <></>;
};

export default Index;
 ```
Where we import [useSelector](https://react-redux.js.org/api/hooks) to get access to our store and paste to this how our selector, then due to`console.log(isThisArchitecturePerfect)` we will see the result.
So save all and run
 ```npm
npm run dev
```
(F12 to open dev tools), I'm kidding because everybody knows that)
I think u we ask me, that our app is so static, and I will answer, yeah, and right now, will add some dynamic. Also to have better look, let's add simple stying and jsx markup and
we need a [useDispatch()](https://react-redux.js.org/api/hooks) to change our store  and imported our action function `toChangeThemePropertyies`, also let's create 2 functions to change value (first to true, second to false) like that:
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2nr85k9i6trpzshn8fqi.png)

as u see I especially, set `'true'` not true, so this is typescript magic, u always know that your code work as you expect. I don't use CSS, because I so love to use  [JSS](https://cssinjs.org/?v=v10.7.1), because it have unbelievable functionality, and I have zero ideas why [JSS](https://cssinjs.org/?v=v10.7.1) is not so popular, but it's not about styling.

```react
import { useDispatch, useSelector } from 'react-redux';
import { toChangeThemePropertyies } from '../store/modules/App/actions';
import { getIsThisArchitecturePerfect } from '../store/modules/App/selectors';

const Index = () => {
  const isThisArchitecturePerfect = useSelector(getIsThisArchitecturePerfect);
  const dispatch = useDispatch();

  const handleSetExampleStatusIsTrue = () => {
    dispatch(toChangeThemePropertyies({ isThisArchitecturePerfect: true }));
  };
  const handleSetExampleStatusIsFalse = () => {
    dispatch(toChangeThemePropertyies({ isThisArchitecturePerfect: false }));
  };

  const containerStyling = {
    width: 'calc(100vw + 2px)',
    margin: -10,
    height: '100vh',
    display: 'grid',
    placeItems: 'center',
    background: '#222222',
  };

  const textStyling = {
    color: 'white',
    fontFamily: 'Monospace',
  };

  const buttonContainerStyling = {
    display: 'flex',
    gap: 10,
    marginTop: 20,
    alignItems: 'center',
    justifyContent: 'center',
  };

  const buttonStyling = {
    ...textStyling,
    borderRadius: 8,
    cursor: 'pointer',
    border: '1px solid white',
    background: 'transparent',
    padding: '8px 42px',
    width: '50%',
    fontSize: 18,
    fontFamily: 'Monospace',
  };

  return (
    <>
      <div style={containerStyling}>
        <div>
          <h1 style={textStyling}>{'- Is This Architecture Perfect?'}</h1>
          <h1 style={textStyling}>{`- ${isThisArchitecturePerfect}`.toUpperCase()}</h1>
          <div style={buttonContainerStyling}>
            <button style={{ ...buttonStyling, textTransform: 'uppercase' }} onClick={handleSetExampleStatusIsTrue}>
              True
            </button>
            <button style={{ ...buttonStyling, textTransform: 'uppercase' }} onClick={handleSetExampleStatusIsFalse}>
              False
            </button>
          </div>
        </div>
      </div>
    </>
  );
};

export default Index;
 ```

If you attentive, i guess u know why code don't work, so try to fix this small detail by yourself, if u don't wanna.
Solution that in `store/modules/App/reducers.ts` we forget to write `case` of our `reducer switch construction` so to fix that we need to write this
```react
 case TypeNames.HANDLE_CHANGE_STATUS_OF_EXAMPLE: {
      const { isThisArchitecturePerfect } = action.payload;
      return { ...state, isThisArchitecturePerfect };
    }
 ```
and I have feature to improve this code to ```react
//if your action.payload is the same as property in initial state u can write like this:
//case TypeNames.HANDLE_CHANGE_STATUS_OF_EXAMPLE:
//case TypeNames.HANDLE_CHANGE_STATUS_OF_EXAMPLE1:
//case TypeNames.HANDLE_CHANGE_STATUS_OF_EXAMPLE2: ({ ...state, ...action.payload });
// if not, just create a new case

case TypeNames.HANDLE_CHANGE_STATUS_OF_EXAMPLE: ({ ...state, ...action.payload });

 ```
So right now all will be work correctly, but that not all, because as I said in the introduction we will write some simple api, so open or create `pages/api` and there create a file with your api route, in my case its `pages/api/example`, referring [oficial docs](https://nextjs.org/docs/api-routes/introduction)
```react
import type { NextApiRequest, NextApiResponse } from 'next';
import { ApiExampleResType } from '../../models/types';

export default (req: NextApiRequest, res: NextApiResponse<ApiExampleResType>) => {
  res.status(200).json({ title: '- Is This Architecture Perfect?' });
};
```

yeah, and also in `models/types.ts` write type 

```react
 export type ApiExampleResType = { title: string }; 
```
thats we need to 'typescript magic'. Then, we have some trobleness with due to [nextjs getServerSideProps](https://nextjs.org/docs/basic-features/data-fetching#getserversideprops-server-side-rendering), so here we will simplify task, but at least u should use [nextjs getServerSideProps](https://nextjs.org/docs/basic-features/data-fetching#getserversideprops-server-side-rendering) in real app.

So task for you is creating your action function with payload type `ApiExampleResType`, just for training, if you are lazy, see result :
```typescript
//enum.ts**

HANDLE_CHANGE_TITLE_OF_EXAMPLE ='HANDLE_CHANGE_TITLE_OF_EXAMPLE',  

//types.ts**

import { $Values } from 'utility-types';
import { TypeNames } from './enums';
import { ApiExampleResType } from './../../../models/types';

export type PayloadTypes = {
  [TypeNames.HANDLE_CHANGE_STATUS_OF_EXAMPLE]: {
    isThisArchitecturePerfect: boolean;
  };
  [TypeNames.HANDLE_CHANGE_TITLE_OF_EXAMPLE]: ApiExampleResType;
};

export type ActionsValueTypes = {
  toChangeSphereCursorTitle: {
    type: typeof TypeNames.HANDLE_CHANGE_STATUS_OF_EXAMPLE;
    payload: PayloadTypes[TypeNames.HANDLE_CHANGE_STATUS_OF_EXAMPLE];
  };
  toChangeTitleOfExample: {
    type: typeof TypeNames.HANDLE_CHANGE_TITLE_OF_EXAMPLE;
    payload: PayloadTypes[TypeNames.HANDLE_CHANGE_TITLE_OF_EXAMPLE];
  };
};
export type AppActionTypes = $Values<ActionsValueTypes>;

export type AppInitialStateType = {
  isThisArchitecturePerfect: boolean;
} & ApiExampleResType;

//reducers.ts

import { TypeNames } from './enums';
import { AppActionTypes, AppInitialStateType } from './types';

const initialState: AppInitialStateType = {
  isThisArchitecturePerfect: true,
  title: 'Nothing',
};

export const appReducer = (state = initialState, action: AppActionTypes): AppInitialStateType => {
  switch (action.type) {
    case TypeNames.HANDLE_CHANGE_STATUS_OF_EXAMPLE:
    case TypeNames.HANDLE_CHANGE_TITLE_OF_EXAMPLE:
      return { ...state, ...action.payload };

    default:
      return state;
  }
};

//action.ts

import { TypeNames } from './enums';
import { AppActionTypes, PayloadTypes } from './types';

export const toChangeThemePropertyies = (
  payload: PayloadTypes[TypeNames.HANDLE_CHANGE_STATUS_OF_EXAMPLE]
): AppActionTypes => ({
  type: TypeNames.HANDLE_CHANGE_STATUS_OF_EXAMPLE,
  payload,
});

export const toChangeTitleOfExample = (
  payload: PayloadTypes[TypeNames.HANDLE_CHANGE_TITLE_OF_EXAMPLE]
): AppActionTypes => ({
  type: TypeNames.HANDLE_CHANGE_TITLE_OF_EXAMPLE,
  payload,
});


 ```

You have written the same, my congratulations), to have access to new property of our app state, we need to write a new selector, the next step is that in `selectors.ts` we adding this  selector```react
export const getTitle= createSelector(
  [(state: RootStoreType) => state.app.title],
  title => title
);
```

Penultimate step, is in `opetations.ts` 
At first import all dependency 
```react
//types 
import { Action, ActionCreator, Dispatch } from 'redux';
import { ThunkAction } from 'redux-thunk';
import { RootStoreType } from '../../../models/types';
import { AppActionTypes } from './types';
//action
import { toChangeTitleOfExample } from './actions';

``` 
Secondary, created the thunk function with this type`ActionCreator<ThunkAction<Promise<Action>, RootStoreType, void, any>>` in which we have `async` [closure](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures) with type 
`(dispatch: Dispatch<AppActionTypes>): Promise<Action> =>`
in which we sending fetch get request, to our `/api/example` and return is `dispatch(toChangeTitleOfExample(awaited result))`. Probably a little bit bilky, but in result we have 
```react
import { Action, ActionCreator, Dispatch } from 'redux';
import { ThunkAction } from 'redux-thunk';
import { RootStoreType } from '../../../models/types';
import { toChangeTitleOfExample } from './actions';
import { AppActionTypes } from './types';

export const operatoToSetExampleTitle:
  ActionCreator<ThunkAction<Promise<Action>, RootStoreType, void, any>> =
    () =>
      async (dispatch: Dispatch<AppActionTypes>): Promise<Action> => {
      const result = await fetch('/api/example', { method: 'GET' });
      const { title } = await result.json();
      return dispatch(toChangeTitleOfExample({ title }));
    };

 ```
And the final step in `pages/index.tsx`:
```react
  const title = useSelector(getTitle);

  useEffect(() => {
    dispatch(operatoToSetExampleTitle());
  }, []);

 ```
Its no the best practice while we use nextjs, but just as example not the worst, [useEffect](https://reactjs.org/docs/hooks-effect.html#example-using-hooks-1)(()=>{...},[]) - runs only on mount, so and `hooks.ts` we need to use while we have repeated logic in `operations.ts` or `reducers.ts`.


####Conclusion
If you anyway think that is so bulky, I guarantee that this structure is awesome if you will just try to use, then you will not be able to use another architecture.

Thanks for reading, I so appreciate this ♥.

[Source code (GitHub)](https://github.com/pas8/bestReactReduxTsArchitecture).