异步函数上带有 React Native 挂钩 returns 空 object/array 的上下文

Context with React Native hooks returns empty object/array on async function

我已经和这个问题斗争了一段时间了,我似乎不知道如何解决它。

在我的主屏幕上,我获取了用户坐标和一系列电影院。 Cinemas 数组包含每个电影院的坐标,我将其与用户坐标一起使用来计算从用户到电影院的距离。

在 cinemaContext 中获取和存储电影院很好并且可以工作,但是一旦我 运行 计算距离的函数,上下文中的电影院对象是空的。 距离计算为数组中的每个电影院和returns新数组添加一个距离值。

奇怪的是当我尝试时电影上下文对象是空的。然后,如果我在 cinemaContext 或 getUserCoordinates 函数中编辑某些内容并导航到电影院概览屏幕,那么电影院就在那里,具有距离值。

它必须具有加载顺序或异步功能,因为代码“有效”,但似乎没有在正确的时间填充上下文或用空值覆盖它。

我应该补充一点,我在另一个屏幕上使用电影院数组,我可以像这样访问它:

const { state } = useContext(Context)

Home.js

import React, { useState, useEffect, useContext } from "react";
import { View, Text, StyleSheet, TouchableOpacity, StatusBar, ActivityIndicator } from "react-native";
import * as Location from 'expo-location';
import { Context } from "../context/CinemaContext";

const Home = ({ navigation }) => {

  const [errorMsg, setErrorMsg] = useState(null);
  const { state, updateCinemas, getCinemas } = useContext(Context)

  // Fetch user coordinates and call updateCinemas with the coordinates and cinemas 
  const getUserCoordinates = async (cinemas) => {
    try {
      const granted = await Location.requestPermissionsAsync();
      
      if (granted) {
        let location = await Location.getCurrentPositionAsync({});

        await updateCinemas(cinemas, location.coords.latitude, location.coords.longitude)
        
      } else {
        throw new Error("Location permission not granted");
      }
    } catch (e) {
      setErrorMsg(e)
    }
  }

  useEffect(() => {
    if (state.cinemas.length === 0) {
      getCinemas();
    }
    getUserCoordinates(state.cinemas);
  }, []);

  if (!state.cinemas) {
    return <ActivityIndicator size="large" style={{ marginTop: 200 }} />
  }

  return ( Some views ..)

CinemaContext.js

import dataContext from "./DataContext";
import _ from "lodash";
import { computeDistance } from "../helpers/utils";


const cinemaReducer = (state, action) => {
  switch (action.type) {
    case "add_error":
      return { ...state, errorMessage: action.payload };
    case "get_cinemas":
      return { ...state, cinemas: action.payload };
    case "update_cinemas":
      return { ...state, cinemas: action.payload };
    default:
      return state
  }
};

const getCinemas = dispatch => async () => {
  try {
    const response = await fetch(
      "url-to-cinemas-array",
      { mode: "no-cors" })
    const cinemas = await response.json();
    dispatch({
      type: "get_cinemas",
      payload: cinemas
    });
  } catch (err) {
    dispatch({
      type: "add_error",
      payload: "Something went wrong with the cinemas"
    })
  }
}

const updateCinemas = (dispatch) => {

  return async (cinemas, referenceLat, referenceLong) => {
    
    const cinemasWithDistance = cinemas.map(cinema => {
      return {
        ...cinema,
        distance: computeDistance([cinema.geo.latitude, cinema.geo.longitude], [referenceLat, referenceLong]) // Calculate the distance
        
      };
    });    
    const orderedCinemas = _.orderBy(cinemasWithDistance, 'distance');
    dispatch({ type: "update_cinemas", payload: orderedCinemas });
  }
}

export const { Context, Provider } = dataContext(
  cinemaReducer,
  { updateCinemas, getCinemas },
  { cinemas: [], errorMessage: '' }
);

DataContext.js

import React, { useReducer } from 'react';

export default (reducer, actions, defaultValue) => {
  const Context = React.createContext();

  const Provider = ({ children }) => {
    const [state, dispatch] = useReducer(reducer, defaultValue);

    const boundActions = {};
    for (let key in actions) {
      boundActions[key] = actions[key](dispatch);
    }

    return (
      <Context.Provider value={{ state, ...boundActions }}>
        {children}
      </Context.Provider>
    );
  };

  return { Context, Provider };
};

App.js

import React from "react";
import RootStackNavigator from "./src/navigation/RootStackNavigator";
import { Provider } from "./src/context/CinemaContext";

export default function App() {

  return (
    <Provider>
      <RootStackNavigator />
    </Provider>
  );
};

非常感谢任何帮助! 提前致谢!

我认为当您调用 getUserCoordinates(state.cinemas);state.cinemas 仍然是空的,因此 "update_cinemas" 操作将 运行 使用空数组,覆盖之前由 "get_cinemas"。您可以通过在 getUserCoordinates 调用之前添加 console.log('state.cinemas.length: ', state.cinemas.length); 来验证这一点。

我认为一个解决方案是在依赖于 state.cinemas 数组的单独 useEffect 中调用 getUserCoordinates(这样它每次都会再次 运行s state.cinemas 变化):

useEffect(() => {
  if (state.cinemas.length === 0) {
    getCinemas();
  }
}, []);

useEffect(() => {
  if (state.cinemas.length === 0) return; // cinemas not retrieved yet
  if (typeof state.cinemas[0].distance !== undefined) return; // distance already computed
  getUserCoordinates(state.cinemas);
}, [state.cinemas]);