I got navigation as undefined in react navigation 5? - javascript

I have a reusable component for Sign in with Apple Button
After user success, i navigate hem to Home screen
But i notes when i log navigation it's log undefined,
and when i log this.props i just got the two actions i made in redux!
So how can i access to navigation in this component and why it's not accessed by default!
Log
props => {"isLogin": [Function isLogin], "storeToken": [Function storeToken]}
navigation => undefined
Code
import appleAuth, {
AppleAuthCredentialState,
AppleAuthError,
AppleAuthRealUserStatus,
AppleAuthRequestOperation,
AppleAuthRequestScope,
AppleButton,
} from '#invertase/react-native-apple-authentication';
import React from 'react';
import {ActivityIndicator, StyleSheet, View} from 'react-native';
import {connect} from 'react-redux';
import API from '../../api/API';
import {isLoginFunc} from '../../redux/actions/isLoginAction';
import {saveToken} from '../../redux/actions/saveTokenAction';
class AppleAuth extends React.Component {
constructor(props) {
super(props);
this.authCredentialListener = null;
this.user = null;
this.state = {
credentialStateForUser: -1,
loading: false,
};
}
componentDidMount() {
const {navigation} = this.props;
console.log('did-navigation', navigation);
console.log('did- this.props', this.props);
/**
* subscribe to credential updates.This returns a function which can be used to remove the event listener
* when the component unmounts.
*/
this.authCredentialListener = appleAuth.onCredentialRevoked(async () => {
// console.warn('Credential Revoked');
this.fetchAndUpdateCredentialState().catch(error =>
this.setState({credentialStateForUser: `Error: ${error.code}`}),
);
});
this.fetchAndUpdateCredentialState()
.then(res => this.setState({credentialStateForUser: res}))
.catch(error =>
this.setState({credentialStateForUser: `Error: ${error.code}`}),
);
}
componentWillUnmount() {
/**
* cleans up event listener
*/
this.authCredentialListener();
}
signIn = async () => {
// start a login request
try {
const appleAuthRequestResponse = await appleAuth.performRequest({
requestedOperation: AppleAuthRequestOperation.LOGIN,
requestedScopes: [
AppleAuthRequestScope.EMAIL,
AppleAuthRequestScope.FULL_NAME,
],
});
this.setState({loading: true});
const {
user: newUser,
email,
nonce,
fullName: {familyName, givenName},
identityToken,
realUserStatus /* etc */,
} = appleAuthRequestResponse;
let username = `${givenName} ${familyName}`;
this.user = newUser;
this.fetchAndUpdateCredentialState()
.then(res => {
this.setState({credentialStateForUser: res});
console.log('res:::', res);
})
.catch(error => {
console.log(`Error: ${error.code}`);
this.setState({credentialStateForUser: `Error: ${error.code}`});
});
if (identityToken) {
console.log('email', email);
console.log('username', username);
console.log('nonce', nonce);
this.sendData(email, username, nonce);
// e.g. sign in with Firebase Auth using `nonce` & `identityToken`
} else {
// no token - failed sign-in?
}
if (realUserStatus === AppleAuthRealUserStatus.LIKELY_REAL) {
console.log("I'm a real person!");
}
// console.warn(`Apple Authentication Completed, ${this.user}, ${email}`);
} catch (error) {
if (error.code === AppleAuthError.CANCELED) {
alert('User canceled Apple Sign in');
// console.warn('User canceled Apple Sign in.');
} else {
console.error(error);
}
}
};
fetchAndUpdateCredentialState = async () => {
if (this.user === null) {
this.setState({credentialStateForUser: 'N/A'});
} else {
const credentialState = await appleAuth.getCredentialStateForUser(
this.user,
);
if (credentialState === AppleAuthCredentialState.AUTHORIZED) {
this.setState({credentialStateForUser: 'AUTHORIZED'});
} else {
this.setState({credentialStateForUser: credentialState});
}
}
};
// Send data "name,image,email" to API
sendData = async (Email, Name, Id) => {
try {
let response = await API.post('/apple', {
email: Email,
name: Name,
id: Id,
});
let {
data: {
data: {
response: {token},
},
},
} = response;
console.log('token:?>:', token);
console.log('props', this.props);
console.log('navigation', this.props.navigation);
this.setState({loading: false});
this.props.storeToken(token);
this.props.isLogin(true);
// this.props.navigation.push('BottomTabNavigator');
} catch (err) {
console.log(err);
alert('Unexpected Error, try again later.');
this.setState({loading: false});
}
};
render() {
return (
<View style={styles.container}>
{this.state.loading ? (
<ActivityIndicator />
) : (
<AppleButton
style={styles.appleButton}
cornerRadius={5}
buttonStyle={AppleButton.Style.WHITE}
buttonType={AppleButton.Type.SIGN_IN}
onPress={() => this.signIn()}
/>
)}
</View>
);
}
}
const styles = StyleSheet.create({
appleButton: {
width: 200,
height: 50,
// margin: 10,
},
container: {
flex: 1,
justifyContent: 'center',
},
});
const mapDispatchToProps = dispatch => {
// to excute the actions we want to invok
return {
isLogin: isLogin => {
dispatch(isLoginFunc(isLogin));
},
storeToken: token => {
dispatch(saveToken(token));
},
};
};
export default connect(
null,
mapDispatchToProps,
)(AppleAuth);
-
singin.js
<AppleAuth /> in the render method

