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()}
Related
I am simply trying to connect() my LoginPage (component) to my Redux Store and dispatch in action via a onClick (event). When I console.log(this.props) my dispatch handler login() isn't in the component's props.
GitHub Repo -- https://github.com/jdavis-software/demo.git
Question: Why isn't my Redux Store either connection or dispatching the actions?
LoginPage:
import React, { Component} from 'react';
import { connect } from 'react-redux';
export class LoginPage extends Component<any> {
render(){
console.log('props doesnt have contain - login(): ', this.props)
return (<button onClick={ () => '' }>Login</button>)
}
}
const mapProps = state => ({ user: state.user })
const dispatchProps = (dispatch) => {
return {
login: () => dispatch({ type: 'USER_LOGGED_IN', payload: true})
}
}
export default connect(mapProps,dispatchProps)(LoginPage)
Redux Configuration:
import { IStore, IUser } from '#interfaces';
import { createStore, combineReducers } from 'redux';
import ReduxPromise from 'redux-promise';
// reducers
import userReducer from './user.reducer';
// define the intial global store state
const initialState:IStore = {
user: {
isAuthenticated: false
}
}
const appReducer = combineReducers({user: userReducer})
export default createStore(appReducer,initialState);
User Reducer:
// initial state
const initalState:IUser = {
isAuthenticated: false
}
// reducer
const userReducer = (state:IUser = initalState, { type, payload}: IPayload): IUser => {
console.log('user reducer start', state)
switch (type) {
case 'USER_LOGGED_IN':
state = { ...state, isAuthenticated: payload }
break;
default:
return state;
}
return state;
};
export default userReducer;
Root Page:
import React from 'react';
import { render } from 'react-dom';
import { Provider } from 'react-redux';
// styles
import './index.scss';
// pages
import { App } from '#pages';
// store
import store from './core/store/store';
render(
<Provider store={store}>
<App/>
</Provider>, document.getElementById('app')
);
I checked your code on git repository. I found out that you're exporting the named export
export class LoginPage
and the default export,
export default connect(mapProps,dispatchProps)(LoginPage)
But when you're accessing it, you're accessing it as
import { /*Other components*/ , LoginPage } from '#pages'
So it is actually taking the named exported component which is not connected to store.
I suggest you to import as
import LoginPage , { /*Other components*/ } from '#pages'
This might solve your problem.
Return statements are missing in the properties of connect.
const mapProps = state => { return {user: state.user} }
const dispatchProps = (dispatch) => {
return {
login: () => dispatch({ type: 'USER_LOGGED_IN', payload: true})
}
}
export default connect(mapProps,dispatchProps)(LoginPage)
Updated:
Please check Redux-dispatch
try:
import React, { Component} from 'react';
import { connect } from 'react-redux';
export class LoginPage extends Component<any> {
render(){
console.log('props doesnt contain - login(): ', this.props)
return (
<button onClick={ this.props.login }>Login</button>
)
}
}
const mapProps = state => ({ user: state.user })
const dispatchProps = (dispatch) => ({
login: () => dispatch({ type: 'USER_LOGGED_IN', payload: true})
})
export default connect(mapProps,dispatchProps)(LoginPage)
to return an object with Arrow Functions you need to wrap your {} with ()
I am new to redux and I am stuck in an error, while dispatching an action I am getting error
"Actions must be plain objects. Use custom middleware for async actions.", I have checked the flow but can't see any problem,here's below code"
My JS container File:
import React from 'react'
import {Redirect} from 'react-router-dom'
import * as actions from './../store/actions/index'
import { connect } from 'react-redux'
class home extends React.Component{
constructor(props){
super(props);
this.state={
isClk:false
}
}
performLogout = (evt)=> {
evt.preventDefault();
this.props.onLogout();
this.setState({isClk: true})
};
render(){
let redirect=null;
if (this.state.isClk){
redirect=<Redirect to="/login"/>
}
return(
<div>
{redirect}
<h1>In Home</h1>
<button onClick={this.performLogout}>Logout</button>
</div>
)
}
}
const mapDispatchToProps = dispatch =>{
return {
onLogout: () => dispatch(actions.logout())
}
};
export default connect(null,mapDispatchToProps)(home)
Index.js:
export {
auth,
logout
} from './auth'
Actions(auth.js):
export const logout =()=>{
return(
actionTypes.AUTH_LOGOUT
)
};
Reducers:
const authLogout=(state,action)=>{
return updateObject(state,{
token:null,
loading:false,
error:null
})
};
const reducer=(state=initialState,action)=>{
switch(action.type){
case actionTypes.AUTH_FAIL: return authFail(state,action);
case actionTypes.AUTH_LOGOUT: return authLogout(state,action);
default:
return state
}
};
Store:
import {Provider} from 'react-redux'
import { createStore, applyMiddleware, compose, combineReducers } from 'redux'
import {BrowserRouter} from "react-router-dom";
import authReducer from './Containers/store/reducers/auth'
import thunk from 'redux-thunk';
const composeEnhancers = compose;
const RootReducer=combineReducers({
auth:authReducer
});
const store=createStore(RootReducer,composeEnhancers(applyMiddleware(thunk)));
const app = (
<Provider store={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>
);
ReactDOM.render(app, document.getElementById('root'));
I want to perform logout action when user click on logout button,I can get where the problem, Is my store is properly initialized or any problem in thunk? or anyother maybe while dispatching, kindly guide?
Your action creator should return an object with a type, currently you're just returning the string constant only.
// Actions(auth.js):
export const logout =()=>{
return {
type: actionTypes.AUTH_LOGOUT,
};
};
The actions in Redux needs to be the plain object, so you need to add an object like below
export const logout = () => ({
type: actionTypes.AUTH_LOGOUT
});
I encountered this problem when I was testing my newly created action and reducer. The prop is not being updated even though I'm setting it to a fixed value within my reducer.
Component:
class <ComponentName> extends Component {
componentDidMount() {
login()
}
render() {
if(this.props.isLogged)
return (
<App/>
);
else
return (
<ErrorScreen/>
);
}
}
function mapStateToProps(state) {
return {
isLogged:state.auth.isLogged
}
}
const mapDispatchToProps = (dispatch) => {
return {
login: () => dispatch(login())
};
};
export default connect(mapStateToProps,mapDispatchToProps)(<ComponentName>)
Action:
export function login() {
return {
type:"TEST"
}
}
Reducer:
const initState = {
isLogged: false,
}
export default (state=initState, action) => {
switch(action.type) {
case "TEST":
return {
...state,
isLogged: true
}
break;
default:
return state
}
}
Combine Reducer:
import {combineReducers} from 'redux'
import AuthenticationReducer from './authenticationReducer'
export default combineReducers({
auth: AuthenticationReducer
})
Provider:
import React, {Component} from "react";
import <ComponentName> from './app/screens/<ComponentName>'
import store from './app/store'
import {Provider} from 'react-redux'
export default () =>
<Provider store={store}>
<<ComponentName>/>
</Provider>;
Been trying to debug this for some time now. I still don't know why this is happening. Maybe I implemented it wrongly? If there are some files I forgot to include, please inform me. Thanks and have a nice day!
The reason your code isn't working as expected is because you're calling the login() action creator, rather than the login() method that is returned from mapDispatchToProps() (and injected into the props of <ComponentName/>).
Try revising your code by adding this.props before your call to login() like so:
class <ComponentName> extends Component {
componentDidMount() {
// Update this line here so that the login() method
// injected by connect() is called (ie via this.props)
this.props.login()
}
render() {
if(this.props.isLogged)
return <App/>
else
return <ErrorScreen/>
}
}
I've got a simple component that calls an action when a user loads a page, and inside that action, I'm trying to dispatch another action to set the loggedIn state of the store to true or false:
import React, { Component } from 'react'
import { Link, browserHistory } from 'react-router'
import $ from 'jquery'
class Login extends Component {
constructor(props) {
super(props)
}
componentDidMount() {
this.props.actions.guestLoginRequest()
}
render() {
return (
<div>
<div classNameName="container">
<div className="row">
We are signing you in as a guest
</div>
</div>
</div>
)
}
}
export default Login
I can get the login information when the guestLoginRequest action is called, but when I try to dispatch another action inside of it, nothing happens:
guestLoginRequest: function(){
var ref = new Firebase("https://penguinradio.firebaseio.com");
ref.authAnonymously(function(error, authData) {
if (error) {
console.log("Login Failed!", error);
} else {
console.log("Authenticated successfully with payload:", authData);
return dispatch => {
dispatch(actions.setLoginStatus(true, authData))
console.log("dispatched");
};
}
});
}
I get an error of Uncaught ReferenceError: dispatch is not defined when I remove the return dispatch => { } statement. In my store I am using redux-thunk, so I can dispatch inside of actions:
// Store.js
import { applyMiddleware, compose, createStore } from 'redux'
import rootReducer from './reducers'
import logger from 'redux-logger'
import thunk from 'redux-thunk'
let finalCreateStore = compose(
applyMiddleware(thunk, logger())
)(createStore)
export default function configureStore(initialState = { loggedIn: false }) {
return finalCreateStore(rootReducer, initialState)
}
I am mapping the dispatch to props in my app.js as well:
function mapStateToProps(state) {
return state
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(actions, dispatch)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(App)
Just in case it could be helpful, here is my client.js and reducer files:
// client.js
import React from 'react'
import { render } from 'react-dom'
import App from '../components/App'
import configureStore from '../redux/store'
import { Provider } from 'react-redux'
let initialState = {
loggedIn: false
}
let store = configureStore(initialState)
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('app')
)
// Reducer.js
import { combineReducers } from 'redux'
let LoginStatusReducer = function reducer(loggedIn = false, action) {
switch (action.type) {
case 'UPDATE_LOGIN_STATUS':
return loggedIn = action.boolean
default:
return loggedIn
}
}
export default LoginStatusReducer
const rootReducer = combineReducers({
loggedIn: LoginStatusReducer
})
export default rootReducer
Any ideas why my dispatch function isn't working? I'm confused since I did set up redux-thunk with my store, and I'm using code similar to the docs when I call return dispatch => { }. Is there something I'm missing? Thank you in advance for any advice!
You need your action to return a function to utilize the thunk middleware, then redux will inject the dispatcher into it. You mixed your dispatcher invocation with the implementation detail. The following snippet fixes both defects.
guestLoginRequest: function(){
return function (dispatch) {
var ref = new Firebase("https://penguinradio.firebaseio.com");
ref.authAnonymously(function(error, authData) {
if (error) {
console.log("Login Failed!", error);
} else {
console.log("Authenticated successfully with payload:", authData);
dispatch(actions.setLoginStatus(true, authData))
console.log("dispatched");
}
});
}
}
In addition, you need to dispatch your action correctly on the Login class.
dispatch(this.props.actions.guestLoginRequest())
Your action invocation is always done by invoking dispatch. The flow should be something like this:
React component --> dispatch ---> API call (thunk middleware) --> dispatch ---> reducer
Make sure useDispatch imported
import { useDispatch } from "react-redux";
First, you need to import
import { useDispatch } from "react-redux";
then call it.
const dispatch = useDispatch();
now you are ready for use.
I'm a beginner with React & Redux and I'm trying to set up a very simple login form & redirection.
I'll add react-router or react-router-redux later.
I don't really understand where i have to put my 'logic code' (an ajax call and a redirection).
Here is what I've write.
index.js (entry point) :
import React from 'react'
import { render } from 'react-dom'
import { createStore } from 'redux'
import { Provider } from 'react-redux'
import App from './containers/App'
import rootReducer from './reducers/reducers'
let store = createStore(rootReducer);
let rootElement = document.getElementById('root');
render(
<Provider store={store}>
<App />
</Provider>,
rootElement
);
containers/App.js :
import { Component } from 'react'
import { connect } from 'react-redux'
import { login } from '../actions/actions'
import LoginForm from '../components/LoginForm'
class App extends Component {
render () {
const { dispatch } = this.props;
return (
<div>
<LoginForm onSubmit={(id, pass) =>
dispatch(login(id, pass))
} />
</div>
)
}
}
const mapStateToProps = (state) => {
return {
}
};
const mapDispatchToProps = (dispatch) => {
return {
}
};
export default connect(mapStateToProps)(App);
components/LoginForm.js :
import { Component, PropTypes } from 'react'
class LoginForm extends Component {
render () {
return (
<div>
<form action="#" onSubmit={(e) => this.handleSubmit(e)}>
<input type="text" ref={node => { this.login = node }} />
<input type="password" ref={node => { this.password = node }} />
<input type="submit" value="Login" />
</form>
</div>
)
}
handleSubmit(e) {
e.preventDefault();
this.props.onSubmit(this.login.value, this.password.value);
}
}
LoginForm.propTypes = {
onSubmit: PropTypes.func.isRequired
};
export default LoginForm;
reducers/root.js :
import { combineReducers } from 'redux'
import user from './user'
const rootReducer = combineReducers({
user
});
export default rootReducer;
reducers/user.js :
import { LOGIN, BAD_LOGIN, LOGOUT } from '../actions/actions'
const initialState = {
cid: null,
username: '',
logo: ''
};
const user = (state = initialState, action) => {
switch (action.type) {
case LOGIN:
const api = new loginApi; //simple version
api.login(action.login, action.password)
.done(res => {
//Right here ?
})
.fail(err => console.error(err));
return state;
case LOGOUT:
//...
return state;
default:
return state;
}
};
export default user;
actions/actions.js :
export const LOGIN = 'LOGIN';
export const LOGOUT = 'LOGOUT';
export function login(login, password) {
return {
type: LOGIN,
login,
password
}
}
following this link : http://redux.js.org/docs/advanced/AsyncActions.html
I hesitate between write my login stuff inside the reducer (but I think reducer's purpose is just to reduce the state object) or to create multiple actions with one 'main' action which call REQUEST_LOGIN and LOGIN_SUCCES / LOGIN_FAILURE for example.
Thanks.
You are correct, reducers are only for mapping data to the state. Create your async logic in the action creator. The key is to use a store enhancer to make async actions possible.
redux-thunk
redux-promise
A tutorial on async redux can be found in the redux documentation.