useState set yöntemi, değişikliği hemen yansıtmıyor


168

Kanca öğrenmeye çalışıyorum ve useState yöntem beni şaşırttı. Ben bir dizi şeklinde bir duruma bir başlangıç ​​değeri ataıyorum. Set yöntemi, veya useStateile bile benim için çalışmıyor . Ben başka bir bilgisayarda bir API yaptım ve devlet olarak ayarlamak istediğiniz veri alınıyor.spread(...)without spread operator

İşte benim kod:

import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";

const StateSelector = () => {
  const initialValue = [
    {
      category: "",
      photo: "",
      description: "",
      id: 0,
      name: "",
      rating: 0
    }
  ];

  const [movies, setMovies] = useState(initialValue);

  useEffect(() => {
    (async function() {
      try {
        //const response = await fetch(
        //`http://192.168.1.164:5000/movies/display`
        //);
        //const json = await response.json();
        //const result = json.data.result;
        const result = [
          {
            category: "cat1",
            description: "desc1",
            id: "1546514491119",
            name: "randomname2",
            photo: null,
            rating: "3"
          },
          {
            category: "cat2",
            description: "desc1",
            id: "1546837819818",
            name: "randomname1",
            rating: "5"
          }
        ];
        console.log(result);
        setMovies(result);
        console.log(movies);
      } catch (e) {
        console.error(e);
      }
    })();
  }, []);

  return <p>hello</p>;
};

const rootElement = document.getElementById("root");
ReactDOM.render(<StateSelector />, rootElement);

setMovies(result) de setMovies(...result)çalışmıyor. Burada biraz yardım alabilir.

Sonuç değişkeninin film dizisine aktarılmasını bekliyorum.

Yanıtlar:


221

Genişletme ile oluşturulan setState in Class bileşenlerine çok benzer React.Componentveya hook React.PureComponenttarafından sağlanan güncelleyiciyi kullanarak durum güncellemesi useStatede eşzamansızdır ve hemen yansıtmaz

Ayrıca burada asıl mesele sadece asenkron doğası değil, durum değerlerinin mevcut kapanışlarına ve durum güncellemelerine dayanan işlevler tarafından kullanılması, mevcut kapakların etkilenmediği ancak yenilerinin yaratıldığı bir sonraki yeniden sunumda yansıtılacaktır . Şimdi mevcut durumda, kancalar içindeki değerler mevcut kapaklar tarafından elde edilir ve yeniden oluşturma gerçekleştiğinde kapaklar, fonksiyonun yeniden yaratılıp yeniden yaratılmamasına göre güncellenir

Bir setTimeoutişlev ekleseniz bile, zaman aşımı yeniden oluşturma işleminin gerçekleşeceği bir süre sonra çalışacak olsa da, setTimout yine de güncellenmiş olanı değil önceki kapanışındaki değeri kullanacaktır.

setMovies(result);
console.log(movies) // movies here will not be updated

Durum güncellemesinde bir eylem gerçekleştirmek istiyorsanız, useState componentDidUpdatetarafından döndürülen ayarlayıcının geri arama modeli olmadığından, sınıf bileşenlerinde olduğu gibi useEffect kancasını kullanmanız gerekir.

useEffect(() => {
    // action on update of movies
}, [movies]);

Durumu güncelleme sözdizimi söz konusu olduğunda, durumdaki setMovies(result)önceki moviesdeğeri zaman uyumsuz istekte bulunan değerlerle değiştirir

Ancak yanıtı önceden varolan değerlerle birleştirmek istiyorsanız durum güncellemesinin geri arama sözdizimini ve bunun gibi doğru yayılma sözdizimini doğru şekilde kullanmanız gerekir.

setMovies(prevMovies => ([...prevMovies, ...result]));

17
Merhaba, bir form gönderme işleyicisi içinde useState çağrısı yapmaya ne dersiniz? Karmaşık bir formu doğrulama üzerinde çalışıyorum ve sendHandler useState kancaları içinde çağırıyorum ve ne yazık ki değişiklikler hemen değil!
da45

2
useEffecteşzamansız çağrıları desteklemediğinden, en iyi çözüm olmayabilir. Dolayısıyla, moviesdurum değişikliği üzerinde eşzamansız bir doğrulama yapmak istiyorsak , bunun üzerinde hiçbir kontrolümüz yoktur.
RA.

2
tavsiyem çok iyi iken, neden açıklama geliştirilebilir olduğunu not edin - olsun ya da olmasın gerçeği ile ilgisi the updater provided by useState hook aksine eşzamansızsa this.stateeğer mutasyona olabilirdi this.setStatesenkron oldu Kapanış etrafında const moviesaynı olsa bile kalacağını useStatesenkron fonksiyon sağladı
cevabımdaki

setMovies(prevMovies => ([...prevMovies, ...result]));benim için çalıştı
Mihir

