Реагирующие перехваты — обновленное состояние не выполняется повторно

#reactjs #typescript #react-native #react-hooks

#reactjs #typescript #реагировать — родной #реагирующие крючки

Вопрос:

Используя следующий useEffect перехват, обновленное состояние не отображается, если пользователь не коснется экрана или даже если мы поместим a console.log под эффектом (как указано в приведенном ниже фрагменте):

  export const ExerciseForm = ({
  definition: { id },
}: {
  definition: ExerciseDefinition;
}) => {
  const initialSet: Set[] = [{ reps: 0, value: 5 }];
  const [sets, setSets]: [Set[], Dispatch<Set[]>] = useState(initialSet);

  // Reset form if definition ID changes
  useEffect(() => {
    setSets(initialSet);
  }, [id]);

  // Uncommenting this will make it the rerender work??
  // console.log('id', id);
  

id Очевидно, что каждый из них уникален и может подтвердить, что он правильно обновляется выше в дереве компонентов, из которого передается prop. Я понимаю, что это распространенная проблема, однако ни один из подобных ответов на S / O (или где-либо еще в Интернете) не дал правильного решения.

Полные компоненты можно найти ниже:

ExerciseForm.tsx

 import React, { useState, Dispatch, useEffect } from 'react';
import { ExerciseDefinition, Set } from '../constants/Interfaces';
import { ScrollView, View } from 'react-native';
import { Input, Button } from 'react-native-elements';

export const ExerciseForm = ({
  definition: { id },
}: {
  definition: ExerciseDefinition;
}) => {
  const initialSet: Set[] = [{ reps: 0, value: 5 }];
  const [sets, setSets]: [Set[], Dispatch<Set[]>] = useState(initialSet);

  // Reset form if definition ID changes
  useEffect(() => {
    setSets(initialSet);
  }, [id]);

  // Uncommenting this will make it the rerender work??
  // console.log('id', id);

  const updateSet = (index: number, field: 'reps' | 'value', value: string) => {
    const updatedSets = [...sets];
    updatedSets[index][field] = parseInt(value);
    setSets(updatedSets);
  };

  const addSet = () => {
    const updatedSets = [...sets, { ...sets[sets.length - 1] }];
    setSets(updatedSets);
  };

  return (
    <ScrollView>
      {sets.map(({ reps, value }: Set, index: number) => (
        <View key={index}>
          <Input
            label="Reps"
            keyboardType="numeric"
            value={reps ? reps.toString() : ''}
            onChange={({ nativeEvent }) =>
              updateSet(index, 'reps', nativeEvent.text)
            }
          />
          <Input
            label="Weight"
            keyboardType="numeric"
            value={value ? value.toString() : ''}
            onChange={({ nativeEvent }) =>
              updateSet(index, 'value', nativeEvent.text)
            }
          />
        </View>
      ))}
      <Button title="Add set" onPress={() => addSet()} />
    </ScrollView>
  );
};  

HomeScreen.tsx

 import React, {
  useContext,
  useReducer,
  useEffect,
  useState,
  Dispatch,
} from 'react';
import {
  StyleSheet,
  Picker,
  ScrollView,
  ActivityIndicator,
} from 'react-native';
import { Text } from '../components/Themed';
import { UserContext } from '../services/context';
import {
  exerciseDefinitionReducer,
  initialExerciseDefinitionState,
  exerciseDefinitionActions,
} from '../reducers/exerciseDefinition';
import { ExerciseDefinition } from '../constants/Interfaces';
import { ExerciseForm } from '../components/ExerciseForm';

export default function HomeScreen() {
  const {
    state: { firstName },
  } = useContext(UserContext);

  const [{ definitions, loading }, definitionDispatch] = useReducer(
    exerciseDefinitionReducer,
    initialExerciseDefinitionState
  );

  const [selectedDefintion, setSelectedDefinition]: [
    ExerciseDefinition | undefined,
    Dispatch<ExerciseDefinition>
  ] = useState();

  // Get definitions on mount
  useEffect(() => {
    exerciseDefinitionActions(definitionDispatch).getDefinitions();
  }, []);

  // Set default definition to first item
  useEffect(() => {
    !selectedDefintion amp;amp; setSelectedDefinition(definitions[0]);
  }, [definitions]);

  return (
    <ScrollView>
      <Text style={styles.title}>{firstName}</Text>
      {loading amp;amp; <ActivityIndicator />}
      {/* TODO: decide whether to use this R/N Picker or NativeBase picker */}
      <Picker
        selectedValue={selectedDefintion?.id}
        onValueChange={(id) => {
          const definition = definitions.find((def) => def.id === id);
          definition amp;amp; setSelectedDefinition(definition);
        }}
      >
        {definitions.map((defintion) => {
          const { title, id } = defintion;
          return <Picker.Item key={id} label={title} value={id} />;
        })}
      </Picker>
      {selectedDefintion amp;amp; <ExerciseForm definition={selectedDefintion} />}
    </ScrollView>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
  },
  title: {
    fontSize: 20,
    fontWeight: 'bold',
  },
  separator: {
    marginVertical: 30,
    height: 1,
    width: '80%',
  },
});  

(Редактирование 29/09/2020)

Интересно, что ни одно из следующих console.logs действий не выполняется до тех пор, пока пользователь не коснется экрана (или отмеченная строка с комментариями не раскомментирована), предполагая, что проблема, безусловно, связана с useEffect оператором или его зависимостью:

   // Reset form if definition ID changes
  useEffect(() => {
    console.log('old ID', id);
    setSets(initialSet);
    console.log('new ID', id);
  }, [id]);

  // Uncommenting this will make it the rerender work??
  // console.log('id', id);