如何在 React Native 中将 AsyncStorage 与上下文提供程序一起使用?
How do I use AsyncStorage with a Context Provider in React Native?
我是 React Native 的新手,甚至是 Context 的新手。所以任何帮助都会很棒。
我正在构建一个 React Native 应用程序,它从 API 外部拉入并创建存储在 'team' 中的对象,用户从 API 填充的数据中选择这些对象。
我正在使用 Context & a Provider 作为我的应用程序的包装器。
我正在尝试使用 AsyncStorage 在本地保存数据。
我已经尝试过几乎所有可能的方式使用 AsyncStorage,但我不确定哪里出错了。
任何帮助都非常感谢,因为这是我需要这个应用程序的最后一件事。
编辑:
目前,我可以保存一个团队,但是,在导航回显示团队的屏幕时,它什么也不会显示。
如果我转到主屏幕并导航回团队屏幕,我将看到我的数据。此过程对于创建的每个团队都是相同的。
此外,即使传递到我的减速器的状态来自 getData(),它从 AsyncStorage 中提取 JSON,数据也不会持续存在。
上下文文件:
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])
...
异常处理不适用于应用程序逻辑,删除 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);
}
您必须注意放入功能组件主体中的内容,请记住代码会在每次呈现时执行。我在你的代码中看到几个部分可以在 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
我是 React Native 的新手,甚至是 Context 的新手。所以任何帮助都会很棒。
我正在构建一个 React Native 应用程序,它从 API 外部拉入并创建存储在 'team' 中的对象,用户从 API 填充的数据中选择这些对象。
我正在使用 Context & a Provider 作为我的应用程序的包装器。 我正在尝试使用 AsyncStorage 在本地保存数据。
我已经尝试过几乎所有可能的方式使用 AsyncStorage,但我不确定哪里出错了。
任何帮助都非常感谢,因为这是我需要这个应用程序的最后一件事。
编辑: 目前,我可以保存一个团队,但是,在导航回显示团队的屏幕时,它什么也不会显示。
如果我转到主屏幕并导航回团队屏幕,我将看到我的数据。此过程对于创建的每个团队都是相同的。
此外,即使传递到我的减速器的状态来自 getData(),它从 AsyncStorage 中提取 JSON,数据也不会持续存在。
上下文文件:
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])
...
异常处理不适用于应用程序逻辑,删除 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);
}
您必须注意放入功能组件主体中的内容,请记住代码会在每次呈现时执行。我在你的代码中看到几个部分可以在 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