Yanlış sonucu günlüğe kaydediyor, çünkü ayarlayıcı eşzamansız olmadığı için eski bir kapanışı günlüğe kaydediyorsunuz. Zaman uyumsuzluk sorunu varsa, zaman aşımından sonra günlüğe kaydedebilirsiniz, ancak bir saat için zaman aşımı ayarlayabilir ve yine de yanlış sonucu günlüğe kaydedebilirsiniz çünkü async soruna neden olan şey değildir.
HMR

127

Önceki cevaba ek detaylar :

En Tepki iken setState ise (sınıflar ve kancalar ikisi) asenkron ve gözlemlenen davranışları açıklamak için gerçeği kullanmak cazip gelebilir, öyle değil mi sebebi o olur.

TLDR: Nedeni değişmez bir değerin etrafındaki kapanma alanı const.


Çözümler:

  • render işlevindeki değeri okuyun (iç içe işlevlerin içinde değil):

    useEffect(() => { setMovies(result) }, [])
    console.log(movies)
  • değişkeni bağımlılıklara ekleyin (ve tepki kancaları / ayrıntılı deps eslint kuralını kullanın):

    useEffect(() => { setMovies(result) }, [])
    useEffect(() => { console.log(movies) }, [movies])
  • değiştirilebilir bir referans kullanın (yukarıdakiler mümkün olmadığında):

    const moviesRef = useRef(initialValue)
    useEffect(() => {
      moviesRef.current = result
      console.log(moviesRef.current)
    }, [])

Neden olduğu hakkında açıklama:

Eğer async tek sebep olsaydı, await setState() .

Howerver, hem propsve stateedilir 1 işlemek sırasında değişmeyen olduğu varsayılır .

this.stateDeğişmezmiş gibi davranın .

Kanca ile bu varsayım kullanılarak geliştirilmiştir sabit değerler ile constanahtar kelime:

const [state, setState] = useState('initial')

Değer 2 render arasında farklı olabilir, ancak renderın içinde ve herhangi bir kapanışın içinde sabit kalır (render tamamlandıktan sonra bile daha uzun süre yaşayan işlevler, ör.useEffect . Olay işleyicileri, herhangi bir Promise veya setTimeout içinde).

Sahte, ancak senkronize , React benzeri bir uygulamayı takip etmeyi düşünün :

// sync implementation:

let internalState
let renderAgain

const setState = (updateFn) => {
  internalState = updateFn(internalState)
  renderAgain()
}

const useState = (defaultState) => {
  if (!internalState) {
    internalState = defaultState
  }
  return [internalState, setState]
}

const render = (component, node) => {
  const {html, handleClick} = component()
  node.innerHTML = html
  renderAgain = () => render(component, node)
  return handleClick
}

// test:

const MyComponent = () => {
  const [x, setX] = useState(1)
  console.log('in render:', x) // ✅
  
  const handleClick = () => {
    setX(current => current + 1)
    console.log('in handler/effect/Promise/setTimeout:', x) // ❌ NOT updated
  }
  
  return {
    html: `<button>${x}</button>`,
    handleClick
  }
}

const triggerClick = render(MyComponent, document.getElementById('root'))
triggerClick()
triggerClick()
triggerClick()
<div id="root"></div>


12
Mükemmel cevap. IMO, bu cevap, okumak ve emmek için yeterli zaman harcarsanız, gelecekte en kalp krizi kurtaracak.
Scott Mallon

Bu, bir bağlamı yönlendiriciyle birlikte kullanmayı çok zorlaştırıyor, değil mi? Bir sonraki sayfaya geldiğimde devlet gitti .. Ve ayarlayamıyorumSadece kullanmanın içindeEtkili kancalar render edemedikleri için ... Cevabın bütünlüğünü takdir ediyorum ve ne gördüğümü açıklıyor, ama ben nasıl yenileceğini anlayamıyorum. Daha sonra etkin bir şekilde devam etmeyen değerleri güncellemek için bağlam içinde işlevleri geçiyorum ... Bu yüzden onları bağlamda kapanıştan çıkarmak zorundayım, değil mi?
Al Joslin

@AlJoslin ilk bakışta, kapanma kapsamından kaynaklansa bile ayrı bir sorun gibi görünüyor. Somut bir sorunuz varsa, lütfen kod örneği ve tümü ile yeni bir
yığın akışı

1
aslında @kentcdobs makalesini (aşağıdaki ref) izleyerek useReducer ile bir yeniden yazmayı bitirdim, bu da bana bu kapatma sorunlarından bir parça bile olmayan sağlam bir sonuç verdi. (ref: kentcdodds.com/blog/how-to-use-react-context-efftively )
Al Joslin

1

UseReducer ile yeni bir yeniden yazmayı bitirdim, @kentcdobs makalesini (aşağıda ref) izleyerek gerçekten bu kapatma sorunlarından bir parça bile olmayan sağlam bir sonuç verdi.

