Redux: Comment couper le passe-partout lors de la mise à jour des entités imbriquées?

Créé le 3 nov. 2015  ·  32Commentaires  ·  Source: reduxjs/redux

Donc j'ai cette structure imbriquée sous mon état

state = {
   plans: [
    {title: 'A', exercises: [{title: 'exe1'}, {title: 'exe2'},{title: 'exe3'}]},
    {title: 'B', exercises: [{title: 'exe5'}, {title: 'exe1'},{title: 'exe2'}]}
   ]
}

J'essaie de créer une réduction qui ne mute pas l'état précédent, mais j'arrive à un point où je passe plus de temps à comprendre comment faire cela, puis à coder le reste de l'application, cela devient assez frustrant.

Par exemple, si je voulais ajouter un nouvel exercice vide ou mettre à jour un exercice existant, muter les données, je ferais simplement:

state.plans[planIdx].exercises.push({})

state.plans[planIdx].exercises[exerciseIdx] = exercise

mais quelle pourrait être ma meilleure approche pour faire de même dans cette structure imbriquée? J'ai lu la documentation Redux et aussi la partie dépannage, mais le plus loin que j'ai obtenu était de mettre à jour un plan, où je ferais:

case 'UPDATE_PLAN':
    return {
      ...state,
      plans: [
      ...state.plans.slice(0, action.idx),
      Object.assign({}, state.plans[action.idx], action.plan),
      ...state.plans.slice(action.idx + 1)
      ]
    };

N'y a-t-il pas un moyen plus rapide de travailler avec cela? Même si je dois utiliser des bibliothèques externes, ou du moins si quelqu'un peut m'expliquer comment mieux gérer cela ...

Je vous remercie!

docs question

Commentaire le plus utile

Oui, nous vous suggérons de normaliser vos données.
De cette façon, vous n'avez pas besoin d'aller «en profondeur»: toutes vos entités sont au même niveau.

Donc votre état ressemblerait à

{
  entities: {
    plans: {
      1: {title: 'A', exercises: [1, 2, 3]},
      2: {title: 'B', exercises: [5, 1, 2]}
     },
    exercises: {
      1: {title: 'exe1'},
      2: {title: 'exe2'},
      3: {title: 'exe3'}
    }
  },
  currentPlans: [1, 2]
}

Vos réducteurs pourraient ressembler à

import merge from 'lodash/object/merge';

const exercises = (state = {}, action) => {
  switch (action.type) {
  case 'CREATE_EXERCISE':
    return {
      ...state,
      [action.id]: {
        ...action.exercise
      }
    };
  case 'UPDATE_EXERCISE':
    return {
      ...state,
      [action.id]: {
        ...state[action.id],
        ...action.exercise
      }
    };
  default:
    if (action.entities && action.entities.exercises) {
      return merge({}, state, action.entities.exercises);
    }
    return state;
  }
}

const plans = (state = {}, action) => {
  switch (action.type) {
  case 'CREATE_PLAN':
    return {
      ...state,
      [action.id]: {
        ...action.plan
      }
    };
  case 'UPDATE_PLAN':
    return {
      ...state,
      [action.id]: {
        ...state[action.id],
        ...action.plan
      }
    };
  default:
    if (action.entities && action.entities.plans) {
      return merge({}, state, action.entities.plans);
    }
    return state;
  }
}

const entities = combineReducers({
  plans,
  exercises
});

const currentPlans = (state = [], action) {
  switch (action.type) {
  case 'CREATE_PLAN':
    return [...state, action.id];
  default:
    return state;
  }
}

const reducer = combineReducers({
  entities,
  currentPlans
});

Alors qu'est-ce qui se passe ici? Tout d'abord, notez que l'état est normalisé. Nous n'avons jamais d'entités à l'intérieur d'autres entités. Au lieu de cela, ils se réfèrent les uns aux autres par des identifiants. Ainsi, chaque fois qu'un objet change, il n'y a qu'un seul endroit où il doit être mis à jour.

