Cómo configurar Redux Toolkit con Next.js usando Typescript

Con la llegada de Zustand como gestor de estado para aplicaciones de React y Next.js, fueron muchas las librerías de gestión de estado, como Redux, que perdieron la atención de la mayoría de usuarios, sobre todo al tratarse de una librería super ligera y super simple de implementar.

Sin embargo, ya sea que quieras darle mantenimiento a una aplicación que utilice Redux Toolkit o que simplemente quieras inicializar un nuevo proyecto de Next.js para probar esta librería, aquí veremos en sencillos pasos cómo podemos configurarlo.

Foodima Image

Written by khizerrehandev. Translated from English by Gabriel Maestre

Es importante tener en cuenta que esta configuración funciona con Next.js 14 (App Router), por lo que también funciona para Next.js 13.

Crear un proyecto nuevo

Crea un nuevo proyecto de Next.js usando el comando create-next-app@latest en la terminal.

terminal
npx create-next-app@latest

What is your project named? my-app
Would you like to use TypeScript? No / Yes
Would you like to use ESLint? No / Yes
Would you like to use Tailwind CSS? No / Yes
Would you like to use "src/" directory? No / Yes
Would you like to use App Router? (recommended) No / Yes
Would you like to customize the default import alias (@/*)? No / Yes
What import alias would you like configured? @/*

Intalar dependencias

terminal
npm install @reduxjs/toolkit react-redux

Habiendo instalado nuestras dependencias, ya podemos proceder a configurar nuestro store.

Configurar el store

Crea una carpeta llamada redux o store. Para este ejemplo crearemos store/. Luego crea un archivo llamado store.ts

app/store/store.ts
import { configureStore } from '@reduxjs/toolkit'

export const store = configureStore({
  reducer: {},
})

// Infer the "RootState" and "AppDispatch" types from the store itself
export type RootState = ReturnType<typeof store.getState>
// Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState}
export type AppDispatch = typeof store.dispatch

Este código crea automáticamente el estado de Redux, y automáticamente también configura las Redux DevTools con su extensión para que podamos inspeccionar.

Definir los hooks useDispatch y useSelector

Para ello crearemos un archivo llamado hooks.ts

app/store/hooks.ts
import { useDispatch, useSelector } from 'react-redux';
import type { TypedUseSelectorHook } from 'react-redux';
import { AppDispatch, RootState } from './store';

// Use throughout your app instead of plain "useDispatch" and "useSelector"
export const useAppDispatch: () => AppDispatch = useDispatch;
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;

¿Qué son y cómo se configuran los slices?

Pensemos en los slices como aquellas funcionalidades que puede tener nuestro proyecto que implican el manejo de un estado global. Si lo llevamos a un ejemplo real, podríamos imaginarnos una tienda de ropa. Podemos tener muchas funciones dentro de esta:

  • Slice para autenticación
  • Slice para perfil de usuario
  • Slice para búsquedas o filtros
  • Slice para carrito de compras

De forma similar a esto, podemos tener muchos slices dentro de una aplicación, y por esa razón hemos tomado un ejemplo muy básico en el que hemos creado un counter slice, el cual incrementa +1, + 10, y decrementa también -1.

app/store/counterSlice.ts
import { createSlice } from '@reduxjs/toolkit'

const initialState = {
  value: 0,
}

export const counterSlice = createSlice({
  name: 'counter',
  initialState,
  reducers: {
    increment: (state) => {
 
      state.value += 1
    },
    decrement: (state) => {
      state.value -= 1
    },
    incrementByAmount: (state, action) => {
      state.value += action.payload
    },
  },
})

// Action creators are generated for each case reducer function
export const { increment, decrement, incrementByAmount } = counterSlice.actions

export default counterSlice.reducer // EXPORT Slice reducer

En Redux Toolkit, createSlice se encarga de generar la acción y los reducers mediante un único slice.

Configurar un Provider

Este es un paso muy importante, debido a que para poder colocar un store en nuestra aplicación de Next.js y que nuestros componentes puedan tener acceso al store necesitaremos envolver el Root Layout con un Provider, al cual podremos facilitarle todos nuestros componentes como children.

app/StoreProvider.tsx
'use client'; // 🔴 Lee la nota de debajo

import { Provider } from 'react-redux';
import { store } from './store';

export function Providers({ children }: { children: React.ReactNode }) {
  return <Provider store={store}>{children}</Provider>;
}

export default Providers;

Es sumanente importante añadir 'use client' para hacerle saber a Next.js que este functional component será tratado como un componente generado del lado del cliente y no del servidor. De otra forma arrojará el siguiente error:

Error: This function is not supported in React Server Components. Please only use this export in a Client Component.

Envolver el root layout con el Provider

Envolver nuestro root layout con el Provider que generamos en el paso anterior nos dará acceso a nuestro Store en cualquier parte de nuestra aplicación, lo que significa que ya podemos acceder a él mediante los hooks que definimos anteriormente.

app/layout.tsx
//...

// layout.tsx Antes de envolver con el Provider 
export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  )
}


// layout.tsx Después de envolver con el Provider ✅

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body className={inter.className}>
        <Providers>{children}</Providers>
      </body>
    </html>
  );
  

Acceder al Store en cualquier componente

Para tener acceso al Store que hemos creado en cualquier parte de nuestra aplicación, basta con importar los hooks y los slices que hemos creado antes, guardarlos en una variable y disparar las respectivas acciones mediante la función dispatch.

app/components/Section.tsx
'use client'; // 🔴 IMPORTANT!!

import { useAppDispatch, useAppSelector } from './store';
import { decrement, increment, incrementByAmount } from './store/slices/counter';

export default function Home() {
  const counter = useAppSelector((state) => state.counter.value); // Return Root State Slices
  const dispatch = useAppDispatch(); // Action Dispatcher

  return (
    <main className="flex min-h-screen flex-col items-center justify-center p-24">
      <div className="text-center mb-12">Count is {counter}</div>

      <div className="flex">
        <button
          className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
          onClick={() => {
            dispatch(increment());
          }}
        >
          Increment
        </button>
        <button
          className="ml-2 bg-green-500 hover:bg-green-700 text-white font-bold py-2 px-4 rounded"
          onClick={() => {
            dispatch(decrement());
          }}
        >
          Decrement
        </button>

        <button
          className="ml-2 bg-green-500 hover:bg-green-700 text-white font-bold py-2 px-4 rounded"
          onClick={() => {
            dispatch(incrementByAmount(10));
          }}
        >
          Incement By 10
        </button>
      </div>
    </main>
  );
}

Conclusión

Este artículo tiene como propósito guiarte paso por paso para conectar tu aplicación de Next.js con un estado global de Redux usando Redux Toolkit.

Este artículo es una traducción de un artículo de Medium escrito por el usuario khizerrehandev. Puedes ver el artículo original aquí.