I'm using Redux in an application for the first time and having trouble understanding how to pass a component's internal state to the global state object.
export default class ComponentOne extends Component {
constructor() {
this.state = {
number: 0
}
handleNumber = (e) => {
this.setState({
number: e.target.value
})
}
render() {
console.log(this.state.number)
return (
<div>
<input onChange={this.handleNumber} type="number">
</div>
)
}
}
}
function mapStateToProps(state) {
return {
number: state
}
}
export default connect(mapStateToProps, { HANDLE_NUMBER_CHANGE })(ComponentOne);
My Actions & Reducers:
const HANDLE_NUMBER_CHANGE = state => {
return {
type: 'HANDLE_NUMBER_CHANGE'
}
}
export default (state = 0, action) {
switch(action.type) {
case 'HANDLE_NUMBER_CHANGE':
//Im lost here - trying to save internal state
default:
return state;
}
}
My store is set up properly, using redux-thunk for middleware.
When I log store.getState() - it is logging 0 regardless of my components internal state.
Can anybody explain how this works?
When you have global state you dont need to save it to the local state. It is accessible to the component as this.props.value.
The way to set global state is by passing the value to the action creator, which returns it in the action. The reducer gets it in the action object and saves it.
There are many simple examples available. Here is one.
Here is your code after changes:
(I didn't run it - there might be errors, but I believe that you will be able to fix them by yourself; I have divided the code between several files - this is how usually how this is done. Look in the example in the above link if you have problems)
// file: src/components/ComponentOne.js
import React from 'react';
import { connect } from 'react-redux';
import { handleNumber } from '../actions';
class ComponentOne extends Component {
constructor(props) {
super(props);
this.handleNumber = this.handleNumber.bind(this);
}
render() {
console.log(this.state.number)
return (
<div>
<input onChange={(e) => this.props.handleNumber(e.target.value)} type="number" />
</div>
);
}
}
function mapStateToProps(state) {
return {
number: state
}
}
export default connect(mapStateToProps, { handleNumber })(ComponentOne);
// end of file
/// separate file: src/reducers/index.js ////
import { combineReducers } from 'redux';
import dataReducer from './dataReducer';
export default combineReducers({
number: dataReducer
});
// end of file
// separate file: src/reducers/dataReducer.js
const DataReducer = (state = 0, action) => {
switch(action.type) {
case 'HANDLE_NUMBER_CHANGE':
return action.payload;
default:
return state;
}
};
export default DataReducer;
// end of file
// separate file: src/actions/index.js
export function handleNumber(value) {
return ({
type: 'HANDLE_NUMBER_CHANGE',
payload: value
});
}
I don't see the logic in making your internal state equal your store. I'm not saying you're wrong, but it doesn't seem to fit within the redux paradigm. However...
Action should be...
export function HANDLE_NUMBER_CHANGE = number => {
return {
type: 'HANDLE_NUMBER_CHANGE'
payload: number
}
}
Reducer should look like...
export default (state = {number: 0}, action) {
switch(action.type) {
case 'HANDLE_NUMBER_CHANGE':
return (state = {
...state,
number: action.payload,
});
Lastly, you'll need to call a dispatch from your onChange function.
dispatch(HANDLE_NUMBER_CHANGE(e.target.value).
If you do not pass the value to the action, there is no way for the reducer to add it to the store.
If you are managing your ComponentOne state using redux then you dont need
react state.
ComponentOne
export default class ComponentOne extends Component {
constructor() {
handleNumber = (e) => {
this.props.updateNumber(e.target.value);//call dispatch method
}
render() {
return (
<div>
<input onChange={this.handleNumber} type="number">
</div>
)
}
}
}
function mapStateToProps(state) {
return {
number: state.number //map updated number here
}
}
function mapDispatchToProps(state) {
return {
updateNumber(number){
dispatch({type: 'HANDLE_NUMBER_CHANGE',number});//dispatch action
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(ComponentOne);
reducers:
export default (state = 0, action) {
switch(action.type) {
case 'HANDLE_NUMBER_CHANGE':
return {
...state,number : actio.number//update number here
}
default:
return state;
}
}
Related
the code below works fine, except something weird,
initial props is an empty object { photos: {} }
after invoking action creator the state in component looks like
{ photos: photos : {...} }
whereas I expect only photos: {...}
What cause this and how can I prevent generate another sub property in state?
component:
import {connect} from 'react-redux'
import {getPhotos} from '../actions'
class Photos extends Component {
constructor(props){
super(props)
}
componentDidMount(){
this.props.getPhotos();
}
invokeFunc= () =>{
this.props.getPhotos();
}
render() {
return (
<div>
<PhotoItem />
<button onClick={()=> this.invokeFunc()} >call action creator</button>
</div>
)
}
}
const mapStateToProps = (state) => {
const {photos} = state;
return {
photos,
}
}
export default connect(mapStateToProps,{getPhotos})(Photos)
action creator:
export const getPhotos = () =>{
return async (dispatch) => {
const response = await PhotosApi.get('/photos')
dispatch({
type:"GET_PHOTOS",
payload:response
})
}
}
reducer.js
const photosReducer = (state = {}, action) =>{
if(action.type=="GET_PHOTOS"){
return {...state, photos:action.payload}
}
return state;
}
export default combineReducers({
photos:photosReducer,
})
Your reducer is returning an object with photos as a property in it. Instead, try returning an object that is the photos property.
const photosReducer = (state = {}, action) =>{
switch(action.type){
case "GET_PHOTOS":
return { ...state, action.payload }; // or just `{ action.payload }` if you want it to wipe out the last state
default:
return state;
}
}
export default combineReducers({
photos:photosReducer,
});
The key here is combineReducers. From docs
The combineReducers helper function turns an object whose values are different reducing functions into a single reducing function you can pass to createStore.
The resulting reducer calls every child reducer, and gathers their results into a single state object. The state produced by combineReducers() namespaces the states of each reducer under their keys as passed to combineReducers()
So, since you are giving the namespace photos to your reducer, photosReducer state is under key photos in your global state.
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!
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
}
}
In console nothing, where can be a mistake ?
Need to get this.props.about and check empty or not.
reducer.js
export default function details(state = initialState, action) {
switch(action.type) {
case DETAILS_SUCCESS:
return { ...state, details: action.payload, error: '' };...
Container.js
class HeaderContainer extends Component {
render() {
const { details } = this.props, { deTails } = this.props.HeaderAction;
return <div><Header deTails={deTails} about={details.details} error={details.error} /></div>
}
}
function mapStateToProps(state) {
return {
details: state.details,
}
}
function mapDispatchToProps(dispatch) {
return {
HeaderAction: bindActionCreators(HeaderAction, dispatch),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(HeaderContainer);
Component.js
componentDidMount() {
console.log(this.props.about);
}
You won't receive updated state as in props in componentDidMount rather you can use:
componentWillReceiveProps(nextProps){ // this is UN_SAFE
}
or
static getDerivedStateFromProps(nextProps, prevState) { // this is recommended
}
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 :)