Async request is not going to Firebase through action creators - react-native

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);

Related

Could not find "store" in the context of "Connect(HomeScreen)". Either wrap the root component in a... or pass a custom React context provider

Check multitude of questioned already asked and but still can't figure this one out.
We are rewriting our authentication layer using
export default AuthContext = React.createContext();
and wrapping it around our AppNavigator
function AppNavigator(props) {
const [state, dispatch] = useReducer(accountReducer, INITIAL_STATE);
const authContext = React.useMemo(
() => ({
loadUser: async () => {
const token = await keychainStorage.getItem("token");
if (token) {
await dispatch({ type: SIGN_IN_SUCCESS, token: token });
}
},
signIn: async (data) => {
client
.post(LOGIN_CUSTOMER_RESOURCE, data)
.then((res) => {
const token = res.data.accessToken;
keychainStorage.setItem("token", token);
dispatch({ type: SIGN_IN_SUCCESS, token: token });
})
.catch((x) => {
dispatch({ type: SIGN_IN_FAIL });
});
},
signOut: () => {
client.delete({
LOGOUT_CUSTOMER_RESOURCE
});
dispatch({ type: SIGN_OUT_SUCCESS });
}
}),
[]
);
console.log("token start", state.token);
return (
<AuthContext.Provider value={authContext}>
<NavigationContainer
theme={MyTheme}
ref={(navigatorRef) => {
NavigationService.setTopLevelNavigator(navigatorRef);
}}
onStateChange={(state) => {
NavigationService.setAnalytics(state);
}}
>
<AppStack.Navigator initialRouteName="App" screenOptions={hideHeader}>
{state.token != null ? (
<AppStack.Screen name="App" component={AuthMainTabNavigator} />
) : (
<>
<AppStack.Screen name="App" component={MainTabNavigator} />
<AppStack.Screen name="Auth" component={AuthNavigator} />
</>
)}
</AppStack.Navigator>
</NavigationContainer>
</AuthContext.Provider>
);
}
export default AppNavigator;
App.js - render fucnction
<Root>
<StoreProvider store={store} context={AuthContext}>
<PersistGate loading={null} persistor={persistor}>
<SafeAreaProvider>
<AppNavigator context={AuthContext}/>
</SafeAreaProvider>
</PersistGate>
</StoreProvider>
</Root>
HomeScreen.js
export default connect(mapStateToProps, mapDispatchToProps, null, { context: AuthContext })(HomeScreen);
But still receiving
Error: Could not find "store" in the context of "Connect(HomeScreen)". Either wrap the root component in a <Provider>, or pass a custom React context provider to <Provider> and the corresponding React context consumer to Connect(HomeScreen) in connect options.
We have gone through the REDUX documentation:
https://react-redux.js.org/using-react-redux/accessing-store#using-the-usestore-hook
Simply can not work out why we are receiving this error.
I'm not sure what you're trying to accomplish here, but this is very wrong:
export default connect(mapStateToProps, mapDispatchToProps, null, { context: AuthContext })(HomeScreen);
It looks like you're mixing up two different things. You're trying to create a context for use with your own auth state, but you're also trying to use that same context instance to override React-Redux's own default context instance. Don't do that! You should not be passing a custom context instance to connect and <Provider> except in very rare situations.
I understand what you are trying to achieve only after reading through your discussion in the comments with #markerikson.
The example from the React Navigation docs creates a context AuthContext in order to make the auth functions available to its descendants. It needs to do this because the state and the dispatch come from the React.useReducer hook so they only exist within the scope of the component.
Your setup is different because you are using Redux. Your state and dispatch are already available to your component through the React-Redux context Provider and can be accessed with connect, useSelector, and useDispatch. You do not need an additional context to store your auth info.
You can work with the context that you already have using custom hooks. Instead of using const { signIn } = React.useContext(AuthContext) like in the example, you can create a setup where you would use const { signIn } = useAuth(). Your useAuth hook can access your Redux store by using the React-Redux hooks internally.
Here's what that code looks like as a hook:
import * as React from 'react';
import * as SecureStore from 'expo-secure-store';
import { useDispatch } from "react-redux";
export const useAuth = () => {
// access dispatch from react-redux
const dispatch = useDispatch();
React.useEffect(() => {
// same as in example
}, []);
// this is the same as the example too
const authContext = useMemo(
() => ({
signIn: async data => {
dispatch({ type: 'SIGN_IN', token: 'dummy-auth-token' });
},
signOut: () => dispatch({ type: 'SIGN_OUT' }),
signUp: async data => {
dispatch({ type: 'SIGN_IN', token: 'dummy-auth-token' });
},
}),
[]
);
// but instead of passing that `authContext` object to a `Provider`, just return it!
return authContext;
}
In your component, which must be inside your React-Redux <Provider>:
function App() {
const { signIn } = useAuth();
const [username, setUsername] = React.useState('');
return (
<Button onPress={() => signIn(username)}>Sign In</Button>
)
}

