What is the proper/right way to use Async Storage? - javascript

I am a react-native newbie.
I'm trying to use Async Storage in my application.
I want the async storage to store token when user log in, it will navigate to homescreen. At homescreen, im trying to get the token through async storage and print it on console but all i get is promise. I just want to know what is the proper way to use Async storage especially in storing token? I know the alternative for this problem is using Redux state management, but I'm trying to learn the basic method.
I've tried to store the token in a variable in ComponentWillMount(), but it still does not work.
class HomeScreen extends Component {
constructor(props) {
super(props);
this.state = {};
}
componentWillMount() {
token = getToken();
}
render() {
const { navigate } = this.props.navigation;
console.log(token);
return (
<View style={styles.container}>
<Text> HomeScreen </Text>
</View>
);
}
}
const getToken = async () => {
let token = "";
try {
token = await AsyncStorage.getItem("token");
} catch (error) {
console.log(error);
}
return token;
};

First, I should note that componentWillMount is deprecated and you can use constructor or componentDidMount instead.
if you log token in getToken and after getting it, it will work fine. if you want to check if the user is logged in, you can do like this
constructor(props) {
super(props);
this.state = {};
getToken().then((token)=>{
console.log(token)
//check if user is logged in
})
}
or you can do this in componentDidMount.
I hope this can help you

Try to use it with Async and await
setValue: function (key, value) {
AsyncStorage.setItem(key, value)
},
getValue: async (key) => {
let value = '';
try {
value = await AsyncStorage.getItem(key) || 'none';
} catch (error) {
// Error retrieving data
console.log(error.message);
}
return value;
}

You should use it something like this,
import { AsyncStorage, Text, View, TextInput, StyleSheet } from 'react-native';
//for storing Data
setName = (value) => {
AsyncStorage.setItem('name', value);
this.setState({ 'name': value });
}
//for Retrieving Data
componentDidMount = () => AsyncStorage.getItem('name').then((value) => this.setState({ 'name': value }))
Here it is another simple example.

Related

Use static fetch service

