Implement FB login with react native and redux - react-native

I want to use Redux framework in my react native based app for implementing Facebook login (I am learning Redux at the moment). I am looking for suggestions on how to structure my Facebook login code to use the redux. More specifically, what actions, reducer and store should I create?
Below is the current Facebook based login code that I have in my app (it does not use redux structure). I have deleted the unrelated code to keep things simple:
index.ios.js
class ProjectXApp extends React.Component {
constructor(props) {
// Set the use to NULL
this.state = {
user: null,
};
}
handleLogin(user) {
this.setState({
// Update the user state once the login is complete
user,
});
}
renderScene(route, navigator) {
const Component = route.component;
return (
<View style={styles.app}>
<Component
user={this.state.user}
navigator={navigator}
route={route}
/>
</View>
);
}
render() {
return (
<Navigator
renderScene={this.renderScene.bind(this)}
initialRoute={{
// Render the Login page in the beginning
component: Login,
props: {
onLogin: this.handleLogin.bind(this),
},
}}
/>
);
}
}
Login.js
// Import Facebook Login Util Component
class Login extends React.Component {
constructor(props) {
super(props);
this.state = {
// 'false' means responseToken is not required. 'true' means responseToken is required
responseToken: false,
};
}
// This method gets the fb access token, if the token is returned then
// I render the Main App component (switchToMain method). If the
// access token is not returned then I render a login Button (Refer to render method)
async getAccessToken() {
let _this = this;
await (FBSDKAccessToken.getCurrentAccessToken((token) => {
if(!token) {
_this.setState({responseToken: true})
return;
}
_this.setState({responseToken: true});
_this.props.route.props.onLogin({user: true});
_this.switchToMain();
}));
}
switchToMain() {
this.props.navigator.push({
component: Main, // Render the app
props: {
onLogOut: this.onLogOut.bind(this)
}
});
}
componentDidMount() {
this.getAccessToken();
}
onLoginButtonPress() {
// Shows transition between login and Main Screen
this.setState({responseToken: false})
FBSDKLoginManager.logInWithReadPermissions(['public_profile','email','user_friends'], (error, result) => {
if (error) {
alert('Error logging in');
} else {
if (result.isCancelled) {
alert('Login cancelled');
} else {
this.setState({result});
this.getAccessToken();
}
}
});
}
onLogOut() {
this.setState({responseToken: true});
}
render() {
// This component renders when I am calling getAccessToken method
if(!this.state.responseToken) {
return (
<Text></Text>
);
}
// This renders when access token is not available after calling getAccessToken
return (
<View style={styles.container}>
<TouchableHighlight
onPress={this.onLoginButtonPress.bind(this)}
>
<View>
// Login Button
</View>
</TouchableHighlight>
</View>
);
}
}
// Removed the styling code
Logout.js
import { FBSDKLoginManager } from 'react-native-fbsdklogin';
class Logout extends React.Component {
onLogOut() {
FBSDKLoginManager.logOut();
this.props.onLogOut();
this.props.navigator.popToTop();
}
render() {
return (
<View>
<TouchableHighlight
onPress={this.onLogOut.bind(this)}
>
<View
// Styles to create Logout button
</View>
</TouchableHighlight>
</View>
);
}
});
// Removed the styling code

Have you looked at this lib:
https://github.com/lynndylanhurley/redux-auth?

Related

Making Sign in form in react native

