Reaguj hooki useState () z Object

116

Jaki jest poprawny sposób aktualizowania stanu, czy obiekt zagnieżdżony w React with Hooks?

export Example = () => {
  const [exampleState, setExampleState] = useState(
  {masterField: {
        fieldOne: "a",
        fieldTwo: {
           fieldTwoOne: "b"
           fieldTwoTwo: "c"
           }
        }
   })

Jak można użyć setExampleStateaktualizacji exampleStatedo a(dołączania pola)?

const a = {
masterField: {
        fieldOne: "a",
        fieldTwo: {
           fieldTwoOne: "b",
           fieldTwoTwo: "c"
           }
        },
  masterField2: {
        fieldOne: "c",
        fieldTwo: {
           fieldTwoOne: "d",
           fieldTwoTwo: "e"
           }
        },
   }
}

b (Zmiana wartości)?

const b = {masterField: {
        fieldOne: "e",
        fieldTwo: {
           fieldTwoOne: "f"
           fieldTwoTwo: "g"
           }
        }
   })

isaacsultan
źródło
masz na myśli dodanie nowej wartości klucza obiektu do istniejącego obiektu?
Po prostu
@Justcode W pierwszym przykładzie tak, w drugim przykładzie tylko zmiana istniejącego obiektu
isaacsultan
onValueChange = {() => setSelection ({... prev, id_1: true})}
Omar bakhsh

Odpowiedzi:

123

W ten sposób możesz przekazać nową wartość

  setExampleState({...exampleState,  masterField2: {
        fieldOne: "c",
        fieldTwo: {
           fieldTwoOne: "d",
           fieldTwoTwo: "e"
           }
        },
   }})
