metoda usestate set nie odzwierciedla natychmiastowej zmiany

Próbuję nauczyć się hooków i Metoda {[1] } sprawiła, że jestem zdezorientowany. Przypisuję wartość początkową do stanu w postaci tablicy. Metoda set w useState nie działa dla mnie nawet z spread(...) lub without spread operator. Zrobiłem API na innym komputerze, który wywołuję i pobieram dane, które chcę ustawić w stanie.

Oto Mój kod:

<script src="https://unpkg.com/@babel/standalone@7/babel.min.js"></script>
<script src="https://unpkg.com/react@17/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.production.min.js"></script>
<div id="root"></div>

<script type="text/babel">
// import React, { useState, useEffect } from "react";
// import ReactDOM from "react-dom";
const { useState, useEffect } = React; // web-browser variant

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 =", result);
        setMovies(result);
        console.log("movies =", movies);
      } catch (e) {
        console.error(e);
      }
    })();
  }, []);

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

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

setMovies(result) jak również setMovies(...result) nie działa. Przydałaby się pomoc.

Oczekuję zmienna wynikowa, która zostanie wepchnięta do tablicy filmów.

Author: 3limin4t0r, 2019-01-07

6 answers

Podobnie jak setState w komponentach klas utworzonych przez rozszerzenie React.Component lub React.PureComponent, Aktualizacja stanu przy użyciu Updater dostarczonego przez useState hook jest również asynchroniczna i nie będzie natychmiast odzwierciedlana.

Ponadto, głównym problemem jest tu nie tylko asynchroniczny charakter, ale fakt, że wartości stanu są używane przez funkcje w oparciu o ich obecne zamknięcia, a aktualizacje stanu będą odzwierciedlać w następnym ponownym renderowaniu, przez które istniejące zamknięcia nie będą miały wpływu, ale nowe są utworzony . Teraz w bieżącym stanie wartości wewnątrz hooków są uzyskiwane przez istniejące zamknięcia, a gdy nastąpi ponowne renderowanie, zamknięcia są aktualizowane na podstawie tego, czy funkcja jest ponownie odtwarzana, czy nie.

Nawet jeśli dodasz setTimeout funkcję, chociaż timeout będzie działał po pewnym czasie, w którym nastąpi ponowne renderowanie, setTimeout nadal będzie używać wartości z poprzedniego zamknięcia, a nie zaktualizowanej.

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

Jeśli chcesz wykonać akcję na stanie update, musisz użyć Hooka useEffect, podobnie jak użycie componentDidUpdate w komponentach klasy, ponieważ setter zwracany przez useState nie ma wzorca wywołania zwrotnego

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

Jeśli chodzi o składnię update state, setMovies(result) zastąpi poprzednią wartość movies w stanie tymi dostępnymi z żądania asynchronicznego.

Jednakże, jeśli chcesz scalić odpowiedź z wcześniej istniejącymi wartościami, musisz użyć składni wywołania zwrotnego state updation wraz z prawidłowym użyciem składnia rozproszona jak

setMovies(prevMovies => ([...prevMovies, ...result]));
 369
Author: Shubham Khatri,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-09-29 12:08:28

dodatkowe informacje do poprzednia odpowiedź:

Podczas gdy React ' s setState jest asynchroniczne (zarówno klasy, jak i Hooki), i kuszące jest wykorzystanie tego faktu do wyjaśnienia obserwowanego zachowania, nie jest to powód zdarza się.

TLDR: powodem jest zamknięcie zakres wokół niezmiennej const wartości.


rozwiązania:

  • Odczyt wartości w render funkcja (nie wewnątrz funkcji zagnieżdżonych):

    useEffect(() => { setMovies(result) }, [])
    console.log(movies)
    
  • Dodaj zmienną do Zależności (i użyj react-hooks/exhaustive-deps eslint rule):

    useEffect(() => { setMovies(result) }, [])
    useEffect(() => { console.log(movies) }, [movies])
    
  • Użyj zmiennego odniesienia (gdy powyższe nie jest możliwe):

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

