React Hooks useState () com Object

114

Qual é a maneira correta de atualizar o estado, é um objeto aninhado, em React with Hooks?

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

Como alguém usaria setExampleStatepara atualizar exampleStatepara a(anexando um campo)?

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

b (Alterando valores)?

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

isaacsultan
fonte
você quer dizer adicionar um novo valor de chave de objeto ao objeto existente?
Basta codificar
@Justcode Para o primeiro exemplo, sim, para o segundo exemplo apenas alterando o objeto existente
isaacsultan
onValueChange = {() => setSelection ({... prev, id_1: true})}
Omar bakhsh

Respostas:

122

Você pode passar um novo valor assim

  setExampleState({...exampleState,  masterField2: {
        fieldOne: "c",
        fieldTwo: {
           fieldTwoOne: "d",
           fieldTwoTwo: "e"
           }
        },
   }})
Aseferov
fonte
2
Ótimo, isso responde à parte a! você conhece as melhores práticas para a parte b?
isaacsultan
1
alterando também o mesmo passado em masterFieldvez de masterField2 setExampleState({...exampleState, masterField:{// novos valores}
aseferov
3
Esteja ciente de cópia superficial ao usar o operador de propagação. Ver por exemplo: stackoverflow.com/questions/43638938/…
João Marcos Gris
9
Se você estiver usando o mesmo estado com seu Dispatcher, deverá usar uma função. setExampleState( exampleState => ({...exampleState, masterField2: {...etc} }) );
Michael Harley
62

Se alguém estiver procurando por useState (), atualize os ganchos para o 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
fonte
3
Exatamente o que eu estava procurando. Impressionante!
StackedActor de
42

Geralmente, você deve estar atento a objetos profundamente aninhados no estado React. Para evitar um comportamento inesperado, o estado deve ser atualizado de forma imutável. Quando você tem objetos profundos, acaba clonando-os profundamente para a imutabilidade, o que pode ser bastante caro no React. Por quê?

Depois de clonar profundamente o estado, o React irá recalcular e renderizar novamente tudo o que depende das variáveis, mesmo que elas não tenham mudado!

Portanto, antes de tentar resolver seu problema, pense em como você pode nivelar o estado primeiro. Assim que fizer isso, você encontrará belas ferramentas que o ajudarão a lidar com grandes estados, como useReducer ().

Caso você tenha pensado sobre isso, mas ainda esteja convencido de que precisa usar uma árvore de estados profundamente aninhada, você ainda pode usar useState () com bibliotecas como immutable.js e Immutability-helper . Eles simplificam a atualização ou clonagem de objetos profundos sem ter que se preocupar com a mutabilidade.

Ilyas Assainov
fonte
Você também pode usar o Hookstate (isenção de responsabilidade: eu sou um autor) para gerenciar dados de estado complexos (locais e globais) sem clonagem profunda e sem se preocupar com atualizações desnecessárias - o Hookstate cuidará disso para você.
Andrew
8

Obrigado Philip isso me ajudou - meu caso de uso foi eu tinha um formulário com muitos campos de entrada, então mantive o estado inicial como objeto e não fui capaz de atualizar o estado do objeto. O post acima me ajudou :)

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

Estou atrasado para a festa .. :)

A resposta @aseferov funciona muito bem quando a intenção é entrar novamente em toda a estrutura do objeto. No entanto, se a meta / objetivo é atualizar um valor de campo específico em um objeto, acredito que a abordagem abaixo é melhor.

situação:

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

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

    }

  });

Atualizar um registro específico exigirá fazer um recall para o estado anterior prevState

Aqui:

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

possivelmente

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

    }));

Espero que isso ajude qualquer pessoa que esteja tentando resolver um problema semelhante.

Olamigoke Philip
fonte
1
Eu tenho uma pergunta. Por que precisamos envolver a função entre parênteses? Não tenho certeza do que está acontecendo sob o capô. Você saberia ou onde posso ler mais sobre isso?
Xenon
2
@MichaelRamage Por que colocamos a função entre parênteses ():: Para responder de forma simples; é porque setInfoData é de alta ordem por natureza, ou seja, pode assumir outra função como argumento, cortesia do poder fornecido pelo Gancho: useState. Espero que este artigo forneça mais clareza sobre funções de ordem superior: sitepoint.com/higher-order-functions-javascript
Olamigoke Philip
2
Muito útil, especialmente para o caso em que você está tentando atualizar uma propriedade aninhada em várias camadas de profundidade, ou seja: primeiro.segundo.terceiro - os outros exemplos cobrem o primeiro.segundo, mas não o primeiro.segundo.terceiro
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
fonte
1

Acho que a melhor solução é o Immer . Ele permite que você atualize o objeto como se estivesse modificando campos diretamente (masterField.fieldOne.fieldx = 'abc'). Mas isso não mudará o objeto real, é claro. Ele coleta todas as atualizações em um objeto de rascunho e fornece um objeto final no final que pode ser usado para substituir o objeto original.

Deniz
fonte
0

Deixo-vos uma função utilitária para atualizar objetos inmutavelmente

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

Então você pode usá-lo assim

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
}

Caso contrário, você tem mais utilitários aqui: https://es.reactjs.org/docs/update.html

Gonzalo
fonte
0

Inicialmente, usei object em useState, mas depois mudei para o gancho useReducer para casos complexos. Senti uma melhora no desempenho ao refatorar o código.

useReducer geralmente é preferível a useState quando você tem uma lógica de estado complexa que envolve vários subvalores ou quando o próximo estado depende do anterior.

useReducer React docs

Já implementei esse gancho para meu próprio 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);
      }
    },
  ];
}

mais ganchos relacionados .

Eyalyoli
fonte
0

, faça como este exemplo:

primeiro estado de criação dos objetos:

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

em seguida, altere o valor deles:

// 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
fonte