aseferov
źródło
2
Świetnie, to odpowiada część a! czy znasz najlepszą praktykę dotyczącą części b?
isaacsultan
1
zmiana również po prostu przeszła masterFieldzamiast masterField2 setExampleState({...exampleState, masterField:{// nowe wartości}
aseferov
3
Należy pamiętać o płytkim kopiowaniu podczas korzystania z operatora rozkładania. Zobacz na przykład: stackoverflow.com/questions/43638938/…
João Marcos Gris
9
Jeśli używasz tego samego stanu z jego Dispatcherem, powinieneś użyć funkcji. setExampleState( exampleState => ({...exampleState, masterField2: {...etc} }) );
Michael Harley,
64

Jeśli ktoś szuka haków useState (), zaktualizuj obiekt

- Through Input

        const [state, setState] = useState({ fName: "", lName: "" });
        const handleChange = e => {
            const { name, value } = e.target;
            setState(prevState => ({
                ...prevState,
                [name]: value
            }));
        };

        <input
            value={state.fName}
            type="text"
            onChange={handleChange}
            name="fName"
        />
        <input
            value={state.lName}
            type="text"
            onChange={handleChange}
            name="lName"
        />
   ***************************

 - Through onSubmit or button click
    
        setState(prevState => ({
            ...prevState,
            fName: 'your updated value here'
         }));
Maheshvirus
źródło
3
Dokładnie to, czego szukałem. Niesamowite!
StackedActor
42

Generalnie należy uważać na głęboko zagnieżdżone obiekty w stanie React. Aby uniknąć nieoczekiwanego zachowania, stan powinien być niezmiennie aktualizowany. Kiedy masz głębokie obiekty, ostatecznie klonujesz je w celu uzyskania niezmienności, co w Reakcie może być dość kosztowne. Czemu?

Gdy głęboko sklonujesz stan, React przeliczy i ponownie wyrenderuje wszystko, co zależy od zmiennych, nawet jeśli się nie zmieniły!

Zanim więc spróbujesz rozwiązać swój problem, zastanów się, jak możesz najpierw spłaszczyć stan. Jak tylko to zrobisz, znajdziesz piękne narzędzia, które pomogą radzić sobie z dużymi stanami, takie jak useReducer ().

Jeśli zastanawiałeś się nad tym, ale nadal jesteś przekonany, że musisz użyć głęboko zagnieżdżonego drzewa stanu, możesz nadal używać useState () z bibliotekami takimi jak immutable.js i Immutability-helper . Ułatwiają aktualizację lub klonowanie głębokich obiektów bez martwienia się o zmienność.

Ilyas Assainov
źródło
Możesz także użyć Hookstate (zastrzeżenie: jestem autorem) do zarządzania złożonymi (lokalnymi i globalnymi) danymi o stanie bez głębokiego klonowania i bez obawy o niepotrzebne aktualizacje - Hookstate zajmie się tym za Ciebie.
Andrew,
8

Dzięki Filipowi to mi pomogło - moim przypadkiem użycia było to, że miałem formularz z wieloma polami wejściowymi, więc zachowałem stan początkowy jako obiekt i nie mogłem zaktualizować stanu obiektu. Powyższy post mi pomógł :)

const [projectGroupDetails, setProjectGroupDetails] = useState({
    "projectGroupId": "",
    "projectGroup": "DDD",
    "project-id": "",
    "appd-ui": "",
    "appd-node": ""    
});

const inputGroupChangeHandler = (event) => {
    setProjectGroupDetails((prevState) => ({
       ...prevState,
       [event.target.id]: event.target.value
    }));
}

<Input 
    id="projectGroupId" 
    labelText="Project Group Id" 
    value={projectGroupDetails.projectGroupId} 
    onChange={inputGroupChangeHandler} 
/>


Kavitha Vikas
źródło
6

Spóźniłem się na imprezę .. :)

Odpowiedź @aseferov działa bardzo dobrze, gdy intencją jest ponowne wejście do całej struktury obiektu. Jeśli jednak celem / celem jest aktualizacja określonej wartości pola w obiekcie, uważam, że poniższe podejście jest lepsze.

sytuacja:

const [infoData, setInfoData] = useState({
    major: {
      name: "John Doe",
      age: "24",
      sex: "M",
    },

    minor:{
      id: 4,
      collegeRegion: "south",

    }

  });

Aktualizacja konkretnego rekordu będzie wymagała przywrócenia do poprzedniego stanu prevState

Tutaj:

setInfoData((prevState) => ({
      ...prevState,
      major: {
        ...prevState.major,
        name: "Tan Long",
      }
    }));

być może

setInfoData((prevState) => ({
      ...prevState,
      major: {
        ...prevState.major,
        name: "Tan Long",
      },
      minor: {
        ...prevState.minor,
        collegeRegion: "northEast"

    }));

Mam nadzieję, że pomoże to każdemu, kto próbuje rozwiązać podobny problem.

Olamigoke Philip
źródło
1
Mam pytanie. Dlaczego musimy zawijać funkcję w nawiasach? Nie jestem pewien, co się dzieje pod maską. Czy zdarzyło Ci się wiedzieć lub gdzie mogę przeczytać więcej na ten temat?
Xenon,
2
@MichaelRamage Dlaczego zawijamy funkcję w nawiasach ():: Aby odpowiedzieć na to proste; setInfoData Dzieje się tak dlatego, że jest z natury wysokiego rzędu, tj. może przyjąć inną funkcję jako argument, dzięki możliwości dostarczonej przez Hook: useState. Mam nadzieję, że ten artykuł zapewni większą przejrzystość funkcji wyższego rzędu: sitepoint.com/higher-order-functions-javascript
Olamigoke Philip
2
Bardzo pomocne, szczególnie w przypadku, gdy próbujesz zaktualizować właściwość zagnieżdżoną w kilku warstwach, np. Pierwsza. Druga trzecia - inne przykłady obejmują pierwszą. Drugą, ale nie pierwszą. Drugą. Trzecią
Craig Presti - MSFT
3
function App() {

  const [todos, setTodos] = useState([
    { id: 1, title: "Selectus aut autem", completed: false },
    { id: 2, title: "Luis ut nam facilis et officia qui", completed: false },
    { id: 3, title: "Fugiat veniam minus", completed: false },
    { id: 4, title: "Aet porro tempora", completed: true },
    { id: 5, title: "Laboriosam mollitia et enim quasi", completed: false }
  ]);

  const changeInput = (e) => {todos.map(items => items.id === parseInt(e.target.value) && (items.completed = e.target.checked));
 setTodos([...todos], todos);}
  return (
    <div className="container">
      {todos.map(items => {
        return (
          <div key={items.id}>
            <label>
<input type="checkbox" 
onChange={changeInput} 
value={items.id} 
checked={items.completed} />&nbsp; {items.title}</label>
          </div>
        )
      })}
    </div>
  );
}
vikas kumar
źródło
1

Myślę, że najlepszym rozwiązaniem jest Immer . Pozwala na aktualizację obiektu tak, jak bezpośrednio modyfikujesz pola (masterField.fieldOne.fieldx = 'abc'). Ale oczywiście nie zmieni to rzeczywistego obiektu. Zbiera wszystkie aktualizacje obiektu roboczego i na końcu daje ostateczny obiekt, którego można użyć do zastąpienia oryginalnego obiektu.

Deniz
źródło
0

Zostawiam ci funkcję narzędzia do nieodwracalnej aktualizacji obiektów

/**
 * Inmutable update object
 * @param  {Object} oldObject     Object to update
 * @param  {Object} updatedValues Object with new values
 * @return {Object}               New Object with updated values
 */
export const updateObject = (oldObject, updatedValues) => {
  return {
    ...oldObject,
    ...updatedValues
  };
};

Więc możesz tego używać w ten sposób

const MyComponent = props => {

  const [orderForm, setOrderForm] = useState({
    specialities: {
      elementType: "select",
      elementConfig: {
        options: [],
        label: "Specialities"
      },
      touched: false
    }
  });


// I want to update the options list, to fill a select element

  // ---------- Update with fetched elements ---------- //

  const updateSpecialitiesData = data => {
    // Inmutably update elementConfig object. i.e label field is not modified
    const updatedOptions = updateObject(
      orderForm[formElementKey]["elementConfig"],
      {
        options: data
      }
    );
    // Inmutably update the relevant element.
    const updatedFormElement = updateObject(orderForm[formElementKey], {
      touched: true,
      elementConfig: updatedOptions
    });
    // Inmutably update the relevant element in the state.
    const orderFormUpdated = updateObject(orderForm, {
      [formElementKey]: updatedFormElement
    });
    setOrderForm(orderFormUpdated);
  };

  useEffect(() => {
      // some code to fetch data
      updateSpecialitiesData.current("specialities",fetchedData);
  }, [updateSpecialitiesData]);

// More component code
}

Jeśli nie, masz więcej narzędzi tutaj: https://es.reactjs.org/docs/update.html

Gonzalo
źródło
0

Początkowo użyłem object w useState, ale potem przeszedłem do haka useReducer dla złożonych przypadków. Odczułem poprawę wydajności, kiedy refaktoryzowałem kod.

useReducer jest zwykle lepszy od useState, gdy masz złożoną logikę stanu, która obejmuje wiele wartości podrzędnych lub gdy następny stan zależy od poprzedniego.

useReducer React docs

Zaimplementowałem już taki hak na własny użytek:

/**
 * Same as useObjectState but uses useReducer instead of useState
 *  (better performance for complex cases)
 * @param {*} PropsWithDefaultValues object with all needed props 
 * and their initial value
 * @returns [state, setProp] state - the state object, setProp - dispatch 
 * changes one (given prop name & prop value) or multiple props (given an 
 * object { prop: value, ...}) in object state
 */
export function useObjectReducer(PropsWithDefaultValues) {
  const [state, dispatch] = useReducer(reducer, PropsWithDefaultValues);

  //newFieldsVal={[field_name]: [field_value], ...}
  function reducer(state, newFieldsVal) {
    return { ...state, ...newFieldsVal };
  }

  return [
    state,
    (newFieldsVal, newVal) => {
      if (typeof newVal !== "undefined") {
        const tmp = {};
        tmp[newFieldsVal] = newVal;
        dispatch(tmp);
      } else {
        dispatch(newFieldsVal);
      }
    },
  ];
}

więcej powiązanych haków .

eyalyoli
źródło
0

, zrób to jak w tym przykładzie:

stan pierwszego stworzenia obiektów:

const [isSelected, setSelection] = useState({ id_1: false }, { id_2: false }, { id_3: false });

następnie zmień wartość jednego z nich:

// if the id_1 is false make it true or return it false.

onValueChange={() => isSelected.id_1 == false ? setSelection({ ...isSelected, id_1: true }) : setSelection({ ...isSelected, id_1: false })}
Omar bakhsh
źródło