¿Cómo usar "next-redux-wrapper" con "Next.js", "Redux-ToolKit" y Typescript correctamente?
Estoy usando RTK (redux-toolkit) dentro de una aplicación Next.js. Y estoy intentando despachar una acción AsyncThunk dentro de "getInitialProps". Al buscar, encontré un paquete llamado "next-redux-wrapper" que expone el "store" dentro de "getInitialProps", pero estoy teniendo dificultades para averiguar cómo hacerlo funcionar con mi proyecto.
Aquí hay un ejemplo básico del proyecto donde estoy usando Typescript con 2 reducers en este momento. Un reducer está usando AsyncThunk para obtener datos de una API. Ya instalé "next-redux-wrapper" pero no sé cómo implementarlo alrededor de para que todas las páginas tengan acceso al "store" dentro de "getInitialProps". La documentación de ese paquete tiene un ejemplo, pero es bastante confuso.
Aquí es cómo se ve mi store.ts ...
import { Action, configureStore, ThunkAction } from '@reduxjs/toolkit';
import { createWrapper, HYDRATE } from 'next-redux-wrapper';
import { counterReducer } from '../features/counter';
import { kanyeReducer } from '../features/kanye';
export const store = configureStore({
reducer: {
counter: counterReducer,
kanyeQuote: kanyeReducer,
},
});
export type AppDispatch = typeof store.dispatch;
export type RootState = ReturnType<typeof store.getState>;
export type AppThunk<ReturnType = void> = ThunkAction<
ReturnType,
RootState,
unknown,
Action<string>
>;
Como pueden ver, importé next-redux-wrapper, pero eso es todo.
Y así es como se ve mi "_app.tsx" ...
import { Provider } from 'react-redux';
import type { AppProps } from 'next/app';
import { store } from '../app/store';
function MyApp({ Component, pageProps }: AppProps) {
return (
<Provider store={store}>
<Component {...pageProps} />
</Provider>
);
}
export default MyApp;
Necesito poder despachar la acción "getKanyeQuote" en "getInitialProps" en esta página ...
import React from 'react';
import { useAppDispatch, useAppSelector } from '../app/hooks';
import { getKanyeQuote } from '../features/kanye';
const kanye: React.FC = () => {
const dispatch = useAppDispatch();
const { data, pending, error } = useAppSelector((state) => state.kanyeQuote);
return (
<div>
<h2>Generate random Kanye West quote</h2>
{pending && <p>Loading...</p>}
{data && <p>{data.quote}</p>}
{error && <p>Oops, something went wrong</p>}
<button onClick={() => dispatch(getKanyeQuote())} disabled={pending}>
Generate Kanye Quote
</button>
</div>
);
};
export default kanye;
Y aquí hay un enlace a un ejemplo completo. https://stackblitz.com/edit/github-bizsur-zkcmca?file=src%2Ffeatures%2Fcounter%2Freducer.ts
Cualquier ayuda es muy apreciada.
Respuesta #1
DEMOSTRACIÓN FUNCIONANDO
Primero, configure el contenedor:
import {
Action,
AnyAction,
combineReducers,
configureStore,
ThunkAction,
} from '@reduxjs/toolkit';
import { createWrapper, HYDRATE } from 'next-redux-wrapper';
import { counterReducer } from '../features/counter';
import { kanyeReducer } from '../features/kanye';
const combinedReducer = combineReducers({
counter: counterReducer,
kanyeQuote: kanyeReducer,
});
const reducer = (state: ReturnType<typeof combinedReducer>, action: AnyAction) => {
if (action.type === HYDRATE) {
const nextState = {
...state, // use previous state
...action.payload, // apply delta from hydration
};
return nextState;
} else {
return combinedReducer(state, action);
}
};
export const makeStore = () =>
configureStore({
reducer,
});
type Store = ReturnType<typeof makeStore>;
export type AppDispatch = Store['dispatch'];
export type RootState = ReturnType<Store['getState']>;
export type AppThunk<ReturnType = void> = ThunkAction<
ReturnType,
RootState,
unknown,
Action<string>
>;
export const wrapper = createWrapper(makeStore, { debug: true });
Aquí la nueva función reductora combina la tienda del servidor recién creada y la tienda del cliente:
- el envoltorio crea un nuevo store de redux del lado del servidor con la función
makeStore - el envoltorio despacha la acción
HYDRATE. Su carga útil es la tienda del servidor recién creada - el reductor fusiona la tienda del servidor con la tienda del cliente.
Solo estamos reemplazando el estado del cliente con el estado del servidor, pero podría ser necesaria una mayor reconciliación si la tienda se vuelve complicada.
envuelve tu _app.tsx
No es necesario proporcionar Provider y store porque wrapper lo hará en consecuencia:
import type { AppProps } from 'next/app';
import { wrapper } from '../app/store';
function MyApp({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />;
}
export default wrapper.withRedux(MyApp);
Y luego puedes enviar la acción thunk en tu página:
import { NextPage } from 'next/types';
import React from 'react';
import { useAppDispatch, useAppSelector } from '../app/hooks';
import { getKanyeQuote } from '../features/kanye';
import { wrapper } from '../app/store';
const kanye: NextPage = () => {
const dispatch = useAppDispatch();
const { data, pending, error } = useAppSelector((state) => state.kanyeQuote);
return (
<div>
<h2>Generate random Kanye West quote</h2>
{pending && <p>Loading...</p>}
{data && <p>{data.quote}</p>}
{error && <p>Oops, something went wrong</p>}
<button onClick={() => dispatch(getKanyeQuote())} disabled={pending}>
Generate Kanye Quote
</button>
</div>
);
};
kanye.getInitialProps = wrapper.getInitialPageProps(
({ dispatch }) =>
async () => {
await dispatch(getKanyeQuote());
}
);
export default kanye;
