I am new to redux, Is this correct way of doing redux in following code, please?
This is a reducer method when action called to execute currentTime.
import { combineReducers } from 'redux';
import { UPDATE_TIME } from './actions';
import { Map } from 'immutable';
const initialState = Map({update:false, currentTime: ""});
function currentTime(state = initialState, action) {
switch (action.type) {
case UPDATE_TIME:
return {...state, update: true, currentTime: action.time };
default:
return state;
}
}
const currentTimeReducer = combineReducers({
currentTime
});
export default currentTimeReducer
There are multiple ways to do it
You can set the value using set() function
case UPDATE_TIME:
state = state.set('update', true);
return state.set('currentTime', action.time);
or even
case UPDATE_TIME:
return state.set('update', true)
.set('currentTime', action.time);
However this is not feasible when you have multiple changes
The other option is merge()
case UPDATE_TIME:
return state.merge({update: true, currentTime: action.time})
However in case of a nested state update you would need to do a deepMerge. See the details of mergeDeep
We use immutable JS to create new instance on each small change in the existing object. Immutable JS MAP has a set method to set attribute and return new instance of the object.
Here you can find api doc for MAP
import { combineReducers } from 'redux';
import { UPDATE_TIME } from './actions';
import { Map } from 'immutable';
const initialState = Map({update:false, currentTime: ""});
function currentTime(state = initialState, action) {
switch (action.type) {
case UPDATE_TIME:
let newState = state;
newState = newState.set('update', true );
newState = newState.set('currentTime', action.time);
return newState;
default:
return state;
}
}
const currentTimeReducer = combineReducers({
currentTime
});
export default currentTimeReducer
Look best practices in this doc
Related
I'm learning Redux and have come across an issue that I have not encountered before when using React without redux. I'm trying to display a piece of my state inside one of my components name loginStatus. The reducer I have setup this state with has an initial state but whenever I try and launch the application I get the console error:
Cannot read property 'loginStatus' of undefined
Here is my code:
Component
import React, {Component} from 'react';
import {connect} from 'react-redux';
import { bindActionCreators } from 'redux';
import * as authActions from './userAuthActions';
class App extends Component {
constructor(props) {
super(props);
}
render() {
if(typeof(this.props.userAuthReducer) !== 'undefined') {
test = this.props.userAuthReducer.loginStatus;
console.log(test)
} else {
console.log("it's undefined")
}
return (
<div className={"popup-logins " + this.props.userAuthReducer.loginStatus}>
</div>
);
}
}
const mapStateToProps = (state) => {
return {
userAuthReducer:state.userAuthReducer
};
};
const mapDispatchToProps = (dispatch) => {
return bindActionCreators(authActions,dispatch);
};
export default connect(mapStateToProps,mapDispatchToProps)(App);
userAuthActions.js
export const loginUser = () => {
return {
type:'loginUser',
loggedIn:true
}
}
export const toggleRegLog = () => {
return {
type:'toggleRegLog'
}
}
userAuthReducer
let initialState = {
loginStatus: "not-logged-in"
, toggleRegLog: 'login'
};
const userAuthReducer = (state = initialState, action) => {
switch (action.type) {
case 'loginUser':
let newState;
if (action.loggedIn) {
Object.assign({}, state, {
loginStatus: "logged-in"
})
}
else {
Object.assign({}, state, {
loginStatus: "not-logged-in"
})
}
return newState;
break;
default:
return state;
}
}
export default userAuthReducer;
combine reducers
import {combineReducers} from 'redux';
import userAuthReducer from './userAuthReducer';
function lastAction(state = null, action) {
return action;
}
export default combineReducers({
lastAction,userAuthReducer
});
What's strange is that I initially get a console.log of it's undefined" when I first start up the app and then immediately after I get the value "not-logged-in". I need to use this to hide/ show certain parts of my app if the user is logged in.
Normally if I use React without Redux I use this method all the time without any issues but can't understand what I might have done wrong here?
Thanks
You're not really assigning a value to newState in your reducer, so essentially you're returning undefined, which of course doesn't have a loginStatus property. Changing your reducer so to something like this will probably solve the problem:
let initialState = {
loginStatus: "not-logged-in"
, toggleRegLog: 'login'
};
const userAuthReducer = (state = initialState, action) => {
switch (action.type) {
case 'loginUser':
let newState;
if (action.loggedIn) {
newState = Object.assign({}, state, {
loginStatus: "logged-in"
})
}
else {
newState = Object.assign({}, state, {
loginStatus: "not-logged-in"
})
}
return newState;
break;
default:
return state;
}
}
export default userAuthReducer;
Object.assign returns a new object, applies the data from state and the last argument containing the loginStatus property and passes that to the newState variable, which gets returned at the end of the switch case.
Edit
This edit below makes it easier to reason about the logic in the reducer:
let initialState = {
loginStatus: "not-logged-in"
, toggleRegLog: 'login'
};
const userAuthReducer = (state = initialState, action) => {
switch (action.type) {
case 'loginUser':
if (action.loggedIn) {
return Object.assign(state, { loginStatus: "logged-in" })
}
return Object.assign(state, { loginStatus: "not-logged-in" })
default:
return state;
}
}
export default userAuthReducer;
Why do I get undefined returned when I access the state? I use the Redux DevTools and see the state correctly updated via an action but I just cannot access the state values for some reason. I get this sort of object returned when I access state.dog which seems wrong:
ct {size: 1, _root: pt, __ownerID: undefined, __hash: undefined, __altered: false}
Here is my container code:
import { connect } from 'react-redux';
import Message from '../../components/message';
const mapStateToProps = (state) => {
console.log(state.dog.hasBarked);
return {
message: state.dog.hasBarked ? 'Barked' : 'It is quiet',
};
};
export default connect(mapStateToProps)(Message);
Here is the dog reducer:
import * as Immutable from 'immutable';
import { MAKE_BARK } from '../actions/dog';
const initialState = Immutable.Map({
hasBarked: false,
});
const dogReducer = (state: Object = initialState, action: Object) => {
switch (action.type) {
case MAKE_BARK:
return state.set('hasBarked', action.payload);
default:
return state;
}
};
export default dogReducer;
Seems like you are using immutable. state.dog is not a simple js array but a immutable map or list. You can access it natively with state.dog.toObject().hasBarked.
I learned how to Redux with Teropa's incredible tutorial. However, his implementation uses Immutable and, while awesome, is something I want to remove from my current app's build. I'm trying to figure out how to maintain the following without relying on Immutable. I somehow need to modify reducer.js to take out Immutable, but the exported function keeps failing without state = Map().
Index.jsx
store.dispatch({
type: 'SET_STATE',
state: { ... }
});
action_creators.js
export function setState(state) {
return {
type: 'SET_STATE',
state
};
}
reducer.js
import { Map } from 'immutable';
function setState(state, newState) {
return state.merge(newState);
}
export default function(state = Map(), action) {
switch (action.type) {
case 'SET_STATE':
return setState(state, action.state);
}
return state;
}
function setState(state, newState) {
return {
...state,
...newState
};
}
export default function(state = {}, action) {
switch (action.type) {
case 'SET_STATE':
return setState(state, action.state);
}
return state;
Check how I use it in my state management library https://github.com/nosovsh/reduceless/blob/master/src/wrapReducerWithSetGlobalState.js#L9
I have to write mapStateToProps like below
function mapStateToProps(state, ownProps) {
return {
node: ownProps.info? state.TreeNodeReducer.tree[ownProps.info.path] : {}
};
}
combine reducer:
import { combineReducers } from 'redux';
import TreeNodeReducer from './TreeNodeReducer'
const rootReducer = combineReducers({
TreeNodeReducer
});
export default rootReducer;
reducers/TreeNodeReducer.js
import { OPEN_NODE, CLOSE_NODE, GET_NODES } from '../constants/NodeActionTypes';
const initialState = {
open: false,
info: {}
}
class NodeModel {
constructor(path, type, right) {
this.name = path;
this.path = path;
this.type = type;
this.right = right;
}
}
let lastId = 0;
function getFileList() {
var testNodes = []
for (var i=0; i< 3; i++) {
testNodes.push(new NodeModel(lastId,'d', i.toString()))
lastId++;
}
return testNodes
}
const getNodes = (state, action) => {
var { path } = action
var tree = state.tree ? state.tree : {}
tree[path] = getFileList(path)
return {
...state,
tree:tree
};
};
export default function (state = initialState, action) {
switch (action.type) {
case OPEN_NODE:
return { ...getNodes(state, action), open:true };
case GET_NODES:
return getNodes(state, action);
case CLOSE_NODE:
return {
...state,
open:false
};
default:
return state;
}
}
because state.TreeNodeReducer.tree is a global state, which hold all node info, I want to access it by state directly.State return by a reducer would wrap by the reducer's name, which is not convenient for simple project. Office doc don't provide the way.Any idea?
PS: I have to say I want to keep using combineReducers, I see some project not use it, directly pass reducer to store which can achieve my purpose but not good.
Achieving what you desire depends a bit on the state handled by the TreeNodeReducer.
If that reducer just handles the tree property, like this:
function treeNodeReducer(state = initialState, action) {
switch (action.type) {
case SOME_ACTION:
return Object.assign({}, state, { tree: action.tree, ... })
default:
return state
}
}
I'd say change the reducer to eliminate the tree property and merge it directly with the state, like this:
function treeNodeReducer(state = initialState, action) {
switch (action.type) {
case SOME_ACTION:
return Object.assign({}, state, action.tree)
default:
return state
}
}
This way we can access the tree object in mapStateToProps with state.treeNodeReducer.
But this is still not what we want. We want to rename treeNodeReducer to tree. There are two solutions for that:
Either:
import { combineReducers } from 'redux';
import TreeNodeReducer from './TreeNodeReducer'
const rootReducer = combineReducers({
tree: TreeNodeReducer
});
export default rootReducer;
Or:
import { combineReducers } from 'redux';
import tree from './TreeNodeReducer'
const rootReducer = combineReducers({
tree
});
export default rootReducer;
This way we can access the tree object in mapStateToProps with state.tree.
This is my reducrer:
export default function dashboardReducer(state=initialState.alerts, action){
switch(action.type){
case constActions.GET_ALERTS_SUCCESS:
return action.alerts;
case constActions.GET_WL_STATISTICS_SUCCESS:
return action.wlStatistics;
default:
return state;
}
};
My root reducer:
const rootReducer = combineReducers({
dashboard
});
In the component, this is the mapStateToProps:
function mapStateToProps(state, ownProps){
return{
alerts: state.dashboard
};
}
Now I have 2 actions GET_ALERTS_SUCCESS and GET_WL_STATISTICS_SUCCESS.
In the component I have the props for actions.alerts, but how can I get a reference to action.wlStatistics in the component? can i call the reducer with an action type?
Your dashboardReducer either returns 'alerts' initialState OR 'alerts' OR 'wlStatistics' for the next state. It should return an object with both of those action payloads as properties:
const initialState = {
alerts: null,
wlStatistics: null
};
export default function dashboardReducer(state=initialState, action){
switch(action.type){
case constActions.GET_ALERTS_SUCCESS:
return Object.assign({}, state, { action.alerts });
case constActions.GET_WL_STATISTICS_SUCCESS:
return Object.assign({}, state, { action.wlStatistics });
default:
return state;
}
};
Your props will now be mapped as
this.props.alerts
and
this.props.wlStatistics
Whenever either action updates the state in the 'dashboardReducer', your component will re-render/receiveProps and the props will be updated with the new values