React & Redux : simple async login - javascript

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.

Related

Redux - Dispatching Action (onClick Event)

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

How do i access redux state from another react component?

I am developing a lottery statistics app that gets data from a csv loaded from an input then I was wanting to read this data to the redux store so I can use it across multiple components.
I have successfully saved the data to the redux store once I import the file and read it through Header.js and using an action, but I am not sure how to access this in other components like e.g. Main.js.
I feel like I am still confused on how react/redux all fits together. I'm sorry if this has been asked before but everything I looked up online I couldn't get to work.
// index.js
import React from "react";
import ReactDOM from "react-dom";
import { createStore, applyMiddleware } from "redux";
import { Provider } from "react-redux";
import thunk from "redux-thunk";
import reducers from "./reducers";
import App from "./components/App";
const store = createStore(reducers, applyMiddleware(thunk));
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.querySelector("#root")
);
// App.js
import React from "react";
import Header from "./Header";
import Main from "./Main";
const App = () => {
return (
<div>
<Header />
<Main />
<div className="numbers-for-draw"></div>
</div>
);
};
export default App;
// Header.js
import React from "react";
import { CSVReader } from "react-papaparse";
import { fetchData } from "../actions";
import { connect } from "react-redux";
class Header extends React.Component {
constructor(props) {
super(props);
this.fileInput = React.createRef();
}
handleReadCSV = data => {
this.props.fetchData(data);
console.log(this.props.data);
};
handleOnError = (err, file, inputElem, reason) => {
console.log(err);
};
handleImportOffer = () => {
this.fileInput.current.click();
console.log("Got to handleImportOffer");
};
render() {
return (
<header>
<CSVReader
onFileLoaded={this.handleReadCSV}
inputRef={this.fileInput}
style={{ display: "none" }}
onError={this.handleOnError}
/>
<button onClick={this.handleImportOffer}>Import</button>
</header>
);
}
}
//Map what is in the redux store (e.g. state) to props
const mapStateToProps = state => ({
data: state.data
});
export default connect(mapStateToProps, {
fetchData: fetchData
})(Header);
// Main.js
import React from "react";
import { fetchData } from "../actions";
import { connect } from "react-redux";
const Main = () => {
console.log("In main");
console.log(this.props.data); //Blows up here.
return <div>Main</div>;
};
//Map what is in the redux store (e.g. state) to props
const mapStateToProps = state => ({
data: state.data
});
export default connect(mapStateToProps, {
fetchData: fetchData
})(Main);
// actions/index.js
export const fetchData = data => dispatch => {
console.log("Action");
const lottoData = {
stringNumbers: [
"one",
"two",
"three",
...
],
allResults: [],
winningNumbers: [],
winningNumbersAsStrings: []
};
const localData = data.data;
localData.shift();
localData.forEach(line => {
const lineObject = {
draw: line[0],
drawDate: line[1],
ballOne: line[2],
ballTwo: line[3],
ballThree: line[4],
ballFour: line[5],
ballFive: line[6],
ballSix: line[7],
bonusBall: line[8],
bonusBall2: line[9],
powerBall: line[10]
};
lottoData.allResults.push(lineObject);
let nums = [];
nums.push(parseInt(line[2]));
nums.push(parseInt(line[3]));
nums.push(parseInt(line[4]));
nums.push(parseInt(line[5]));
nums.push(parseInt(line[6]));
nums.push(parseInt(line[7]));
nums.sort((a, b) => {
if (a < b) {
return -1;
} else if (a > b) {
return 1;
} else {
return 0;
}
});
lottoData.winningNumbers.push(nums);
lottoData.winningNumbersAsStrings.push(nums.toString());
});
dispatch({ type: "FETCH_DATA", payload: lottoData });
};
// lottoReducer.js
export default (state = {}, action) => {
switch (action.type) {
case "FETCH_DATA":
return action.payload;
default:
return state;
}
};
// reducers/index.js
import { combineReducers } from "redux";
import lottoReducer from "./lottoReducer";
export default combineReducers({
data: lottoReducer
});
I haven't tested your code, but it seems to me that the only problem is in your Main.js
While you use a function component and not a class, you shouldn't use this to access your props. The following should work as expected:
const Main = (props) => {
console.log("In main");
console.log(props.data);
return <div>Main</div>;
};
//Map what is in the redux store (e.g. state) to props
const mapStateToProps = state => ({
data: state.data
});
export default connect(mapStateToProps, {
fetchData: fetchData
})(Main);
In your main.js you used functional components so this.props doesn't work there. You must pass props to your component and console.log(props.data).

React Redux this.props always return undefined

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()}

Redux Thunk Action Not Firing

