12
votes

Comment utiliser Redux-Thunk avec createSlice de Redux Toolkit?

==================== TLDR ==========================

@markerikson (voir la réponse acceptée) a gentiment indiqué une solution actuelle et une solution future.

EDIT: 15 novembre 2020: Lien vers Docs pour utiliser un Thunk Async dans Slice

RTK prend en charge les thunks dans les réducteurs utilisant le middleware thunk (voir la réponse).

Dans la version 1.3.0 ( actuellement alpha en février 2020 ), il existe une méthode d'aide createAsyncThunk() createAsyncThunk qui fournira des fonctionnalités utiles (c'est-à-dire déclenche 3 réducteurs «étendus» en fonction de l'état de la promesse).

Versions de ReduxJS / Toolkit NPM

========================= Original Post Février 2020 ======================== ====

Je suis assez nouveau sur Redux et j'ai rencontré Redux Toolkit (RTK) et je souhaite mettre en œuvre d'autres fonctionnalités qu'il fournit (ou dans ce cas peut-être pas?) (Février 2020)

Mon application distribue les tranches de réducteurs créées via createSlice({}) (voir la documentation de l'API createSlice )

Cela fonctionne jusqu'à présent à merveille. Je peux facilement utiliser le dispatch(action) et useSelector(selector) pour distribuer les actions et recevoir / réagir aux changements d'état dans mes composants.

Je voudrais utiliser un appel asynchrone d'axios pour récupérer les données de l'API et mettre à jour le magasin car la demande est A) démarrée B) terminée.

J'ai vu redux-thunk et il semble qu'il soit entièrement conçu dans ce but ... mais le nouveau RTK ne semble pas le supporter dans un createSlice() suite à une recherche générale sur Google.

Est-ce que ce qui précède est l'état actuel de l'implémentation de thunk avec des tranches?

J'ai vu dans la documentation que vous pouvez ajouter des réducteurs supplémentaires à la tranche, mais je ne sais pas si cela signifie que je pourrais créer des réducteurs plus traditionnels qui utilisent Thunk et que la tranche les implémente?

Dans l'ensemble, c'est trompeur car la documentation RTK montre que vous pouvez utiliser thunk ... mais ne semble pas mentionner qu'il n'est pas accessible via la nouvelle api slices.

Exemple tiré du middleware Redux Tool Kit

import { configureStore } from '@reduxjs/toolkit';
import thunk from 'redux-thunk';

import bundlesReducer from '../slices/bundles-slice';
import servicesReducer from '../slices/services-slice';
import menuReducer from '../slices/menu-slice';
import mySliceReducer from '../slices/my-slice';

const store = configureStore({
    reducer: {
        bundles: bundlesReducer,
        services: servicesReducer,
        menu: menuReducer,
        redirect: mySliceReducer
    }
});
export default store;

Mon code pour une tranche montrant où un appel asynchrone échouerait et quelques autres exemples de réducteurs qui fonctionnent.

import { getAxiosInstance } from '../../conf/index';

export const slice = createSlice({
    name: 'bundles',
    initialState: {
        bundles: [],
        selectedBundle: null,
        page: {
            page: 0,
            totalElements: 0,
            size: 20,
            totalPages: 0
        },
        myAsyncResponse: null
    },

    reducers: {
        //Update the state with the new bundles and the Spring Page object.
        recievedBundlesFromAPI: (state, bundles) => {
            console.log('Getting bundles...');
            const springPage = bundles.payload.pageable;
            state.bundles = bundles.payload.content;
            state.page = {
                page: springPage.pageNumber,
                size: springPage.pageSize,
                totalElements: bundles.payload.totalElements,
                totalPages: bundles.payload.totalPages
            };
        },

        //The Bundle selected by the user.
        setSelectedBundle: (state, bundle) => {
            console.log(`Selected ${bundle} `);
            state.selectedBundle = bundle;
        },

        //I WANT TO USE / DO AN ASYNC FUNCTION HERE...THIS FAILS.
        myAsyncInSlice: (state) => {
            getAxiosInstance()
                .get('/')
                .then((ok) => {
                    state.myAsyncResponse = ok.data;
                })
                .catch((err) => {
                    state.myAsyncResponse = 'ERROR';
                });
        }
    }
});

export const selectBundles = (state) => state.bundles.bundles;
export const selectedBundle = (state) => state.bundles.selectBundle;
export const selectPage = (state) => state.bundles.page;
export const { recievedBundlesFromAPI, setSelectedBundle, myAsyncInSlice } = slice.actions;
export default slice.reducer;

Ma configuration de magasin (configuration de magasin).