I am trying to create a sign in form using custom text field. Inside a custom form and use that form details to validate user in login page.
Custom text field
export default class UserInput extends Component {
render() {
return (
<View style={styles.inputWrapper}>
<Image source={this.props.source} style={styles.inlineImg} />
<TextInput
style={styles.input}
placeholder={this.props.placeholder}
secureTextEntry={this.props.secureTextEntry}
/>
</View>
);
}
}
using custom text field to create a form.
export default class Form extends Component {
constructor(props) {
super(props);
this.state = {
email: "",
password: ""
};
}
render() {
const { onChange, ...rest } = this.props;
return (
<KeyboardAvoidingView behavior="padding" style={styles.container}>
<UserInput
source={usernameImg}
placeholder="Username"
/>
<UserInput
source={passwordImg}
secureTextEntry={this.state.showPass}
placeholder="Password"
/>
</KeyboardAvoidingView>
);
}
}
using form and other components in the sign in screen.
class SignInScreen extends Component {
constructor(props) {
super(props);
this.state = {
email: "",
password: "",
isLoading: false
};
}
signIn = async () => {
console.log("signIn");
firebase
.auth()
.signInWithEmailAndPassword("entered email", "entered password")
.then(
() => {
console.log("then");
this.moveTohome();
},
error => {
console.log(error, "eroor in login");
}
);
console.log("App");
};
moveTohome = async () => {
console.log("move to home");
await AsyncStorage.setItem("userToken", this.state.email);
this.props.navigation.navigate("App");
};
render() {
const changeWidth = this.buttonAnimated.interpolate({
inputRange: [0, 1],
outputRange: [myWidth * 0.9, myHeight * 0.08]
});
return (
// <DismissKeyboard>
<Wallpaper>
<Form />
<SignupSection navigation={this.props.navigation} />
<ButtonSubmit
onPress={() => {
this.signIn();
}}
/>
</Wallpaper>
// </DismissKeyboard>
);
}
}
I want to access the email and password entered in the sign in screen.
I Did not wanted to use any custom forms available. I dont know the redux to maintain state also. So what would be the simple way I can do? in the app.
I searched a lot but nothing is working for me.
If you want to access the email/password in some other screen then you can send them in a param object in your react navigation method.
this.props.navigation.navigate("App",{
email:this.state.email,password:this.state.password
});
But this would only be sent to App route in this case and if you want to access at other page then you need to pass them again.
Another way will be to hook up your app with Redux as it works so good when combined with React for state management.

How do I update Different Screens form another independent screen?

