Implementing State Management in JavaScript with Redux

Best practices for managing application state using Redux to build predictable and maintainable JavaScript applications.

0 likes
13 views

Rule Content

{
  "title": "Implementing State Management in JavaScript with Redux",
  "description": "Best practices for managing application state using Redux to build predictable and maintainable JavaScript applications.",
  "category": "JavaScript Cursor Rules",
  "rules": [
    {
      "name": "Use Redux Toolkit for Simplified Redux Logic",
      "description": "Utilize Redux Toolkit (RTK) to streamline Redux development by reducing boilerplate code and enforcing best practices.",
      "recommendation": "Adopt Redux Toolkit's `createSlice` and `configureStore` functions to simplify the creation of reducers and store configuration.",
      "example": {
        "before": "const initialState = { value: 0 };\nfunction counterReducer(state = initialState, action) {\n  switch (action.type) {\n    case 'increment':\n      return { ...state, value: state.value + 1 };\n    default:\n      return state;\n  }\n}",
        "after": "import { createSlice, configureStore } from '@reduxjs/toolkit';\nconst counterSlice = createSlice({\n  name: 'counter',\n  initialState: { value: 0 },\n  reducers: {\n    increment: state => { state.value += 1; }\n  }\n});\nconst store = configureStore({ reducer: counterSlice.reducer });"
      },
      "references": [
        "https://redux.js.org/style-guide/#use-redux-toolkit-for-writing-redux-logic"
      ]
    },
    {
      "name": "Normalize State Shape",
      "description": "Structure the Redux state in a flat, normalized manner to improve efficiency and manageability.",
      "recommendation": "Use libraries like `normalizr` to transform nested data into a normalized format, reducing redundancy and simplifying state updates.",
      "example": {
        "before": "const state = {\n  posts: [\n    { id: 1, title: 'Post 1', author: { id: 1, name: 'Alice' } },\n    { id: 2, title: 'Post 2', author: { id: 2, name: 'Bob' } }\n  ]\n};",
        "after": "const state = {\n  entities: {\n    posts: {\n      1: { id: 1, title: 'Post 1', authorId: 1 },\n      2: { id: 2, title: 'Post 2', authorId: 2 }\n    },\n    users: {\n      1: { id: 1, name: 'Alice' },\n      2: { id: 2, name: 'Bob' }\n    }\n  },\n  result: [1, 2]\n};"
      },
      "references": [
        "https://redux.js.org/style-guide/#normalize-state-shape"
      ]
    },
    {
      "name": "Use Selectors for Accessing State",
      "description": "Encapsulate state access logic using selectors to improve code maintainability and performance.",
      "recommendation": "Define selectors to abstract state structure and use memoization libraries like Reselect to optimize derived state computations.",
      "example": {
        "before": "const mapStateToProps = state => ({\n  posts: state.posts.filter(post => post.published)\n});",
        "after": "import { createSelector } from 'reselect';\nconst selectPublishedPosts = createSelector(\n  state => state.posts,\n  posts => posts.filter(post => post.published)\n);\nconst mapStateToProps = state => ({\n  posts: selectPublishedPosts(state)\n});"
      },
      "references": [
        "https://redux.js.org/style-guide/#use-selectors-for-accessing-state"
      ]
    },
    {
      "name": "Keep Reducers Pure and Side-Effect Free",
      "description": "Ensure reducers are pure functions without side effects to maintain predictability and testability.",
      "recommendation": "Perform side effects like API calls or routing transitions outside of reducers, typically in middleware or action creators.",
      "example": {
        "before": "function counterReducer(state = { value: 0 }, action) {\n  switch (action.type) {\n    case 'increment':\n      fetch('/api/increment'); // Side effect\n      return { ...state, value: state.value + 1 };\n    default:\n      return state;\n  }\n}",
        "after": "function counterReducer(state = { value: 0 }, action) {\n  switch (action.type) {\n    case 'increment':\n      return { ...state, value: state.value + 1 };\n    default:\n      return state;\n  }\n}\n\nfunction incrementAsync() {\n  return dispatch => {\n    fetch('/api/increment').then(() => {\n      dispatch({ type: 'increment' });\n    });\n  };\n}"
      },
      "references": [
        "https://redux.js.org/style-guide/#reducers-must-not-have-side-effects"
      ]
    },
    {
      "name": "Avoid Mutating State",
      "description": "Prevent state mutations to ensure application predictability and enable features like time-travel debugging.",
      "recommendation": "Use libraries like Immer to write immutable update logic in a more concise and readable manner.",
      "example": {
        "before": "function addTodo(state, action) {\n  state.todos.push(action.payload); // Mutating state\n  return state;\n}",
        "after": "import produce from 'immer';\nfunction addTodo(state, action) {\n  return produce(state, draft => {\n    draft.todos.push(action.payload);\n  });\n}"
      },
      "references": [
        "https://redux.js.org/style-guide/#do-not-mutate-state"
      ]
    },
    {
      "name": "Use Middleware for Asynchronous Actions",
      "description": "Handle asynchronous operations using middleware to keep reducers pure and maintainable.",
      "recommendation": "Implement middleware like Redux Thunk or Redux Saga to manage side effects and asynchronous logic.",
      "example": {
        "before": "function fetchUser(userId) {\n  return {\n    type: 'FETCH_USER',\n    payload: fetch(`/api/users/${userId}`).then(response => response.json())\n  };\n}",
        "after": "function fetchUser(userId) {\n  return async dispatch => {\n    dispatch({ type: 'FETCH_USER_REQUEST' });\n    try {\n      const response = await fetch(`/api/users/${userId}`);\n      const data = await response.json();\n      dispatch({ type: 'FETCH_USER_SUCCESS', payload: data });\n    } catch (error) {\n      dispatch({ type: 'FETCH_USER_FAILURE', error });\n    }\n  };\n}"
      },
      "references": [
        "https://redux.js.org/style-guide/#use-middleware-for-async-logic"
      ]
    },
    {
      "name": "Organize Code by Feature",
      "description": "Structure your Redux codebase by feature to enhance scalability and maintainability.",
      "recommendation": "Group related actions, reducers, and components within feature-specific directories.",
      "example": {
        "before": "src/\n  actions/\n    userActions.js\n    postActions.js\n  reducers/\n    userReducer.js\n    postReducer.js\n  components/\n    UserComponent.js\n    PostComponent.js",
        "after": "src/\n  features/\n    user/\n      actions.js\n      reducer.js\n      UserComponent.js\n    post/\n      actions.js\n      reducer.js\n      PostComponent.js"
      },
      "references": [
        "https://redux.js.org/style-guide/#structure-files-as-feature-folders-with-single-file-logic"
      ]
    }
  ]
}