Deuxièmement, notez comment nous réagissons à CREATE_PLAN en ajoutant à la fois une entité appropriée dans le réducteur plans _et_ en ajoutant son ID au réducteur currentPlans . C'est important. Dans les applications plus complexes, vous pouvez avoir des relations, par exemple plans reducer peut gérer ADD_EXERCISE_TO_PLAN de la même manière en ajoutant un nouvel ID au tableau à l'intérieur du plan. Mais si l'exercice lui-même est mis à jour, _il n'y a pas besoin de plans reducer pour le savoir, car l'ID n'a pas changé_.

Troisièmement, notez que les réducteurs d'entités ( plans et exercises ) ont des clauses spéciales pour surveiller action.entities . C'est au cas où nous aurions une réponse du serveur avec une «vérité connue» que nous voulons mettre à jour toutes nos entités pour refléter. Pour préparer vos données de cette manière avant d'envoyer une action, vous pouvez utiliser normalizr . Vous pouvez le voir utilisé dans l'exemple du «monde réel» dans le repo Redux.

Enfin, remarquez à quel point les réducteurs d'entités sont similaires. Vous voudrez peut-être écrire une fonction pour les générer. C'est hors de portée de ma réponse - parfois vous voulez plus de flexibilité, et parfois vous voulez moins de passe-partout. Vous pouvez consulter le code de pagination dans des exemples de réducteurs «du monde réel» pour un exemple de génération de réducteurs similaires.

Oh, et j'ai utilisé la syntaxe { ...a, ...b } . Il est activé dans Babel étape 2 en tant que proposition ES7. Il s'appelle «opérateur de propagation d'objet» et équivaut à écrire Object.assign({}, a, b) .

