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
Related
We have set up a project with redux. In this project, we get an info objecat from an api and insert it into the store. Now we noticed that the function components re-render even if the api return the same state as in the previous request.
We think it's because we are overwriting the store but we are not sure.
ChatContainer.js
const mapStateToProps = function (state) {
return {
content: state.info.content,
loading: state.info.loading,
}
}
const ChatContainer = connect(
mapStateToProps,
)(Chat)
export default ChatContainer
Chat.js
function Chat(props) {
const { content, loading } = props;
return (
<Info content={content} loading={loading} />
)
}
action.js
export function setInfo(info) {
return {
type: SET_INFO, info: {
content: info,
loading: false
}
}
}
reducer.js
function setInfo(state = { content: [], loading: true }, action) {
switch (action.type) {
case SET_INFO:
return action.info
default:
return state
}
}
const appReducer = combineReducers({
...
info: setInfo,
...
})
export default appReducer
If state.info.content is an object, every time you change it with setInfo it will have a new reference. React-redux does a shallow compare on the result of mapStateToProps, so if your content is a different reference every time your component will re-render. connect HOC has an options parameter that you can use to implement a custom compare.
My advice would be to add a check to your setInfo or to the code calling setInfo and not calling your API if data is already loaded/didn't change(don't know your business logic).
I've tried all of the related questions here in Stack Overflow and still didn't find a solution to this problem.
I have a reducer called me and I'm trying to update an array of objects in it called folders, whenever I update the me reducer the component doesn't update.
Here's how I'm updating the reducer in my component:
class ComponentA extends Component {
...
updateUploadedFiles(file) {
console.log(this.props.store);
const newFolders = this.props.me.folders.map(
folder =>
folder._id === file.parent._id
? {
...folder,
files: [...folder.files, file.file]
}
: folder
);
this.props.updateMe({
...this.props.me,
folders: newFolders
});
}
...
}
function mapStateToProps(state) {
return {
me: state.me,
path: state.path,
filesToUpload: state.uploads
};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators(
{
updatePath,
updateMe,
updateUploads
},
dispatch
);
}
export default connect(mapStateToProps, mapDispatchToProps, null, {
pure: false
})(Upload);
this is my updateMe action's code:
export const updateMe = state => ({
type: "UPDATED_ME",
payload: state
});
And this is the me reducer's code:
export default function(state = "NOT_AUTHENTICATED", action) {
switch (action.type) {
case "UPDATED_ME":
return action.payload;
default:
return state;
}
}
Also here's how I'm combining the reducers:
import me from "./me";
...
import { combineReducers } from "redux";
const reducers = combineReducers({
me,
...
});
export default reducers;
This is not how redux works.
In order to update any part of your Redux store you must dispatch an action in order to let Redux "know" that the store changed and update any dependent component.
You state object must be immutable.
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;
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
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.