Обновление useState и AsyncStorage и повторная отрисовка экрана на основе заданного результата

Я новичок в React Native, и в настоящее время я пытаюсь получить учетные данные с веб-сайта и сохранить их в памяти устройства. Однако я сталкиваюсь с некоторыми проблемами!

Мой поток:

  1. Когда приложение запускается, оно проверяет asyncStorage на наличие учетных данных.

  2. Если да, то сразу заходим в приложение. В противном случае мы заходим в RootStackScreen (он же экран входа в систему) *** (проблема №1)

  3. (ВХОД) Пользователь вводит свой логин в текстовый ввод, который будет сохранен в useState.

  4. С помощью функции 'loginHandle' запрос выборки вызывается с использованием информации из useState, и мы устанавливаем Credential с возвращенными данными. ** (проблема № 2)

  5. Приложение должно выполнить повторную визуализацию и увидеть, что есть учетные данные, и загрузиться на главный экран. *** (выпуск №3)

Однако я сталкиваюсь с двумя проблемами.

  1. *** Даже когда asyncStorage действительно содержит учетные данные, когда я проверяю appLoad.credentials в return (...), он возвращает значение null. Однако он возвращает правильное значение в моем useEffect (). Я думал, что с помощью useEffect () я вызываю функции во время ComponentsDidMount, перед отрисовкой экрана. Так разве appLoad.credentials не должен содержать строку?

  2. В моем console.log (учетные данные) Он всегда на шаг отстает. Т.е. Я нажимаю кнопку входа в систему один раз. Журнал консоли вернет значение null. Однако я подумал, что, поскольку console.log () находится после команды fetch, учетные данные должны быть установлены перед вызовом console.log!

  3. Как мне заставить приложение повторно отрендерить? Я видел в сети, что касается повторного рендеринга, но слышал, что это плохо!

App.js


import AsyncStorage from '@react-native-community/async-storage';
...

export default function App() {

  const STORAGE_KEY = '@deviceCredentials';
  const [data, setData] = useState('');

  const appLoad = {
    isLoading: true,
    credentials: null
  };


  //Runs during ComponentsDidMount
  useEffect(() => {
    setTimeout(async () => {

      //In 1000ms try to get credentials
      try {
        appLoad.credentials = await AsyncStorage.getItem(STORAGE_KEY);
        console.log(appLoad.credentials);
      } catch (e) {
        console.log(e);
      }
    }, 1000);
  }, []);


  //Render
  return (
 
      <NavigationContainer>
        {/* If credentials is not null then we can go straight into Main screen, 
            otherwise we go to Login Screen */}
        {appLoad.credentials !== null ? (
          <Drawer.Navigator drawerContent={props => <DrawerContent {...props} />}>
            <Drawer.Screen name="MusteringDrawer" component={MainTabScreen} />
          </Drawer.Navigator>
        )
          :
          <RootStackScreen />
        }
      </NavigationContainer>

  );
};

SignIn.js

...
...
const [data, setData] = useState({
                               username: '',
                               password: '',
                               ...
})
const [credential, setCredential] = useState({ data: null });

const STORAGE_KEY = '@deviceCredentials';

...
...
//Handles user data
const handleValidUser = (val) => {
            setData({
                ...data,
                username: val,
            });
}


const loginHandle = () => {
        fetch(url + data.password + data.username)
            .then(x => x.text())
            .then(y => {
                setCredential({ data: y });
            });
            console.log(credential);
            AsyncStorage.setItem(STORAGE_KEY, credential);
    }



return( 
     <Text>Username</Text>
     <TextInput
        placeholder="Your Username"
        onChangeText={(val) => handleValidUser(val)}
        onEndEditing={(e) => handleValidUser(e.nativeEvent.text)}
      />
     <Text>Password</Text>
     <TextInput
         placeholder="Your Password"
         onChangeText={(val) => handlePasswordChange(val)}
      />


      <View style={styles.button}>
            <TouchableOpacity  onPress={() => { loginHandle(); }}>
                   <Text>Sign In</Text>
            </TouchableOpacity>
      </View>


person Alan Chen    schedule 22.07.2020    source источник
comment
можешь выложить на гитхаб и оставить ссылку?   -  person Amir Doreh    schedule 23.07.2020


Ответы (1)


Вы осведомлены о том, что:

const appLoad = {
    isLoading: true,
    credentials: null
};

переопределяется каждый рендер? Более того, если значение устанавливается в ловушке эффекта, react не знает, что оно было изменено, поэтому не будет повторно визуализироваться. И если он выполняет повторный рендеринг (по какой-то совершенно другой причине, этот не будет повторно рендерить) ... ну, новая переменная: D.

Вам нужно запоминать такие данные во всех рендерах. Вы делаете это, как вы наверняка знаете, с помощью хука useState.

Отказ от ответственности: очень важно, чтобы вы прочитали комментарии, которые я поместил в код, они имеют решающее значение для того, чтобы это работало / было лучшей практикой. Этот код также не был протестирован, но вы сможете заставить его работать, используя некоторые из ваших замечательных навыков разработчика :)

export default function App() {
    // Remove this, seems to be a leftover from testing 
    // const [data, setData] = useState('');
    
    const [isLoading, setIsLoading] = useState(true);
    const [credentials, setCredentials] = useState(null);

    // Check for credentials in the first render
    useEffect(() => {
        // 1. I'm not sure why you add a delay, It will probably only make your app appear sluggish
        // 2. Use effect callbacks should not be made async, see this post 
        // https://stackoverflow.com/questions/53332321/react-hook-warnings-for-async-function-in-useeffect-useeffect-function-must-ret
        async function fetchCredentials() {
            setCredentials(await AsyncStorage.getItem(STORAGE_KEY));
            setIsLoading(false); 
        }

        fetchCredentials();
    }, []);

    // Whatever your render code may be
    return null;
}

Вполне может быть, что я не решил вашу проблему (я могу не понимать вас, и большую часть времени я вообще немного глуп). В этом случае не стесняйтесь обращаться ко мне через комментарии.

Приветствую и счастливого кодирования!

person Elias    schedule 22.07.2020
comment
Привет! Большое вам спасибо, что вы действительно помогли мне в этом. Теперь мне было интересно, как мне вызвать App.js из моего метода SignInScreen.js для его повторной визуализации, а также при выходе из системы. - person Alan Chen; 23.07.2020
comment
Чтобы уточнить. Когда я имею в виду повторную визуализацию, я имею в виду, как для App.js, чтобы перепроверить, существуют ли учетные данные в Asyncstorage, и перенаправить пользователя на соответствующий экран (экран входа в систему или главный экран) - person Alan Chen; 23.07.2020
comment
В основном вы хотите, чтобы после выхода из системы приложение переоценивалось? Для этого вам нужно иметь что-то вроде глобального состояния или передать setCredentials в качестве параметра вашему дочернему компоненту. После того, как вы установите значение, вызвав это, response будет знать, что нужно обновить и повторно выполнить рендеринг. - person Elias; 23.07.2020