Usare il plugin Redux per acquisire le modifiche di stato
Video Tutorial
Guarda come tracciare lo stato con Redux
Se non ami leggere, puoi seguire questo video tutorial che ti mostra come tracciare lo stato delle tue app React quando usi Redux
Se hai bisogno di maggiore visibilità mentre riguardi le sessioni dei tuoi utenti, dare un’occhiata allo stato dell’applicazione può essere molto utile.
Nel caso di Redux, OpenReplay offre un plugin che ti permette di integrarti con il funzionamento interno dello store. Questo plugin ti consente di vedere lo stato (gioco di parole non voluto!) dello store Redux e le action inviate durante la sessione registrata.
Una volta configurato, dovresti riuscire a osservare le modifiche nello store come mostrato nello screenshot seguente:

Configurare Redux in un progetto Next.js
Section titled Configurare Redux in un progetto Next.jsPer questo tutorial useremo questo repository (branch redux-store) di un sito e-commerce generico realizzato con Next.js.
In questo progetto sostituiremo un insieme di prodotti in evidenza con un nuovo insieme di prodotti presi da un’API esterna.
Per farlo, aggiungeremo una funzione che richiede i prodotti tramite Axios, e lo faremo dall’interno di una action Redux.
Nota: questa è un’applicazione Next.js complessa, quindi potrebbe non seguire la struttura standard delle classiche app To-Do, ma seguendo questo tutorial dovresti riuscire a tenere il passo con le modifiche.
Ricorda: puoi sempre clonare il repository e rivedere il codice in autonomia.
Iniziamo installando tutte le dipendenze principali con:
npm i next-redux-wrapper redux react-redux redux-thunk redux-devtools-extension
Fatto questo, crea una cartella chiamata store nella directory principale del tuo progetto e riproduci la struttura seguente:

Il file types.js conterrà la definizione dei tipi per le due action che andremo a definire:
export const GET_PRODUCTS = 'GET_PRODUCTS'
export const PRODUCTS_ERROR = 'PRODUCTS_ERROR'
Il file store.js esporterà una funzione che, QUANDO viene chiamata, creerà un nuovo store Redux. Questo perché dovremo aggiungere un nuovo middleware Redux restituito dal plugin Redux (ne parliamo tra un attimo).
import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
import { composeWithDevTools } from 'redux-devtools-extension'
import rootReducer from './reducers'
const initalState = {}
export default function createReduxStore(extraMiddleware = []) {
const middleware = [thunk, ...extraMiddleware]
const store = createStore(
rootReducer,
initalState,
composeWithDevTools(applyMiddleware(...middleware))
)
return store
}
Il nostro file reducer (makeUpReducer.js) aggiornerà lo stato con l’elenco dei prodotti oppure con il messaggio di errore restituito in caso di problemi.
import { GET_PRODUCTS, PRODUCTS_ERROR } from '../types'
const initialState = {
makeUpProducts: [],
loading: true,
}
export default function (state = initialState, action) {
switch (action.type) {
case GET_PRODUCTS:
return {
...state,
makeUpProducts: action.payload,
loading: false,
}
case PRODUCTS_ERROR:
return {
loading: false,
error: action.payload,
}
default:
return state
}
}
Infine, il file delle action definirà una sola funzione, che si occupa di recuperare l’elenco dei prodotti da un’API esterna e di inviare l’action e il payload corretti:
import { GET_PRODUCTS, PRODUCTS_ERROR } from '../types'
import axios from 'axios'
import slugify from 'slugify'
export const getMakeUpProducts = () => async (dispatch: any) => {
console.log('Getting the makeup products')
try {
let { data } = await axios.get(
'https://makeup-api.herokuapp.com/api/v1/products.json?brand=maybelline&apiKey=123fff132'
)
const products = data
let newProds = products.map((p: any) => {
return {
id: '' + p.id,
slug: slugify(p.name),
name: p.name,
description: '',
images: [{ url: p.image_link }],
variants: [],
price: {
value: +p.price,
},
options: [],
}
})
dispatch({
type: GET_PRODUCTS,
payload: newProds,
})
} catch (e) {
dispatch({
type: PRODUCTS_ERROR,
payload: e,
})
}
}
Configurare il tracker provider
Section titled Configurare il tracker providerQuesto provider ti permette di configurare un insieme di plugin; nel nostro caso useremo il plugin Redux, in questo modo (dall’interno del file _app.tsx)
//...more imports here....
import TrackerProvider from '../context/trackerProvider'
import trackerRedux from '@openreplay/tracker-redux'
// ... more code here....
export default function MyApp({ Component, pageProps }: AppProps) {
const Layout = (Component as any).Layout || Noop
useEffect(() => {
document.body.classList?.remove('loading')
}, [])
let plugins = [
{
fn: trackerRedux,
name: 'redux',
config: {},
},
]
return (
<TrackerProvider config={{ plugins }}>
<Head />
<ManagedUIContext>
<Layout pageProps={pageProps}>
<Component {...pageProps} />
</Layout>
</ManagedUIContext>
</TrackerProvider>
)
}
Questo codice ti permette di configurare il tracker con il plugin giusto, ma perché il plugin funzioni dovremo accedere al middleware restituito quando il plugin viene chiamato. Questo significa che dovremo tenere traccia dei valori restituiti dai nostri plugin, in modo che possano essere usati altrove. In questo caso, ci serviranno quando chiameremo la funzione createReduxStore vista sopra.
Per farlo, dobbiamo estendere il TrackerProvider in modo da conservare il valore restituito nello stato, in questo modo (puoi consultare la versione completa di questo file qui):
import { createContext, useCallback } from 'react'
import Tracker from '@openreplay/tracker'
import { v4 as uuidV4 } from 'uuid'
import { useReducer } from 'react'
export const TrackerContext = createContext()
function defaultGetUserId() {
return uuidV4()
}
function newTracker(config) {
///code here
}
function reducer(state, action) {
switch (action.type) {
case 'init': {
if (!state.tracker) {
console.log('Instantiaing the tracker for the first time...')
let t = newTracker(state.config)
let pluginsReturnedValue = {}
if (state.config.plugins) {
state.config.plugins.forEach((p) => {
console.log('Using plugin...')
pluginsReturnedValue[p.name] = t.use(p.fn(p.config)) //keep track
})
}
return {
...state,
pluginsReturnedValue: pluginsReturnedValue, //update the state
tracker: t,
}
}
return state
}
case 'start': {
console.log('Starting tracker...')
state.tracker.start()
return state
}
}
}
export default function TrackerProvider({ children, config = {} }) {
let [state, dispatch] = useReducer(reducer, {
tracker: null,
pluginsReturnedValue: {},
config,
})
let value = {
startTracking: () => dispatch({ type: 'start' }),
initTracker: () => dispatch({ type: 'init' }),
pluginsReturnedValues: { ...state.pluginsReturnedValue }, //inject the state
}
return (
<TrackerContext.Provider value={value}>{children}</TrackerContext.Provider>
)
}
All’interno dell’action init teniamo traccia anche dei valori restituiti dal metodo use quando viene chiamato con i nostri plugin. E conserviamo quel dizionario nella proprietà state.pluginsReturnedValue, che rendiamo disponibile a tutti i componenti figli tramite la variabile pluginsReturnedValues.
Questa logica ti permette di usare il plugin durante l’inizializzazione del tracker e poi di accedere al middleware e usarlo in seguito.
Creare lo store Redux con il nuovo middleware
Section titled Creare lo store Redux con il nuovo middlewareOra che il plugin funziona, dobbiamo inizializzare lo store Redux, e dobbiamo farlo dopo l’inizializzazione del tracker e prima che venga chiamato il metodo start.
Per questo ho scelto il componente ManagedUI, che viene usato direttamente nel file _app.tsx. Questo componente è avvolto dal nostro Tracker Provider, il che significa che avrà accesso al contesto che stiamo condividendo.
Il componente ha questo aspetto:
export const ManagedUIContext: FC = ({ children }) => {
const { initTracker, pluginsReturnedValues } = useContext(TrackerContext)
const [store, setStore] = useState<Store>()
useEffect(() => {
initTracker()
}, [])
useEffect(() => {
if (!pluginsReturnedValues['redux']) return
let middleWares = pluginsReturnedValues['redux']
? [pluginsReturnedValues['redux']]
: []
setStore(createReduxStore(middleWares))
}, [pluginsReturnedValues])
return (
<div>
{store && (
<Provider store={store}>
<UIProvider>
<ThemeProvider>{children}</ThemeProvider>
</UIProvider>
</Provider>
)}
</div>
)
}
I punti chiave di questo file sono:
- Otteniamo dal contesto la funzione
initTrackere l’attributopluginsReturnedValues. - Chiamiamo la prima una sola volta, quando il componente viene montato (tramite il primo
useEffect). - Creiamo poi lo store Redux solo quando la variabile
pluginsReturnedValuescontiene il nostro valore restituito. Il secondouseEffectverrà chiamato due volte: una al caricamento della pagina e poi quando il metodoinitTrackermodifica la nostra variabile di stato. La seconda volta creeremo lo store con il middleware memorizzato inpluginsReturnedValues.
Avviare il tracker
Section titled Avviare il trackerCon il plugin configurato e lo store Redux creato correttamente, non ci resta che chiamare il metodo start del tracker.
La logica per farlo verrà aggiunta nel file index.tsx e puoi consultare il codice sorgente completo di questo file qui.
La parte rilevante di questo codice che dobbiamo esaminare è la seguente:
// imports and more logic goes here...
export default function Home({
products,
}: InferGetStaticPropsType<typeof getStaticProps>) {
const { startTracking } = useContext(TrackerContext)
const dispatch = useDispatch()
const makeUpProductsList = useSelector((state: any) => state.makeUpProducts)
const { makeUpProducts } = makeUpProductsList
useEffect(() => {
async function getProds() {
await startTracking()
dispatch(getMakeUpProducts() as any)
}
getProds()
}, [dispatch])
return (
<>
<Grid variant="filled">
{products.slice(0, 3).map((product: any, i: number) => (
<ProductCard
key={product.id}
product={product}
imgProps={{
width: i === 0 ? 1080 : 540,
height: i === 0 ? 1080 : 540,
priority: true,
}}
/>
))}
</Grid>
<Marquee variant="secondary">
{makeUpProducts.slice(0, 3).map((product: any, i: number) => (
<ProductCard key={product.id} product={product} variant="slim" />
))}
</Marquee>
<!-- more code here -->
</>
)
}
Useremo soltanto la funzione startTracking del context provider del tracker e l’hook useSelector di Redux per acquisire l’elenco dei prodotti restituiti.
L’hook useEffect attiverà la chiamata a startTracking e il recupero del nuovo elenco di prodotti make-up inviando la chiamata alla funzione getMakeUpProducts.
Hai domande?
Section titled Hai domande?Puoi dare un’occhiata a questo repository per il codice sorgente completo di un’applicazione funzionante basata su Next.js con uno store Redux.
Se hai problemi a configurare il plugin Redux, contattaci nella nostra community Slack e chiedi direttamente ai nostri sviluppatori!