如何在 React Native 中将 AsyncStorage 与 Context Provider 一起使用?

问题描述 投票:0回答:2

我对 React Native 很陌生,对 Context 也很陌生。所以任何帮助都会很棒。

我正在构建一个 React Native 应用程序,它引入外部 API 并创建存储在“团队”中的对象,用户从 API 填充的数据中选择这些对象。

我使用 Context & Provider 作为我的应用程序的包装器。 我正在尝试使用 AsyncStorage 在本地保存数据。

我尝试了几乎所有可能的方式使用 AsyncStorage,但不确定我哪里出错了。

非常感谢任何帮助,因为这是我这个应用程序最不需要的东西。

编辑: 目前,我可以保存团队,但是,在导航回到显示团队的屏幕时,它不会显示任何内容。

如果我转到主屏幕并导航回团队屏幕,我将看到我的数据。对于每个创建的团队来说,此过程都是相同的。

此外,即使传递给减速器的状态来自从 AsyncStorage 提取 JSON 的 getData(),数据也不会持久化。

上下文文件:

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

import createDataContext from './createDataContext';

const teamsReducer = (state, action) => {

  switch (action.type) {
    case 'edit_team' :
      return state.map((team) => {
        return team.id === action.payload.id ? 
        action.payload
        : 
        team
      });
    case 'delete_team':
      return state.filter(team => team.id !== action.payload)
    case 'add_team':
      return [
        ...state, 
        { 
          id: uuid.v4(), 
          name: action.payload.name,
          content: action.payload.content
        }
      ];
    default:
      return state;
  }
};

const addTeam = dispatch => {
  return (name, content) => {
    dispatch({ type: 'add_team', payload: { name, content } });
  };
};

const editTeam = dispatch => {
  return (id, name, content) => {
    dispatch({ type: 'edit_team', payload: { id, name, content } })
  };
};

const deleteTeam = dispatch => {
  return (id) => {
    dispatch({ type: 'delete_team', payload: id })
  };
};

export const { Context, Provider } = createDataContext(
  teamsReducer, 
  { addTeam, deleteTeam, editTeam },
  [{ id: 0, name: 'Build Your Team', content: {} }]
);

创建数据上下文:

import React, { useCallback, useEffect, useReducer } from 'react';
import AsyncStorage from '@react-native-async-storage/async-storage';

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

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

    // console.log(state);

    const storeData = async (value) => {
      // console.log(value);
      try {
        const jsonValue = JSON.stringify(value)
        await AsyncStorage.setItem('@storage_Key', jsonValue)
      } catch (e) {
        // saving error
      }
    }
    storeData(state)

    const getData = async () => {
      try {
        const jsonValue = await AsyncStorage.getItem('@storage_Key')
        return jsonValue != null ? JSON.parse(jsonValue) : null;
      } catch(e) {
        // error reading value
      }
    }

    const storedState = async() => await getData()
    console.log(storedState());

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

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

  return { Context, Provider };
};

如何创建团队:

const BuildTeamsScreen = (props) => {
  const [searchTerm, setSearchTerm] = useState('');
  const [teamName, setTeamName] = useState('');
  const [buildSearchApi, buildResults] = useBuildResults();
  const [teamMembers, setTeamMembers] = useState([])

  const { state, addTeam } = useContext(TeamsContext);

  const showPokemonCard = (param) => {
    if (param !== '') {
      return <FlatList 
      scrollEnabled={false}
      data={buildResults}
      style={{height: 'auto'}}
      keyExtractor={(item) => item.id}
      renderItem={({ item }) => {
        const showAddButton = (el) => {
            return (
              teamMembers.length < 6 ?
                <Pressable onPress={() => setTeamMembers([...teamMembers].concat(item))}>
                  <AddPokemonButton name={item.name.replaceAll('-', ' ')} width={'90%'} height={40} />
                </Pressable>
              :
                null
            )
          }
          return(
            <View style={styles.addMonCard}>
              <Pressable style={{flex: 1}} onPress={() => props.navigation.navigate('Detail Modal', { results: [item] })}>
                <ShowAdvancedSearchResult results={item} />
              </Pressable>
              {showAddButton(item)}
            </View>
          )
        }}
      />
    } else return null;
  }

  const addTeamAndGoBack = useCallback(async (name, content) => {;
    if (teamMembers[0] !== null) {
      await addTeam(name, content)
      return (props.navigation.navigate('Teams Tab Nav', { results: content }))
    } else return (props.navigation.navigate('Teams Tab Nav'))
  }, [])

  const showClear = (el, term) => {
    if(el !== null || term !== '') {
      return (
        <Pressable 
          onPress={async() => {
            await buildSearchApi()
            setSearchTerm('')
          }} 
          style={styles.clear}
        >
          <Ionicons name="ios-close-circle" size={18} color="rgb(175, 175, 175)" />
        </Pressable>
      )
    } else return null
  }

  const createTeamMember = (el) => {
    if (el[0] !== undefined) {
      return el.map((item) => {
        const id = uuid.v4()
        return (
          <View key={id} style={{ flexDirection: 'row', width: '90%', alignSelf: 'center' }}>
            <Pressable onPress={() => props.navigation.navigate('Detail Modal', { results: [item] })}>
              <PokemonSlotCard results={item} />
            </Pressable>
            <Pressable style={{ position: 'absolute', alignSelf: 'center', right: 5, zIndex: 1 }} onPress={() => deleteTeamMember(item)}>
              <Ionicons name="ios-remove-circle-outline" size={16} color="#ff0000" />
            </Pressable>
          </View>
        )
      })
    } else return (
      <View style={{ width: '90%', alignSelf: 'center', marginTop: 12 }}>
        <Text adjustsFontSizeToFit={true} numberOfLines={1} style={styles.nullMessage}>Search for a Pokemon to add it to your team</Text>
      </View>
    )
  }

  const deleteTeamMember = (el) => {
    const arr = [...teamMembers];
    const idx = teamMembers.indexOf(el);
    if (idx !== -1) {
      arr.splice(idx, 1)
      setTeamMembers(arr)
    }
  }

  return (
    <HideKeyboard>
      <ScrollView style={styles.container}>
        <View style={styles.teamNameContainer}>
          <TextInput 
            placeholder={'Team Name'} 
            showSoftInputOnFocus={false}
            autoCapitalize='none'
            autoCorrect={false}
            style={styles.inputStyle} 
            placeholderTextColor='rgb(175, 175, 175)'
            value={teamName}
            onChangeText={(text) => setTeamName(text)}
            clearButtonMode='never'
            keyboardAppearance='dark'
            returnKeyType={'done'}
            allowFontScaling={false}
            maxLength={10}
          />
        </View>
        <View style={styles.searchBarContainer}>
          <BuildTeamSearchBar 
            searchTerm={searchTerm} 
            onSearchTermChange={setSearchTerm} 
            onSearchTermSubmit={() => buildSearchApi(searchTerm.replaceAll(' ', '-').toLowerCase())}
            style={styles.searchBar}
          />
          {showClear(buildResults, searchTerm)}
        </View>
        <View style={{height: 'auto'}}>
          {showPokemonCard(searchTerm)}
        </View>
        <View style={{height: 5 }} />
        <View style={styles.teamInfoContainer}>
          <View style={styles.teamSlotContainer}>
            {createTeamMember(teamMembers)}
          </View>
          <Pressable onPress={() => addTeamAndGoBack(teamName, teamMembers)} >
            <SaveTeamButton height={54} width={'90%'} />
          </Pressable>
        </View>
      </ScrollView>
    </HideKeyboard>
  );
};

