I am new to React Native and trying to build an App with login function. Here is the overview of my App.
In my root app.js
const rootReducer = combineReducers({
login: loginReducer,
preload: preloadReducer,
});
const store = createStore(rootReducer, applyMiddleware(ReduxThunk));
export default function App() {
return (
<Provider store={store}>
<MyNavigator/>
</Provider>
);
};
In my LoginScreen.js, I have
import * as authActions from '../store/actions/auth';
const LoginScreen = props => {
const loginHandler = async () => {
try {
// login here
await dispatch(authActions.login(username, password));
// I want to get login result and navigate to different screens
if (alreadyLogin) {
props.navigation.navigate('MainScreen');
} else {
props.navigation.navigate('StartupScreen');
}
}
catch (err) {
console.log(err);
}
}
return (<View style={styles.login}>
<Button title='Login' onPress={loginHandler}
</View>);
}
In the ../store/action/auth.js
export const login = (username, password) ={
return async dispatch => {
const response = await fetch(url);
const resData = await response.json();
let results = JSON.parse(resData.results);
// if login success, I dispatch the token for later use
if (resData.errno===0){
dispatch({ type: LOGIN , result: results });
} else {
dispatch({ type: RESET , result: null });
}
}
}
Can I achieve what I want with current structure? I want to get login result and navigate to different screens in my LoginScreen.js above. Thank you.
Related
I have the following React Native modules:
_localStorage.js
import AsyncStorage from '#react-native-community/async-storage';
const _storeData = async (key, value) => {
try {
await AsyncStorage.setItem(key, value);
} catch (error) {
console.log(error);
}
}
const _retrieveData = async (key) => {
try {
await AsyncStorage.getItem(key);
} catch (error) {
console.log(error);
}
}
export {_storeData, _retrieveData};
AppHeader.js
import React from 'react';
import {Button} from 'react-native-paper';
import {_retrieveData, _storeData} from '../../utils/_localStorage'
const LoginButton = () => {
return (
<Button icon='login' color='yellow' onPress={() => navigation.navigate('Login')}>
Login
</Button>
)
}
const UserButton = (user) => {
return (
<Button color='yellow' onPress={() => console.log('Botón usuario presionado...')}>
text
</Button>
)
}
const AppHeader = ({navigation, route}) => {
const user = _retrieveData('user');
console.log(user);
return user === ''? <LoginButton />: <UserButton user={user} />;
}
export default AppHeader;
I expect _retrieveData() to return the value of the key parameter, or null if it doesn't exits, but what I am getting in the console is this: {"_U": 0, "_V": 0, "_W": null, "_X": null}.
This is not how documentation of AsyncStorage indicates it works.
It's because you're not waiting for _retrieveData to finish. You're just setting user to the async function instead of waiting for its returned value.
Try something like this:
const AppHeader = ({navigation, route}) => {
const [user, setUser] = useState();
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
fetchUser();
}, [])
const fetchUser = async () => {
setIsLoading(true);
const userData = await _retrieveData('user');
setUser(userData);
setIsLoading(false);
}
if (isLoading) return <LoadingIndicator />
if (!!user) return <UserButton user={user} />
return <LoginButton />;
}
I've called fetchUser in the initial useEffect that gets called when the AppHeader component is first loaded. It sets a loading boolean to true and then requests the user data. When the userData is returned it sets it in state and sets loading to false.
You don't need the loading bit but I included it otherwise your app would show the login button while it's fetching the data. You'll have to create the LoadingIndicator component yourself.
_retrieveData is returning promise here. You need to await for that promise to resolve. Try writing it like this:
const _retrieveData = async (key) => {
try {
const data = await AsyncStorage.getItem(key);
return data;
} catch (error) {
console.log(error);
}
}
AppHeader.js
const AppHeader = ({navigation, route}) => {
_retrieveData('user').then((user)=>{
console.log(user);
return user === ''? <LoginButton />: <UserButton user={user} />;
});
}
Read this for more clarity : https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Async_await
You're not returning anything from your _retrieveData function. Try writing it like so:
const _retrieveData = async (key) => {
try {
const data = await AsyncStorage.getItem(key);
return data;
} catch (error) {
console.log(error);
}
}
So I'm trying to figure out the best way to display a Toast error and success function when the API call fires from redux.
My line of thinking: Create action for the API call. If successful, then I want the screen to change to the home screen. If it fails, then display the message in a Toast.
Here's what some of my actions look like:
export function getTokenAPI(username, password) {
return async function action(dispatch) {
try {
dispatch({ type: t.AUTH_GET_TOKEN });
dispatch(setLoading(true));
const { data } = await API.authGetToken(username, password);
const { success } = data;
if (success) {
const { access_token, refresh_token } = data;
dispatch(setAccessToken(access_token));
dispatch(setRefreshToken(refresh_token));
await dispatch(setLoading(false));
} else if (!success) {
const { errorMessage } = data;
throw Error(errorMessage);
}
} catch (e) {
dispatch(setError(e.message));
dispatch(setLoading(false));
}
};
}
The setError action sets the error key to true and sets the errorMessage. Here's what my screen looks like:
import React from 'react';
import { Container, View, Toast } from 'native-base';
import styles from './styles';
import { connect } from 'react-redux';
import { authActions } from '_ducks/auth';
const LoginScreen = props => {
const { getToken, navigation } = props;
const { navigate } = navigation;
const navigateToHome = () => navigate('Home');
const handleLogin = async () => {
const { error, errorMessage } = props;
await getToken('sample', 'pass123');
if (error) {
Toast.show({
text: errorMessage,
buttonText: 'kay',
});
} else {
navigateToHome();
}
};
return (
<Container>
<View style={styles.container}>
<LoginButton onPress={handleLogin} />
</View>
</Container>
);
};
const mapDispatchToProps = dispatch => ({
getToken: () => dispatch(authActions.getTokenAPI()),
});
const mapStateToProps = state => ({
isLoading: state.authReducer.isLoading,
error: state.authReducer.error,
errorMessage: state.authReducer.errorMessage,
});
export default connect(
mapStateToProps,
mapDispatchToProps,
)(LoginScreen);
So if there's an error, then display the toast. If it's successful, navigate to the home screen. Essentially, error will not be true quick enough to make the check within handleLogin work appropriately.
Any recommendations on the pattern or process? Should I be using a useEffect hook here?
I am trying to create a LoginForm in which I am placing the the UI using react-native, but the backend logic is through redux framework. I have integrated with the firebase libraries and am trying to make an async call to the firebase using the action creators and reducers through redux-thunk.
App.js
.........
.........
render()
{
const store = createStore(reducers, {}, applyMiddleware(ReduxThunk));
return(
<Provider store={ store } >
<LoginForm />
</Provider>
);
}
LoginForm.js
class LoginForm extends Component
{
.........
.........
onButtonPress () {
const { email, password } = this.props;
this.props.loginUser({ email, password });
}
render()
{
<CardSection>
<Button onPress={this.onButtonPress.bind(this)} >
Login
</Button>
</CardSection>
}
const mapStateToProps = ( state ) => {
return {
email: state.auth.email,
password: state.auth.password
};
};
export default connect (mapStateToProps, { emailChanged, passwordChanged, loginUser })(LoginForm);
Actions
index.js
export const loginUser = ({ email, password }) => {
console.log("Shikher1");
return (dispatch) => {
firebase.auth().signInWithEmailAndPassword( email, password ).then( user => {
dispatch ({ type: 'LOGIN_USER_SUCCESS' , payload: user });
});
};
};
Nothing is mentioned in the Reducer as such, I just wanted to make sure that the action gets triggered and the Async call is made. But nothing gets happened here. As I printed from the console.logs I can see that the callback function is getting executed and it calls the action creator too, but the firebase statement is not getting executed, as after its execution, it will return an object. Why is the firebase statement is not getting executed?
Where am I going wrong here?
in in your LoginForm.js, try to add these lines
const mapStateToProps = ( state ) => {
return {
email: state.auth.email,
password: state.auth.password
};
};
const mapDispatchToProps = dispatch => ({
emailChanged: payload => dispatch(emailChanged(payload)),
passwordChanged: payload => dispatch(passwordChanged(payload)),
loginUser : payload => dispatch(loginUser (payload))
})
export default connect (mapStateToProps ,mapDispatchToProps )(LoginForm);
I have started redux with react-native. I am trying to fetch data from API listing that data using map. Following is my code to fetch data from API.
App.js
const store = createStore(
rootReducer,
applyMiddleware(thunk)
);
const App = () => {
return (
<Provider store={store}>
<ProductList />
</Provider>
);
};
export default App;
productList.js
class ProductList extends React.Component {
componentDidMount() {
this.props.dispatch(fetchProducts());
}
render() {
const { products } = this.props;
return (
<View>
{products.map(product => (
<Text key={product.title}>{product.title}</Text>
))}
</View>
);
}
}
const mapStateToProps = state => ({
products: state.products.items,
});
export default connect(mapStateToProps)(ProductList);
productAction.js
async function getProducts() {
return fetch("https://rallycoding.herokuapp.com/api/music_albums")
.then(res => res.json());
}
export function fetchProducts() {
return async dispatch => {
return getProducts()
.then(json => {
dispatch(fetchProductsSuccess(json));
return json;
})
.catch(error =>
console.log(error)
);
};
}
export const FETCH_PRODUCTS_SUCCESS = "FETCH_PRODUCTS_SUCCESS";
export const fetchProductsSuccess = products => ({
type: FETCH_PRODUCTS_SUCCESS,
payload: { products }
});
productReducer.js
const initialState = {
items: [],
};
export default function productReducer(
state = initialState,
action
) {
switch (action.type) {
case FETCH_PRODUCTS_SUCCESS:
return {
...state,
items: action.payload.products
};
default:
return state;
}
}
rootReducer.js
export default combineReducers({
products
});
It is working perfectly. It is showing the list as well.
But can anyone please tell me is it a correct way if I will use this method in big projects then will it be useful or should I follow some other method? Thanks in advance.
I've not used fetch with react-native, but I think this should work fine. I've used axis though. And it is easy to use
import * as axios from 'axios';
import Constant from './../utilities/constants';
axios.defaults.baseURL = Constant.api_base_url;;
axios.defaults.headers.post['Content-Type'] = 'application/json';
// Axios interceptor for handling common HTTP errors
// Need to use it with reducers
axios.interceptors.response.use(res => res, err => Promise.reject(error));
/**
* HTTP request to search item in The MovieDB
*
* #returns {object | Promise}
*/
const getConfiguration = () => {
return axios.get(`/configuration?${Constant.api_key}`)
}
export { getConfiguration }
You can view complete code https://github.com/SandipNirmal/React-Native-MovieDB.
I am trying to use react navigation authentication flow to manage the login screen if the user is logged in or not. But now I got stuck in AsyncStorage. So while the user is not logged in I presume that componentWillMount will wait until the user will input the credentials, tap the login button, receive the user_id from API call and then try again. For me now it is calling what in the beginning which is fine but then I have to exit from app and go back to get the dashboard rendered. Any solution?
This is my code from App.js where I'm creating the routes as well. Also I am loading redux map on bottom.
export const createRootNavigator = (signedIn = false) => {
return SwitchNavigator(
{
SignedIn: {
screen: SignedIn
},
SignedOut: {
screen: SignedOut
}
},
{
initialRouteName: signedIn ? "SignedIn" : "SignedOut"
}
);
};
class App extends Component {
constructor(props) {
super(props);
this.state = {
signedIn: false,
checkedSignIn: false
};
}
async componentWillMount() {
await isSignedIn()
.then(res => this.setState({ signedIn: res, checkedSignIn: true }))
.catch(err => alert("An error occurred"));
}
render() {
const { checkedSignIn, signedIn } = this.state;
// If we haven't checked AsyncStorage yet, don't render anything (better ways to do this)
if (!checkedSignIn) {
return null;
}
const Layout = createRootNavigator(signedIn);
return (
<SafeAreaView style={styles.safeArea}>
<View style={{flex: 1, backgroundColor: '#ffffff'}}>
<StatusBar barStyle="light-content"/>
<Layout />
<AlertContainer/>
</View>
</SafeAreaView>
)
}
};
And here is the Auth.js where I am waiting for the user_key.
export let USER_KEY = 'myKey';
export const onSignIn = async () => { await AsyncStorage.setItem(USER_KEY, 'true') };
export const onSignOut = async () => { await AsyncStorage.removeItem(USER_KEY) };
export const isSignedIn = () => {
return new Promise((resolve, reject) => {
AsyncStorage.getItem(USER_KEY)
.then(res => {
if (res !== null) {
// console.log('true')
resolve(true);
} else {
resolve(false);
// console.log('false')
}
})
.catch(err => reject(err));
});
};
A solution would be to make use of Splashscreen. You can add a splashscreen to the App. While Splashscreen is being displayed, check if user exists in Asyncstorage, if they do, navigate user to the Dashboard/Homescreen and if asynstorage responds null, navigate user to the Login page. Once Navigation is complete, you can hide the splashscreen. Checkout this package in npmjs for Splashscreen setup react-native-splash-screen