I need to store boolean values in AsyncStorage in react native application. The value stored will be used in all over the application. Actually, the user will set a variable to true or false in the settings page and based on that value, i make some changes in other pages in the application.
here is the code i'm using
Settings Page (set value to true or false but the default value is true)
constructor(props) {
super(props);
this.state ={
status: true
}
};
toggleStatus(){
this.setState({
status:!this.state.status
});
// AsyncStorage.setItem('myCheckbox',JSON.stringify(this.state.status));
//I even hardcoded the value stored and set it to false but it didn't work
AsyncStorage.setItem('myCheckbox', JSON.stringify(false));
}
..
<TouchableOpacity onPress={this.toggleStatus.bind(this)}>
<Text> touch me </Text>
</TouchableOpacity>
and in one of the other pages where I'll be using that value stored in the AsyncStorage:
componentDidMount() {
const value = AsyncStorage.getItem('myCheckbox', (value) => {
JSON.parse(value)
console.log("my check box value ", value)
});
But I keep getting this in the console :
On the React Native docs for AsyncStorage (here), it says that all methods return a Promise.
You can either use async/await but we'll do it the ES6 way.
AsyncStorage.getItem('myCheckbox').then((value) => {
console.log(value);
});
Related
I'm kinda new to React Native. I'm using the getFamily() on my screen MyFamily but when I go to another screen there change the value of the Family and come back to my MyFamily screen then I don't see the changes.
I tried doing it with the useEffect but still nothing happens, also the log doesn't happen. How can I solve this?
export default function MyFamily({ navigation, props, person, inheritors }) {
console.log(getFamily());
let [family, setFamily] = useState(getFamily());
useEffect(() => {
console.log(getFamily());
setFamily(getFamily());
}, [getFamily]);
In the screen where I set the Family again I do this:
And I know that's correct because the Json that is shown shows the updated value.
import { setFamily } from '../../utilities/family';
setFamily(responseJson.family);
This is the way family is formulated:
let family = '';
export default family;
export function getFamily() {
return family;
}
export function setFamily(f) {
family = f;
}
React doesn't actually know that the value returned from the getFamily function changes each render. In the useState function, it's only used in the initial state, and the useEffect function never gets re-run because the getFamily function itself doesn't ever change and re-trigger the useEffect. You have to change the getFamily() function to use a state that's stored in a parent component and pass it into the MyFamily component as a prop.
e.g.
// the parent component that renders the MyFamily screen
function Router() {
const [family, setFamily] = useState('')
return (
<Navigator>
<Screen component={<MyFamily family={family} setFamily={setFamily} />
<Screen component={<OtherComponent family={family} setFamily={setFamily} />
</Navigator>
}
)
}
And then from MyFamily:
function MyFamily({ family }) {
console.log(family); // this should be updated
}
and from OtherComponent:
function OtherComponent({ setFamily }) {
return (<Button onClick={() => setFamily('newFamily')>Change family</Button>)
}
hi everyone i'm just learning react native and i have a question. How do I wait for an asynchronous method to finish and then pass the result to another screen?
My code is:
export default class AAA extends Component {
constructor(props){
super(props);
this.state={
comments: [],
datetime: []
}
}
//getPost is a network call which gets and store the result in the state of the class
async getPost(){
const utils=new Utils();
const responseJson = await utils.getPost("ok","yes")
const comment = (responseJson?.posts ?? []).map((data) => data.comment)
this.setState({comments:comment})
console.log("now i change state with new value")
}
componentDidMount(){
this.getPost()
}
render(){
return(
<NextScreen
comment={this.state.comments}
/>
)
}
}
I want the getPost () method to finish and then go to the other screen, passing the result of the getPost () method. I tried to use the componentWillMount method as well but it is deprecated. How can I do?
You can use conditional rendering in this case as follows:
render() {
if (this.state.comments.length === 0) {
return null;
}
return (
<NextScreen
comment={this.state.comments}
/>
)
}
The initial state comments contains an empty array, thus it is never undefined. However, its length is equal to zero until the async call returns with its result.
You can use react-router params
if you are fetching data in the splash screen, you want to pass it to the next page.
in then() or after await expression
you can learn about this in React router, pass data when navigating programmatically?
Screenshot
In the banner above the chat, there are two buttons that temporarily turn off the banner and always turn it off. When the user presses the always off button, I hope it applies only to the chat room, but maybe because it is a global variable, it affects other chat rooms. Is there a way to use the global variable independently for each chat?
I thought about making it a key value object and using the id value of the chatroom as the key value, but there is no way to get the id value because I have to allocate it as a global variable.
// ChatScreen
...
<InfoBanner postId={postId} errandInfo={errandInfo} />
<View style={styles.contents}>
<GiftedChat
messages={messages}
...
// InfoBanner
let alwaysOption = true
export default InfoBanner = (props) => {
const { postId, errandInfo } = props;
const [bannerVisible, setBannerVisible] = useState(true)
return (
<Banner
visible={alwaysOption && bannerVisible}
style={styles.banner}
actions={[
{
label: 'turn off',
color: 'black',
onPress: () => setBannerVisible(false),
},
{
label: 'always turn off',
color: 'black',
style: {justifyContent: 'center'},
onPress: () => {alwaysOption=false; setBannerVisible(false)},
},
]}
>
...
We could create a custom "hook" that implements a global application cache. We can decide later whether we want to store the chats that should show a banner or not, along with the user ID, on a server or in the phone's local storage.
I will implement a solution using the phone's local storage. It might be useful to store this information in a global cache once it is loaded. I will use Vercel's SWR and react-native-async-storage.
The hook: useChatBanner
import AsyncStorage from '#react-native-async-storage/async-storage';
import useSWR from 'swr'
const keyTemplate = `chat/banner/`
const useChatBanner = (postId) => {
const {data, error, mutate} = useSWR(postId ? `${keyTemplate}${postId}` : null, () => {
const value = await AsyncStorage.getItem(`${keyTemplate}${postId}`).then(response => response === "true" ? true : false)
return value
})
const setBannerVisible = React.useCallback((id, visible) => {
const newData = visible ? "true" : "false"
mutate(newData, false)
AsyncStorage.set(`${keyTemplate}${postId}`, new).then(() => {
mutate(newData)
})
}, [data, mutate])
return {
isVisible: data,
isLoading: !error && !data,
isError: error,
setBannerVisible
}
}
We need to an additional config wrapper component around our main application for SWR.
import { SWRConfig } from "swr"
export default const App = () => {
return <SWRConfig>
// main entry point, navigator code or whatever
</SWRConfig>
}
Then you can use it as follows in your InfoBanner component.
export default InfoBanner = (props) => {
const { postId, errandInfo } = props;
const [bannerVisible, setBannerVisible] = useState(true)
const { isLoading, isVisible } = useChatBanner(postId)
if (isLoading) {
// might be useful to show some loading indicator here
return null
}
return (
<Banner
visible={isVisible && bannerVisible}
...
I have included a setter function in the hook in case you want to allow the user to enable the global flag again.
Using the above, it is possible to hold a boolean flag (or any other data) globally for each postId. Notice that we can implement a similar behavior using the Context API.
If you have a server and an user management, then it is very simple to adapt the above code. Replace the async storage calls with your API and you are good to go. If we manage this using userId's rather then postIds, then the data would be an array and we can filter for postIds to retrieve the correct boolean value.
I am using react-native where my first screen is Welcome screen and I want to set dashboard on my first screen when the user is login.
Here is my code:
componentWillMount(){
var self = this;
AsyncStorage.getItem(AppStrings.contracts.IS_LOGGED_IN).then((json) =>{
try{
var userDetail = JSON.parse(json);
if(userDetail.isLoggedIn != undefined && userDetail.isLoggedIn == true){
Actions.dashboard();
}
}catch(e){
}
})
}
I set this code on the Welcome screen and its working fine in IOS. But in android issue is it shows the Welcome screen for 5 to 10 seconds before going to dashboard screen when the user is login.
Here I am using react-native-router-flux for the navigation bar.
Because AsyncStorage.getItem() is asynchronous, your render() function is being called BEFORE it has been fulfilled.
So the flow of your application is:
Call componentWillMount()
AsyncStorage.getItem()
render() - This is where you see your Welcome Screen for 5-10 seconds
AsyncStorage has been fulfilled - .then() and then the User gets redirected to the dashboard.
I would set an isLoaded flag in your state:
constructor() {
super();
this.state = {
isLoaded: false,
}
}
Then inside of your componentWillMount() function, set the value to true once AsyncStorage has fulfilled its Promise.
try {
var userDetail = JSON.parse(json);
if(userDetail.isLoggedIn != undefined && userDetail.isLoggedIn == true){
Actions.dashboard();
}
this.setState({ isLoaded: true });
}
And finally, I would add some sort of loading indicator inside of render() to show the User that your application is still performing some logic.
render() {
if(this.state.isLoading) {
return <Text>I am loading</Text>
} else {
return ...
}
}
My code is
const main = () => {
let caption;
AsyncStorage.getItem("XXX", (err, result) => {
caption = <View>...</View>
});
render (
...
{caption}
...
);
}
But I got an error as below.
RawText "" must be wrapped in an explicit <Text> component.
I'm going to assume that, based on your pseudo-code, you understand how to get data from AsyncStorage, that it's not a good idea to be using AsyncStorage inside your render function, and that you don't actually mean ajax but rather local storage.
But the error is showing up because you need to make sure you wrap text inside a <Text> element. If you look at this paragraph it says:
In React Native, we are more strict about it: you must wrap all the text nodes inside of a <Text> component; you cannot have a text node directly under a <View>.
EDIT:
class MyComponent extends Component {
constructor(props) {
super(props);
this.state = {
data: '',
};
}
componentDidMount() {
AsyncStorage.getItem('XXX', (err, result) => {
// #TODO: You should handle errors too
this.setState({
data: result.text,
});
});
}
render() {
// Returning null will not render anything
// Once the results come in, it will update automatically
if (!this.state.data) return null;
// Raw text must be wrapped in Text
return (
<Text>{this.state.data}</Text>
);
}
}
You can try to stock data in a state and display your component whit a function:
AsyncStorage.getItem("XXX", (err, result) => {
this.setState({result:result})
});
function element(){
if([check if your state is not null])
return(
<View>... {this.state.result} ...</View>
)
}
render (
...
{this.element()}
...
);