if you render your component as component, not as a navigation screen, it will not receive navigation prop. It was like this in all versions of react-navigation
Access the navigation prop from any component

Related

TypeError: Cannot read property 'setState' of undefined even after all proper Binding for WebRTC and React

I am getting the following error,
"Uncaught TypeError: Cannot read property 'setState' of undefined
at Socket.eval (CommunicationContainer.js?41c1:40)
at Socket.Emitter.emit (index.js?7297:133)
at Socket.onevent (socket.js?2851:278)
at Socket.onpacket (socket.js?2851:236)
at Manager.eval (index.js?40de:21)
at Manager.Emitter.emit (index.js?7297:133)
at Manager.ondecoded (manager.js?78eb:345)
at Decoder.eval (index.js?40de:21)
at Decoder.Emitter.emit (index.js?7297:133)
at Decoder.add (index.js?568d:251)
"
Through all I've looked up this is because the binding is not properly done inside the constructor, however after everything I've done as far as I can tell I have all proper bindings and have no idea why this error is occurring. The error says it occurs in the CommmunicationContainer which I have attached here
Communication Container
import React from 'react'
import { PropTypes } from 'prop-types';
import Remarkable from 'remarkable-react'
import MediaContainer from './MediaContainer'
import Communication from '../components/Communication'
import store from '../store/configureStore'
import { connect } from 'react-redux'
class CommunicationContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
sid: '',
message: '',
audio: true,
video: true
};
this.handleInvitation = this.handleInvitation.bind(this);
this.handleHangup = this.handleHangup.bind(this);
this.handleInput = this.handleInput.bind(this);
this.toggleVideo = this.toggleVideo.bind(this);
this.toggleAudio = this.toggleAudio.bind(this);
this.send = this.send.bind(this);
this.hideAuth = this.hideAuth.bind(this);
this.full = this.full.bind(this);
}
hideAuth() {
this.props.media.setState({bridge: 'connecting'});
}
full() {
this.props.media.setState({bridge: 'full'});
}
componentDidMount() {
console.log('Comments componentDidMount: ');
console.log(this);
const socket = this.props.socket;
console.log('props', this.props)
this.setState({video: this.props.video, audio: this.props.audio});
socket.on('create', () =>
this.props.media.setState({user: 'host', bridge: 'create'}));
socket.on('full', this.full);
socket.on('bridge', role => this.props.media.init());
socket.on('join', () =>
this.props.media.setState({user: 'guest', bridge: 'join'}));
socket.on('approve', ({ message, sid }) => {
this.props.media.setState({bridge: 'approve'});
this.setState({ message, sid });
});
socket.emit('find');
this.props.getUserMedia
.then(stream => {
this.localStream = stream;
this.localStream.getVideoTracks()[0].enabled = this.state.video;
this.localStream.getAudioTracks()[0].enabled = this.state.audio;
});
}
handleInput(e) {
this.setState({[e.target.dataset.ref]: e.target.value});
}
send(e) {
e.preventDefault();
this.props.socket.emit('auth', this.state);
this.hideAuth();
}
handleInvitation(e) {
e.preventDefault();
this.props.socket.emit([e.target.dataset.ref], this.state.sid);
this.hideAuth();
}
getContent(content) {
return {__html: (new Remarkable()).render(content)};
}
toggleVideo() {
const video = this.localStream.getVideoTracks()[0].enabled = !this.state.video;
this.setState({video: video});
this.props.setVideo(video);
}
toggleAudio() {
const audio = this.localStream.getAudioTracks()[0].enabled = !this.state.audio;
this.setState({audio: audio});
this.props.setAudio(audio);
}
handleHangup() {
this.props.media.hangup();
}
render(){
console.log(this.media);
return (
<Communication
{...this.state}
toggleVideo={this.toggleVideo}
toggleAudio={this.toggleAudio}
getContent={this.getContent}
send={this.send}
handleHangup={this.handleHangup}
handleInput={this.handleInput}
handleInvitation={this.handleInvitation} />
);
}
}
const mapStateToProps = store => ({video: store.video, audio: store.audio});
const mapDispatchToProps = dispatch => (
{
setVideo: boo => store.dispatch({type: 'SET_VIDEO', video: boo}),
setAudio: boo => store.dispatch({type: 'SET_AUDIO', audio: boo})
}
);
CommunicationContainer.propTypes = {
socket: PropTypes.object.isRequired,
getUserMedia: PropTypes.object.isRequired,
audio: PropTypes.bool.isRequired,
video: PropTypes.bool.isRequired,
setVideo: PropTypes.func.isRequired,
setAudio: PropTypes.func.isRequired,
media: PropTypes.instanceOf(MediaContainer)
};
export default connect(mapStateToProps, mapDispatchToProps)(CommunicationContainer);
I've included the other Containers and Components for reference in case the error has something to do with them, but as far as is being shown the error is within the CommunicationContainer with the state being undefined at the socket line. As well I have included the error image.
This application is a WebRTC React page within a React website where the user sets the room ID and upon pressing the button launches into the room.
The Error Code
The Error Line
Chat Room Page
import React, { Component } from 'react';
import MediaContainer from './MediaContainer'
import CommunicationContainer from './CommunicationContainer'
import { connect } from 'react-redux'
import store from '../store/configureStore'
import io from 'socket.io-client'
class ChatRoomPage extends React.Component {
constructor(props) {
super(props);
this.getUserMedia = navigator.mediaDevices.getUserMedia({
audio: true,
video: true
}).catch(e => alert('getUserMedia() error: ' + e.name))
this.socket = io.connect('https://localhost:8080');
}
componentDidMount() {
this.props.addRoom();
}
render() {
console.log(this.socket);
console.log(this.getUserMedia);
console.log(this.media);
return (
<div>
<MediaContainer media={media => this.media = media} socket={this.socket} getUserMedia={this.getUserMedia} />
<CommunicationContainer socket={this.socket} media={this.media} getUserMedia={this.getUserMedia} />
<h1>AppointmentSetup</h1>
</div>
);
}
}
//commented out
const mapStateToProps = store => ({rooms: new Set([...store.rooms])});
const mapDispatchToProps = (dispatch, ownProps) => (
{
addRoom: () => store.dispatch({ type: 'ADD_ROOM', room: ownProps.match.params.room })
}
);
export default connect(mapStateToProps, mapDispatchToProps)(ChatRoomPage);
Media Container
import React, { Component } from 'react';
import { PropTypes } from 'prop-types';
class MediaBridge extends Component {
constructor(props) {
super(props);
this.state = {
bridge: '',
user: ''
}
this.onRemoteHangup = this.onRemoteHangup.bind(this);
this.onMessage = this.onMessage.bind(this);
this.sendData = this.sendData.bind(this);
this.setupDataHandlers = this.setupDataHandlers.bind(this);
this.setDescription = this.setDescription.bind(this);
this.sendDescription = this.sendDescription.bind(this);
this.hangup = this.hangup.bind(this);
this.init = this.init.bind(this);
this.setDescription = this.setDescription.bind(this);
}
componentWillMount() {
// chrome polyfill for connection between the local device and a remote peer
window.RTCPeerConnection = window.RTCPeerConnection || window.webkitRTCPeerConnection;
this.props.media(this);
}
componentDidMount() {
this.props.getUserMedia
.then(stream => this.localVideo.srcObject = this.localStream = stream);
this.props.socket.on('message', this.onMessage);
this.props.socket.on('hangup', this.onRemoteHangup);
}
componentWillUnmount() {
this.props.media(null);
if (this.localStream !== undefined) {
this.localStream.getVideoTracks()[0].stop();
}
this.props.socket.emit('leave');
}
onRemoteHangup() {
this.setState({user: 'host', bridge: 'host-hangup'});
}
onMessage(message) {
if (message.type === 'offer') {
// set remote description and answer
this.pc.setRemoteDescription(new RTCSessionDescription(message));
this.pc.createAnswer()
.then(this.setDescription)
.then(this.sendDescription)
.catch(this.handleError); // An error occurred, so handle the failure to connect
} else if (message.type === 'answer') {
// set remote description
this.pc.setRemoteDescription(new RTCSessionDescription(message));
} else if (message.type === 'candidate') {
// add ice candidate
this.pc.addIceCandidate(
new RTCIceCandidate({
sdpMLineIndex: message.mlineindex,
candidate: message.candidate
})
);
}
}
sendData(msg) {
this.dc.send(JSON.stringify(msg))
}
// Set up the data channel message handler
setupDataHandlers() {
this.dc.onmessage = e => {
var msg = JSON.parse(e.data);
console.log('received message over data channel:' + msg);
};
this.dc.onclose = () => {
this.remoteStream.getVideoTracks()[0].stop();
console.log('The Data Channel is Closed');
};
}
setDescription(offer) {
this.pc.setLocalDescription(offer);
}
// send the offer to a server to be forwarded to the other peer
sendDescription() {
this.props.socket.send(this.pc.localDescription);
}
hangup() {
this.setState({user: 'guest', bridge: 'guest-hangup'});
this.pc.close();
this.props.socket.emit('leave');
}
handleError(e) {
console.log(e);
}
init() {
// wait for local media to be ready
const attachMediaIfReady = () => {
this.dc = this.pc.createDataChannel('chat');
this.setupDataHandlers();
console.log('attachMediaIfReady')
this.pc.createOffer()
.then(this.setDescription)
.then(this.sendDescription)
.catch(this.handleError); // An error occurred, so handle the failure to connect
}
// set up the peer connection
// this is one of Google's public STUN servers
// make sure your offer/answer role does not change. If user A does a SLD
// with type=offer initially, it must do that during the whole session
this.pc = new RTCPeerConnection({iceServers: [{url: 'stun:stun.l.google.com:19302'}]});
// when our browser gets a candidate, send it to the peer
this.pc.onicecandidate = e => {
console.log(e, 'onicecandidate');
if (e.candidate) {
this.props.socket.send({
type: 'candidate',
mlineindex: e.candidate.sdpMLineIndex,
candidate: e.candidate.candidate
});
}
};
// when the other side added a media stream, show it on screen
this.pc.onaddstream = e => {
console.log('onaddstream', e)
this.remoteStream = e.stream;
this.remoteVideo.srcObject = this.remoteStream = e.stream;
this.setState({bridge: 'established'});
};
this.pc.ondatachannel = e => {
// data channel
this.dc = e.channel;
this.setupDataHandlers();
this.sendData({
peerMediaStream: {
video: this.localStream.getVideoTracks()[0].enabled
}
});
//sendData('hello');
};
// attach local media to the peer connection
this.localStream.getTracks().forEach(track => this.pc.addTrack(track, this.localStream));
// call if we were the last to connect (to increase
// chances that everything is set up properly at both ends)
if (this.state.user === 'host') {
this.props.getUserMedia.then(attachMediaIfReady);
}
}
render(){
console.log(this.media);
console.log(this.getUserMedia)
return (
<div className={`media-bridge ${this.state.bridge}`}>
<video className="remote-video" ref={(ref) => this.remoteVideo = ref} autoPlay></video>
<video className="local-video" ref={(ref) => this.localVideo = ref} autoPlay muted></video>
</div>
);
}
}
MediaBridge.propTypes = {
socket: PropTypes.object.isRequired,
getUserMedia: PropTypes.object.isRequired,
media: PropTypes.func.isRequired
}
export default MediaBridge;
Any help is greatly appreciated as I don't see any binding issues within this yet continue to get the error no matter what I try.
Those methods have not bin bind on your controller and you are calling them from other methods , it may be the reason!
hideAuth()
full()