bkz. https://kentcdodds.com/blog/how-to-use-react-context-efftively

Okunabilir ısıtıcı plakasını tercih ettiğim DRYness seviyesine yoğunlaştırdım - sanal alan uygulamasını okumak size nasıl çalıştığını gösterecektir.

Zevk, biliyorum ben !!

import React from 'react'

// ref: https://kentcdodds.com/blog/how-to-use-react-context-effectively

const ApplicationDispatch = React.createContext()
const ApplicationContext = React.createContext()

function stateReducer(state, action) {
  if (state.hasOwnProperty(action.type)) {
    return { ...state, [action.type]: state[action.type] = action.newValue };
  }
  throw new Error(`Unhandled action type: ${action.type}`);
}

const initialState = {
  keyCode: '',
  testCode: '',
  testMode: false,
  phoneNumber: '',
  resultCode: null,
  mobileInfo: '',
  configName: '',
  appConfig: {},
};

function DispatchProvider({ children }) {
  const [state, dispatch] = React.useReducer(stateReducer, initialState);
  return (
    <ApplicationDispatch.Provider value={dispatch}>
      <ApplicationContext.Provider value={state}>
        {children}
      </ApplicationContext.Provider>
    </ApplicationDispatch.Provider>
  )
}

function useDispatchable(stateName) {
  const context = React.useContext(ApplicationContext);
  const dispatch = React.useContext(ApplicationDispatch);
  return [context[stateName], newValue => dispatch({ type: stateName, newValue })];
}

function useKeyCode() { return useDispatchable('keyCode'); }
function useTestCode() { return useDispatchable('testCode'); }
function useTestMode() { return useDispatchable('testMode'); }
function usePhoneNumber() { return useDispatchable('phoneNumber'); }
function useResultCode() { return useDispatchable('resultCode'); }
function useMobileInfo() { return useDispatchable('mobileInfo'); }
function useConfigName() { return useDispatchable('configName'); }
function useAppConfig() { return useDispatchable('appConfig'); }

export {
  DispatchProvider,
  useKeyCode,
  useTestCode,
  useTestMode,
  usePhoneNumber,
  useResultCode,
  useMobileInfo,
  useConfigName,
  useAppConfig,
}

buna benzer bir kullanımla:

import { useHistory } from "react-router-dom";

// https://react-bootstrap.github.io/components/alerts
import { Container, Row } from 'react-bootstrap';

import { useAppConfig, useKeyCode, usePhoneNumber } from '../../ApplicationDispatchProvider';

import { ControlSet } from '../../components/control-set';
import { keypadClass } from '../../utils/style-utils';
import { MaskedEntry } from '../../components/masked-entry';
import { Messaging } from '../../components/messaging';
import { SimpleKeypad, HandleKeyPress, ALT_ID } from '../../components/simple-keypad';

export const AltIdPage = () => {
  const history = useHistory();
  const [keyCode, setKeyCode] = useKeyCode();
  const [phoneNumber, setPhoneNumber] = usePhoneNumber();
  const [appConfig, setAppConfig] = useAppConfig();

  const keyPressed = btn => {
    const maxLen = appConfig.phoneNumberEntry.entryLen;
    const newValue = HandleKeyPress(btn, phoneNumber).slice(0, maxLen);
    setPhoneNumber(newValue);
  }

  const doSubmit = () => {
    history.push('s');
  }

  const disableBtns = phoneNumber.length < appConfig.phoneNumberEntry.entryLen;

  return (
    <Container fluid className="text-center">
      <Row>
        <Messaging {...{ msgColors: appConfig.pageColors, msgLines: appConfig.entryMsgs.altIdMsgs }} />
      </Row>
      <Row>
        <MaskedEntry {...{ ...appConfig.phoneNumberEntry, entryColors: appConfig.pageColors, entryLine: phoneNumber }} />
      </Row>
      <Row>
        <SimpleKeypad {...{ keyboardName: ALT_ID, themeName: appConfig.keyTheme, keyPressed, styleClass: keypadClass }} />
      </Row>
      <Row>
        <ControlSet {...{ btnColors: appConfig.buttonColors, disabled: disableBtns, btns: [{ text: 'Submit', click: doSubmit }] }} />
      </Row>
    </Container>
  );
};

AltIdPage.propTypes = {};

Artık tüm sayfalarımın her yerinde her şey yolunda gidiyor

Güzel!

Teşekkürler Kent!


-2
// replace
return <p>hello</p>;
// with
return <p>{JSON.stringify(movies)}</p>;

Şimdi, kodunuzun gerçekten işe yaradığını görmelisiniz . Çalışmayan şey console.log(movies). Bunun nedeni eski devletimovies işaret ediyor . Dışınızın dışına taşınırsanız , dönüşün hemen üstünde, güncellenen filmler nesnesini görürsünüz.console.log(movies)useEffect

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.