I'm trying to get some information about a user and store it in redux when the user logs in. I dispatch an action to the the uid which works fine. Directly after (I've commented out where in the SignIn.js code) I try to dispatch a redux thunk action that gets a list of product images from firebase. For some reason this action isn't dispatching. Not sure why? I export the function then import the function and use connect so I have access to the dispatch method. What am I missing? Thanks!
SignIn.js
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { fbAuth, db } from '~/config/firebase/firebaseConfig';
import { getId, getRetailerData } from '~/redux/modules/retailer';
class SignIn extends Component {
static propTypes = {
dispatch: PropTypes.func.isRequired,
push: PropTypes.func.isRequired
}
constructor(props) {
super(props);
this.state = {
email: '',
password: ''
};
this.handleEmailChange = this.handleEmailChange.bind(this);
this.handlePasswordChange = this.handlePasswordChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleEmailChange (event) {
this.setState({email: event.target.value});
}
handlePasswordChange (event) {
this.setState({password: event.target.value});
}
handleSubmit (event) {
event.preventDefault();
const email = this.state.email;
const password = this.state.password;
// Register user
fbAuth.signInWithEmailAndPassword(email, password)
.catch((error) => {
// Handle Errors here.
var errorCode = error.code;
var errorMessage = error.message;
console.log(errorMessage);
// ...
})
.then(() => {
const user = fbAuth.currentUser;
console.log(user);
this.props.dispatch(getId(user.uid));
// This action below is not being dispatched
this.props.dispatch(getRetailerData());
this.props.push(`/dashboard`);
})
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<div className="form-group">
<label>Email address</label>
<input
type="email"
className="form-control"
placeholder="Enter email"
onChange={this.handleEmailChange}
/>
</div>
<div className="form-group">
<label>Password</label>
<input
type="password"
className="form-control"
placeholder="Password"
onChange={this.handlePasswordChange}
/>
</div>
<button className="btn btn-default" type="submit">Continue</button>
</form>
);
}
}
export default connect()(SignIn);
My reducer
const GET_ID = 'GET_ID';
const GET_PRODUCT_IMAGES = 'GET_PRODUCT_IMAGES';
import { db, fbAuth } from '~/config/firebase/firebaseConfig';
const initialState = {
uid: '',
name: '',
productImages: [],
inventory: {}
}
export function getId (id) {
return {
type: GET_ID,
id
}
}
function getProductImages (images) {
return {
type: GET_PRODUCT_IMAGES,
images
}
}
export function getRetailerData () {
return (dispatch, getState) => {
const user = fbAuth.currentUser;
const productImages = db.ref('users/' + user.uid + '/productImages/');
productImages.on('child_added', (data) => {
console.log(data.val());
dispatch(getProductImages(data.val()))
});
}
}
export default function retailer (state = initialState, action) {
switch (action.type) {
case GET_ID :
return {
...state,
uid: action.id
}
case GET_PRODUCT_IMAGES :
return {
...state,
productImages: action.images
}
default :
return state;
}
}
Lastly I was thinking it could be something with my Index.js?
import React from 'react';
import ReactDOM from 'react-dom';
import { App } from '~/containers';
import { createStore, applyMiddleware, combineReducers, compose } from
'redux';
import { Provider } from 'react-redux';
import thunk from 'redux-thunk';
import './index.css';
import * as reducers from './redux';
const store = createStore(
combineReducers(reducers),
compose(
applyMiddleware(thunk),
window.devToolsExtension ? window.devToolsExtension() : f => f
)
);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
The way I usually use the connect method with actions is like this.
const mapState = state => ({
propsIWantBoundToReduxStore: store.field,
});
const mapDispatch = {
actionMethod,
//in your case: getRetailerData
};
export default connect(mapState, mapDispatch)(MyComponent);
Then you could call this.props.getRetailerData() to fire off the action.
my getRetailerData method would look something like this:
export function getRetailerData() {
return {
type: 'GET_RETAILER_DATA'
};
}

React + Redux, component does not get update while the state is changing

Hi All I am new to redux. I am creating a sample app as below:
entry point: index.js
import 'babel-polyfill'
import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import inboundApp from './reducers'
import App from './components/App'
let store = createStore(inboundApp)
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('app')
)
/components/App.js
import React from 'react'
import HeaderContainer from '../containers/HeaderContainer'
import LoginForm from '../containers/LoginForm'
const App = () => (
<div>
<HeaderContainer />
<LoginForm />
</div>
)
export default App
/containers/LoginForm.js
import React from 'react'
import { connect } from 'react-redux'
import { login } from '../actions'
let LoginForm = ({ dispatch }) => {
let input
return (
<div>
<form onSubmit={e => {
e.preventDefault()
if (!input.value.trim()) {
return
}
dispatch(login(input.value))
input.value = ''
}}>
<input ref={node => {
input = node
}} />
<button type="submit">
Login
</button>
</form>
</div>
)
}
LoginForm = connect()(LoginForm)
export default LoginForm
/actions/index.js
export const login = (supplierId) => {
return {
type: 'LOGIN',
supplierId
}
}
/containers/HeaderContainer.js
import { connect } from 'react-redux'
import Header from '../components/Header'
const mapStateToProps = (state) => {
return {
supplierId: state.supplierId
}
}
const HeaderContainer = connect(
mapStateToProps,
null
)(Header)
export default HeaderContainer
/components/Header.js
import React, { PropTypes } from 'react'
const Header = ({ supplierId}) => {
return (
<div>
<span>Your Supplier ID: </span> {supplierId}
</div>
)
}
export default Header
/reducers/loginForm.js
const loginForm = (state = '', action) => {
switch (action.type) {
case 'LOGIN':
return Object.assign({}, state, {
supplierId: action.supplierId
})
default:
return state;
}
}
export default loginForm
/reducers/index.js
import { combineReducers } from 'redux'
import loginForm from './loginForm'
const inboundApp = combineReducers({
loginForm
})
export default inboundApp
The problem is my presentation component Header does not get update by the action LOGIN which is firing by click on the button in the LoginForm.js.
would you please help me to find what am I missing? what's wrong with this code?
thanks
I think you try to get the supplierId, from the wrong namespace and your default state of loginForm is not good. Try like that:
const loginForm = (state = {supplierId: ''}, action) => {
switch (action.type) {
case 'LOGIN':
return Object.assign({}, state, {
supplierId: action.supplierId
})
default:
return state;
}
}
And the connect
const mapStateToProps = (state) => {
return {
supplierId: state.loginForm.supplierId
}
}

Categories

Resources