React挂钩-更新后的状态不会重新呈现

时间:2020-09-28 19:26:40

标签: reactjs typescript react-native react-hooks

使用以下useEffect钩子,除非用户触摸屏幕,或者即使我们将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显然是唯一的,并且可以确认它在传递道具的组件树中正确地更新了。我意识到这是一个普遍的问题,但是,关于S / O(或Internet上其他地方)的类似答案均未找到合适的解决方案。

完整的组件可以在下面找到:

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 && setSelectedDefinition(definitions[0]);
  }, [definitions]);

  return (
    <ScrollView>
      <Text style={styles.title}>{firstName}</Text>
      {loading && <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 && setSelectedDefinition(definition);
        }}
      >
        {definitions.map((defintion) => {
          const { title, id } = defintion;
          return <Picker.Item key={id} label={title} value={id} />;
        })}
      </Picker>
      {selectedDefintion && <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);

0 个答案:

没有答案