wyjaśnienie dlaczego tak się dzieje:

Gdyby asynchronizacja była jedynym powodem, byłoby możliwe await setState().

Howerver, zarówno props jak i statezakłada się, że niezmienny podczas 1 renderowania .

Traktuj this.state jakby to było niezmienne.

Z hookami założenie to jest wzmocnione przez użycie wartości stałych ze słowem kluczowym const:

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

Wartość może być różna pomiędzy dwoma renderami, ale pozostaje stałą wewnątrz samego renderowania i wewnątrz dowolnych zamknięć (funkcje, które żyją dłużej nawet po zakończeniu renderowania, np. useEffect, procedury obsługi zdarzeń, wewnątrz dowolnej obietnicy lub innych setTimeout).

Rozważ podążanie za fałszywą, ale synchroniczną , implementacją podobną do Reacta:

// 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>
 217
Author: Aprillion,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-01-02 15:36:01

Właśnie skończyłem przepisać z useReducer, po artykule @kentcdobs (ref poniżej), który naprawdę dał mi solidny wynik, który nie cierpi ani trochę z powodu tych problemów z zamknięciem.

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

Skondensowałem jego czytelną płytkę kotła do mojego preferowanego poziomu suchości-przeczytanie jego implementacji piaskownicy pokaże Ci, jak to naprawdę działa. Enjoy, I know I am !!
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,
}

Z użyciem podobne do tego:

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 = {};

Teraz wszystko utrzymuje się płynnie wszędzie na wszystkich moich stronach

Nieźle! Dzięki Kent!
 4
Author: Al Joslin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-06-16 17:02:52

UseEffect ma swój własny stan / cykl życia, nie będzie aktualizowany, dopóki nie przekażesz funkcji w parametrach lub efekt zniszczony.

Object i array spread lub rest nie będą działać wewnątrz useEffect.

React.useEffect(() => {
    console.log("effect");
    (async () => {
        try {
            let result = await fetch("/query/countries");
            const res = await result.json();
            let result1 = await fetch("/query/projects");
            const res1 = await result1.json();
            let result11 = await fetch("/query/regions");
            const res11 = await result11.json();
            setData({
                countries: res,
                projects: res1,
                regions: res11
            });
        } catch {}
    })(data)
}, [setData])
# or use this
useEffect(() => {
    (async () => {
        try {
            await Promise.all([
                fetch("/query/countries").then((response) => response.json()),
                fetch("/query/projects").then((response) => response.json()),
                fetch("/query/regions").then((response) => response.json())
            ]).then(([country, project, region]) => {
                // console.log(country, project, region);
                setData({
                    countries: country,
                    projects: project,
                    regions: region
                });
            })
        } catch {
            console.log("data fetch error")
        }
    })()
}, [setData]);
 1
Author: Muhammad Imran Siddique,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-09-04 06:19:25

Możesz go rozwiązać używając Hooka useRef, ale wtedy nie będzie ponownie renderowany, gdy zostanie zaktualizowany. Stworzyłem Hooki o nazwie useStateRef, które dają ci dobro z obu światów. To jest jak stan, który po aktualizacji komponent ponownie renderuje i jest jak "ref", który zawsze ma najnowszą wartość.

Zobacz ten przykład:

var [state,setState,ref]=useStateRef(0)

Działa dokładnie tak jak useState, ale dodatkowo Podaje aktualny stan pod ref.current

Ucz się więcej:

 1
Author: Aminadav Glickshtein,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-12-24 09:28:15
// replace
return <p>hello</p>;
// with
return <p>{JSON.stringify(movies)}</p>;

Teraz powinieneś zobaczyć, że Twój kod faktycznie działa . Nie działa console.log(movies). Dzieje się tak dlatego, że movies wskazuje na stary stan . Jeśli przeniesiesz console.log(movies) poza useEffect, tuż nad return, zobaczysz obiekt zaktualizowane filmy.

 -7
Author: Nikita Malyschkin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-05-18 06:49:22