I have a reducer for login to application.
I want to display Alert when something goes wrong.
Login action that dispatch show modal action:
export function loginUser(username, password) { ...
dispatch({type: 'SHOW_MODAL', modalType: 'SUCESSFULL_LOGIN'})
Modal reducer:
export default function reducer(state={}, action) {
switch (action.type) {
case 'SHOW_MODAL':
return {
modalType: action.modalType
}
}
return state
}
But now I don't know where to go next.
Where to read state now and display Alert view.
You need connect your component to redux and get needed properties from state.
Example:
const stateMap = (state, props, ownProps) => {
return(
/* Here all your state */
/* name_property: state.name_action_import.name_property_on_state*/
modalType: state.login.modalType
)
}
function mapDispatchToProps(dispatch) {
return {
// This you put action on this component if you need
};
};
Here you connect your component to redux
export default connect(stateMap, mapDispatchToProps)(Alers);
And this variable in component you can get from props like:
this.props.modalType
Your component Alert will look like:
import React from "react";
import { connect } from 'react-redux';
import login from './../../../actions'; /* Here your inport to action */
class Alers extends Component {
render() {
return (
<div >
{this.props.modalType /*here you property from state */}
</div>
);
}
}
const stateMap = (state, props, ownProps) => {
return {
modalType: state.login.modalType
};
};
function mapDispatchToProps(dispatch) {
return {
};
};
export default connect(stateMap, mapDispatchToProps)(Alers);
Related
I am new to React/Redux, and appreciate your help. I am taking a Udemy course on this topic. The course instructor creates a component like this.
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchUser } from '../actions';
class User extends Component {
componentDidMount(){
this.props.fetchUser(this.props.userId);
}
render(){
const { user } = this.props;
if(!user) return null;
return(
<div className="header"> User Info: {user.name}</div>
);
}
}
const mapStateToProps = (state, ownProps) => {
return { user: state.users.find( user => user.id === ownProps.userId)};
};
export default connect(mapStateToProps, { fetchUser })(User)
my question: why inside the componentDidMount() he is prefixing fetchUsers() with this.props?
it is not the case that he is passing fetchUsers() as props from the parent component. This is how the parent is using this component <User userId={post.userId}/>
Note: this code works
It is because of this line :
export default connect(mapStateToProps, { fetchUser })(User)
the second parameter to connect is called mapDispatchToProps, It adds the actions to props
From the docs :
connect can accept an argument called mapDispatchToProps, which lets
you create functions that dispatch when called, and pass those
functions as props to your component.
const mapDispatchToProps = dispatch => {
return {
// dispatching plain actions
increment: () => dispatch({ type: 'INCREMENT' }),
decrement: () => dispatch({ type: 'DECREMENT' }),
reset: () => dispatch({ type: 'RESET' })
}
}
Your code is using the “object shorthand” form.
The way the mapDispatchToProps in the example is shorthanded. It might be easier to tell what is going if it was written like so:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchUser } from '../actions';
class User extends Component {
componentDidMount(){
this.props.fetchUser(this.props.userId);
}
render(){
const { user } = this.props;
if(!user) return null;
return(
<div className="header"> User Info: {user.name}</div>
);
}
}
const mapStateToProps = (state, ownProps) => {
return { user: state.users.find( user => user.id === ownProps.userId)};
};
const mapDispatchToProps = () => ({
fetchUser
});
export default connect(mapStateToProps, mapDispatchToProps)(User)
Maybe this shows it more clearly, but the dispatch function (fetchUser) is being mapped to the components properties. Just like the state value (user) is being mapped to the properties of the component. I think you just got confused because of the shorthand that was used.
EDIT: I solve my issue and it is working for me now - also edited my code to reflect new changes.
I am getting this error and I am not sure what is the cause of this error.
I cannot show code as it is company's material, so I will try my best to describe it:
App.js:
`class App extends React.Component {
constructor(props) {
super(props);
}
render () {
return (
<div>
<Header />
<RouteList />
<Footer />
</div>
)
}
}`
My <RouteList /> is a a stateless function that returns all Routes for the web-application.
Header.js:
class Header extends React.Component {
constructor (props) {
super(props);
this.changeHeader = this.changeHeader.bind(this);
}
changeHeader(headerType) {
this.props.actions.changeHeader(headerType)
}
GetHeader() {
// if-else statement which will return a different sub header class
const HeaderType = this.props.renderHeader.headerType
if (headerType == 'abc') {
<aHeader changeHeader={this.changeHeader} />
} [...] {
// Final else block return something
}
}
render () {
return (
<div>{this.GetHeader()}</div>
)
}
}
function mapStateToProps(state, ownProps) {
return { renderHeader: state.renderHeader};
}
function mapDispatchToProps(dispatch) {
return { actions: bindActionCreators(headerActions, dispatch) };
}
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Header));
this.props.action.changeHeader(headerType) is an if-else statement which depending on what the value of headerType is, will fire a different action.
state.renderHeader is declared in my rootReducer.
I pass changerHeader() into individual header.js which are stateless (i.e. aHeader.js, bHeader.js...). When a navlink is clicked it will invoke the method and also route the page to another UI. This is how i embed the method into the navlink: onClick={changeHeader(input')}.
rootReducer.js
const rootReducer = combineReducers({renderHeader});
export default rootReducer;
The renderHeader is the renderHeaderReducer.
headerAction.js
export function changeHeader(headerType) {
if (headerType == "abc") {
return {type: type, headerType: "abc"}
} [...] {
// something default
}
}
renderHeaderReducer.js
export default function renderHeaderReducer(state = initialState, action) {
switch(action.type) {
case "abc":
return (Object.assign({}, ...state, {headerType: action.headerType}));
[...];
default:
return state;
}
}
At this point when the link is clicked, the web browser should refresh, leaving the Header in place but modifying the part. However my website goes into an infinite loop, and the error is:
Error: Cannot update during an existing state transition (such as within render or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved to componentWillMount.
When I did a console.log to see what is going on, it seems to be looping over all the various options that i defined which will render Header.js
It turns out that the main problem was when i called my onClick method.The infinite loop that bugged my code was a result of the onClick function firing even without being clicked.
Original: onClick={this.changeHeader('abc')}
New: onClick={() => changeHeader('abc')}
Refer to this post for an explanation.
Thank you.
time for some pseudo code :)
From what I understand, you have a Header Component which is connected to a rootReducer component which contains the header for which Router Link you are on.
I have some similar code in my application where we use individual components dispatch action to update the rootReducer header. The header just listens for updates using redux and re-renders itself.
class Header extends React.Component {
// render the header
render() {...}
}
const mapStateToProps = (state) => {
return {
header: state.rootReducer.header
}
}
export default withRouter(connect(mapStateToProps, {})(Header));
the component
class MySpecialRouteComponent extends React.Component {
componentDidMount() {
this.props.changeHeader("Special Component")
}
render() {...}
}
const mapStateToProps = (state) => {
return {
...whatever
}
}
export default withRouter(connect(mapStateToProps, {changeHeader})(MySpecialRouteComponent));
you shouldn't make render do the setState in React ever!
I'll just show how I would set everything up to handle this situation.
redux/header-actions.js (call these action creators from your components):
export const changeHeader = (headerType) => {
return {
type: 'CHANGE_HEADER',
payload: {
headerType: headerType
}
}
}
redux/header-reducers.js (note: this will be handled when you call the action):
const INITIAL_STATE = {
headerType: 'header-a'
};
export default function(state = INITIAL_STATE, action) {
switch (action.type) {
case 'CHANGE_HEADER':
return changeHeader(state, action.payload);
default:
return state;
}
}
const changeHeader = (state, payload) => {
// this is where your app will update the state, to indicate
// which header should be displayed:
return {
...state,
headerType: payload.headerType
}
}
redux/index.js:
import headerReducers from './reducers/header-reducers';
import { combineReducers } from 'redux';
const allReducers = combineReducers({
header: headerReducers
});
export default allReducers;
Now you can set up your header.js component like this:
import { changeHeader } from '../redux/header-actions';
class Header extends Component {
render() {
return (
<div>{this.renderHeader()}</div>
);
}
renderHeader() {
if (this.props.headerType === 'header-a')
return <aHeader changeHeader={this.props.changeHeader} />
else
return <bHeader changeHeader={this.props.changeHeader} />;
}
}
function mapStateToProps(store, ownProps) {
return {
headerType: store.header.headerType
};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({
changeHeader: changeHeader
},
dispatch);
}
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Header));
Then in, for example, aHeader.js:
class aHeader {
constructor() {
super();
this.changeHeader = this.changeHeader.bind(this);
}
render() {
return <div onClick={this.changeHeader}>header a</div>;
}
changeHeader() {
this.props.changeHeader('header-b'); // call the action creator here
}
}
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 :)
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}
}
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.