Click the login button
Auth's action is called
Reducer called
Connect mapDispatchToProps is called
However, it is not redrawn
I am in trouble because the render method of React.Component 5 is not called and redrawing is not executed.
After reading this article, I think that using Render.Component's Object.assign should call Render of React.Component.
But it does not work.
Where is wrong?
app.js
import { connect } from 'react-redux'
import App from '../components/app'
function mapStateToProps(state){
return {login: state.login}
}
function mapDispatchToProps(dispatch) {
return {
dispatch
};
}
export default connect(mapStateToProps,mapDispatchToProps)(App);
components/app.js
import React from 'react'
import { Link } from 'react-router'
import { auth } from '../actions/auth'
export default class App extends React.Component {
render() {
const { dispatch } = this.props;
return (
<div className="columns is-gapless">
<div className="column is-10 content">
<div className="content-body">
<div className="has-text-right">
{(this.props.login)?"TRUE":"FALSE"}
<button className="button is-primary" onClick={()=>dispatch(auth("test#test.com","aaaa"))}>login</button>
</div>
</div>
</div>
</div>
);
}
}
actions/auth.js
import {constant} from './constant';
export function auth(email,password) {
return {
type: constant.ACTION.AUTH,
email: email,
password:password
}
}
reducer
import {constant} from './actions/constant';
const initialState = {
login: false
};
export default function reducersIndex(state = initialState, action) {
console.log("reducers");
if (typeof state === 'undefined') {
return 0
}
switch (action.type) {
case constant.ACTION.AUTH:
return Object.assign({}, state,{
login:!state.login
});
default:
return state
}
}
Your state contains the property login, which is set in your reducer. In your component, you are also checking this.props.login.
However, in mapStateToProps, you are mapping state.state to the prop state, leaving this.props.login === undefined. For this reason, {(this.props.login)?"TRUE":"FALSE"} will always evaluate to "FALSE".
To resolve this, map login from your state to the prop login in your container:
function mapStateToProps(state){
return {login: state.login}
}
Related
I am trying to map an action to props however however I'm getting an error:
TypeError: _this2.props.updateUsername is not a function
How does one successfully map redux actions to props and call the function successfully? I havnt seen this error pop up in any other stackoverflow question/answers is it a simple mistake? Could it be a wrong setup of redux in .index or .app?
I have tried:
- importing without using default export
- having different formats of mapDispatchToProps (eg without using bindactioncreators)
- fixing typos
Component:
import { updateUsername } from "../../actions/user-actions";
import React, { Component } from "react";
import { InputText } from "primereact/inputtext";
import { Button } from "primereact/button";
import { Password } from "primereact/password";
import "./UserLogin.css";
import { connect } from "react-redux";
import { bindActionCreators } from 'redux'
export class UserLoginPage extends Component {
constructor(props) {
super(props);
this.state = { //used to be using states so ill leave these here for now
username: "",
password: "",
renderTryAgain: false
};
this.checkLoginDetails.bind(this.checkLoginDetails);
}
async checkLoginDetails() {
...
}
render() {
const usernameBox = (
<InputText
...
value={this.props.username}
onChange={e => this.props.updateUsername(e.target.value)}
/>
);
const passwordBox = (
<Password
...
/>
);
const loginButton = (
<Button
...
/>
);
return (
<header className="User-login">
<p>Dashboard User Login</p>
<div className="p-grid">
<div className="p-col">{usernameBox}</div>
<div className="p-col">{passwordBox}</div>
<div className="p-col">{loginButton}</div>
</div>
</header>
);
}
}
const mapStateToProps = state => ({
username: state.username
});
const mapDispatchToProps = dispatch => bindActionCreators(
{
updateUsername,
},
dispatch,
)
export default connect(
mapStateToProps,
mapDispatchToProps
)(UserLoginPage);
Reducers:
import { UPDATE_USERNAME} from '../actions/user-actions'
export function passReducer(state = "", {type, payload}) {
switch (type) {
case true:
return payload
default:
return state
}
}
export function usernameReducer(state = '', {type, payload}) {
switch (type) {
case UPDATE_USERNAME:
return payload.username
default:
return state
}
}
export default { passReducer, usernameReducer };
Action:
export const UPDATE_USERNAME = 'username:updateUsername'
export function updateUsername(newUsername){
return {
type: UPDATE_USERNAME,
payload: {
username: newUsername
}
}
}
export default {UPDATE_USERNAME, updateUsername}
Many Thanks
Can you check once after updating your constructor as below?
constructor(props) {
super(props);
//...
}
Don't use mapDispatchToProps. Instead just wrap all the actions you want to map inside an object and pass them as the second argument to the connect helper method.
Like this connect(mapStateToProps, { updateUsername })(UserLoginPage)
Hope this helps!
trying to make reducer works, but everytime "default" case fires.
Here is the code:
AC:
import {INPUT_IDEA_HANDLE} from "./types"
export function inputIdeaHandle(idea) {
console.log('it is working, I have access to the idea');
return {
type: INPUT_IDEA_HANDLE,
payload: idea
}
}
Reducer :
import {INPUT_IDEA_HANDLE} from "../actions/types"
export default function (state = null, action) {
switch (action.type) {
case INPUT_IDEA_HANDLE :
console.log('never fires');
return action.payload;
default:
console.log('fires everytime');
return state
}
}
import { combineReducers } from "redux";
import inputIdeaReducer from "./inputIdeaReducer.js"
export default combineReducers({
inputIdea: inputIdeaReducer
});
UPDATE
I changed my trigger code, but keep getting
Cannot read property 'props' of undefined
in return this.props.inputHandle(value);
Event trigger :
import React, { Component } from "react";
import { connect } from "react-redux";
import { bindActionCreators } from "redux";
import { inputIdeaHandle } from "../actions";
class Dashboard extends Component {
changeHandle(e) {
e.preventDefault();
let value = e.target.value;
return this.props.inputHandle(value);
}
render() {
return (
<div className="dashboard container">
<div className="row">
<div className="col-12">
<h4>Type your idea here</h4>
<input
type="text"
onChange={this.changeHandle}
value={this.props.inputIdea}
/>
</div>
</div>
</div>
);
}
}
function mapStateToProps(state) {
return {
inputIdea: state.inputIdea
};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators(
{
inputHandle: inputIdeaHandle
},
dispatch
);
}
export default connect(mapStateToProps, mapDispatchToProps)(Dashboard);
Triple checked everything, but still keep getting 'fires everytime' in console. Count on you, guys
Regards
Issue is with handling this context.
You can handle this context as follows
constructor
constructor() {
super(props);
this.changeHandle = this.changeHandle.bind(this)};
}
Arrow function in render : onChange={e => this.changeHandle(e)}
React.createClass : React binds all functions to this.
Bind in render:
onChange={this.changeHandle.bind(this)}
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
you have to import your actions from your action file
import { postSelected } from '../containers/actions';
class Home extends Component {
changeHandle(e) {
e.preventDefault();
let value = e.target.value;
return this.props.post(value);
}
<input type="text" onChange={this.changeHandle} />
} //end of class
function mapStateToProps(state) {
return{
view: state.view
}
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({
post: postSelected,
},dispatch)
}
export default connect(mapStateToProps, mapDispatchToProps)(Dashboard);
this should work edit it to suit your needs
I am reusing the same reducer logic for two different events. The idea is to toggle a class depending on which text you clicked on. Each event fires, but my object is not toggling. Any thoughts?
App:
import React from "react"
import { bindActionCreators } from 'redux'
import { connect } from "react-redux"
import * as toggleactionCreators from '../actions/toggleActions';
function mapStateToProps(state) {
return {
hiddenA: state.toggleA.hidden,
hiddenB: state.toggleB.hidden
}
}
function mapDispachToProps(dispatch) {
return bindActionCreators({...toggleactionCreators}, dispatch)
}
class Main extends React.Component {
toggleDiv() {
this.props.toggleDiv();
console.log(this.props)
}
render() {
const { hiddenA, hiddenB } = this.props;
return (
<div>
<div>
<h3 onClick={this.toggleDiv.bind(this)} className={ hiddenA ? null : "toggled"} >Good Day!</h3>
<h1 onClick={this.toggleDiv.bind(this)} className={ hiddenB ? null : "toggled"} >Hello There!</h1>
</div>
</div>
)
}
}
export default connect(mapStateToProps, mapDispachToProps)(Main);
Index Reducer:
import { combineReducers } from "redux"
import toggle from "./toggleReducer"
function createNamedWrapperReducer(reducerFunction, reducerName) {
return (state, action) => {
const {name} = action;
const isInitializationCall = state === undefined;
if(name !== reducerName && !isInitializationCall) return state;
return reducerFunction(state, action);
}
}
const thereducer = combineReducers({
toggleA : createNamedWrapperReducer(toggle, 'A'),
toggleB : createNamedWrapperReducer(toggle, 'B'),
});
export default thereducer;
toggleReducer:
const toggle = (state = { hidden: true}, action) => {
switch(action.type) {
case 'TOGGLE_DIV':
return Object.assign({}, ...state, {hidden: !state.hidden});
default:
return state;
}
};
export default toggle;
toggleAction:
export const toggleDiv = () => {
return {
type: 'TOGGLE_DIV',
}
}
This is how I would debug this.
Download Redux DevTools for your browser. This is the URL for chrome: https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd
Download React devtools for you browser. This is the URL for chrome: https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi
Look in Redux Devtools:
Is the action emitted from your action creator
Does the reducer update the state correctly?
If both the actions, and reducers looks correctly, check your React component:
Does the component receive the correct props? If yes, it's something with how the props are rendered. If no, it's something with how the store is connected to your component.
Hope this debugging tutorial is useful for you. If you have any follow up questions, please don't hesitate to ask :)
Im not 100% sure if it is working correct, but it does noet give the result of the video course that I followed.
The renderPosts is just suppose to render the list, but instead it get a blank array the first time round. and when mapStateToProps is called the second time, the array is filled with the expected values.
it is as if the first time mapStateToProps is invoked, it did not pass through the action creator first or something.
COMPONENT
import React, {Component} from 'react';
import { connect } from 'react-redux';
import { fetchPosts } from '../actions/index';
import { Link } from 'react-router';
class PostsIndex extends Component {
componentWillMount() {
console.log("componentWillMount");
this.props.fetchPosts();
}
renderPosts() {
// console.log("renderPosts - this.props.posts",this.props.posts);
if(this.props.posts){
return this.props.posts.map((post) => {
return (
<li className="list-group-itme" key="{post.id}">
<span className="pull-xs-right">{post.catagories}</span>
<strong>{post.title}</strong>
</li>
);
});
}
}
render() {
return (
<div>
<div className="text-xs-right">
<Link to="/posts/new" className="btn btn-primary">
Add New Post
</Link>
</div>
<h3>Posts</h3>
<ul className="list-group">
{this.renderPosts()}
</ul>
</div>
);
}
}
function mapStateToProps(state) {
console.log("mapStateToProps",state.posts);
return {posts: state.posts.all}
}
export default connect(mapStateToProps, {fetchPosts})(PostsIndex);
ACTION
import axios from 'axios';
export const FETCH_POSTS = 'FETCH_POSTS';
export const CREATE_POST = 'CREATE_POST';
const ROOT_URL = 'http://reduxblog.herokuapp.com/api';
const API_KEY = '?key=qwerty123';
export function fetchPosts(){
const request = axios.get(`${ROOT_URL}/posts${API_KEY}`);
return {
type: FETCH_POSTS,
payload: request
};
}
export function createPost(props) {
const request = axios.post(`${ROOT_URL}/posts${API_KEY}`, props);
return{
type: CREATE_POST,
payload: request
}
}
REDUCER
import { FETCH_POSTS } from '../actions/index';
const INITIAL_STATE = { postsList:[], post:null };
export default function(state = INITIAL_STATE, action){
console.log("action.type",action.type);
switch (action.type) {
case FETCH_POSTS:
return {...state, postsList: action.payload.data};
default:
return state;
}
}
mapStateToProps is called twice. on the initial call the array is empty. on the second call I have my ten posts inside the array.
Problem is that it seems to want to render the first array and ignores the second
I have put an consol.log in the
renderPosts
and
mapStateToProps
and it renders as follows.
Console
any Ideas?
I think the error is coming from the way you handle the Promise. The first time you see the mapStateToProps in the console you can see you have no data so this is PENDING, the second is when it's FULFILLED. You need to find a way to handle this.
Example but not the best, I think you can just change you if statement.
import React, {Component} from 'react';
import { connect } from 'react-redux';
import { fetchPosts } from '../actions/index';
import { Link } from 'react-router';
class PostsIndex extends Component {
componentWillMount() {
console.log("componentWillMount");
this.props.fetchPosts();
}
renderPosts() {
return this.props.posts.map((post) => {
return (
<li className="list-group-itme" key="{post.id}">
<span className="pull-xs-right">{post.catagories}</span>
<strong>{post.title}</strong>
</li>
);
});
}
render() {
return (
<div>
<div className="text-xs-right">
<Link to="/posts/new" className="btn btn-primary">
Add New Post
</Link>
</div>
<h3>Posts</h3>
<ul className="list-group">
{this.props.posts !== [] this.renderPosts() : <h1>Loading...</h1>}
</ul>
</div>
);
}
}
function mapStateToProps(state) {
console.log("mapStateToProps",state.posts);
return {posts: state.posts.all}
}
export default connect(mapStateToProps, {fetchPosts})(PostsIndex);
The second one should be by changing the way you do the promise. A good library is redux-promise-middleware
This is a example of my app what I did.
Actions
export const reqAllGames = games => {
const promise = new Promise((resolve, reject) => {
request
.get(`${config.ROOT_URL}/${config.API_KEY}`)
.end((err, res) => {
if (err) {
reject(err);
} else {
resolve(res.body.top);
}
});
});
return {
type: types.RECEIVE_ALL_GAMES,
payload: promise
};
};
Reducer
import * as types from "../constants/";
const gameReducer = (games = { isFetched: false }, action) => {
switch (action.type) {
case `${types.RECEIVE_ALL_GAMES}_PENDING`:
return {};
case `${types.RECEIVE_ALL_GAMES}_FULFILLED`:
return {
games: action.payload,
err: null,
isFetched: true
};
case `${types.RECEIVE_ALL_GAMES}_REJECTED`:
return {
games: null,
err: action.payload,
isFetched: true
};
default:
return games;
}
};
export default gameReducer;
Component
const Games = ({ games, err, isFetched }) => {
if (!isFetched) {
return <LoadingCircular />;
}
else if (err === null) {
return (
<div>
<GamesList games={games} />
</div>
);
} else {
return <h1>Games not find!</h1>;
}
};
const mapStateToProps = (state) => state.games;
export default connect(mapStateToProps)(Games);
If you using react-router you can use the onEnter api and do the actions right here. With that you know your component gonna get the post. A good tutorial is this one from RallyCoding https://www.youtube.com/watch?v=JicUNpwLzLY
Hope that can help you
https://www.udemy.com/react-redux/learn/v4/questions/1693796
In your reducer you're assigning the list of posts to the key postsList.
case FETCH_POSTS:
return {...state, postsList: action.payload.data};
We can confirm that they are properly being assumed to postsList by looking at the mapStateToProps console log you have in your screenshot.
Your mapStateToProps, however, is looking at the property state.posts.all
return {posts: state.posts.all}
The list of posts are not assigned to the all property, they are assigned to the postsList property. This is why you don't see the updated list of posts in your component. You'll need to update either the property the reducer is placing the list of posts on or update your mapStateToProps to pull the list of posts from the correct property.
-Stephen Grider
I am not able to understand why reducer is being called in one case and not in another case. The footer component is given below.
File Footer.jsx
import React from 'react';
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import './Footer.scss';
import * as footerActions from '../../redux/actions/footer';
const mapStateToProps = (state) => {
return state;//{state: state, footerState: getFooterData(state)};
};
// Footer component is aware of only footer actions
const mapDispatchToProps = (dispatch) => {
return {
actions: bindActionCreators(footerActions, dispatch)
};
};
var Footer = React.createClass({
propTypes:{
state: React.PropTypes.object,
actions: React.PropTypes.object
},
componentWillMount() {
this.props.actions.changeLanguage("dutch");//<-- This calls reducer and changes language
},
changeLang(language) {
this.props.actions.changeLanguage("spanish");//<--- This doesn't calls reducer
},
render() {
var that = this;
return (
<div className="footer">
<div onClick={that.changeLang.bind(that, 'english')}>English</div>
<div onClick={that.changeLang.bind(that,'german')}>German</div>
</div>
);
}
});
export default connect(mapStateToProps, mapDispatchToProps)(Footer);
actions file footer
export const LANGUAGE_CHANGE = 'LANGUAGE_CHANGE';
export function changeLanguage(data, context) {
return {
type: LANGUAGE_CHANGE,
data
};
}
Reducer file footer
import { LANGUAGE_CHANGE } from '../actions/footer';
export default function footer(state = {}, action) {
switch (action.type) {
case LANGUAGE_CHANGE:
debugger;
return Object.assign({}, state, {language:action.data});
default:
return state;
}
}
In footer.jsx component code, dispatching LANGUAGE_CHANGE action from componentDidMount method successfully calls footer reducer but, reducer is not called when LANGUAGE_CHANGE action dispatched from changeLang method. Not able to figure out what is going wrong ? Help Appreciated.