En ce qui concerne les bibliothèques, vous pouvez utiliser Lodash (faites attention à ne pas muter, par exemple merge({}, a, b} est correct mais merge(a, b) ne l'est pas), updeep , -addons-update ou autre chose. Cependant, si vous avez besoin de faire des mises à jour approfondies, cela signifie probablement que votre arborescence d'états n'est pas assez plate et que vous n'utilisez pas suffisamment la composition fonctionnelle. Même votre premier exemple:

case 'UPDATE_PLAN':
  return {
    ...state,
    plans: [
      ...state.plans.slice(0, action.idx),
      Object.assign({}, state.plans[action.idx], action.plan),
      ...state.plans.slice(action.idx + 1)
    ]
  };

peut être écrit comme

const plan = (state = {}, action) => {
  switch (action.type) {
  case 'UPDATE_PLAN':
    return Object.assign({}, state, action.plan);
  default:
    return state;
  }
}

const plans = (state = [], action) => {
  if (typeof action.idx === 'undefined') {
    return state;
  }
  return [
    ...state.slice(0, action.idx),
    plan(state[action.idx], action),
    ...state.slice(action.idx + 1)
  ];
};

// somewhere
case 'UPDATE_PLAN':
  return {
    ...state,
    plans: plans(state.plans, action)
  };

Tous les 32 commentaires

Il est recommandé de normaliser JSON imbriqué comme: https://github.com/gaearon/normalizr

Oui, nous vous suggérons de normaliser vos données.
De cette façon, vous n'avez pas besoin d'aller «en profondeur»: toutes vos entités sont au même niveau.

Donc votre état ressemblerait à

{
  entities: {
    plans: {
      1: {title: 'A', exercises: [1, 2, 3]},
      2: {title: 'B', exercises: [5, 1, 2]}
     },
    exercises: {
      1: {title: 'exe1'},
      2: {title: 'exe2'},
      3: {title: 'exe3'}
    }
  },
  currentPlans: [1, 2]
}

Vos réducteurs pourraient ressembler à

import merge from 'lodash/object/merge';

const exercises = (state = {}, action) => {
  switch (action.type) {
  case 'CREATE_EXERCISE':
    return {
      ...state,
      [action.id]: {
        ...action.exercise
      }
    };
  case 'UPDATE_EXERCISE':
    return {
      ...state,
      [action.id]: {
        ...state[action.id],
        ...action.exercise
      }
    };
  default:
    if (action.entities && action.entities.exercises) {
      return merge({}, state, action.entities.exercises);
    }
    return state;
  }
}

const plans = (state = {}, action) => {
  switch (action.type) {
  case 'CREATE_PLAN':
    return {
      ...state,
      [action.id]: {
        ...action.plan
      }
    };
  case 'UPDATE_PLAN':
    return {
      ...state,
      [action.id]: {
        ...state[action.id],
        ...action.plan
      }
    };
  default:
    if (action.entities && action.entities.plans) {
      return merge({}, state, action.entities.plans);
    }
    return state;
  }
}

const entities = combineReducers({
  plans,
  exercises
});

const currentPlans = (state = [], action) {
  switch (action.type) {
  case 'CREATE_PLAN':
    return [...state, action.id];
  default:
    return state;
  }
}

const reducer = combineReducers({
  entities,
  currentPlans
});

Alors qu'est-ce qui se passe ici? Tout d'abord, notez que l'état est normalisé. Nous n'avons jamais d'entités à l'intérieur d'autres entités. Au lieu de cela, ils se réfèrent les uns aux autres par des identifiants. Ainsi, chaque fois qu'un objet change, il n'y a qu'un seul endroit où il doit être mis à jour.

Deuxièmement, notez comment nous réagissons à CREATE_PLAN en ajoutant à la fois une entité appropriée dans le réducteur plans _et_ en ajoutant son ID au réducteur currentPlans . C'est important. Dans les applications plus complexes, vous pouvez avoir des relations, par exemple plans reducer peut gérer ADD_EXERCISE_TO_PLAN de la même manière en ajoutant un nouvel ID au tableau à l'intérieur du plan. Mais si l'exercice lui-même est mis à jour, _il n'y a pas besoin de plans reducer pour le savoir, car l'ID n'a pas changé_.

Troisièmement, notez que les réducteurs d'entités ( plans et exercises ) ont des clauses spéciales pour surveiller action.entities . C'est au cas où nous aurions une réponse du serveur avec une «vérité connue» que nous voulons mettre à jour toutes nos entités pour refléter. Pour préparer vos données de cette manière avant d'envoyer une action, vous pouvez utiliser normalizr . Vous pouvez le voir utilisé dans l'exemple du «monde réel» dans le repo Redux.

Enfin, remarquez à quel point les réducteurs d'entités sont similaires. Vous voudrez peut-être écrire une fonction pour les générer. C'est hors de portée de ma réponse - parfois vous voulez plus de flexibilité, et parfois vous voulez moins de passe-partout. Vous pouvez consulter le code de pagination dans des exemples de réducteurs «du monde réel» pour un exemple de génération de réducteurs similaires.

Oh, et j'ai utilisé la syntaxe { ...a, ...b } . Il est activé dans Babel étape 2 en tant que proposition ES7. Il s'appelle «opérateur de propagation d'objet» et équivaut à écrire Object.assign({}, a, b) .

En ce qui concerne les bibliothèques, vous pouvez utiliser Lodash (faites attention à ne pas muter, par exemple merge({}, a, b} est correct mais merge(a, b) ne l'est pas), updeep , -addons-update ou autre chose. Cependant, si vous avez besoin de faire des mises à jour approfondies, cela signifie probablement que votre arborescence d'états n'est pas assez plate et que vous n'utilisez pas suffisamment la composition fonctionnelle. Même votre premier exemple:

case 'UPDATE_PLAN':
  return {
    ...state,
    plans: [
      ...state.plans.slice(0, action.idx),
      Object.assign({}, state.plans[action.idx], action.plan),
      ...state.plans.slice(action.idx + 1)
    ]
  };

peut être écrit comme

const plan = (state = {}, action) => {
  switch (action.type) {
  case 'UPDATE_PLAN':
    return Object.assign({}, state, action.plan);
  default:
    return state;
  }
}

const plans = (state = [], action) => {
  if (typeof action.idx === 'undefined') {
    return state;
  }
  return [
    ...state.slice(0, action.idx),
    plan(state[action.idx], action),
    ...state.slice(action.idx + 1)
  ];
};

// somewhere
case 'UPDATE_PLAN':
  return {
    ...state,
    plans: plans(state.plans, action)
  };

Ce serait bien d'en faire une recette.

Idéalement, nous voulons un exemple de tableau Kanban.
C'est parfait pour les entités imbriquées car les «voies» peuvent contenir des «cartes».

@ andre0799 ou vous pouvez simplement utiliser Immutable.js))

