VueX: How To Build My Store With Nested Objects

I am currently coding an application in VueJS (and with Vuex in particular). However, my question is not strongly related to this library, but rather an architecture having a repository like flux / redux / Vuex.

Simply put, I have multiple APIs (one API / database for each command), and for each command / API, I have multiple users. These commands and users are represented by simple objects, and each has its own pool. Important note: Team bullets are of course unique, but bullet users are unique to their own team. The uniqueness constraint for the user would then be "teamSlug / userSlug". And given the large number of users, I can't just load all users of all teams.

My question is how to properly archive my application / storage to restore the data of a given user pool (with its command): if I haven't loaded that user yet, make an API request to get it. Currently I have created a getter that returns a user object that takes the slug from the user and the command. If it returns "null" or ".loading" to "false", then I need to fire the "loadOne" action that will take care of fetching it:

import * as types from '../../mutation-types'
import users from '../../../api/users'

// initial state
const state = {
  users: {}
}

// getters
const getters = {
  getOne: state => (team, slug) => (state.users[team] || {})[slug] || null
}

// actions
const actions = {
  loadOne ({ commit, state }, { team, slug }) {
    commit(types.TEAM_USER_REQUEST, { team, slug })
    users.getOne(team, slug)
      .then(data => commit(types.TEAM_USER_SUCCESS, { team, slug, data }))
      .catch(error => commit(types.TEAM_USER_FAILURE, { team, slug, error }))
  }
}

// mutations
const mutations = {
  [types.TEAM_USER_REQUEST] (state, { team, slug }) {
    state.users = {
      ...state.users,
      [team]: {
        ...(state.users[team] || {}),
        [slug]: {
          loading: true,
          error: null,
          slug
        }
      }
    }
  },

  [types.TEAM_USER_SUCCESS] (state, { team, slug, data }) {
    state.users = {
      ...state.users,
      [team]: {
        ...(state.users[team] || {}),
        [slug]: {
          ...data,
          slug,
          loading: false
        }
      }
    }
  },

  [types.TEAM_USER_FAILURE] (state, { team, slug, error }) {
    state.users = {
      ...state.users,
      [team]: {
        ...(state.users[team] || {}),
        [slug]: {
          slug,
          loading: false,
          error
        }
      }
    }
  }
}

export default {
  namespaced: true,
  state,
  getters,
  actions,
  mutations
}

      

Can you imagine that the team has not only users, I have many other models of this type and I have to tie them together. This method works, but I find it quite cumbersome to create (especially since it's easy access, I will have many other similar actions). Do you have any advice on my architecture?

Thank!

+5


source to share


1 answer


I've found that the best way to keep the Vuex store flexible is to normalize it and make your data items as flat as possible. This means keeping all of your users in one structure and finding a way to uniquely identify them.

What if we combine the command and user slug to create a unique identifier? This is how I represent your users with the red team and the blue team:

const state = {
  users: {
    allTeamSlugs: [
      'blue1',
      'blue2',
      'blue3',
      'red1',
      'red2',
      // etc...
    ],
    byTeamSlug: {
      blue1: {
        slug: 1,
        team: 'blue',
        teamSlug: 'blue1'
      },
      // blue2, blue3, etc...
      red1: {
        slug: 1,
        team: 'red',
        teamSlug: 'red1'
      },
      // red2, etc...
    }
  }
}

      

And the property teamSlug

doesn't have to exist for every user in your API. You can create it in your mutation when loading data into the store.



const mutations = {
  [types.TEAM_USER_SUCCESS] (state, { team, slug, data }) {
    const teamSlug = [team, slug].join('')
    state.users.byTeamSlug = {
      ...state.users.byTeamSlug,
      [teamSlug]: {
        ...data,
        slug: slug,
        team: team,
        teamSlug: teamSlug
      }
    }
    state.users.allTeamSlugs = [
      ...new Set([ // The Set ensures uniqueness
        ...state.users.allTeamSlugs,
        teamSlug
      ])
    ]
  },

  // ...
}

      

Then your getters can work like this:

const getters = {
  allUsers: state => {
    return state.users.allTeamSlugs.map((teamSlug) => {
      return state.users.byTeamSlug[teamSlug];
    });
  },
  usersByTeam: (state, getters) => (inputTeam) => {
    return getters.allUsers.filter((user) => user.team === inputTeam);
  },
  getOne: state => (team, slug) => { // or maybe "userByTeamSlug"?
    const teamSlug = [team, slug].join('');
    return state.users.byTeamSlug[teamSlug]; // or undefined if none found
  }
}

      

Redux has a great article on normalization that I always come back to: https://redux.js.org/recipes/structuring-reducers/normalizing-state-shape#designing-a-normalized-state

0


source







All Articles