How can I update High Order Component

I created array of routing in ReactJS
const routes = [
{ id: 0, path: '/', view: Home, parent: 0 },
{ id: 1, path: '/a', view: Home2, parent: 0 },
{ id: 2, path: '/b', view: Home3, parent: 1 }
]
Created HOC withAuth which should back to parent routing when user isn't logged. When i going to route (as not logged) - its ok and withAuth back me to parent route, but when i am on route and logout page isn't refresh and I am stay on route for logged users.
import React, { Component } from "react";
import AuthHelper from "./AuthHelper";
export default function withAuth(AuthComponent) {
const Auth = new AuthHelper();
class AuthWrapped extends Component {
constructor(props) {
super(props);
this.state = {
confirm: null,
loaded: false
};
}
checkLogged = () => {
if (!Auth.loggedIn()) {
const parent = this.props.parent;
const obj = this.props.routes
.filter(v => v.id === parent);
this.props.history.replace(obj[0].path);
} else {
try {
const confirm = Auth.getConfirm();
this.setState({
confirm: confirm,
loaded: true
});
} catch (err) {
Auth.logout();
this.props.history.replace("/");
}
}
}
componentDidMount() {
this.checkLogged();
}
render() {
if (this.state.loaded) {
if (this.state.confirm) {
return (
<AuthComponent
history={this.props.history}
confirm={this.state.confirm}
/>
);
} else {
return null;
}
} else {
return null;
}
}
};
return AuthWrapped;
}
I believe that you are using the authentication system the wrong way
In React everything should exist in a hierarchical manner.
In your case, you have an Auth state that would change and when the loggedIn state changes, everything should re-render. the correct way to do this is using the Context API to handle the logged in state so when the state changes, the whole screen would re-render
here is the solution to your problem:
AuthContext.js
const AuthContext = React.createContext();
export class AuthProvider extends React.Component {
state = {
isLoggedIn: false,
};
login = (username, password) => {
someLoginRequestToServer(username, password).then(response => {
this.setState({
isLoggedIn: response.isLoggedIn,
});
});
};
logout = () => {
someLogoutRequestToServer().then(() => {
this.setState({ isLoggedIn: false });
});
};
render() {
return (
<AuthContext.Provider
value={{
loggedIn: this.state.isLoggedIn,
login: this.login,
logout: this.logout,
}}>
{this.props.children}
</AuthContext.Provider>
);
}
}
export const AuthConsumer = AuthContext.Consumer;
SomeCustomAuthComponent
class CustomAuthComponent extends React.Component {
render() {
return (
<AuthConsumer>
{({ loggedIn, login, logout }) => (
<div>
<p>You Are {loggedIn ? 'Logged in' : 'Logged out'}</p>
<button onClick={loggedIn ? () => logout() : () => login('abcd', '12345')} />
</div>
)}
</AuthConsumer>
);
}
}
Or you can use the redux for state management and react-redux as it uses the react Context API under the hood.
hope this helps you! :)
the problem lays here
componentDidMount() {
this.checkLogged();
}
you're checking if the user is logged only when the component is mounted (after the instantiation). you should be checking it every time the page updates, you have to identify a way to handle this mechanism for example by using the componentDidUpdate hook.
export default function withAuth(AuthComponent) {
const Auth = new AuthHelper();
class AuthWrapped extends Component {
constructor(props) {
super(props);
this.state = {
confirm: null,
loaded: false
};
}
checkIsNotLogged = () => {
const parent = this.props.parent;
const obj = this.props.routes
.filter(v => v.id === parent);
this.props.history.replace(obj[0].path);
}
checkLogged = () => {
if (!Auth.loggedIn()) {
this.checkIsNotLogged();
} else {
try {
const confirm = Auth.getConfirm();
this.setState({
confirm: confirm,
loaded: true
});
} catch (err) {
Auth.logout();
this.props.history.replace("/");
}
}
}
componentDidMount() {
this.checkLogged();
}
componentDidUpdate() {
// do not call here the checkLogged method otherwise you could trigger an infinite loop
this.checkIsNotLogged();
}
render() {
if (this.state.loaded) {
if (this.state.confirm) {
return (
<AuthComponent
history={this.props.history}
confirm={this.state.confirm}
/>
);
} else {
return null;
}
} else {
return null;
}
}
};
return AuthWrapped;
}