Idéalement, nous voulons un exemple de tableau Kanban.

J'en ai écrit un . Peut-être pourriez-vous le modifier et l'ajuster à votre guise.

Immutable.js n'est pas toujours une bonne solution. Il recalcule le hachage de chaque nœud d'état parent à partir du nœud que vous avez modifié et cela devient un goulot d'étranglement dans des cas particuliers (ce n'est pas des cas très courants). Donc, idéalement, vous devriez faire quelques benchmarks avant d'intégrer Immutable.js dans votre application.

Merci @gaearon pour votre réponse, bonne explication!

Ainsi, lorsque vous faites CREATE_PLAN vous devez créer automatiquement un exercice par défaut et y ajouter. Comment dois-je gérer des cas comme celui-ci? Dois-je alors appeler 3 actions consécutives? CREATE_PLAN, CREATE_EXERCISE, ADD_EXERCISE_TO_PLAN D'où dois-je faire ces appels, si tel est le cas?

Ainsi, lorsque vous effectuez CREATE_PLAN, vous devez créer automatiquement un exercice par défaut et y ajouter. Comment dois-je gérer des cas comme celui-ci?

Alors que je suis généralement en faveur de nombreux réducteurs gérant la même action, cela peut devenir trop compliqué pour les entités avec des relations. En effet, je suggère de modéliser ces actions comme des actions distinctes.

Vous pouvez utiliser le middleware Redux Thunk pour écrire un créateur d'action qui les appelle tous les deux:

function createPlan(title) {
  return dispatch => {
    const planId = uuid();
    const exerciseId = uuid();

    dispatch({
      type: 'CREATE_EXERCISE',
      id: exerciseId,
      exercise: {
        id: exerciseId,
        title: 'Default'
      }
    });

    dispatch({
      type: 'CREATE_PLAN',
      id: planId,
      plan: {
        id: planId,
        exercises: [exerciseId],
        title
      }
    });
  };
}

Ensuite, si vous appliquez le middleware Redux Thunk, vous pouvez l'appeler normalement:

store.dispatch(createPlan(title));

Alors disons que j'ai un éditeur de publication sur le backend quelque part avec de telles relations (publications, auteurs, balises, pièces jointes, etc.).

Comment puis-je afficher currentPosts similaire au tableau currentPlans de clés? Dois-je mapper chaque clé de currentPosts à son objet correspondant dans entities.posts dans la fonction mapStateToProps ? Que diriez-vous de trier currentPosts ?

Tout cela appartient-il à une composition réductrice?

Il me manque quelque chose ici ...

En ce qui concerne la question initiale, je pense que les React Immutability Helpers ont été créés à cet effet

Comment afficher currentPosts similaire au tableau de clés currentPlans? Aurais-je besoin de mapper chaque clé dans currentPosts à son objet correspondant dans entity.posts dans la fonction mapStateToProps? Que diriez-vous de trier les messages actuels?

C'est correct. Vous feriez tout lors de la récupération des données. Veuillez consulter les exemples «panier» et «monde réel» fournis avec Redux repo.

Merci, j'ai déjà commencé à me faire une idée après avoir lu Computing Derived Data sur la documentation.

Je vais revoir ces exemples. Je n'ai probablement pas compris beaucoup de ce qui se passait au début quand je les ai lus.

@ andre0799

Tout composant connect() ed a dispatch injecté comme accessoire par défaut.

this.props.dispatch(createPlan(title));

Il s'agit d'une question d'utilisation qui n'est pas liée à ce fil. Il est préférable de consulter des exemples ou de créer des questions StackOverflow pour cela.

