File size: 2,851 Bytes
1c72248
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
import React from 'react';

/**
 * Updates a deeply nested value in an object using a string path
 * @param obj The object to update
 * @param value The new value to set
 * @param path String path to the property (e.g. 'config.process[0].model.name_or_path')
 * @returns A new object with the updated value
 */
export function setNestedValue<T, V>(obj: T, value: V, path?: string): T {
  // Create a copy of the original object to maintain immutability
  const result = { ...obj };

  // if path is not provided, be root path
  if (!path) {
    path = '';
  }

  // Split the path into segments
  const pathArray = path.split('.').flatMap(segment => {
    // Handle array notation like 'process[0]'
    const arrayMatch = segment.match(/^([^\[]+)(\[\d+\])+/);
    if (arrayMatch) {
      const propName = arrayMatch[1];
      const indices = segment
        .substring(propName.length)
        .match(/\[(\d+)\]/g)
        ?.map(idx => parseInt(idx.substring(1, idx.length - 1)));

      // Return property name followed by array indices
      return [propName, ...(indices || [])];
    }
    return segment;
  });

  // Navigate to the target location
  let current: any = result;
  for (let i = 0; i < pathArray.length - 1; i++) {
    const key = pathArray[i];

    // If current key is a number, treat it as an array index
    if (typeof key === 'number') {
      if (!Array.isArray(current)) {
        throw new Error(`Cannot access index ${key} of non-array`);
      }
      // Create a copy of the array to maintain immutability
      current = [...current];
    } else {
      // For object properties, create a new object if it doesn't exist
      if (current[key] === undefined) {
        // Check if the next key is a number, if so create an array, otherwise an object
        const nextKey = pathArray[i + 1];
        current[key] = typeof nextKey === 'number' ? [] : {};
      } else {
        // Create a shallow copy to maintain immutability
        current[key] = Array.isArray(current[key]) ? [...current[key]] : { ...current[key] };
      }
    }

    // Move to the next level
    current = current[key];
  }

  // Set the value at the final path segment
  const finalKey = pathArray[pathArray.length - 1];
  current[finalKey] = value;

  return result;
}

/**
 * Custom hook for managing a complex state object with string path updates
 * @param initialState The initial state object
 * @returns [state, setValue] tuple
 */
export function useNestedState<T>(initialState: T): [T, (value: any, path?: string) => void] {
  const [state, setState] = React.useState<T>(initialState);

  const setValue = React.useCallback((value: any, path?: string) => {
    if (path === undefined) {
      setState(value);
      return;
    }
    setState(prevState => setNestedValue(prevState, value, path));
  }, []);

  return [state, setValue];
}