const store = configureStore({
  reducer: rootReducer,
  middleware: [thunk, logger]
})

Toute aide ou conseils supplémentaires seraient grandement appréciés.


0 commentaires

3 Réponses :


6
votes

Utilisez redux-toolkit v1.3.0-alpha.8

Essaye ça

import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';

export const myAsyncInSlice = createAsyncThunk('bundles/myAsyncInSlice', () =>
  getAxiosInstance()
    .get('/')
    .then(ok => ok.data)
    .catch(err => err),
);

const usersSlice = createSlice({
  name: 'bundles',
  initialState: {
    bundles: [],
    selectedBundle: null,
    page: {
      page: 0,
      totalElements: 0,
      size: 20,
      totalPages: 0,
    },
    myAsyncResponse: null,
    myAsyncResponseError: null,
  },
  reducers: {
    // add your non-async reducers here
  },
  extraReducers: {
    // you can mutate state directly, since it is using immer behind the scenes
    [myAsyncInSlice.fulfilled]: (state, action) => {
      state.myAsyncResponse = action.payload;
    },
    [myAsyncInSlice.rejected]: (state, action) => {
      state.myAsyncResponseError = action.payload;
    },
  },
});



5 commentaires

Merci beaucoup. Je vois qu'il est en alpha mais maintenant, en regardant autour de lui, il semble très encourageant! Je vais jouer avec l'alpha maintenant et vous récupérer.


Merci beaucoup. Cela fonctionne à merveille. J'ai hâte que cela devienne une version officielle. Il a juste besoin de la version finale de npm comme: "1.3.0-alpha.8" par opposition à "1.3.0-alpha-8"


Ceci est dans la version officielle maintenant.


Pourriez-vous s'il vous plaît aider avec cette question? stackoverflow.com/questions/62145732/...


Pouvez-vous s'il vous plaît répondre à ma question stackoverflow.com/questions/63885139/...



37
votes

Je suis un mainteneur de Redux et un créateur de Redux Toolkit.

FWIW, rien sur les appels asynchrones avec les modifications Redux avec Redux Toolkit.

Vous utiliseriez toujours un middleware asynchrone (généralement redux-thunk ), récupérez des données et expédiez des actions avec les résultats.

Depuis Redux Toolkit 1.3, nous avons une méthode d'assistance appelée createAsyncThunk qui génère les créateurs d'action et demande la répartition des actions du cycle de vie pour vous, mais c'est toujours le même processus standard.

Cet exemple de code de la documentation résume l'utilisation;

import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'
import { userAPI } from './userAPI'

// First, create the thunk
const fetchUserById = createAsyncThunk(
  'users/fetchByIdStatus',
  async (userId, thunkAPI) => {
    const response = await userAPI.fetchById(userId)
    return response.data
  }
)

// Then, handle actions in your reducers:
const usersSlice = createSlice({
  name: 'users',
  initialState: { entities: [], loading: 'idle' },
  reducers: {
    // standard reducer logic, with auto-generated action types per reducer
  },
  extraReducers: {
    // Add reducers for additional action types here, and handle loading state as needed
    [fetchUserById.fulfilled]: (state, action) => {
      // Add user to the state array
      state.entities.push(action.payload)
    }
  }
})

// Later, dispatch the thunk as needed in the app
dispatch(fetchUserById(123))

Consultez la page de documentation «Guide d'utilisation: Async Logic and Data Fetching» de Redux Toolkit pour obtenir des informations supplémentaires sur ce sujet.

Espérons que cela vous oriente dans la bonne direction!


7 commentaires

Merci. Je vous avais vu commenter un problème Git mais comme il était toujours ouvert, je supposais qu'il n'y avait pas encore d'implémentation (et donc je ne m'étais pas aventuré beaucoup plus loin). Excellent travail d'ailleurs et j'ai hâte de voir la prochaine version majeure! Je suis principalement un développeur backend Spring Boot et étant nouveau, tout le monde frontal me fait tourner la tête, mais votre travail le rend tellement compréhensible et utilisable. J'aurais probablement dû regarder le 'Advanced', mais après avoir fait redux pendant quelques jours (et RTK pendant quelques heures), je n'ai pas poussé aussi loin, ha!


Super, merci pour les commentaires très positifs! Et oui, les documents RTK sont actuellement écrits sous l'hypothèse que vous savez déjà comment Redux fonctionne, donc ils se concentrent sur l'explication en quoi l'utilisation de RTK est différente de l'écriture de la logique Redux "à la main". Ma prochaine tâche après RTK 1.3.0 est d'ajouter une nouvelle page "Démarrage rapide" de la documentation du noyau Redux qui suppose que vous êtes nouveau sur Redux, et montre le moyen le plus simple de passer à l'écriture de code Redux avec RTK à partir de zéro.