How to get dispatch result in react native?

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.

Fetch API in react-native using redux

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.

Can I loop componentWillMount until I get the user_key from API?

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

React native redux map state to props not working

I am new to react-native and I am trying to implement a simple sign up functionality using react-redux. For some reasons , mapping the state to props in connect is not working.
Below is my code :
SignUp.js ( Component )
import React from 'react';
import { View, Text , TouchableOpacity , TextInput } from 'react-native';
import { connect } from "react-redux";
import { bindActionCreators } from "redux";
import * as signUpActions from "../actions/SignUpActions";
class SignUp extends React.Component {
constructor(){
super();
this.state = {
name : '',
password : '',
};
}
saveUser(){
let user = {};
user.name = this.state.name;
user.password = this.state.password;
this.props.registerUser(user);
}
static navigationOptions = {
title : 'Sign Up',
};
render(){
return (
<View>
<TextInput
placeholder="Username"
onChangeText={(text) => this.setState({name : text})}
/>
<TextInput
placeholder="Password"
onChangeText={(text) => this.setState({password : text})}
/>
<TouchableOpacity onPress = {() => this.saveUser()} >
<Text>DONE</Text>
</TouchableOpacity>
</View>
);
}
}
export default connect(
state => ({
user : state.user
}),
dispatch => bindActionCreators(signUpActions, dispatch)
)(SignUp);
SignUpAction.js
function storeUser(user) {
return {
type : 'REGISTER_USER',
payload : user,
};
};
export function registerUser(user) {
return function (dispatch, getState) {
fetch(<the-url>)
.then((response) => {return response.json()})
.then((responseData) => dispatch(storeUser(responseData)))
.catch((err) => console.log(err));
};
};
SignUpReducer.js
const initialState = {
data : {},
};
export default function signUpReducer(state = initialState, action) {
console.log(action.payload)
//This gives {id:26 , name : "xyz" ,password:"pass"}
switch (action.type) {
case 'REGISTER_USER' :
return {
...state ,
user : action.payload
}
default :
return state;
}
}
This my root reducer
export default function getRootReducer(navReducer) {
return combineReducers({
nav: navReducer,
signUpReducer : signUpReducer,
});
}
The register user function is being called. And the fetch request is also successfully executed over a network. It returns the same user object back after storing it in a database. It dispatches to the storeUser function as well. The reducer is getting called as well.
But , for some reasons , the state is not mapped to the props inside the connect. this.props.user returns undefined.
I must be doing something wrong in this but I am not able to figure it out. Based on what I have seen till now when we dispatch any action using bindActionCreators the result from reducer needs to be mapped to component's props using connect. Please correct me if wrong.
Please help me with this issue.
From your store defination,
return combineReducers({
nav: navReducer,
signUpReducer : signUpReducer,
});
You defined the key signUpReducer for your SignUp component state.
In order to access the state of this component,you should use this key followed by the state name.
The correct way to access user is :
export default connect(
state => ({
user : state.signUpReducer.user
})
//use signUpReducer key
FOR ME ALSO THIS IS WORKING componentWillReceiveProps(nextProps)
componentWillReceiveProps(nextProps) {
console.log();
this.setState({propUser : nextProps.user})
}