Currently in my React-Redux app, I would like to use React to set the state of show to either false, or true. When set to true, the app will initialize. (There are multiple components, so it would make sense to do this using react/redux.)
However, my current issue is that even though I have connected my app using react redux, and my store using provider, the dispatch action will be called, without updating the store(I am using redux dev tools to double check, as well as in app testing).
I have attached the code I feel is relevant, however, the entire code base is available as a branch specifically made for this question here. I have spent quite sometime on this(actually an understatement) and any contributions would be greatly appreciated.
Component Relevant Code
hideBlock(){
const{dispatch} = this.props;
dispatch(hideBlock);
}
return(
<div className = "works">
<button id="show-block" type="button" className="show-hide-button" onClick={this.showBlock}>show</button>
<button id="hide-block" type="button" className="show-hide-button" onClick={this.hideBlock}>Hide</button>
</div>
);
function mapStateToProps(state) {
const {environment} = state;
return{
environment
}
}
export default connect(mapStateToProps)(Form);
Action
import * as types from "../constants/ActionTypes";
export function showBlock(show) {
return {
type: types.SHOW,
show: true
};
}
export function hideBlock(hide) {
return {
type: types.HIDE,
show: false
};
}
Reducer
import * as types from "../constants/ActionTypes";
const initialState = {
show: false
};
export default function environment(state = initialState, action) {
switch(action.type) {
case types.HIDE:
return Object.assign({}, state, {
type: types.HIDE
});
case types.SHOW:
return Object.assign({}, state, {
type: types.SHOW
});
default:
return state;
}
}
Thank you, and once again any help is very much so appreciated.
So, I asked a co-worker for help and it turns out that I was returning my action as an object instead of a function. So, for instance, changing the following code:
hideBlock(){
const{dispatch} = this.props;
dispatch(hideBlock);
}
to
hideBlock(){
const{dispatch} = this.props;
//change hideBlock to hideBlock()
dispatch(hideBlock());
}
solved the issue. Thanks Andrew!
It looks like state.show is set in initialState but never modified in any of the cases inside the reducer. The action has show: true, but the reducer never uses this to update the state.
Here's a simplified reducer that updates state.show based on the action's show field:
export default function environment(state = initialState, action) {
switch(action.type) {
case types.HIDE:
case types.SHOW:
return Object.assign({}, state, {
show: action.show
});
default:
return state;
}
}
Alternatively, because action.show and action.type have the same data, you could remove show from the actions and rely on the action type:
export default function environment(state = initialState, action) {
switch(action.type) {
case types.HIDE:
return Object.assign({}, state, {
show: false
});
case types.SHOW:
return Object.assign({}, state, {
show: true
});
default:
return state;
}
}
Related
I'm trying to push a new value in the store's state. It works fine the first time I click on the button "Add item", but the second time I got the following error: "state.basket.push is not a function". I configure the action to console log the state and got the following results:
1st click: {...}{basketItems: Array [ "44" ]}
2nd click: Object {basketItems: 0 }
Why the variable type is changing from array to an int?
Here is the code for the rendered component:
function Counter({ basketItems,additem }) {
return (
<div>
<button onClick={additem}>Add item</button>
</div>
);
}
const mapStateToProps = state => ({
basketItems: state.counterReducer.basketItems,
});
const mapDispatchToProps = dispatch => {
return {
additem: ()=>dispatch({type: actionType.ADDITEM, itemName:'Dummy text' }),
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(Counter);
And the reducer looks like this:
import {ADDITEM} from "../actions/types";
const initialState = { basket: [], };
export default function reducer(state = initialState, action) {
switch (action.type) {
case ADDITEM:
console.log(state);
// let newBasket = state.basket.push('44');
return {
...state,
basket: state.basket.push('44')
};
default:
return state;
}
}
I'm copying the state before updating the basket to prevent weird behaviors.
There's two problems here:
state.basket.push() mutates the existing state.basket array, which is not allowed in Redux
It also returns the new size of the array, not an actual array
So, you're not doing a correct immutable update, and you're returning a value that is not an array.
A correct immutable update here would look like:
return {
...state,
basket: state.basket.concat("44")
}
Having said that, you should really be using our official Redux Toolkit package, which will let you drastically simplify your reducer logic and catch mistakes like this.
I am creating a single page web application with React, with the state stored using Redux. When I access something like state.workspace.guest.data in mapStateToProps() it causes an exception crashing the application.
It appears mapStateToProps() is invoked before componentDidMount(), which invokes this.props.getGuests() action that actually causes state.workspace.guest to be initialized. The page loads properly when I manually enter the URL and press enter. However, mapStateToProps() causes an exception when I navigate through the application, with state.workspace.guest being undefined.
A possible solution would be to check if state.workspace.guest is defined. However, I feel there is a better way to do it?
The answers in the following link suggest to use selectors. I am not sure if this is the solution to my problem, given I am new to Redux.
react js mapStateToProps triggers Uncaught TypeError: Cannot read property 'map' of undefined
Here are the mapping functions.
function mapDispatchToProps(dispatch) {
return bindActionCreators({
getGuests: Actions.getGuests
}, dispatch);
}
function mapStateToProps(state, ownProps) {
return {
guests : state.workspace.guest.data
}
}
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(GuestSearchTable));
The componentDidMount() function which actually triggers an action which in turn loads the data.
componentDidMount() {
this.props.getGuests();
}
Here is the reducer.
const initialState = {
data : []
};
function guestReducer(state = initialState, action) {
switch (action.type) {
case Actions.GET_GUESTS: {
return {
...state,
data : action.payload.data
};
}
case Actions.GET_GUEST: {
return {
...state,
guest: action.payload.data
};
}
case Actions.SAVE_GUEST: {
return {
...state,
guest: action.payload.data
};
}
default: {
return state;
}
}
};
I expect the initial value of the state.workspace.guest to be { data : [] }. But the actual value is undefined. The same problem appears in all the other pages.
You are trying to access data from guest key.
function mapStateToProps(state, ownProps) {
return {
guests : state.workspace.guest.data
}
}
I think you want to access state.workspace.data or you have to change key name to guests in reducer and the intialize state as follow:
return {
guests: state.workspace.guests
}
I can't get my reducer to return updated state.
The action (confirmed with debugger) is an array of objects - something like: [{name: "test"}, {name: "second_test"}]
I think something must be wrong with my spread operator, although I've also tried Object.assign() in the debugger and that seems to return the expected result.
My components is apparently just getting the default state. Here's my reducer code. Any help would be appreciated. Thank you!
const initialState = {
current: {},
all: []
}
export default function deckReducer(state = initialState, action) {
switch(action.type) {
case 'CREATE_DECK':
return {...state, all: [...state.all, action.payload]}
case 'FETCH_DECKS':
debugger;
return {...state, all: action.payload}
default: return state
}
}
I had this very issue recently (exactly as you describe), and I resolved the problem by using the immutability-helper package which is listed here on the ReactJS docs.
This approach allows you to delegate the heavy lifting of state immutability and nested state updates. You should find this resolves your issue:
import update from 'immutability-helper';
const initialState = {
current: {},
all: []
}
export default function deckReducer(state = initialState, action) {
switch(action.type) {
case 'CREATE_DECK': {
/* Add payload to nested state.all list */
return update(state, { all : { $push : [action.payload] }});
}
case 'FETCH_DECKS': {
/* Replace state.all with incoming payload */
return update(state, { all : { $set : action.payload }});
}
default: return state
}
}
I have difficulties understanding how the global store should be dispatched with actions from my react components. I'm very new to the whole concept and I don't get my component to re-render on dispatch(). I invested deeply and found that although the reducer returns the updated global state the values are not mapped back to the component props. But a proper function (mapStateToProps) is defined.
Minimal example: Please have a look at this plunkr (or minimal example code below).
Explanation:
I have a component Controls with a method switchActivities. The component is connected to the global store and my global state is available in the component props.
var PullFromStoreControls = function (state) {
return {
concrete: state.me.bool,
nested: state.me.nested.later
}
}
var PushToStoreControls = function (dispatch) {
return {
switchFilter: function (type, value) {
dispatch({
type: 'SET_VAL',
value: value
})
}
}
}
Controls = connect(
PullFromStoreControls,
PushToStoreControls
)(Controls)
I wired the variable state.me.bool to props.conrete to avoid side-effects of a deep state tree. I also connected a dispatcher to update the global state via the reducer. However, if the dispatcher is invoked by 'switchActivities' the new value of the checkbox makes it up to the reducer correctly and then gets lost. The global state seems never updated correctly.
What am I missing?
index.html
<!DOCTYPE html>
<html>
<head>
<script data-require="react#*" data-semver="15.5.0" src="https://cdnjs.cloudflare.com/ajax/libs/react/15.5.0/react.min.js"></script>
<script data-require="react#*" data-semver="15.5.0" src="https://cdnjs.cloudflare.com/ajax/libs/react/15.5.0/react-dom.min.js"></script>
<script data-require="redux#*" data-semver="3.2.1" src="https://cdnjs.cloudflare.com/ajax/libs/redux/3.2.1/redux.js"></script>
<script data-require="react-redux#*" data-semver="4.4.5" src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/4.4.5/react-redux.js"></script>
<!-- support for jsx on my localhost, on Plunkr jsx will be automatically transpiled to js -->
<script src="https://unpkg.com/babel-standalone#6/babel.min.js"></script>
<script type = "text/babel" src="minimal.jsx"></script>
</head>
<body>
<div id="app"></div>
</body>
</html>
minimal.jsx
function d(x){
console.log(x);
}
const AppState = {
me: {
bool: false,
nested: {
later: "I also want to change values deeper in the tree."
}
}
}
function reducer(state, action) {
if (state === undefined) {
return AppState;
}
switch (action.type) {
case 'SET_VAL':
state.me.bool = action.value;
break;
}
console.log("The reducer returns the changed state");
console.log(state);
return state;
}
// create global store with initial configuration `AppState`
const store = Redux.createStore(reducer, AppState);
// create provider and connect function not having webpack available
var Provider = ReactRedux.Provider;
var connect = ReactRedux.connect;
class Controls extends React.Component {
switchActivities() {
console.log("------------------ clicked ------------------");
console.log("set value from:");
console.log(this.props.concrete);
// inverse current state
const state = !this.props.concrete;
// output
console.log("to:");
console.log(state);
// call dispatcher
this.props.switchFilter("show_act", state);
}
render() {
console.log("I would like to re-render if this.props.concrete has updated!");
const switchActivities = <MapSwitch name="switch_act" label="Show something" checked={this.props.concrete} onChange = {() => this.switchActivities()} />;
return <div id="map-controls">
{switchActivities}
</div>
}
}
var PullFromStoreControls = function (state) {
return {
concrete: state.me.bool,
nested: state.me.nested.later
}
}
var PushToStoreControls = function (dispatch) {
return {
switchFilter: function (type, value) {
dispatch({
type: 'SET_VAL',
value: value
})
}
}
}
Controls = connect(PullFromStoreControls, PushToStoreControls)(Controls)
const MapSwitch = ({name, label, checked, onChange}) => (
<label for={name}>{label}
<input type="checkbox" className="switch" data-toggle="switch"
name={name}
onChange={onChange}
checked={checked}
/>
</label>
)
ReactDOM.render(
<Provider store={store}>
<Controls/>
</Provider>,
document.getElementById('app')
);
Solution (update)
It is a difference whether I alter the state object within the reducer and return that or if I create a new object and return that. Although both returned objects are the same the former is a reference while the latter is a real new variable. I learned that the hard way.
Good explanation:
https://github.com/reactjs/redux/blob/master/docs/recipes/reducers/ImmutableUpdatePatterns.md
function reducer(state, action) {
switch (action.type) {
case 'SET_VAL':
return {
...state,
me : {
...state.me,
bool: action.value
}
}
}
return state;
}
Your problem is that you are mutating state. The second principle of Redux is that the state should never be mutated directly - rather, your reducer is a pure function which should return a new state: https://redux.js.org/docs/introduction/ThreePrinciples.html#changes-are-made-with-pure-functions
Your issue is here:
switch (action.type) {
case 'SET_VAL':
// you are attempting to mutate state.me.bool - this is an antipattern!
state.me.bool = action.value;
break;
}
Instead, write your reducer in a way that returns a new copy of state.
function reducer(state, action) {
switch (action.type) {
case 'SET_VAL':
return {
...state,
me : {
...state.me,
bool: action.value
}
};
default:
return state;
}
}
Notice that you need to copy each level of state for nested structures. I'm using the Object spread operator here, but Object.assign() with all work. Hope this helps!
After starting my development with reactjs and redux I was thinking that it would be better to work with immutable.js while using redux.
But... Maybe am I retarded or one need some practice before using it properly, everything crashed.
And if you can help understand what's wrong here, it would be awesome!
So, here was my first code:
export function posts(state = {
isFetching: true,
didInvalidate: false,
items: []
}, action) {
switch (action.type) {
case INVALIDATE_REQ:
return Object.assign({}, state, {
didInvalidate: true
});
case REQUEST_POSTS:
return Object.assign({}, state, {
isFetching: true,
didInvalidate: false
});
case RECEIVE_POSTS:
return Object.assign({}, state, {
isFetching: false,
didInvalidate: false,
items: action.posts
});
default:
return state;
};
};
That I transformed this way:
const initPostState = Map({
isFetching: true,
didInvalidate: false,
items: []
});
export function posts(state = initPostState, action) {
switch (action.type) {
case INVALIDATE_REQ:
return state.set('didInvalidate', true);
case REQUEST_POSTS:
return state.set({
isFetching: true,
didInvalidate: false
});
case RECEIVE_POSTS:
return state.set({
isFetching: false,
didInvalidate: false,
items: action.posts
});
default:
return state;
};
};
And my container MapStateToProps:
function mapStateToProps(state) {
const {
posts: isFetching,
posts
} = state.posts;
console.log(state);
...
So the problem is, how do I access my states?
The console of state report that:
I'm lost! Help!
Never use toJS() in mapStateToProps. From the Redux docs:
Converting an Immutable.JS object to a JavaScript object using toJS() will return a new object every time. If you do this in mapSateToProps, you will cause the component to believe that the object has changed every time the state tree changes, and so trigger an unnecessary re-render.
If your app requires high performance, you have to use Immutable.js in your Dumb components with their get() and getIn() helpers.
Also since ImmutableJS has versatile API, in most cases it removes the
need for helper libraries like lodash.
But it mosts cases, you can use the code they proposed to separate Immutable.js from your components by sacrificing performance.
A HOC component (with the latest immutable.js using the isIterable predicate):
import React from 'react';
import { Iterable } from 'immutable';
export const toJS = (WrappedComponent) =>
(wrappedComponentProps) => {
const KEY = 0;
const VALUE = 1;
const propsJS = Object.entries(wrappedComponentProps)
.reduce((newProps, wrappedComponentProp) => {
newProps[wrappedComponentProp[KEY]] =
Iterable.isIterable(wrappedComponentProp[VALUE])
? wrappedComponentProp[VALUE].toJS()
: wrappedComponentProp[VALUE];
return newProps;
}, {});
return <WrappedComponent {...propsJS} />
};
A HOC component (with the latest immutable.js using the isImmutable predicate):
import React from 'react';
import { isImmutable } from 'immutable';
export const toJS = (WrappedComponent) =>
(wrappedComponentProps) => {
const KEY = 0;
const VALUE = 1;
const propsJS = Object.entries(wrappedComponentProps)
.reduce((newProps, wrappedComponentProp) => {
newProps[wrappedComponentProp[KEY]] =
isImmutable(wrappedComponentProp[VALUE])
? wrappedComponentProp[VALUE].toJS()
: wrappedComponentProp[VALUE];
return newProps;
}, {});
return <WrappedComponent {...propsJS} />
};
How to use:
import { connect } from 'react-redux';
import { toJS } from './to-js';
import DumbComponent from './dumb.component';
const mapStateToProps = (state) => {
return {
/**
obj is an Immutable object in Smart Component, but it’s converted to a plain
JavaScript object by toJS, and so passed to DumbComponent as a pure JavaScript
object. Because it’s still an Immutable.JS object here in mapStateToProps, though,
there is no issue with errant re-renderings.
*/
obj: getImmutableObjectFromStateTree(state)
}
};
export default connect(mapStateToProps)(toJS(DumbComponent));
There are a lot of links to move on in the Redux immutable-js-best-practices documentation section.
You need to use get method from immutableJS
Use state.get('didInvalidate') to access the value of didInvalidate, similarly for other values.
If you're using a javascript object, then you can get it like state.get('something').toJS()
Doing this should give you the idea
function mapStateToProps(state){
const isFetching = state.get('isFetching'),
const items = state.get('items').toJS();
}
If you use ImmutableJS with redux, you're whole app state is an immutable. In the connect function, use state.get("posts") to access to the posts state. Then you will have to use get() to access the posts state properties. Or you can use toJs() to avoid having to manipulate immutable inside your component.