Reaccionar Hooks useState () con Object

100

¿Cuál es la forma correcta de actualizar el estado, es un objeto anidado, en React with Hooks?

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

¿Cómo se usaría setExampleStatepara actualizar exampleStatea a(agregar un campo)?

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

b (Cambio de valores)?

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

isaacsultan
fuente
¿Te refieres a agregar un nuevo valor de clave de objeto al objeto existente?
Solo codifique el
@Justcode Para el primer ejemplo sí, para el segundo ejemplo simplemente modifica el objeto existente
isaacsultan

Respuestas:

107

Puedes pasar un nuevo valor como este

  setExampleState({...exampleState,  masterField2: {
        fieldOne: "c",
        fieldTwo: {
           fieldTwoOne: "d",
           fieldTwoTwo: "e"
           }
        },
   }})
aseferov
fuente
2
¡Genial, eso responde a la parte a! ¿Conoce las mejores prácticas para la parte b?
isaacsultan
1
cambiando también el mismo pasado en masterFieldlugar de masterField2 setExampleState({...exampleState, masterField:{// nuevos valores}
aseferov
3
Tenga en cuenta las copias poco profundas cuando utilice el operador de extensión. Ver por ejemplo: stackoverflow.com/questions/43638938/…
João Marcos Gris
7
Si está usando el mismo estado con su Dispatcher, debe usar una función. setExampleState( exampleState => ({...exampleState, masterField2: {...etc} }) );
Michael Harley
41

En general, debe tener cuidado con los objetos profundamente anidados en el estado React. Para evitar un comportamiento inesperado, el estado debe actualizarse de forma inmutable. Cuando tienes objetos profundos, terminas clonándolos en profundidad por inmutabilidad, lo que puede ser bastante costoso en React. ¿Por qué?

Una vez que clone en profundidad el estado, React recalculará y volverá a renderizar todo lo que depende de las variables, ¡aunque no hayan cambiado!

Por lo tanto, antes de intentar resolver su problema, piense cómo puede aplanar el estado primero. Tan pronto como lo haga, encontrará hermosas herramientas que lo ayudarán a lidiar con estados grandes, como useReducer ().

En caso de que lo haya pensado, pero aún está convencido de que necesita usar un árbol de estado profundamente anidado, aún puede usar useState () con bibliotecas como immutable.js e Immutability-helper . Hacen que sea sencillo actualizar o clonar objetos profundos sin tener que preocuparse por la mutabilidad.

Ilyas Assainov
fuente
También puede usar Hookstate (descargo de responsabilidad: soy un autor) para administrar datos de estado complejos (locales y globales) sin clonación profunda y sin preocuparse por actualizaciones innecesarias: Hookstate lo manejará por usted.
Andrew
40

Si alguien está buscando useState (), actualice los ganchos para el objeto

- 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
fuente
3
Exactamente lo que estaba buscando. ¡Increíble!
StackedActor
7

Gracias, Felipe, esto me ayudó: mi caso de uso fue que tenía un formulario con muchos campos de entrada, así que mantuve el estado inicial como objeto y no pude actualizar el estado del objeto. La publicación anterior me ayudó :)

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
fuente
6

Llego tarde a la fiesta .. :)

La respuesta de @aseferov funciona muy bien cuando la intención es volver a ingresar a toda la estructura del objeto. Sin embargo, si el objetivo / objetivo es actualizar un valor de campo específico en un Objeto, creo que el enfoque a continuación es mejor.

situación:

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

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

    }

  });

La actualización de un registro específico requerirá hacer un retiro del estado anterior prevState

Aquí:

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

quizás

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

    }));

Espero que esto ayude a cualquiera que intente resolver un problema similar.

Olamigoke Philip
fuente
Tengo una pregunta. ¿Por qué necesitamos envolver la función entre paréntesis? No estoy seguro de lo que está pasando debajo del capó. ¿Sabrías por casualidad o dónde puedo leer más sobre eso?
Xenon
1
@MichaelRamage ¿Por qué envolvemos la función entre paréntesis ():: Para responder esto simplemente; es porque setInfoData es de alto orden por naturaleza, es decir, puede tomar otra función como argumento, cortesía del poder proporcionado por el Hook: useState. Espero que este artículo brinde más claridad sobre las funciones de orden superior: sitepoint.com/higher-order-functions-javascript
Olamigoke Philip
1
Muy útil, especialmente para el caso en el que está intentando actualizar una propiedad anidada en varias capas de profundidad, es decir: primero.segundo.tercero - los otros ejemplos cubren el primero.segundo, pero no primero.segundo.tercero
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
fuente
1

Creo que la mejor solución es Immer . Le permite actualizar el objeto como si estuviera modificando campos directamente (masterField.fieldOne.fieldx = 'abc'). Pero, por supuesto, no cambiará el objeto real. Recopila todas las actualizaciones de un objeto de borrador y le brinda un objeto final al final que puede usar para reemplazar el objeto original.

Deniz
fuente
0

Te dejo una función de utilidad para actualizar objetos de forma inmutable

/**
 * 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
  };
};

Entonces puedes usarlo así

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
}

Si no tiene más utilidades aquí: https://es.reactjs.org/docs/update.html

Gonzalo
fuente
0

Inicialmente usé object en useState, pero luego pasé a useReducer hook para casos complejos. Sentí una mejora en el rendimiento cuando refactoricé el código.

useReducer generalmente es preferible a useState cuando tiene una lógica de estado compleja que involucra múltiples subvalores o cuando el siguiente estado depende del anterior.

useReducer React docs

Ya implementé tal gancho para mi propio uso:

/**
 * 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);
      }
    },
  ];
}

ganchos más relacionados .

eyalyoli
fuente