Je suis d'accord avec Dan pour normaliser les données et aplatir autant que possible la structure de l'état. Cela peut être mis comme recette / meilleure pratique dans la documentation, car cela m'aurait évité quelques maux de tête.

Comme j'ai commis l'erreur d'avoir un peu de profondeur dans mon état, j'ai créé cette bibliothèque pour aider à la modernisation et à la gestion de l'état profond avec Redux: https://github.com/baptistemanson/immutable-path
Peut-être que je me suis trompé, mais je serais intéressé par vos commentaires. J'espère que cela aidera quelqu'un.

Cela a été utile. Merci à tous.

Comment allez-vous ajouter un exercice à un plan, en utilisant la même structure que l'exemple du monde réel? Disons que l'ajout d'un exercice a renvoyé l'entité d'exercice nouvellement créée, avec un champ planId . Est-il possible d'ajouter le nouvel exercice à ce plan sans avoir à écrire un réducteur pour les plans et d'écouter spécifiquement une action CREATE_EXERCISE ?

Excellente discussion et informations ici. Je voudrais utiliser normalizr pour mon projet, mais j'ai une question concernant la sauvegarde des données mises à jour sur un serveur distant. Principalement, existe-t-il un moyen simple de rétablir la forme normalisée à la forme imbriquée fournie par l'API distante après la mise à jour? Ceci est important lorsque le client apporte des modifications et doit les renvoyer à l'API distante où il n'a aucun contrôle sur la forme de la demande de mise à jour.

Par exemple: le client récupère les données d'exercice imbriquées -> le client les normalise et les stocke dans un redux -> l'utilisateur apporte des modifications aux données normalisées côté client -> l'utilisateur clique sur enregistrer -> l'application client transforme les données normalisées mises à jour au format imbriqué afin qu'il puisse le soumettre au serveur distant -> le client soumet au serveur

Si j'utilisais normalizr, aurais-je besoin d'écrire un transformateur personnalisé pour l'étape en gras ou y a-t-il une bibliothèque ou une méthode d'aide que vous recommanderiez pour cela? Toutes les recommandations seraient très appréciées.

Merci

Il y a quelque chose qui s'appelle https://github.com/gpbl/denormalizr mais je ne suis pas sûr à quel point il suit les mises à jour de normalizr. J'ai écrit normalizr en quelques heures pour l'application sur laquelle j'ai travaillé, vous êtes invités à la bifurquer et à ajouter la dénormalisation 😄.

Cool, je vais certainement jeter un oeil à la dénormalisation et contribuer à votre projet une fois que j'aurai quelque chose. Excellent travail pendant quelques heures ;-) Merci de me répondre.

Je suis un peu dans la même situation avec la modification des données profondément imbriquées, cependant, j'ai trouvé une solution de travail en utilisant immutable.js.

Est-ce que je peux créer un lien vers une publication StackOverflow que j'ai faite pour demander des commentaires sur la solution ici?

Je le lie pour le moment, veuillez supprimer mon message ou indiquer si le lien n'est pas approprié ici:
http://stackoverflow.com/questions/37171203/manipulating-data-in-nested-arrays-in-redux-with-immutable-js

J'ai vu cette approche être recommandée dans le passé. Cependant, cette approche ne semble pas bien fonctionner lorsqu'un objet imbriqué doit être supprimé. Dans ce cas, le réducteur aurait besoin de parcourir toutes les références à l'objet et de les supprimer, une opération qui serait O (n), avant de pouvoir supprimer l'objet lui-même. Quelqu'un a rencontré un problème similaire et l'a résolu?

@ariofrio : ah ... je suis confus. Le point de normalisation est que les objets ne sont _pas_ stockés imbriqués, et qu'il n'y a qu'une seule référence à un élément donné, ce qui facilite la mise à jour de cet élément. Maintenant, s'il y a plusieurs autres entités qui "référençaient" cet élément par ID, bien sûr, elles devraient également être mises à jour, comme si les choses n'étaient pas normalisées.