Cela serait parfait. Je me suis retrouvé à plonger dans le monde du front-end ... et en l'espace de 2 semaines, j'ai traversé l'évolution de: 1. ReactJS avec cycle de vie et état uniquement. 2. Avec cycle de vie et redux et MaterialUI. 3. Avec crochets et redux sans crochet. 4. Avec redux-hooks. 5. Avec RTK. 6. (Aujourd'hui) avec le thunk et aussi essayer votre alpha 1.3.0 plutôt génial avec createAsyncThunk (). Un «que dois-je faire pour commencer avec redux pour le noob total» serait un autre excellent ajout à mon humble avis. Encore une fois, merci pour ce qui est un excellent projet!


Yipes, c'est beaucoup de choses à faire! Nous recommandons généralement aux gens de ne s'attaquer à Redux qu'une fois qu'ils sont déjà à l'aise avec React. De cette façon, il y a moins de nouveaux concepts à apprendre à la fois, et il est plus évident de savoir comment Redux s'intègre dans une application React et pourquoi cela pourrait être utile. Mais oui, heureux que RTK se révèle utile! Gardez un œil sur la documentation Redux - j'espère commencer à créer cette page "Démarrage rapide" dans les prochaines semaines.


@markerison Comment surveilleriez-vous un démontage d'un composant fonctionnel en utilisant ce scénario? Je sais que vous pouvez le faire en utilisant useEffect () et la méthode abort () sur l'appel de répartition, mais si la répartition est sur la répartition, cliquez sur le bouton (fetchUserById (123)), alors où vérifieriez-vous le démontage?


Les commentaires sont un mauvais endroit pour cela :) Je suggérerais de poser cela comme une question distincte.


@markerikson Pouvez-vous s'il vous plaît répondre à ma question stackoverflow.com/questions/63885139/...



3
votes

Vous pouvez utiliser createAsyncThunk pour créer une thunk action createAsyncThunk , qui peut être déclenchée à l'aide de dispatch

teamSlice.ts

import React from "react";
import { useSelector, useDispatch } from "react-redux";

import { fetchPlayerList } from './teamSlice';

const Team = (props) => {
  const dispatch = useDispatch();
  const playerList = useSelector((state: any) => state.team.playerList);

  return (
    <div>
      <button
        onClick={() => { dispatch(fetchPlayerList('1838315')); }}
      >Fetch Team players</button>

      <p>API status {playerList.status}</p>
      <div>
        { (playerList.status !== 'loading' && playerList.data.length) &&
          playerList.data.map((player) => 
            <div style={{display: 'flex'}}>
              <p>Name: {player.name}</p>
              <p>Games Played: {player.games_played}</p>
            </div>
          )
        }
      </div>
    </div>
  )
}

export default Team;

Composant Team.tsx

import {
  createSlice,
  createAsyncThunk,
} from "@reduxjs/toolkit";
const axios = require('axios');

export const fetchPlayerList = createAsyncThunk('team/playerListLoading', 
  (teamId:string) =>
  axios
    .get(`https://api.opendota.com/api/teams/${teamId}/players`)
    .then(response => response.data)
    .catch(error => error),
);

const teamInitialState = {
   playerList: {
     status: 'idle',
     data: {},
     error: {}
   }    
};

const teamSlice = createSlice({
  name: 'user',
  initialState: teamInitialState,
  reducers: {},
  extraReducers: {
    [fetchPlayerList.pending.type]: (state, action) => {
        state.playerList = {
        status: 'loading',
        data: {},
        error: {}
      };
    },
    [fetchPlayerList.fulfilled.type]: (state, action) => {
        state.playerList = {
        status: 'idle',
        data: action.payload,
        error: {}
     };
    },
    [fetchPlayerList.rejected.type]: (state, action) => {
        state.playerList = {
        status: 'idle',
        data: {},
        error: action.payload,
      };
    },
  }
});

export default teamSlice;


3 commentaires

pourquoi avez-vous mis [fetchPlayerList.fulfilled.type] ie .type supplémentaire?


@AkshayVijayJain Pour dactylographié, je l'ai ajouté. Si vous utilisez uniquement es6, vous pouvez utiliser [fetchPlayerList.fulfilled]


@AkshayVijayJain vous m'avez sauvé la vie. Je n'ai trouvé que la syntaxe étrange du builder , mais cela fonctionne aussi. Il suffit d'ajouter ces .type à la fin. Merci!