How to show props from reducers in component

I have a problem when want to show
const mapStateToProps = state => {
return {
loading: state.auth.loading,
error: state.auth.error,
userId: state.auth.userId,
tokenId: state.auth.token
}
}
this in my function
register = (event) => {
event.preventDefault()
this.props.onAuth( this.state.email, this.state.password, this.state.isSignup );
localStorage.setItem('token', this.props.tokenId);
localStorage.setItem('userId', this.props.userId);
}
I see token and userId after the second click. But I can't see after the first click. What I need more to show immediately?
This is my auth.js reducers
import * as actionTypes from '../actions/actionsTypes';
import { updateObject } from '../utility';
const initialState = {
token: null,
userId: null,
error: null,
loading: false
};
const authStart = ( state, action ) => {
return updateObject( state, { error: null, loading: true } );
};
const authSuccess = (state, action) => {
return updateObject( state, {
token: action.idToken,
userId: action.userId,
error: null,
loading: false
} );
};
const authFail = (state, action) => {
return updateObject( state, {
error: action.error,
loading: false
});
}
const reducer = ( state = initialState, action ) => {
switch ( action.type ) {
case actionTypes.AUTH_START: return authStart(state, action);
case actionTypes.AUTH_SUCCESS: return authSuccess(state, action);
case actionTypes.AUTH_FAIL: return authFail(state, action);
default:
return state;
}
};
export default reducer;
But, after the first click, I got token in my render function.
{this.props.tokenId}
Could you please help me? I think I need to use async/await. But I am not sure.
Here you go Header.js
import React, { Component } from 'react'
import { connect } from 'react-redux'
import * as actions from '../../store/actions/index'
import PropTypes from 'prop-types'
import './header.css'
class Header extends Component {
constructor(props) {
super(props)
this.state = {
email: '',
password: '',
isSignup: true,
token: false
}
this.handleChange = this.handleChange.bind(this);
}
handleChange (evt) {
this.setState({ [evt.target.name]: evt.target.value });
}
switchAuthModeHandler = (event) => {
event.preventDefault()
this.setState(prevState => {
return {
isSignup: !prevState.isSignup
}
})
}
register = (event) => {
event.preventDefault()
this.props.onAuth( this.state.email, this.state.password, this.state.isSignup );
localStorage.setItem('token', this.props.tokenId);
localStorage.setItem('userId', this.props.userId);
}
render() {
let regBtn = ''
if (this.state.isSignup) {
regBtn = 'Register'
}
else {
regBtn = 'Login'
}
let login = null
if(!this.props.tokenId) {
login = (
<div className="login">
<form onSubmit={this.register}>
<input type="email" placeholder="email" name="email" onChange={this.handleChange} />
<input type="password" placeholder="password" name="password" onChange={this.handleChange} />
<button>{regBtn}</button>
</form>
<div onClick={this.switchAuthModeHandler} className="switch">Switch to {this.state.isSignup ? 'Login' : 'Register'}</div>
</div>
)
}
else {
login = (
<div>
<p>Hello: {this.props.userId}</p>
<button>Logout</button>
</div>
)
}
if(this.props.loading) {
login = <div>Loading...</div>
}
return (
<div>
<div className="header-inner">
{this.props.tokenId}
{login}
<img src="http://civcic.com/assets/images/header-bg.jpg" alt="img" />
<div className="header-content">
<h2>React.JS DEVELOPER</h2>
<a className="knowmore-btn" href="https://www.upwork.com/freelancers/~01f507600be26cc2a3" rel="noopener noreferrer" target="_blank">Upwork profile</a><br />
<a className="knowmore-btn" href="https://www.linkedin.com/in/boris-civcic-37244378/" rel="noopener noreferrer" target="_blank">Linkedin</a><br />
<a className="knowmore-btn" href="https://github.com/fixman93" rel="noopener noreferrer" target="_blank">GitHub</a>
</div>
</div>
</div>
)
}
}
Header.defaultProps = {
tokenId: ''
}
Header.propTypes = {
tokenId: PropTypes.string
}
const mapStateToProps = state => {
return {
loading: state.auth.loading,
error: state.auth.error,
userId: state.auth.userId,
tokenId: state.auth.token
}
}
const mapDispatchToProps = dispatch => {
return {
onAuth: ( email, password, isSignup) => dispatch( actions.auth(email, password, isSignup))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Header)
import axios from 'axios';
import * as actionTypes from './actionsTypes';
export const authStart = () => {
return {
type: actionTypes.AUTH_START
}
}
export const authSuccess = (token, userId) => {
return {
type: actionTypes.AUTH_SUCCESS,
idToken: token,
userId: userId
}
}
export const authFail = (error) => {
return {
type: actionTypes.AUTH_FAIL,
error: error
};
};
export const auth = (email, password, isSignup) => {
return dispatch => {
dispatch(authStart());
const authData = {
email: email,
password: password,
fullName: 'Boris Civcic',
role: 'admin',
returnSecureToken: true
};
let url = 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/signupNewUser?key=AIzaSyC5nW8-XOJADEvU7Mi7sgmhUNhHfRxXNQI';
if (!isSignup) {
url = 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPassword?key=AIzaSyC5nW8-XOJADEvU7Mi7sgmhUNhHfRxXNQI';
}
axios.post(url, authData)
.then(response => {
console.log(response);
dispatch(authSuccess(response.data.idToken, response.data.localId));
// dispatch(checkAuthTime(response.data.expiresIn));
})
.catch(err => {
dispatch(authFail(err.response.data.error));
})
};
};
this is auth.js action
and this is utility
export const updateObject = (oldObject, updatedProperties) => {
return {
...oldObject,
...updatedProperties
};
};
In your register handler, onAuth is an asynchronous action but you've populated localStorage immediately. You should wait onAuth returns and then set your localStorage items.
first return a promise from your thunk ( simply by adding return before axios ):
...
return axios.post(url, authData)
.then(response => {
console.log(response);
dispatch(authSuccess(response.data.idToken, response.data.localId));
// dispatch(checkAuthTime(response.data.expiresIn));
})
.catch(err => {
dispatch(authFail(err.response.data.error));
})
...
Then set your localStorage items like this:
register = (event) => {
event.preventDefault();
this.props.onAuth( this.state.email, this.state.password, this.state.isSignup )
.then(() => {
localStorage.setItem('token', this.props.tokenId);
localStorage.setItem('userId', this.props.userId);
});
}

Async data loading in Redux-form

I am working with React and Redux, and I'm using Redux-form for my forms.
I am trying to load some initial data from database. In the docs it is recommended to use
{
load: loadAccount
}
in connect component, but I'm struggling to set it properly.
I can load initial data in a different way: adding a dispatch action to connect component and call it from componentDidMount, but I would like to understand how to set { load: loadAccount }.
This is my actions file —I show only the action that matters—:
const actions = {
requestProject: () => {
return {
type: C.LOAD_PROJECT_STARTED,
};
},
receiveProject: (id, data) => {
return {
type: C.LOAD_PROJECT_SUCCESS,
id,
data,
};
},
loadProject: (id = '') => {
const url = '/api/projects/';
const encodedURI = isBrowser
? encodeURI(window.location.origin + url + id)
: encodeURI('http://localhost:' + config.SERVER + url + id);
return isBrowser
? function(dispatch) {
dispatch(actions.requestProject());
return fetch(encodedURI)
.then(
(response) => {
return response.json();
},
(error) => {
return console.log('An error occurred.', error);
}
)
.then((data) => {
return dispatch(actions.receiveProject(id, data));
});
}
: fetch(encodedURI)
.then((response) => {
return response.json();
})
.then((data) => {
return data;
})
.catch((error) => {
return Promise.reject(Error(error.message));
});
},
};
export default actions;
Then my reducers —again, only one—:
export const Project = (state = {}, action) => {
switch (action.type) {
case C.LOAD_PROJECT_STARTED:
console.log('LOAD_PROJECT_STARTED');
return Object.assign({}, state, {
isFetching: true,
});
case C.LOAD_PROJECT_SUCCESS:
return Object.assign({}, state, {
...action.data.Project,
isFetching: false,
});
case C.SUBMIT_PROJECT_FORM_STARTED:
return Object.assign({}, state, {
isFetching: true,
});
case C.SUBMIT_PROJECT_FORM_SUCCESS:
return Object.assign({}, state, {
...action.data.Project,
isFetching: false,
});
default:
return state;
}
};
export const form = formReducer;
This is the connect ProjectForm component for the form:
import { connect } from 'react-redux';
import ProjectFormUi from './ProjectFormUi';
import actions from '../../redux/actions';
import { load as loadAccount } from './account'
export const ProjectForm = connect(
(state) => {
return {
initialValues: state.Project,
};
},
(dispatch) => {
return {
loadProject(id) {
dispatch(actions.loadProject(id));
},
onSubmit(data) {
dispatch(actions.sendingProject(data));
},
};
},
{
load: loadAccount,
}
)(ProjectFormUi);
export default ProjectForm;
And finally, the actual form component, ProjectFormUi.
import React from 'react';
import { Field, reduxForm } from 'redux-form';
class ProjectFormUi extends React.Component {
constructor(props) {
super(props);
this.state = {
};
}
componentDidMount() {
this.props.loadProject(1);
}
static getDerivedStateFromProps(newProps, prevState) {
return newProps != prevState ? newProps : null;
}
componentDidUpdate(prevProps) {
if (this.props.Project !== prevProps.Project) {
this.setState({
isFetching: this.props.Project.isFetching,
});
}
}
render() {
const { handleSubmit, load, pristine, reset, submitting } = this.props;
return (
<form onSubmit={handleSubmit}>
<div>
<label>First Name</label>
<div>
<Field name="title" component="input" type="text" placeholder="First Name" />
</div>
</div>
<div>
<button type="submit" disabled={submitting}>
Submit
</button>
<button type="button" disabled={pristine || submitting} onClick={reset}>
Undo Changes
</button>
</div>
</form>
);
}
}
export default reduxForm({
form: 'ProjectForm',
enableReinitialize: true,
})(ProjectFormUi);
As I said, currently I'm loading data in connect ProjectForm component with:
loadProject(id) {
dispatch(actions.loadProject(id));
},
which is called from componentDidMount in ProjectFormUi component.
But I would like to understand how to load data as in the docs, setting
{
load: loadAccount
}
in connect ProjectForm component.

React Native, GraphQL, Apollo - Error thrown during mutation

I'm working on a React Native project with Apollo Client and GraphQL as the backend. The problem I'm having is when I register a new user using a mutation, I get back a positive response that the user has been created, but I also get thrown an error saying Error caught: TypeError: Cannot read property 'variables' of undefined
I'm not sure what the problem is, or why the error is being returned at all. Here's the code:
'use strict'
import React, { Component } from 'react';
import { Text, View, StyleSheet, AsyncStorage, Modal, TouchableHighlight } from 'react-native'
import { Actions, ActionConst } from 'react-native-router-flux'
import { Button } from 'react-native-elements'
// Apollo and GraphQL Packages
import ApolloClient, { graphql, withApollo } from 'react-apollo'
import gql from 'graphql-tag'
import { filter } from 'graphql-anywhere'
import { connect } from 'react-redux'
import Auth0Lock from 'react-native-lock'
// Styling Packages
import LinearGradient from 'react-native-linear-gradient'
import EStyleSheet from 'react-native-extended-stylesheet'
// View Packages
import ViewContainer from './ViewContainer'
// Auth0 Credentials
const clientId = "XXXX"
const domain = "XXXX"
// Props Declaration
type Props = {
client: ApolloClient,
createUser: ({
variables: {
idToken: string,
name: ?string,
email: ?string
}
}) => { id: string}
}
class Login extends Component {
_lock: Auth0Lock
props: Props
constructor(props) {
super(props)
this._lock = new Auth0Lock({
clientId: clientId,
domain: domain,
useBrowser: true
})
}
_showLogin() {
this._lock.show({
closable: true,
connections: ['Username-Password-Authentication']
}, async (err, profile, token) => {
if (!err) {
AsyncStorage.setItem('token', token.idToken)
.then(() => {
this.props.client.resetStore()
})
this.props.createUser({
variables: {
idToken: token.idToken,
name: profile.name
email: profile.email
}
})
.then((data) => {
console.log("Data received: " + data)
Actions.registration({ type: ActionConst.REPLACE })
})
.catch((err) => {
console.log("Error caught: " + err)
AsyncStorage.removeItem('token')
.then(() => this.props.client.resetStore())
// Actions.home({ type: ActionConst.REPLACE })
})
} else {
console.log(err)
}
})
}
render() {
return(
<LinearGradient colors={['#34E89E', '#0F3443']} style={styles.linearGradient}>
<ViewContainer style={styles.viewContainer}>
<Button
small
raised
buttonStyle= {styles.button}
color="#fff"
title="Login"
onPress={() => this._showLogin()} />
</ViewContainer>
</LinearGradient>
)
}
}
const styles = EStyleSheet.create({
viewContainer: {
flex: 1,
paddingTop: 70,
paddingLeft: 20,
paddingRight: 20
},
linearGradient: {
height: '$height',
},
logo: {
fontFamily: 'LiberatorHeavy',
fontSize: 52,
alignSelf: 'center',
marginBottom: 400,
color: 'white',
backgroundColor: 'transparent'
},
button: {
backgroundColor: '#222222'
}
});
const createUserMutation = gql`
mutation createUser($idToken: String!, $name: String, $email: String) {
createUser(
authProvider: {
auth0: {
idToken: $idToken
}
},
name: $name,
email: $email
){
id
}
}`
export default withApollo(
graphql(createUserMutation,
{ name: 'createUser' }
)(Login))
So the error was that the
createUser({variables...})
function was firing twice which resulted in the error being thrown once the variables were cleared. I changed my code to:
_showLogin() {
this._lock.show({
closable: true,
connections: ['Username-Password-Authentication']
}, async (err, profile, token) => {
if (!err) {
AsyncStorage.setItem('token', token.idToken)
.then(() => {
this.props.client.resetStore()
// Create user function was put in to AsyncStorage function to stop function from running twice. (Not sure why that was happening)
this.props.createUser({
variables: {
idToken: token.idToken,
name: profile.name,
email: profile.email
}
})
.then((data) => {
console.log("Data received: " + data)
Actions.registration({ type: ActionConst.REPLACE })
})
.catch((err) => {
console.log("Error caught: " + err)
AsyncStorage.removeItem('token')
.then(() => this.props.client.resetStore())
// Actions.home({ type: ActionConst.REPLACE })
})
})
} else {
console.log(err)
}
This solved the problem and I was getting the proper response, although I'm still not sure why it was running twice after the Async function.
If anyone has any ideas, please let me know!

Categories

Resources