I have an App with a navigationbar with 2 Screens.
When i apply a function on Screen/Component 1 , I want to render or trigger a change in the Second Screen.
is there a way to either re-render the screen on Enter or to update the state of the other screen ?
Component one:
export default class HomeScreen extends React.Component {
constructor() {
super();
}
_onPress(){
try {
await AsyncStorage.setItem('value', 'changed Value');
} catch (error) {
console.log(error.message);
}
console.log("saved: " + this.state.userName )
}
render() {
return (
<View style={styles.container}>
<Button title="btn" onPress={() => this._onPress()} >
</Button>
</View>
)
}
component 2:
export default class SecondScreen extends React.Component {
constructor() {
super();
this.state = {some : ''}
}
async getValue () {
let recievedValue = '';
try {
let promise = await AsyncStorage.getItem('value') || 'cheeseCake';
promise.then((value) => recievedValue = value)
} catch (error) {
// Error retrieving data
console.log(error.message);
}
return recievedValue
}
render() {
var value= this.getValue();
return (
<View style={styles.container}>
<Text>
HERE CHANGED VALUE: {value}
</Text>
<Button onPress={()=> this.setState((prev)=> {some:'Thing'})}>
</Button>
</View>
)
}
When i press the Button on screen 1(HomeScreen) the value is saved.
But it only shows in the secont screen when I trigger a statechange via Button Press.
How do I render the screen when I visit the screen via navigation bar ?
Did you try EventEmiter?
Use this custom event listener: https://github.com/meinto/react-native-event-listeners
eg:
import { EventRegister } from 'react-native-event-listeners'
/*
* RECEIVER COMPONENT
*/
class Receiver extends PureComponent {
constructor(props) {
super(props)
this.state = {
data: 'no data',
}
}
componentWillMount() {
this.listener = EventRegister.addEventListener('myCustomEvent', (data) => {
this.setState({
data,
})
})
}
componentWillUnmount() {
EventRegister.removeEventListener(this.listener)
}
render() {
return <Text>{this.state.data}</Text>
}
}
/*
* SENDER COMPONENT
*/
const Sender = (props) => (
<TouchableHighlight
onPress={() => {
EventRegister.emit('myCustomEvent', 'it works!!!')
})
><Text>Send Event</Text></TouchableHighlight>
)

Why react-router-native does not render my component?

Trying to implement a protectedRoute utils for react-native project, which basicly looks for JWT. First it shows loading indicator, and if there is no JWT present it would redirect to /login.
const LoadingComponent = () => (
<View>
<ActivityIndicator/>
</View>
)
class PrivateRoute extends React.Component {
state = {
loading: true,
jwt: null,
}
componentDidMount() {
storage.retrieve('JWT').then(jwt => this.setState({ loading: false, jwt }))
}
render() {
const { children } = this.props;
const { jwt, loading } = this.state;
if (loading) {
return <Route {...children} component={LoadingComponent}/>
}
if (!jwt) {
return <Redirect to="/signup" />;
}
return <Route {...children} />
}
}
export default PrivateRoute;
this.props.children has all the required information to make a Route in the application. The original idea is that in case of loading we would just overwrite this.props.children.component with custom loading screen.
BUT the solution which does not give me error is (only warning):
if (loading) {
return LoadingComponent
}
I have tried also to manually inline component as
component={() => <View>{...}</View>
render={() => ...}
However it also ends up as the same error. Invariant Violation: Element type is invalid: expected a string ..... but got: undefined

React Native - Component update parent

I'm making an app in react native and I'm facing a little problem.
I finished the first layout and now I want to change the style all over the app with a second layout
This is what I have in my parent.
As you can see I use AsyncStorage to check when you open again the app the last selected layout. It all working perfectly.
export default class Home extends React.Component
{
constructor(props){
super(props);
this.state = {
view:0
}
}
componentWillMount()
{
this.checkStructureView();
}
checkStructureView = async() =>
{
const StructureView = await
AsyncStorage.getItem('#StructureView');
if(StructureView == 1)
{
this.setState({
view:1
})
}
else
{
this.setState({
view:0
})
}
}
render()
{
if(this.state.view == 1)
{
return(
<ChangeView/>
...
)
}
else
{
return(
<ChangeView/>
...
)
}
}
}
And this is my component ChangeView. It's a little bit messy because I have for each button active/inactive styles. This is also working perfectly, but the problem is that when I click on the button to change the layout will not change it, only after I refresh the app.
First I added this inside the parent and after I updated the state, the layout has changed instantly but I have more pages where I need to add this component, that's why I'm using an component.
So my question is how can I update instantly the parent state so my layout changes every time I click on the component button without reloading the app.
import React, { Component } from 'react'
import {
View,
Text,
Image,
TouchableOpacity,
AsyncStorage
} from 'react-native'
export default class ChangeView extends Component {
constructor(props){
super(props);
this.state = {
position: this.props.position,
view:0,
view1:require(`../assets/icons/view1_inactive.png`),
view2:require(`../assets/icons/view2_active.png`)
}
}
componentDidMount()
{
this.checkViewStructure();
}
checkViewStructure = async()=>
{
const StructureView = await AsyncStorage.getItem('#StructureView');
if(StructureView == '0')
{
this.setState({
view1:require(`../assets/icons/view1_inactive.png`),
view2:require(`../assets/icons/view2_active.png`)
})
}
else
{
this.setState({
view1:require(`../assets/icons/view1_active.png`),
view2:require(`../assets/icons/view2_inactive.png`)
})
}
}
changeToList = async() =>
{
const StructureView = await AsyncStorage.getItem('#StructureView');
if(StructureView == '0')
{
await AsyncStorage
.setItem('#StructureView', '1')
.then( () => {
//
})
.catch( () => {
alert('Something happened! Please try again later.');
});
this.setState({
view1:require(`../assets/icons/view1_active.png`),
view2:require(`../assets/icons/view2_inactive.png`)
})
}
}
changeToPics = async() =>
{
const StructureView = await AsyncStorage.getItem('#StructureView');
if(StructureView == '1')
{
await AsyncStorage
.setItem('#StructureView', '0')
.then( () => {
//
})
.catch( () => {
alert('Something happened! Please try again later.');
});
this.setState({
view1:require(`../assets/icons/view1_inactive.png`),
view2:require(`../assets/icons/view2_active.png`)
})
}
}
render()
{
if(this.state.position === 0)
return(
<View style={{alignItems:'flex-end',marginTop:20,marginBottom:10,justifyContent:'flex-end',flexDirection:'row'}}>
<View>
<TouchableOpacity
onPress= {() => this.changeToList()}
>
<Image
source={this.state.view1}
style={{width:15,height:21,margin:5}}
/>
</TouchableOpacity>
</View>
<View>
<TouchableOpacity
onPress= {() => this.changeToPics()}
>
<Image
source={this.state.view2}
style={{width:15,height:21,margin:5}}
/>
</TouchableOpacity>
</View>
</View>
)
else
return null
}
}
The ChangeView component only changes state in that specific component. There are several ways of propagating change to the parent component. One way is to implement an onChange prop for the ChangeView component. Your Home component render function would then look like something like this:
render() {
if(this.state.view == 1) {
return(
<ChangeView onChange={ (view) => this.setState({ view }) } />
...
)
} else {
return(
<ChangeView onChange={ (view) => this.setState({ view }) } />
...
)
}
}
You can read more about props here: https://reactjs.org/docs/typechecking-with-proptypes.html
There are other ways of doing this if you have state handler for your application such as Redux.

Update state when user press back button in React Native

I use react-navigation for manage routes. This is my Home component:
class HomeScreen extends React.Component {
constructor(props) {
this.state = {
userProfile: {
firstname: 'John',
avatar: 'john-profile.png',
location: 'Canada',
}
}
}
componentDidMount() {
AsyncStorage.getItem('userProfile', (errs, result) => {
this.setState({userProfile: JSON.parse(result)});
});
}
render() {
return (
<View>
<Image src="{this.state.userProfile.avatar}" />
<Text>Firstname: {this.state.userProfile.firstname}</Text>
<Text>Location: {this.state.userProfile.location}</Text>
</View>
);
}
}
And this is the Profile screen:
class ProfileScreen extends React.Component {
constructor(props) {
this.state = {
userProfile: null,
}
}
componentDidMount() {
AsyncStorage.getItem('userProfile', (errs, result) => {
this.setState({userProfile: JSON.parse(result)});
});
}
save() {
var userSavedProfile = this.state.userProfile;
userSavedProfile.firstname = "Peter";
userSavedProfile.avatar = "peter-avatar.png";
userSavedProfile.location = "EEUU";
this.setState({userProfile: userSavedProfile});
AsyncStorage.setItem('userProfile', JSON.stringify(this.state.userProfile), () => {});
}
render() {
return (
<View>
<Button title="Save" onPress={() => this.save()} />
</View>
);
}
}
When I save the new user information and I press back button in header (react-navigation) the user profile is old, firstname = John, etc... How update state from Home when user press back button and refresh data?
You can use BackHandler from react-native
https://facebook.github.io/react-native/docs/backhandler.html
You can change state inside function of backhandler
I think that your application would need a state manager, where you could store your user information and access it anywhere in the app. You should take a look at Redux. It would fit your needs and the info in your Home screen would automatically update.
but for anyone who will need this functionality in there react native application here is the solution you can try.
using react navigation.
import {withNavigationFocus} from "react-navigation";
class Profile extends Component {
...
}
export default withNavigationFocus(Profile);
There can be two workarounds check it out -
1 Send callback in params
class HomeScreen extends React.Component {
constructor(props) {
this.state = {
userProfile: {
firstname: 'John',
avatar: 'john-profile.png',
location: 'Canada',
}
}
this.getUserData = this.getUserData.bind(this);
}
componentDidMount() {
this.getUserData;
}
getUserData = () =>{
AsyncStorage.getItem('userProfile', (errs, result) => {
this.setState({userProfile: JSON.parse(result)});
});
}
render() {
return (
<View>
<Image src="{this.state.userProfile.avatar}" />
<Text onPress={()=>this.props.navigation.navigate('ProfileScreen', this.getUserData)}>Firstname: {this.state.userProfile.firstname}</Text>
<Text>Location: {this.state.userProfile.location}</Text>
</View>
);
}
}
class ProfileScreen extends React.Component {
constructor(props) {
this.state = {
userProfile: null,
}
}
componentDidMount() {
AsyncStorage.getItem('userProfile', (errs, result) => {
this.setState({userProfile: JSON.parse(result)});
});
}
save() {
var userSavedProfile = this.state.userProfile;
userSavedProfile.firstname = "Peter";
userSavedProfile.avatar = "peter-avatar.png";
userSavedProfile.location = "EEUU";
this.setState({userProfile: userSavedProfile});
AsyncStorage.setItem('userProfile', JSON.stringify(this.state.userProfile), () => {});
//this is the magic
this.props.navigation.state.params.getUserData();
}
render() {
return (
<View>
<Button title="Save" onPress={() => this.save()} />
</View>
);
}
}
2 On HomeScreen Constructor add this (Dirty one)
this.props.navigation.addListener(
'didFocus',
payload => {
this.setState({is_updated:true});
}
);
You can use componentDidUpdate(){...} insted componentDidMount(){}