Avez-vous une préoccupation particulière ou un scénario problématique auquel vous faites face?

Voici ce que je veux dire. Disons que l'état actuel ressemble à:

{
  entities: {
    plans: {
      1: {title: 'A', exercises: [1, 2, 3]},
      2: {title: 'B', exercises: [5, 6]}
     },
    exercises: {
      1: {title: 'exe1'},
      2: {title: 'exe2'},
      3: {title: 'exe3'}
      5: {title: 'exe5'}
      6: {title: 'exe6'}
    }
  },
  currentPlans: [1, 2]
}

Dans cet exemple, chaque exercice ne peut être référencé que par un plan. Lorsque l'utilisateur clique sur "Supprimer l'exercice", le message peut ressembler à ceci:

{type: "REMOVE_EXERCISE", payload: 2}

Mais pour l'implémenter correctement, il faudrait parcourir tous les plans, puis tous les exercices dans chaque plan, pour trouver celui qui fait référence à l'exercice avec l'ID 2, afin d'éviter une référence pendante. C'est l'opération O (n) qui m'inquiétait.

Un moyen d'éviter cela est d'inclure l'ID du plan dans la charge utile de REMOVE_EXERCISE, mais à ce stade, je ne vois pas l'avantage d'utiliser l'imbrication des structures. Si nous avons utilisé l'état imbriqué à la place, l'état pourrait ressembler à:

{
   plans: [
    {title: 'A', exercises: [{title: 'exe1'}, {title: 'exe2'},{title: 'exe3'}]},
    {title: 'B', exercises: [{title: 'exe5'}, {title: 'exe6'}]}
   ]
}

Et le message pour supprimer l'exercice pourrait ressembler à:

{type: "REMOVE_EXERCISE", payload: {plan_index: 0, exercise_index: 1}}

Quelques réflexions:

  • Vous pouvez maintenir une recherche inversée des exercices aux plans pour simplifier ce scénario. De même, ce que fait la bibliothèque Redux-ORM est de générer automatiquement des "tables traversantes" pour les relations de type multiple. Donc, dans ce cas, vous auriez une "table" "PlanExercise" dans votre magasin, qui contiendrait des triplets {id, planId, exerciseId} . Certainement un scan O (n), mais simple.
  • Une opération O (n) n'est pas intrinsèquement une mauvaise chose. Cela dépend entièrement de la taille de N, du facteur constant devant le terme, de la fréquence à laquelle cela se produit et de ce qui se passe dans votre application. Itérer sur une liste de 10 ou 15 éléments et effectuer des vérifications d'égalité sur un clic sur un bouton utilisateur sera une chose totalement différente que, par exemple, d'itérer une liste de 10 millions d'éléments entrant dans le système toutes les 500 ms et d'effectuer une opération coûteuse pour chacun article. Dans ce cas, il y a de fortes chances que même la vérification de milliers de plans ne soit pas un goulot d'étranglement significatif.
  • S'agit-il d'un problème de performance réel que vous voyez, ou envisagez-vous simplement de possibles problèmes théoriques?

En fin de compte, l'état imbriqué et normalisé sont des conventions. Il y a de bonnes raisons d'utiliser l'état normalisé avec Redux, et il peut y avoir de bonnes raisons de garder votre état normalisé. Choisissez ce qui fonctionne pour vous :)

ma solution comme ceci:

function deepCombinReducer(parentReducer, subReducer) {
    return function (state = parentReducer(void(0) /* get parent reducer initial state */, {}) {
        let finalState = {...state};

        for (var k in subReducer) {
          finalState[k] = subReducer(state[k], action);
        }

       return parentReducer(finalState, action);
    };
}

const parentReducer = function(state = {}, action) {
    return state;
}

const subReducer = function(state = [], action) {
    state = Immutable.fromJS(state).toJS();
    switch(action.type) {
       case 'ADD':
          state.push(action.sub);
           return state;
       default:
          return state;
   }
}

export default combineReducers({
   parent: deepCombinReducer(parentReducer, {
       sub: subReducer
   })
})

Ensuite, vous pouvez obtenir le magasin comme ceci:

{
    parent: {
       sub: []
    }
}

dispatch({
    type: 'ADD',
    sub: '123'
});

// the store will change to:
{
    parent: {
       sub: ['123']
    }
}

@smashercosmo immutable.js avec un état imbriqué profond? Je suis curieux de savoir comment

@gaearon

Nous n'avons jamais d'entités à l'intérieur d'autres entités.

Je ne comprends pas. Nous avons au moins trois niveaux d'imbrication ici:

{
  entities: {
    plans: {
      1: {title: 'A', exercises: [1, 2, 3]},
      2: {title: 'B', exercises: [5, 1, 2]}
     },
    exercises: {
      1: {title: 'exe1'},
      2: {title: 'exe2'},
      3: {title: 'exe3'}
    }
  },
  currentPlans: [1, 2]
}

entities.plans[1] - three levels
entities.exercises[1] - three levels

Il s'agit d'un objet non imbriqué. Un seul niveau.

{
   plans: [1,2, 3],
   exercises: [1,2,3],
   'so forth': [1,2,3]
}

@wzup : Pour

La signification du terme «imbrication» ici est lorsque les données elles-mêmes sont imbriquées, comme dans l'exemple ci-dessus dans le thread:

{
   plans: [
    {title: 'A', exercises: [{title: 'exe1'}, {title: 'exe2'},{title: 'exe3'}]},
    {title: 'B', exercises: [{title: 'exe5'}, {title: 'exe6'}]}
   ]
}

Dans cet exemple, la seule façon d'accéder à l'exercice "exe6" est de creuser dans la structure, comme plans[1].exercises[2] .

Je suis intéressé par la question de @tmonte :

Comment allez-vous ajouter un exercice à un plan, en utilisant la même structure que l'exemple du monde réel? Disons que l'ajout d'un exercice a renvoyé l'entité d'exercice nouvellement créée, avec un champ planId. Est-il possible d'ajouter le nouvel exercice à ce plan sans avoir à écrire un réducteur pour les plans et d'écouter spécifiquement une action CREATE_EXERCISE?

Lorsque vous avez plusieurs entités, créer un réducteur pour chaque entité peut être douloureux, mais cette approche pourrait le résoudre. Je n'ai pas encore trouvé de solution pour cela.

J'utilise généralement mergeWith au lieu de merge pour plus de flexibilité:

import mergeWith from 'lodash/mergeWith';

// Updates an entity cache in response to any action with `entities`.
function entities(state = {}, action) {
  // Here where we STORE or UPDATE one or many entities
  // So check if the action contains the format we will manage
  // wich is `payload.entities`
  if (action.payload && action.payload.entities) {
    // if the entity is already in the store replace
    // it with the new one and do not merge. Why?
    // Assuming we have this product in the store:
    //
    // products: {
    //   1: {
    //     id: 1,
    //     name: 'Awesome product name',
    //     rateCategory: 1234
    //   }
    // }
    //
    // We will updated with
    // products: {
    //   1: {
    //     id: 1,
    //     name: 'Awesome product name',
    //   }
    // }
    //
    // The result if we were using `lodash/merge`
    // notice the rate `rateCategory` hasn't changed:
    // products: {
    //   1: {
    //     id: 1,
    //     name: 'Awesome product name',
    //     rateCategory: 1234
    //   }
    // }
    // for this particular use case it's safer to use
    // `lodash/mergeWith` and skip the merge
    return mergeWith({}, state, action.payload.entities, (oldD, newD) => {
      if (oldD && oldD.id && oldD.id === newD.id) {
        return newD;
      }
      return undefined;
    });
  }

  // Here you could register other handlers to manipulate 
  // the entities
  switch (action.type) {
    case ActionTypes.SOME_ACTION:
      // do something;
    default:
      return state;
  }
}

const rootReducer = combineReducers({
  entities,
});
export default rootReducer;
Cette page vous a été utile?
0 / 5 - 0 notes