I'm new to React and Firebase and I'm trying get the user object that's populated after sign in/sign up to a new component so I can access it and create a database for each user, however I can't seem to access the this.state.user from another component. Here's my code:
import React, { Component } from 'react';
import fire from './config/Fire';
import Login from './Login';
import Home from './Home';
class App extends Component {
constructor(props){
super(props);
this.state = {
user: {},
}
}
// After this component renders, we will call on auth lister which will begin auth listener process
componentDidMount(){
this.authListener();
}
authListener() {
fire.auth().onAuthStateChanged((user) => {
console.log(user);
if (user) {
this.setState({ user });
} else {
this.setState({ user: null });
}
});
}
render() {
return (
<div className="App">
{this.state.user ? (<Home user={this.state.user}/>) : (<Login/>)}
</div>
);
}
}
export default App;
and in my new component i have:
componentDidMount(){
fire.auth().onAuthStateChanged((user) => {
if (this.props.user){
console.log(this.props.user);
// const userId = user.uid;
// fire.database().ref(`users/${userId}`).set({
// username: '',
// age: ''
// });
} else {
console.log('No user');
}
});
}
You are listening for the user in componentDidMount of the App component, then you are passing that data into the home component in <Home user={this.state.user}/>
This means that you can refrence the user object inside the Home component as this.props.user.
You don't need to call the onAuthStateChanged handler again inside Home, you already did that in the App Component.
Hope that helps.
Related
I want my child to rerender when my parent updates its state. Seems simple but I haven't gotten it to work.
In App.js i make the API call in componentWillMount. I then want "PrivateRoute" to update the state "auth" but it seems like componentDidUpdate never runs and I always get rediercted.
The purpose of the code is that the API call checks if the user has a valid authentication token and then sets the "isLoggedIn" state to true or false. This should make the child "PrivateRoute" either redirect (if isLoggedIn is false) or render another page (if isLoggedIn is true).
I have checked out a lot of other similar questions but no solution has worked this far.
My app.js:
import React, { Component } from "react";
import {
BrowserRouter as Router,
Route,
Switch,
Link,
Redirect,
} from "react-router-dom";
import axios from "axios";
import PrivateRoute from "./components/PrivateRoute";
// Pages
import IndexPage from "./pages/index";
import HomePage from "./pages/home";
class App extends Component {
constructor() {
super();
console.log("Constructor");
this.state = {
loggedInStatus: false,
test: "TEST",
};
}
// Checks if user is logged in
checkAuth() {
let token = localStorage.getItem("token");
let isLoggedIn = false;
if (token === null) token = "bad";
console.log("Making API call");
// API call
axios
.get("http://localhost:8000/authentication/checkAuth", {
headers: { Authorization: "token " + token },
})
.then((res) => {
console.log("Updateing state");
this.setState({ loggedInStatus: true });
})
.catch((error) => {
console.log("Updating state");
this.setState({ loggedInStatus: false });
});
return isLoggedIn;
}
componentWillMount() {
this.checkAuth();
}
render() {
//console.log("Render");
// console.log("isLoggedIn: ", this.state.loggedInStatus);
return (
<Router>
<Switch>
<PrivateRoute
exact
path="/home"
component={HomePage}
auth={this.state.loggedInStatus}
/>
<Route exact path="/" component={IndexPage} />
</Switch>
</Router>
);
}
}
export default App;
PrivateRoute.jsx:
import { Redirect } from "react-router-dom";
import React, { Component } from "react";
class PrivateRoute extends Component {
state = {};
constructor(props) {
super(props);
this.state.auth = false;
}
// Update child if parent state is updated
componentDidUpdate(prevProps) {
console.log("Component did update");
if (this.props.auth !== prevProps.auth) {
console.log("Child component update");
this.setState({ auth: this.props.auth ? true : false });
}
}
render() {
console.log("Props: ", this.props);
console.log("State: ", this.state);
//alert("this.props.auth: ", this.props.auth);
//alert("TEST: ", this.props.test);
if (this.props.auth) {
return <h1>Success!</h1>;
//return <Component {...this.props} />;
} else {
return (
<Redirect
to={{ pathname: "/", state: { from: this.props.location } }}
/>
);
}
}
}
export default PrivateRoute;
checkAuth should be sync, if you want auth status before first-ever render in the component.
Your checkAuth will return instantly, making the auth status always false.
async checkAuth() {
try {
let token = localStorage.getItem("token");
let isLoggedIn = false;
if (token === null) token = "bad";
const res = await axios
.get("http://localhost:8000/authentication/checkAuth", {
headers: {Authorization: "token " + token},
})
console.log("Updateing state");
this.setState({loggedInStatus: true});
} catch (e) {
// for non 2XX axios will throw error
console.log("Updating state");
this.setState({loggedInStatus: false});
}
}
async componentWillMount() {
await this.checkAuth();
}
In the child component, you have to set state from props.
constructor(props) {
super(props);
this.state.auth = props.auth;
}
I'm now at React and I'm doing some apps to study, learn more about. Aand right now I'm trying to add the logged user info to redux state, but when I try to check the value of this.props.user my app always returns undefined.
My reducer.js
import { LOG_USER } from '../actions/actions';
let initialState = {
user: {
userName: '',
imageUrl: ''
}
}
const userInfo = (state = initialState, action) => {
switch (action.type) {
case LOG_USER:
return {
...state,
user: action.user
};
default:
return state;
}
}
const reducers = userInfo;
export default reducers;
My actions.js
export const LOG_USER = 'LOG_USER';
My SignupGoogle.js component:
import React, { Component } from 'react';
import Button from '#material-ui/core/Button';
import firebase from '../../config/firebase';
import { connect } from 'react-redux'
import { LOG_USER } from '../../actions/actions';
import './SignupGoogle.css'
class SignupGoogle extends Component {
constructor(props) {
super(props);
}
signup() {
let provider = new firebase.auth.GoogleAuthProvider();
firebase.auth().signInWithPopup(provider).then(function(result) {
console.log('---------------------- USER before login')
console.log(this.props.user)
let user = {
userName: result.user.providerData[0].displayName,
imageUrl: result.user.providerData[0].photoURL
}
console.log(user)
this.props.logUser(user)
console.log('---------------------- USER after login')
console.log(this.props.user)
}).catch((error) => {
console.log(error.code)
console.log(error.message)
console.log(error.email)
})
}
render() {
return (
<Button onClick={this.signup} variant="contained" className="btn-google">
Sign Up with Google
<img className='imgGoogle' alt={"google-logo"} src={require("../../assets/img/search.png")} />
</Button>
);
}
}
const mapStateToProps = state => {
return {
user: state.user
};
}
const mapDispatchToProps = dispatch => {
return {
logUser: (user) => dispatch({type: LOG_USER, user: user})
};
}
export default connect(mapStateToProps, mapDispatchToProps)(SignupGoogle);
And my index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import { BrowserRouter as Router } from 'react-router-dom';
import { Provider } from 'react-redux'
import { createStore } from 'redux';
import reducers from './reducers/reducers';
const store = createStore(reducers)
ReactDOM.render(
<Provider store={store}>
<Router>
<App />
</Router>
</Provider>,
document.getElementById('root')
);
serviceWorker.unregister();
This is what I can get at my browser log after login with Google firebase:
That's because you're onClick handler method is not bound to the instance of the component, modify your constructor like this and your props should no longer return undefined:
constructor(props) {
super(props);
this.signup = this.signup.bind(this);
}
Alternatively you could also modify your onClick method to look like this:
<Button onClick={() => this.signup()} variant="contained" className="btn-google">
or turn your onClick handler method into an arrow function:
signup = () => {
// ...
}
...
<Button onClick={this.signup} variant="contained" className="btn-google">
but the first option using bind is the preferred one.
Refer to the docs for more information on event handling.
EDIT:
I missed that there was another callback function involved.
You're accessing this.props from within another function in the signInWithPopup-callback. Change your callback to an arrow function, which should preserve the context of the signup method and fix your issue:
firebase.auth().signInWithPopup(provider).then(result => {
// ...
}).catch(error => {
// ...
});
It's all about context. Since your signup function is bound to the onclick event, the this context is the <button>.
You can either in the constructor set the this context:
constructor(props) {
super(props);
this.signup = this.signup.bind(this);
}
or use arrow syntax:
signup = () => {
}
React documentation has a good answer for event binding here: https://reactjs.org/docs/handling-events.html
Your signup definition is fine, but you can just wrap it in an arrow function that has the proper 'this' value.
onClick={()=>signup()}
My ReactJS app get Firebase database asynchronously, but I want to load a database first and then render app with database. How can I wait or re-render app when database loads?
import React from 'react';
import * as firebase from "firebase/app";
import {config} from './dbinit'
import "firebase/database";
import Home from './panels/Home';
class App extends React.Component {
constructor(props) {
super(props);
this.setState = this.setState.bind(this);
this.state = {
activePanel: 'home',
db: null,
loading: true,
};
console.log("init");
}
componentDidMount = () => {
var self = this;
let ref = firebase
.initializeApp(config)
.database()
.ref();
ref.once("value").then(res => {
// Async request to Firebase
console.log("success", res.val())
if (res.val() != undefined){
// If response not undefined then load it to 'state'
this.setState({db: res.val(), loading: false})
}
});
console.log("after")
}
go = (e) => {
this.setState({ activePanel: e.currentTarget.dataset.to })
};
render() {
const { loading, db } = this.state;
return loading ? (
<div>loading...</div>
) : (
<View activePanel={this.state.activePanel}>
<Home id="home" fetchedUser={this.state.fetchedUser} go={this.go} db = {this.db}/>
</View>
);
}
}
export default App;
In render() in 'Home' I pass 'this.db' and trying to get it on 'Home' but there it's undefined.
In your line for rendering your table, you don't need this.db just simply db
<Home id="home" fetchedUser={this.state.fetchedUser} go={this.go} db = {this.db}/>
should just be
<Home id="home" fetchedUser={this.state.fetchedUser} go={this.go} db = {db}/>
I have a react-native app with top level App.js that is:
import { createStackNavigator } from 'react-navigation';
class App extends Component {
render() {
return <AppStack {..this.props} />
}
}
export withAuth(App)
Where the higher order component withAuth adds user and authenticating props to App:
const withAuth = (Component) =>
class WithAuth extends React.Component {
constructor(props) {
super(props)
this.state = {
authenticating: true,
user: false
}
}
componentDidMount() {
// authenticating logic ...
firebase.auth().onAuthStateChanged(user => {
if (user)
this.setState({
authenticating: false,
user: user
})
else
this.setState({
authenticating: false
})
})
}
render() {
return (<Component user={this.state.user} authenticating={this.state.authenticating} {...this.props} />)
}
}
And the AppStack is:
const AppStack = createStackNavigator(
{ AppContainer, Profile },
{ initialRouteName : 'AppContainer' }
)
Note there is no code passing the props that was passed down from class App down to AppContainer and Profile.
Consequentially the user and authenticating props inside AppContainer is undefined.
class AppContainer extends Component {
// this.props.user is false and this.props.authenticating is true
componentDidMount() {
console.log(`Debug Did mount with user: ${this.props.user}`, this.props.authenticating)
}
render() {
// if I `export withAuth(AppContainer)`, then this prints the user information. but not if I log inside `componentDidMount`
console.log(`Debug loaded with user: ${this.props.user}`, this.props.authenticating)
return <Text>'hello world'</Text>
}
}
export default AppContainer
I could wrap AppContainer inside withAuth and do export default withAuth(AppContainer), but then AppContainer cannot read this.props.user property in componentDidMount, only in render() { ... }.
Ideally I would like to pass down the user and authenticating property from AppStack, how would I do so?
Note: for now I would like to not use redux if possible. This is a simple app.
You can pass props from your high order component to child screen through screenProps. Example:
const withTest = Component => (
class WithTest extends React.Component {
render() {
return (<Component screenProps={{ user: "abc" }} />);
}
});
Check Navigator Props for more detail.
I've spent about 1 week reading up on redux before plunging into anything sizeable. After completing most of the tutorials I've done I realised, ok I understand redux but how the hell do I make a complex system :P
I started going about by creating my systems actions:
function requestLogin(creds) {
return {
type: LOGIN_REQUEST,
isFetching: true,
isAuthenticated: false,
creds
}
}
function receiveLogin(user) {
return {
type: LOGIN_SUCCESS,
isFetching: false,
isAuthenticated: true,
id_token: user.id_token
}
}
function loginError(message) {
return {
type: LOGIN_FAILURE,
isFetching: false,
isAuthenticated: false,
message
}
}
But how can I with each router used (using react-router) check to see if the user has a session after storing the users logged in state in the redux state?
I wanted to create something that gets executed with each view. Just simply write a function that exec()'s in each view?
Yes, you create a function that executes whenever you go to a route that requires a login.
import LoginActions from '../loginActions';
const requireLogin = (nextState, replace) => {
store.dispatch(LoginActions.CheckLogin());
const {login} = store.getState();
if (!login.isAuthenticated)
replace('/login');
};
Call it in your router:
<Router component={Root}>
<Route path="/dashboard" onEnter={requireLogin} component={dashboard}/>
</Router>
You can implement auth filter for paths requiring the user to be authenticated using Higher Order Components.
Create wrapping component
import React from 'react';
import { connect } from 'react-redux';
export default function(ComposedComponent) {
class AuthFilter extends React.Component {
// triggered when component is about to added
componentWillMount() {
if (!this.props.userAuthenticated) {
console.log("navigate to login");
this.context.router.push('/login');
}
}
// before component updated
componentWillUpdate(nextProps) {
if (!nextProps.userAuthenticated) {
console.log("navigate to login");
this.context.router.push('/login');
}
}
render() {
return <ComposedComponent {...this.props} />
}
}
AuthFilter.contextTypes = {
router: React.PropTypes.func.isRequired
}
function mapStateToProps(state) {
return { userAuthenticated: state.authenticated };
}
return connect(mapStateToProps)(AuthFilter);
}
And then add wrapping to your route component as:
Route path="/asset" component={AuthFilter(AssetRoute)}/>