I have created an express mongoose api. I want to use that api from my React-application.
I want to create a service that would manage those api requests. But I am new in react-native and I can't use that service. I tried creating a static class but I cannot make it works. Here is an example :
// apiService.js
class ApiService {
static fetchUsers = () => {
return fetch('XXX/users')
.then((response) => {
return response.json()
.then((data) => {
return data;
})
})
.catch((error) => {
console.error(error);
});
}
}
export default ApiService;
And my screen
// UserScreen.js
import ApiService from '../services/apiService';
export default class UserScreen extends Component {
constructor(props) {
super(props);
this.state = {
users: [],
isLoading: true,
}
}
componentDidMount = () => {
let users = ApiService.fetchUsers();
this.setState({data: users});
this.setState({isLoading: false});
}
render() {
if (this.state.isLoading) {
return (
<View style={styles.container}>
<ActivityIndicator/>
</View>
)
} else {
return (
<View style={{ flex: 1, marginTop: 100 }}>
{
this.state.data.map((val, key) => {
return <TouchableOpacity
style={styles.homeButton}
key={key}
onPress={() => this.redirectHandler(val)}>
</TouchableOpacity>
})
}
</View>
)
}
}
}
I tried using async and wait but I can't find a way to retrieve data. The data are well retrieve in the apiService but I can't share them with the UserScreen.
How can I use a (static or not) class/function in react-native and get the data from the screen
Update
Here is what I tried with async
class ApiService {
static fetchUsers = async () => {
try {
let response = await fetch('XXXXX/users/');
let json = await response.json();
return json;
} catch (error) {
console.error(error);
}
}
}
export default ApiService;
And in my Userscreen
componentDidMount = async () => {
try {
let users = await ApiService.fetchUsers();
this.setState({isLoading: false});
this.setState({data: users});
} catch (error) {
console.log(error);
}
}
The problem lies in the setState that you are performing twice. If you look at the logic of the component, first we check for isLoading, if true we show some message/spinner otherwise we are showing a list of users/data.
Sequence of the Set State:
this.setState({isLoading: false});
this.setState({data: users});
Note that each setState triggers a re-render of the component, so in this case first we set isLoading to false (1st Re-Render) and then we set the data (2nd Re-Render)
The problem is, when 1st Re-Render is done, isLoading is set to false and the condition which we talked about above, now enters the "showing the user/data list" part. Another thing to note here is we have defined users: [] in state and when we are setting the users array (from the api call), we set that in a variable called data. The variable data is never defined in state so essentially it is "undefined".
Issue in your code:
this.state.data.map(...)
You cannot map over an undefined variable and this is where the problem lies. Doing so will throw an error saying "cannot read property map of undefined".
To fix this:
When setting the users list, instead of doing this.setState({ data: users }) just do this.setState({ users: users }) and change this.state.data.map( to users.map(
Also, unnecessary re-renders are costly and in case of React Native, they are costlier. Merge your setState(...) calls when possible. For example,
this.setState({
isLoading: false,
users: users
})

React Native trigger useEffect from changes in AsyncStorage

So I'm working on a react native authentication screen. I'm storing the token in AsyncStorage (yea I know it's not the best solution).
So what's happening is when I log in, the token is stored, but the getItem on my Authentication.js screen is not being triggered, and the profile screen is not being called.
If I log in and then manually refresh the app, I am redirected to the profile screen.
Login.js
function Login({navigation}) {
const [signIn, {data}] = useMutation(USER_SIGNIN_MUTATION);
const [userName, setUserName] = useState('');
const [password, setPassword] = useState('');
function handleLogIn() {
signIn({
variables: {
email: userName,
password: password,
},
});
}
useEffect(() => {
if (data != null) {
setToken();
}
});
const setToken = async () => {
try {
console.log('before');
await AsyncStorage.setItem('token', data.signIn.token);
console.log('after');
} catch (error) {
console.log(error);
}
};
return(
...
)
}
Authentication.js
function Authentication() {
const [localToken, setLocalToken] = useState(false);
useEffect(() => {
const fetchUser = async () => {
try {
console.log('before get');
const userData = await AsyncStorage.getItem('token');
if (userData !== null) {
setLocalToken(true);
}
} catch (error) {
console.log(error);
}
};
fetchUser();
}, [localToken]);
console.log(`auth screen - ${localToken}`);
return (
<NavigationContainer>
{localToken === true ? <ProfileStack /> : <AuthStack />}
</NavigationContainer>
);
}
export default Authentication;
also same happens with the logout function when fired. (the function runs, but I need to refresh the app to get back to the login screen)
Profile.js
function Profile({navigation}) {
function signOut() {
logOut();
}
const logOut = async () => {
try {
console.log('before clear');
await AsyncStorage.removeItem('token');
console.log('after clear');
} catch (error) {
console.log(error);
}
};
return (
...
)
}
I'm grateful for any insight on this.
useEffect(() => {
const fetchUser = async () => {
try {
console.log('before get');
const userData = await AsyncStorage.getItem('token');
if (userData !== null) {
setLocalToken(true);
}
} catch (error) {
console.log(error);
}
};
fetchUser();
}, [localToken]);
Here you added the localToken variable in the dependency array of the useEffect. So you are basically saying: run this effect only if the localToken variable changes. But you change that from within the effect only. So try to remove it and keep the dependency as []. This way the effect will run when the component is rendered.
About the fact that you have to refresh the page, it is important to understand why this happens.
<NavigationContainer>
{localToken === true ? <ProfileStack /> : <AuthStack />}
</NavigationContainer>
Here you are rendering ProfileStack or AuthStack based on the localToken value. When you logout, you remove the token from the AsyncStorage but this is not enough. You actually need to trigger a rerender in the Authentication component so the localToken is reevaluated. Basically, when you logout you also need to set setLocalToken(false). So you need to access setLocalToken function from the Profile component. You can pass this function as a prop or better you can use Context API

NextJS getServerSideProps pass data to Page Class

i know this has probably been already asked, but i'm at a point where i don't know what to do.
I'm not a (very) experienced developer in javascript or NextJS.
My Problem(1):
I got the method: export const getServerSideProps: GetServerSideProps = async () => {} implemented to fetch some data from a integrated API (pages/api from NextJS). The code itself is probably not well(or worse) written, but it works. (for now at least)
export const getServerSideProps: GetServerSideProps = async () => {
try {
// get userID
await fetch("http://localhost:32147/api/v1/user/get?requestedField=userID&fieldName=username&fieldValue=<value removed>").then(
(userIDResponse: Response): any => {
// get userID as json
userIDResponse.json().then((userIDResult: Response): any => {
// get messages
fetch(
"http://localhost:32147/api/v1/message/get?requestedField=*&fieldName=userID&fieldValue=" +
JSON.stringify(userIDResult[0].userID)
).then((messageResponse: Response): any => {
// get messages as json
messageResponse.json().then((messageResult) => {
return {
props: { messages: messageResult },
{/* marker1 */}
}
})
})
})
}
)
} catch (error) {
console.log(error)
}
}
just to be clear, this method works, data fetching works but just if i access it at marker1
that one part where i return the props:
return {
props: { messages: messageResult },
}
i can't do that 'cause nextjs is gonna break because of getServerSideProps() didn't return anything.
I tried to store the final data into a variable, that i declared on the first line of this method, but it ended up being empty the whole time.
How can i solve this?
My Problem(2): if i set a manual value at the end of this method for testing, it doesn't get passed to the main Page Class (index.tsx)
i can just access it using this.props.<prop name>, in this case: this.props.messages, right?
The whole index.tsx:
import React, { Component } from "react"
import { GetServerSideProps } from "next"
import Router from "next/router"
import Head from "next/head"
import Navbar from "../lib/Navbar"
import MessagesModal from "../lib/MessagesModal"
export const getServerSideProps: GetServerSideProps = async () => {
try {
// get userID
await fetch("http://localhost:32147/api/v1/user/get?requestedField=userID&fieldName=username&fieldValue=<value removed>").then(
(userIDResponse: Response): any => {
// get userID as json
userIDResponse.json().then((userIDResult: Response): any => {
// get messages
fetch(
"http://localhost:32147/api/v1/message/get?requestedField=*&fieldName=userID&fieldValue=" +
JSON.stringify(userIDResult[0].userID)
).then((messageResponse: Response): any => {
// get messages as json
messageResponse.json().then((messageResult) => {
return {
props: { messages: messageResult },
}
})
})
})
}
)
} catch (error) {
console.log(error)
}
}
interface HomeProps {
messages?: []
}
export default class Home extends Component<HomeProps> {
constructor(props) {
super(props)
}
state = {
messagesModal: false,
messages: [],
}
// triggers logout
triggerLogOut(): void {}
render(): JSX.Element {
return (
<>
<Head>
<title>OneDrive Event Connector</title>
</Head>
<Navbar
ItemClickCallback={(callbackItem: string): void => {
if (callbackItem === "messages") {
this.setState({ messageModal: !this.state.messageModal })
} else if (callbackItem === "log_out") {
this.triggerLogOut()
} else {
Router.push("/" + callbackItem)
}
}}
/>
<div className="app-content"></div>
<MessagesModal
messages={this.props.messages}
isOpen={this.state.messagesModal}
toggleModal={() => {
this.setState({ messageModal: !this.state.messagesModal })
}}
/>
</>
)
}
}
This is just a "fun" project for me to practise and learn.
Would be greate if anyone could give me even a hint on what is my problem/mistake here...
Thanks.
Kind regards
Oliver
i can't do that 'cause nextjs is gonna break because of getServerSideProps() didn't return anything.
exactly - in your code, you are returning values inside of a chain of promises - you need to make sure, that values are returned from each step
here's a working example - similar flow with swapped API - to help you understand how to return something, going back from the inside of your chained promises
export const getServerSideProps: GetServerSideProps = async () => {
try {
// initial fetch
const result = await fetch("https://jsonplaceholder.typicode.com/todos/1")
.then((todosResponse: Response): any => {
return todosResponse.json().then((todo) => {
// fetch something more
return fetch(
"https://jsonplaceholder.typicode.com/users/" + todo.userId
).then((userResponse: Response): any => userResponse.json());
})
})
return {
props: { messages: result },
}
} catch (error) {
console.log(error)
}
}
My advise is also to read more on promises / async await in JS world
My Problem(2)
i can just access it using this.props., in this case: this.props.messages, right?
yes, that's right
interface HomeProps {
messages?: []
}
export default class Home extends Component<HomeProps> {
constructor(props) {
super(props)
}
render() {
return (
<>
{JSON.stringify(this.props.messages)}
</>
)
}
}

apollo's useQuery data does not update after client.resetStore()

I am having an issue with useQuery from #apollo/client
I have a Navbar component
const Navbar = () => {
const { loading, data, error } = useQuery(GET_CURRENT_USER);
console.log('navbar', data)
return <ReactStuff />
}
And I also have a UserProfile component which allows the user to logout via a button. When the user presses the button, this code is ran:
const {
getCurrentUser,
changePassword,
changePasswordData,
logoutUser,
} = useUsers();
const logout = () => {
logoutUser();
localStorage.removeItem("authToken");
props.history.push("/");
};
useUsers is a custom hook which houses the resetStore function:
const useUser = () => {
const { client, loading, error, data, refetch } = useQuery(GET_CURRENT_USER);
const logoutUser = () => {
console.log("firing logout user");
client
.resetStore()
.then((data) => {
console.log("here in reset store success");
})
.catch((err) => {
console.log("here in error");
}); // causes uncaught error when logging out.
};
return {
// other useUser functions
logoutUser
}
}
Now I can't for the life of me figure out why the Navbar component, does not get updated when logout is pressed.
I have a withAuth higher-order component which does the exact same query, and this works absolutely fine. The user is sent to the /login page, and If I was to console.log(data) it is updated as undefined - as expected.
import { GET_CURRENT_USER } from "../graphql/queries/user";
/**
* Using this HOC
* we can check to see if the user is authed
*/
const withAuth = (Component) => (props) => {
const history = useHistory();
const { loading, data, error } = useQuery(GET_CURRENT_USER);
if (error) console.warn(error);
if (loading) return null;
if (data && data.user) {
return <Component {...props} />;
}
if (!data) {
history.push("/login");
return "null";
}
return null;
};
For some reason, this useQuery inside Navbar is holding onto this stale data for some reason and I can't figure out how to make it work correctly.
Update 1:
I've changed the logoutUser function to use clearStore() and the same thing happens, the Navbar is not updated, but I am redirected to /login and withAuth is working as intended.
const logoutUser = () => {
client
.clearStore()
.then((data) => console.log(data)) // logs []
.catch((err) => console.log(err)); // no error because no refetch
};
You're not waiting for the store to reset, probably the redirection and storage clean up happen before the reset completes, try doing that once it has finished
const logout = () => {
logoutUser(() => {
localStorage.removeItem('authToken');
props.history.push('/');
});
};
const logoutUser = onLogout => {
console.log('firing logout user');
client
.resetStore()
.then(data => {
onLogout();
})
.catch(err => {
console.log('here in error');
}); // causes uncaught error when logging out.
};
Check: do you have only ONE ApolloClient instance?
In some cases, if you configuring ApolloClient in the custom class or file you can implicitly create multiple apolloClient instances, for example by 'import' statement. In that case you clearing only one of the caches you made.

How to reload current page in ReactJS?

How to reload current page in ReactJS? in case of javascript we can write window.location.reload();
How to do the same in Reactjs? I'm able to add new data by UI. But without refreshing, I'm not able to see the list. I want it so that whenever I'm adding some data, it refreshes by itself.
onAddBucket() {
let self = this;
let getToken = localStorage.getItem('myToken');
var apiBaseUrl = "...";
let input = {
"name" : this.state.fields["bucket_name"]
}
axios.defaults.headers.common['Authorization'] = getToken;
axios.post(apiBaseUrl+'...',input)
.then(function (response) {
if(response.data.status == 200){
let result = self.state.buckets.concat(response.data.buckets)
}else{
alert(response.data.message);
}
})
.catch(function (error) {
console.log(error);
});
}
use this might help
window.location.reload();
You can use window.location.reload(); in your componentDidMount() lifecycle method. If you are using react-router, it has a refresh method to do that.
Edit: If you want to do that after a data update, you might be looking to a re-render not a reload and you can do that by using this.setState(). Here is a basic example of it to fire a re-render after data is fetched.
import React from 'react'
const ROOT_URL = 'https://jsonplaceholder.typicode.com';
const url = `${ROOT_URL}/users`;
class MyComponent extends React.Component {
state = {
users: null
}
componentDidMount() {
fetch(url)
.then(response => response.json())
.then(users => this.setState({users: users}));
}
render() {
const {users} = this.state;
if (users) {
return (
<ul>
{users.map(user => <li>{user.name}</li>)}
</ul>
)
} else {
return (<h1>Loading ...</h1>)
}
}
}
export default MyComponent;
Since React eventually boils down to plain old JavaScript, you can really place it anywhere! For instance, you could place it in a `componentDidMount()' function in a React class.
For your edit, you may want to try something like this:
class Component extends React.Component {
constructor(props) {
super(props);
this.onAddBucket = this.onAddBucket.bind(this);
}
componentWillMount() {
this.setState({
buckets: {},
})
}
componentDidMount() {
this.onAddBucket();
}
onAddBucket() {
let self = this;
let getToken = localStorage.getItem('myToken');
var apiBaseUrl = "...";
let input = {
"name" : this.state.fields["bucket_name"]
}
axios.defaults.headers.common['Authorization'] = getToken;
axios.post(apiBaseUrl+'...',input)
.then(function (response) {
if (response.data.status == 200) {
this.setState({
buckets: this.state.buckets.concat(response.data.buckets),
});
} else {
alert(response.data.message);
}
})
.catch(function (error) {
console.log(error);
});
}
render() {
return (
{this.state.bucket}
);
}
}
You can use useNavigate and navigate to the same url you are on. For example, instead of window.location.reload(), you can say navigate("/...your current url....")
window.location.reload() is not the best option everytime. It works on localhost, but for example on when you deploy it to the internet by using services such as "Netlify", it can can cause "not found url" error
Creating some extra state and tracking them for re-rendering your page might unnecessarily complicate your code.
And using useEffect() to re-render your page, again, will unnecesarily complicate your code.
This is my code .This works for me
componentDidMount(){
axios.get('http://localhost:5000/supplier').then(
response => {
console.log(response)
this.setState({suppliers:response.data.data})
}
)
.catch(error => {
console.log(error)
})
}
componentDidUpdate(){
this.componentDidMount();
}
window.location.reload(); I think this thing is not good for react js
use useHistory method in react
import {useHistory} from 'react-router-dom'
const history = useHistory()
history.go(0) // it will refresh particullar page
or use useEffect method
const [data, setData] = useState([])
useEffect(()=>{
setData(reponseApidata)},[data])
//in useEffect dependcy you mention particullar state for you store reposnse data

Categories

Resources