...

如何显示团队:

const TeamsScreen = (props) => {
  const { state, addTeam, deleteTeam } = useContext(TeamsContext);

  const showTeams = useCallback((el) => {
    console.log(el);
    return <FlatList 
      horizontal={false}
      data={el._W}
      keyExtractor={(item) => item.id}
      renderItem={({ item }) => {
        // console.log(item.name);
        if (item.id !== 0) {
          return (
            <Pressable onPress={() => props.navigation.navigate('Team Detail', { id: item.id, results: item.content, name: item.name })}>
              <ViewTeams results={item} id={item.id} height={'auto'} width={'100%'} />
            </Pressable>
          )
        } else return null
      }}
    />
  }, [state])

...
react-native api react-hooks asyncstorage
2个回答
2
投票

您必须注意在功能组件主体中放置的内容,请记住代码每次渲染时都会执行。我在您的代码中看到几个部分可以位于 Provider 函数体之外,这可能就是您没有存储正确数据的原因,因为您调用的第一个渲染

storeData
,并且在该时间点状态具有初始值,因此您的存储中将始终保留
initialState
值。

要修复它,您需要放置一个仅在提供程序安装时执行的

useEffect
,您可以从存储中获取数据并补充状态。

要在每次更改中保存状态,您还可以放置

useEffect
来在每次状态更改时执行
storeData
函数。

在您的提供程序中进行一些清理并实现我描述的逻辑,它看起来像这样

import React, { useCallback, useEffect, useReducer } from 'react';
import AsyncStorage from '@react-native-async-storage/async-storage';

const storeData = async (value) => {
  try {
    const jsonValue = JSON.stringify(value)
    await AsyncStorage.setItem('@storage_Key', jsonValue)
  } catch (e) {
    // saving error
  }
}

const getData = async () => {
  try {
    const jsonValue = await AsyncStorage.getItem('@storage_Key')
    return jsonValue != null ? JSON.parse(jsonValue) : null;
  } catch(e) {
    // error reading value
  }
}

const Context = React.createContext();

const Provider = ({ children }) => {
  const [state, dispatch] = useReducer(reducer, initialState);
  useEffect(() => {
    async function rehydrate() {
      const storedState = await getData()
      if (storedState) {
        dispatch({ type: "hydrate", payload: storedState });
      }
    }
    
    rehydrate()
  }, []);

  useEffect(() => {
    storeData(state);
  }, [state])

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

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


export default {
  Context,
  Provider
}

并在你的减速器中添加水合物作用

  case "hydrate":
    return action.payload

我认为这篇文章或多或少描述了您试图完成的任务https://medium.com/persondrive/persist-data-with-react-hooks-b62ec17e843c


0
投票

异常处理不适用于应用程序逻辑,删除try/catch

    try {
            return (
              <Pressable onPress={() => props.navigation.navigate('Team Detail', { id: item.id, results: item.content, name: item.name })}>
                <ViewTeams results={item} id={item.id} height={'auto'} width={'100%'} />
              </Pressable>
            )
          } catch (error) {
            console.log(error);
          }
© www.soinside.com 2019 - 2024. All rights reserved.