{"record":{"id":"8c3a2c026f4ebb6b","repo":"facebook/react","slug":"copywithrename-expects-paths-of-the-same-length","errorCode":null,"errorMessage":"copyWithRename() expects paths of the same length","messagePattern":"copyWithRename\\(\\) expects paths of the same length","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"packages/react-reconciler/src/ReactFiberReconciler.js","lineNumber":666,"sourceCode":"      // $FlowFixMe[incompatible-use] number or string is fine here\n      updated[oldKey] = copyWithRenameImpl(\n        // $FlowFixMe[incompatible-use] number or string is fine here\n        obj[oldKey],\n        oldPath,\n        newPath,\n        index + 1,\n      );\n    }\n    return updated;\n  };\n\n  const copyWithRename = (\n    obj: Object | Array<any>,\n    oldPath: Array<string | number>,\n    newPath: Array<string | number>,\n  ): Object | Array<any> => {\n    if (oldPath.length !== newPath.length) {\n      console.warn('copyWithRename() expects paths of the same length');\n      return;\n    } else {\n      for (let i = 0; i < newPath.length - 1; i++) {\n        if (oldPath[i] !== newPath[i]) {\n          console.warn(\n            'copyWithRename() expects paths to be the same except for the deepest key',\n          );\n          return;\n        }\n      }\n    }\n    return copyWithRenameImpl(obj, oldPath, newPath, 0);\n  };\n\n  const copyWithSetImpl = (\n    obj: Object | Array<any>,\n    path: Array<string | number>,\n    index: number,","sourceCodeStart":648,"sourceCodeEnd":684,"githubUrl":"https://github.com/facebook/react/blob/eafeac097ba51e1eab809c07102126bd5f8e5425/packages/react-reconciler/src/ReactFiberReconciler.js#L648-L684","documentation":"copyWithRename is the immutable clone helper behind the DevTools override APIs overrideHookStateRenamePath (ReactFiberReconciler.js:766) and overridePropsRenamePath (ReactFiberReconciler.js:813). It validates that oldPath and newPath have identical length, because it only renames the deepest key of a fixed path. On mismatch it warns and returns undefined - which the caller then assigns to hook.memoizedState/baseState or fiber.pendingProps, silently wiping that state or props to undefined.","triggerScenarios":"Calling overrideHookStateRenamePath(fiber, id, ['user'], ['profile','name']) or overridePropsRenamePath(fiber, ['a'], ['a','b']) - any rename where the two path arrays differ in length. Only reachable from DevTools-style tooling or tests that drive the DevTools hook, never from normal rendering.","commonSituations":"Custom DevTools forks or inspector UIs that build the two path arrays from different tree states (one captured before expanding a node, one after); test harnesses simulating inline state editing; tools that allow renaming a key while also moving it between depths.","solutions":["Pass paths of the same length: rename only the deepest key, e.g. ['user','name'] to ['user','username']","Fix the tool's path builder so both arrays come from the same selected node","For a move between depths, use delete then set (overrideHookStateDeletePath followed by overrideHookState) instead of rename"],"exampleFix":"// before: lengths differ (1 vs 2) - warns, state becomes undefined\noverridePropsRenamePath(fiber, ['user'], ['profile', 'name']);\n\n// after: same depth, only deepest key renamed\noverridePropsRenamePath(fiber, ['user', 'name'], ['user', 'username']);","handlingStrategy":"type-guard","validationCode":"// validate before calling the DevTools override\nif (!isValidRenamePath(oldPath, newPath)) {\n  throw new Error('rename requires equal-length paths');\n}\noverrideHookStateRenamePath(fiber, id, oldPath, newPath);","typeGuard":"function isValidRenamePath(oldPath, newPath) {\n  return (\n    Array.isArray(oldPath) &&\n    Array.isArray(newPath) &&\n    oldPath.length === newPath.length &&\n    oldPath.length > 0 &&\n    oldPath.every((key, i) => i === oldPath.length - 1 || key === newPath[i])\n  );\n}","tryCatchPattern":null,"preventionTips":["Build oldPath and newPath from the same selected tree node","Remember copyWithRename only renames the deepest key - use delete+set for structural moves","Cover rename paths with unit tests when writing DevTools-like tooling"],"tags":["devtools","path-validation","internal-api","state-editing","immutability"],"backgroundTag":"invalid-path-arguments","analyzedSha":"eafeac097ba51e1eab809c07102126bd5f8e5425","analyzedAt":"2026-08-21T22:01:08.818Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}