I have been trying to learn to This is a simple login, logout app using Redux. On pressing the Login Button from Display.js, the login_action function should be called. But an error is showing with title Cannot read property login_action of undefined. I tried logging the props in Display.js and I am able to see the functions in the logs but somehow the functions aren't being called. What is it that I'm missing or unable to find out?
Basic App :
/* App.js */
import React, {Component} from 'react';
import { createStore, applyMiddleware, combineReducers } from 'redux';
import { Provider } from 'react-redux';
import thunk from 'redux-thunk';
import * as reducers from './reducers';
import SceneContainer from './containers/SceneContainer';
const createStoreWithMiddleware = applyMiddleware(thunk)(createStore);
const reducer = combineReducers(reducers);
const store = createStoreWithMiddleware(reducer);
export default class App extends Component {
render() {
return (
<Provider store={store}>
<SceneContainer />
</Provider>
);
}
}
Container:
/* containers/SceneContainer.js */
'use strict';
import React, {Component, PropTypes} from 'react';
import Display from '../components/display';
import * as loginActions from '../actions/';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
class SceneContainer extends Component {
constructor(props) {
super(props);
}
render() {
const {actions} = this.props;
console.log(actions);
return (
<Display {...actions}/>
);
}
}
SceneContainer.propTypes = {
user: PropTypes.object.isRequired,
actions: PropTypes.object.isRequired
};
function mapStateToProps(state) {
return {user: state.auth.user};
}
function mapDispatchToProps(dispatch) {
console.log(loginActions);
return {
actions: bindActionCreators(loginActions, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(SceneContainer);
Component :
/* components/display.js */
import React, {Component, PropTypes} from 'react';
import {
View,
Text,
StyleSheet,
TouchableHighlight,
} from 'react-native';
class Display extends Component {
constructor(props) {
super(props);
console.log(props.login_action);
}
onLoginPress() {
this.props.login_action({
username: 'ranat',
password: 'password'
});
}
onLogoutPress() {
this.props.logout_action();
}
render() {
return (
<View>
<TouchableHighlight onPress={this.onLoginPress}>
<Text>Login</Text>
</TouchableHighlight>
<TouchableHighlight onPress={this.onLogoutPress}>
<Text>Logout</Text>
</TouchableHighlight>
</View>
);
}
}
Display.propTypes = {
logout_action: PropTypes.func.isRequired,
login_action: PropTypes.func.isRequired
};
export default Display;
Actions file :
/* actions/index.js */
import {LOGIN_ACTION, LOGOUT_ACTION, LOGIN_SUCCESS, LOGIN_FAILURE} from './actionTypes';
export var login_action = (userCredentials) => {
if(userCredentials.username === 'ranat' && userCredentials.password === 'password') {
return {
type: LOGIN_ACTION,
value: LOGIN_SUCCESS,
};
}
else {
return {
type: LOGIN_ACTION,
value: LOGIN_FAILURE,
};
}
};
export var logout_action = () => {
return {
type: LOGOUT_ACTION,
}
};
Reducers :
/* reducers/login.js */
import {LOGIN_ACTION, LOGOUT_ACTION, LOGIN_SUCCESS, LOGIN_FAILURE} from '../actions/actionTypes'
let cloneObject = (obj) => {
if(obj)
return JSON.parse(JSON.stringify(obj));
else
return {};
}
const initialState = {
user: {
loggedIn: false,
},
};
const auth = (state = initialState, action = {}) => {
switch(action.type) {
case LOGIN_ACTION: {
if(action.value === LOGIN_SUCCESS) {
return {
...state,
user: {
loggedIn: true
}
};
}else {
return {
...state,
user: {
loggedIn: false
}
};
}
}
case LOGOUT_ACTION: {
if(action.value === LOGIN_SUCCESS) {
return {
...state,
user: {
loggedIn: false
}
};
}else {
return state;
}
}
default: {
return state || initialState;
}
}
}
export default auth;
/* reducers/index.js */
import { combineReducers } from 'redux';
import auth from './login';
export {
auth,
};
Change onPress={this.onLoginPress} to onPress={this.onLoginPress.bind(this}.
Do the same for onPress={this.onLogoutPress} also.
Related
I am trying to combine navigation, more exactly stack navigation with my react native redux application, and I encountered this error during debugging. From what I've already searched, it's because the navigation isn't really compatible with the redux mode of work. So I tried a solution to my problem, but still I have the same error. Here is my code:
Login.js
import React, { Component } from 'react';
import { View, ActivityIndicator, TouchableHighlight } from 'react-native';
import { getLogger, issueToText } from '../core/utils';
import styles from '../core/styles';
import { Card, Button, FormLabel, FormInput } from "react-native-elements";
import { connect } from 'react-redux'
import { loginAction } from '../actions/LoginActions'
class LoginComponent extends Component {
constructor(props) {
super(props);
this.login = this.login.bind(this)
}
render() {
const { error, isLoading } = this.props;
const inputFormProp = {
username: '',
password: ''
};
return (
<View style={{ paddingVertical: 20 }}>
<Card>
<FormLabel>Email</FormLabel>
<FormInput value={inputFormProp.username} onChangeText={(text) => inputFormProp.username = text} />
<FormLabel>Password</FormLabel>
<FormInput value={inputFormProp.password} onChangeText={(text) => inputFormProp.password = text} />
<Button
buttonStyle={{ marginTop: 20 }}
backgroundColor="#03A9F4"
title="SIGN IN"
onPress={this.login(inputFormProp)}
/>
</Card>
<ActivityIndicator animating={this.props.isLoading} style={styles.activityIndicator} size="large" />
</View>
);
}
login(inputFormProp) {
console.log(this.props)
const { store } = this.props.screenProps.store;
console.log(loginAction)
const { dispatch } = this.props
console.log(dispatch)
dispatch(loginAction(inputFormProp))
.then(() => {
if (this.props.error === null && this.props.isLoading === false) {
if (store.getState().auth.token) {
this.props.navigation.navigate('ProductList', { token: store.getState().auth.token });
}
}
})
.catch(error => {
});
}
}
function mapStateToProps(state) {
const { error, isLoading } = state.auth
return {
error,
isLoading,
}
}
export default connect(mapStateToProps)(LoginComponent)
App.js
import React, { Component } from 'react';
import { createStore, applyMiddleware, combineReducers, compose } from 'redux';
import { createLogger } from 'redux-logger';
import thunk from 'redux-thunk';
import { authReducer } from "./src/reducers/LoginReducer";
import { productReducer } from "./src/product/service";
import { ProductList } from "./src/product/ProductList";
import { LoginComponent } from "./src/components/Login";
import { Provider, connect } from "react-redux";
import { StackNavigator, addNavigationHelpers } from "react-navigation";
import Routes from "./src/core/routes";
const AppNavigator = StackNavigator(Routes, {
navigationOptions: {
title: ({ state }) => {
if (state.params) {
return `${state.params.title}`;
}
}
}
});
const navReducer = (state, action) => {
const newState = AppNavigator.router.getStateForAction(action, state);
return newState || state;
};
#connect(state => ({
nav: state.nav
}))
class AppWithNavigationState extends Component {
render() {
return (
<AppNavigator
navigation={addNavigationHelpers({
dispatch: this.props.dispatch,
state: this.props.nav
})}
/>
);
}
}
const initialState = {
auth: { isLoading: false, error: null },
};
const rootReducer = combineReducers({ product: productReducer, auth: authReducer, nav: navReducer });
let store = createStore(rootReducer, initialState,
compose(applyMiddleware(thunk, createLogger())));
export default function App() {
return (
<Provider store={store}>
<AppWithNavigationState />
</Provider>
);
}
Routes.js
import { ProductList } from "../product/ProductList";
import { LoginComponent } from "../components/Login";
const Routes = {
Login: { screen: LoginComponent },
ProductList: { screen: ProductList }
};
export default Routes;
Here is my error: Route Login should declare a screen.
What did I do wrong with my code? Thank you.
I fixed the error. It was because I added between LoginComponent {} in the routes.js file at:
import { LoginComponent } from "../components/Login";
After one of the Redux tutorials decided to implement that facny action->reducer->store->view chain for simple app with only login part.
Seems like all setted up but when I run my app - in mapStateToProps(currentState) no any sign of the any custom state fields which I expected to see! (default state from reducer). But the action function is fine, as you can see on the screenshot
I can't see whats wrong here so, decided to ask it.
So here is the code
So, first of all - store.js
import { createStore, applyMiddleware } from 'redux';
import rootReducer from '../reducers';
import thunk from 'redux-thunk';
export default function configureStore(initialState) {
const store = createStore(rootReducer, initialState, applyMiddleware(thunk));
if (module.hot) {
module.hot.accept('../reducers', () => {
const nextRootReducer = require('../reducers');
store.replaceReducer(nextRootReducer);
});
}
return store;
}
then login reducer
const initialState = {
user: {
name: '',
password: ''
},
fetching: false
}
export default function login(state = initialState, action) {
switch (action.type) {
case LOGIN_REQUEST: {
return { ...state, fetching: true }
}
case LOGIN_SUCCESS: {
return { ...state, user: action.data, fetching: false }
}
case LOGIN_FAIL: {
return { ...state, user: -1, fetching: false }
}
default: {
return state;
}
}
}
and the root (reducers/index.js):
import login from './login/login';
import { combineReducers } from 'redux'
export default combineReducers({
login
});
the action
import {
LOGIN_REQUEST,
LOGIN_SUCCESS,
LOGIN_FAIL
} from '../../constants/login.js';
export function onLoginAttempt(userData) {
return (dispatch) => {
dispatch({
type: LOGIN_REQUEST,
user: userData
})
tryLogin(userData);
}
};
function tryLogin(userData) {
let url = 'SignIn/Login ';
return (dispatch) => {
axios.post(url, userData)
.then((response) => dispatch({
type: LOGIN_SUCCESS,
data: response.data
})).error((response) => dispatch({
type: LOGIN_FAIL,
error: response.error
}))
}
};
So here is entrance point:
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import App from './containers/app.js';
import configureStore from './store/configureStore';
let store = createStore(configureStore);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("content")
);
and here is the app.js (Login is just sompe custom div with two fields nothing special)
import React, { Component } from 'react';
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux';
import Login from '../components/login/Login';
import * as pageActions from '../actions/login/login'
class App extends Component {
render() {
const { user, fetching } = this.props;
const { onLoginAttempt } = this.props.pageActions;
return <div>
<Login name={user.name} password={user.password} fetching={fetching} onLoginAttempt={onLoginAttempt} />
</div>
}
}
function mapStateToProps(currentState) {
return {
user: currentState.user,
fetching: currentState.fetching
}
}
function mapDispatchToProps(dispatch) {
return {
pageActions: bindActionCreators(pageActions, dispatch)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(App)
I see that you have state which looks like:
reduxState = {
login: {
user: {
name: '',
password: ''
},
fetching: false
}
}
but then you try to access properties that don't exist.
function mapStateToProps(currentState) {
return {
user: currentState.user,
fetching: currentState.fetching
}
}
I think you need to:
function mapStateToProps(currentState) {
return {
user: currentState.login.user,
fetching: currentState.login.fetching
}
}
You have this line let store = createStore(configureStore); on your entry point. However, inside configureStore, you have a call to createStore()
Basically you're calling something like createStore(createStore(reducers)). That's probably the cause of the problem.
You should probably call it like
let store = configureStore( /* pass the initial state */ )
I try to add more props to existing list i am able to fire dispatch action but reducer doesn't get trigger
i have my headercontainer.js file like
import React, { Component, PropTypes } from 'react'
import { connect } from 'react-redux'
import { fetchAccount } from 'actions/account'
import { getAccount } from 'reducers/account'
import { fetchCart } from 'actions/cart'
import { getCart } from 'reducers/cart'
//import * as cls from 'actions/cls'
import { getReadmore } from 'reducers/cls'
import * as cls from 'actions/cls'
import { fetchAuthenticityToken } from 'actions/authenticityToken'
import { getAuthenticityToken } from 'reducers/authenticityToken'
import Header from 'components/Header'
class HeaderContainer extends Component {
static propTypes = {
account: PropTypes.object,
authenticityToken: PropTypes.object,
cart: PropTypes.object,
dispatch: PropTypes.func
}
componentDidMount() {
if (typeof document !== 'undefined') {
if (!this.props.account.isFetching) {
this.props.dispatch(fetchAccount())
}
if (!this.props.authenticityToken.isFetching) {
this.props.dispatch(fetchAuthenticityToken())
}
if (!this.props.cart.isFetching) {
this.props.dispatch(fetchCart())
}
}
}
constructor(props) {
super(props);
this.state = {classToSend: true };
}
stateToRender(){
(this.state.classToSend) ? this.setState({classToSend: false}) : this.setState({classToSend: true});
}
onClickHandler(){
this.stateToRender();
let action = cls.readMore(this.state.classToSend)
this.props.dispatch(action)
// this.props.readMore(this.state.classToSend);
}
render() {
const { account, cart, authenticityToken } = this.props
if(!account || !cart) {
return false
}
return (
<div id ="cvb">
<div id="toggle-nav" className={this.state.toggleClass?'visible-xs nav-open':'visible-xs'} onClick={() => this.onClickHandler()} >
<span data-action="toggle-nav" className="action mt-menu-label">
<span className="mt-menu-bread mt-menu-bread-top">
<span className="mt-menu-bread-crust mt-menu-bread-crust-top"></span>
</span>
<span className="mt-menu-bread mt-menu-bread-middle">
<span className="mt-menu-bread-crust mt-menu-bread-crust-middle"></span>
</span>
<span className="mt-menu-bread mt-menu-bread-bottom">
<span className="mt-menu-bread-crust mt-menu-bread-crust-bottom"></span>
</span>
</span>
</div>
<Header account={account} cart={cart} />
</div>
)
}
}
const mapStateToProps = (state) => {
return {
account: getAccount(state),
cart: getCart(state),
classToSend: getReadmore(state),
authenticityToken: getAuthenticityToken(state)
}
}
export default connect(mapStateToProps)(HeaderContainer)
My cls.js reducer
export function getReadmore(state) {
console.log(state.readMore1)
console.log("are yar")
return state.readMore1
}
export function readMore1 (state="", action) {
console.log(action.type)
switch(action.type){
case READ_MORE:
return action.payload;
}
return state;
}
cls.js Action
export function readMore(class_something) {
const READ_MORE = 'READ_MORE';
console.log("--------------------------")
console.log(class_something)
return {
type: READ_MORE,
payload: class_something
};
}
Though i am able to call action cls.js file but reducer not getting trigger
can anyone help me to get out of this trouble.
My edits
my index.js in reducer folder
import { combineReducers } from 'redux'
import { reducer as form } from 'redux-form'
import { routerReducer } from 'react-router-redux'
import { currency } from './currency'
import { cart } from './cart'
import { account } from './account'
import { alerts } from './alerts'
import { login } from './login'
import { authenticityToken } from './authenticityToken'
import { products } from './products'
import { product } from './product'
const routing = routerReducer
const rootReducer = combineReducers({
form,
routing,
currency,
cart,
cls,
account,
alerts,
authenticityToken,
login,
products,
product
})
export default rootReducer
Your case in your reducer file should be "READ_MORE" (with quotations) not READ_MORE (without quotations).
I have component App which render its children and Header component. I use Preloader from react-loader repo which takes bool loaded and render preloader or page in depended from bool. When App componentWillMount, data fetch via actionCreators, action use redux-api-middleware, then when execute render in App, Header fetch data via actionCreator boundGetPhotos which execute recursively look PHOTOS_GET_SUCCESS in console screenshot here i log action.type in my fetchingMiddleware . All actions pass from my middleware fetchingMiddleware look belowe. Which can be reasons of recursive behavior why it execute again and again and how i can solve it
App
import React, { Component, PropTypes } from 'react';
import Counterpart from 'counterpart';
import { connect } from 'react-redux';
import Loader from 'react-loader';
import { bindActionCreators } from 'redux';
import { getFriends, getMessages } from '../data/Data.Actions';
import { getUsers } from '../users/Users.Actions';
import Header from './Header';
class App extends Component {
componentWillMount() {
const { boundGetFriends, boundGetMessages, boundGetUsers } = this.props;
boundGetFriends();
boundGetMessages();
boundGetUsers();
}
render() {
const { children, fetching } = this.props;
return (
<Loader loaded={!fetching.size}>
<div>
<Header/>
{children}
</div>
</Loader>
);
}
}
App.propTypes = {
boundGetUsers: PropTypes.func,
boundGetMessages: PropTypes.func,
boundGetFriends: PropTypes.func,
fetching: PropTypes.array
};
export default connect((store) => {
return {
fetching: store.fetching
};
}, (dispatch) => {
return {
boundGetUsers: bindActionCreators(getUsers, dispatch),
boundGetFriends: bindActionCreators(getMessages, dispatch),
boundGetMessages: bindActionCreators(getFriends, dispatch)
};
})(App);
Header
import React, { Component, PropTypes } from 'react';
import React, { Component, PropTypes } from 'react';
import { Navbar, Nav, NavItem, NavDropdown, MenuItem } from 'react-bootstrap';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { getPhotos } from '../user/User.Actions';
class Header extends Component {
componentWillMount() {
const { boundGetPhotos } = this.props;
boundGetPhotos();
}
render() {
return (
<Navbar fluid collapseOnSelect>
<Navbar.Brand>
MyProject
</Navbar.Brand>
</Navbar>
);
}
}
Header.propTypes = {
boundGetPhotos: PropTypes.func.isRequired
};
export default connect((store) => null, (dispatch) => {
return {
boundGetPhotos: bindActionCreators(getPhotos, dispatch)
};
})(Header);
FetchingMiddleware
import { startFetching, endFetching } from './FetchingMiddleware.Actions';
export default store => next => action => {
console.log(action.type);
if (typeof action !== 'function' && action.type.search(/REQUEST/) !== -1) {
store.dispatch(startFetching());
}
if (typeof action !== 'function' && action.type.search(/SUCCESS|FAILURE/) !== -1) {
store.dispatch(endFetching());
}
next(action);
};
FetchingMiddlewareReducers
import Immutable from 'immutable';
import { END_FETCHING, START_FETCHING, RESET_FETCHING } from './FetchingMiddleware.Actions';
import createReducer from '../utils/utils';
function addFetching(state, action) {
return state.push(true);
}
function removeFetching(state, action) {
return state.pop();
}
function resetFetching(state, action) {
return Immutable.List();
}
export default createReducer({
[END_FETCHING]: removeFetching,
[START_FETCHING]: addFetching,
[RESET_FETCHING]: resetFetching
}, Immutable.List());
FetchingMiddlewareActions
export const END_FETCHING = 'END_FETCHING';
export const START_FETCHING = 'START_FETCHING';
export const RESET_FETCHING = 'RESET_FETCHING';
export function endFetching() {
return {
type: END_FETCHING
};
}
export function startFetching() {
return {
type: START_FETCHING
};
}
export function resetFetching() {
return {
type: RESET_FETCHING
};
}
getPhotos
import { CALL_API } from 'redux-api-middleware';
export const PHOTOS_GET_SUCCESS = 'PHOTOS_GET_SUCCESS';
export function getPhotos() {
return {
[CALL_API]: {
endpoint: '/photos',
method: 'GET',
headers: {
'Content-Type': 'application/json'
},
credentials: 'include',
types: ['REQUESTPHOTOS', PHOTOS_GET_SUCCESS, 'FAILURE']
}
};
}
Your <Header /> component should be a pure component that knows nothing about your state container (Redux) or dispatch.
Using the approach you have here would litter your component tree with 'connect' and add awareness of Redux to all of your components. This is bad practice in terms of scalability - what if you wanted to replace Redux with another state container?.
I would recommend that all state and actions should be bound to props and passed down the tree into your components such as the <Header /> component.
This should also resolve the issues you are having.
App
import React, { Component, PropTypes } from 'react';
import Counterpart from 'counterpart';
import { connect } from 'react-redux';
import Loader from 'react-loader';
import { getMasterDataSchema, getMasterDataData } from '../masterdata/MasterData.Actions';
import { getQuestionnaireSchema } from '../questionnaireschema/QuestionnaireSchema.Actions';
import Header from './Header';
class App extends Component {
componentWillMount() {
const {
GetMasterDataData,
GetMasterDataSchema,
GetQuestionnaireSchema
} = this.props;
GetMasterDataData();
GetMasterDataSchema();
GetQuestionnaireSchema();
}
render() {
const { children, fetching, GetPrincipal } = this.props;
return (
<Loader loaded={!fetching.size}>
<div>
<Header GetPrincipal={GetPrincipal} />
{children}
</div>
</Loader>
);
}
}
App.propTypes = {
GetPrincipal: PropTypes.func,
GetQuestionnaireSchema: PropTypes.func,
GetMasterDataSchema: PropTypes.func,
GetMasterDataData: PropTypes.func,
fetching: PropTypes.array
};
export default connect(({ fetching }) => ({
fetching
}), {
GetPrincipal,
GetQuestionnaireSchema,
GetMasterDataData,
GetMasterDataSchema,
})(App);
Header
import React, { Component, PropTypes } from 'react';
import { Navbar, Nav, NavItem, NavDropdown, MenuItem } from 'react-bootstrap';
export default class Header extends Component {
componentWillMount() {
const { GetPrincipal } = this.props;
GetPrincipal();
}
render() {
return (
<Navbar fluid collapseOnSelect>
<Navbar.Brand>
EMS
</Navbar.Brand>
</Navbar>
);
}
}
Header.propTypes = {
GetPrincipal: PropTypes.func.isRequired
};
When I click the DIV in Home container, I have confirmed the set function is called (I see the console log)
teamReducer function is never called. Maybe bindActionCreators should be used differently? How can i have my action creator send action to reducer to update the league store?
// teamReducer.js
export function teamReducer(state = initialState, action){
switch (action.type) {
case 'SET_TEAM':
return {
...state,
called: true
};
default:
return state;
}
};
// reducers/index.js
import { combineReducers } from 'redux';
import { routeReducer } from 'redux-simple-router';
import { teamReducer } from './teamReducer';
const rootReducer = combineReducers({
routing: routeReducer,
league: teamReducer,
});
export default rootReducer;
// actions/setTeam.js
export function setTeam(team, position) {
console.log(team, position);
return {
type: 'SET_TEAM',
team,
position
};
}
}
// Home.js
import React, { PropTypes, Component } from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import {setTeam } from '../../actions/teams';
const mapStateToProps = ({league}) => {
return {
called: league.called
};
};
const mapDispatchToProps = (dispatch) => {
return bindActionCreators({
setTeam,
}, dispatch);
};
#connect(mapStateToProps, mapDispatchToProps)
export class Home extends Component {
constructor(props) {
super(props);
}
render() {
const {set} = this.props.setTeam
return <div onClick={set} />
}
}
The issue in the render function. You use destructuring assignment wrong.
render() {
const {set} = this.props.setTeam;
return <div onClick={set} />
}
This assignment is the same as in the following code:
const set = this.props.setTeam.set;
But setTeam is a function and doesn't have set property. The correct code is:
render() {
const {setTeam} = this.props;
return <div onClick={setTeam} />
}