The State
The tracing results
Each time when I click the button, the dispatch run twice, like the picture above.
This is the AppBar component and mapDispatchToProps function.
const mapStateToProps = state => {
return {
title: state.title
};
};
const mapDispatchToProps = {
onClick: () => {
return {
type: "TOGGLE_SIDEBAR"
};
}
};
const AppBar = props => (
<Box>
<Button icon={<Notification />} onClick={props.onClick} />
</Box>
);
const AppBatContainer = connect(
mapStateToProps,
mapDispatchToProps
)(AppBar);
export default AppBatContainer;
This is the reducer
import {
TOGGLE_SIDEBAR
} from "../constants/action-types";
const initialState = {
showBar: false
};
const rootReducer = (state = initialState, action) => {
switch (action.type) {
case TOGGLE_SIDEBAR:
return Object.assign({}, state, {
showBar: !state.showBar
});
default:
return state;
}
};
export default rootReducer;
This is the store.js
import { createStore, applyMiddleware, compose } from "redux";
import reduxThunk from "redux-thunk";
import rootReducer from "./reducers/index";
const composeEnhancers =
typeof window === "object" && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
// Specify extension’s options like name, actionsBlacklist, actionsCreators, serialize...
trace: true,
traceLimit: 20
})
: compose;
const enhancer = composeEnhancers(applyMiddleware(reduxThunk));
const store = createStore(rootReducer, enhancer);
export default store;
All libraries are well imported. I tried remove redux-thunk, it is still the same result.
Thanks in advance.
Ref
React-Redux Counter example action firing twice I have viewed this questions on stackoverflow, and tried the answers, but no of them solved it.
Related
I am unable to access my state values saved in store on any screen. values reach to actions but when I access it from store it returns always undefined.
Every thing is in separate files
Reducer 1
import * as Actions from '../actionTypes'
import initialStore from './initialStore'
const homeModuleReducer = (state = initialStore, action) => {
switch (action.type) {
case Actions.SET_PROFILE_ONE:
console.log('call here')
return {
...state,
currentUser: action.profile
}
default:
return state;
}
}
export default homeModuleReducer
Reducer 2
import * as Actions from '../actionTypes'
import initialStore from './initialStore'
const loginModuleReducer = (state = initialStore, action) => {
switch (action.type) {
case Actions.SET_PROFILE:
return {
...state,
currentUser: action.profile
}
case Actions.SET_INITIAL_LOADING_STATUS:
return {
...state,
isInitialLoadingDone: action.loadingStatus
}
default:
return state;
}
}
export default loginModuleReducer
Combine Reducer
import { combineReducers } from 'redux'
import homeModuleReducer from './homeModuleReducer'
import loginModuleReducer from './loginModuleReducer'
export default combineReducers({
homeModuleReducer,
loginModuleReducer,
})
Store
import { createStore, applyMiddleware } from 'redux'
import thunkMiddleware from 'redux-thunk';
import rootReducer from './reducers'
let store = createStore(rootReducer, applyMiddleware(thunkMiddleware));
export default store;
usage:
const mapStateToProps = (state) => ({
stateLoaded: state.rootReducer.isInitialLoadingDone,
profile: state.rootReducer.currentUser
});
Error:
undefined is not an object (evaluating 'state.rootReducer.isInitialLoadingDone')
You already combined your reducers so you can access reducer by it's key like this :
const mapStateToProps = (state) => ({
stateLoaded: state.homeModuleReducer.isInitialLoadingDone, // here homeModuleReducer is just an example. Change with reducer key in which isInitialLoadingDone is belong
profile: state.loginModuleReducer.currentUser
});
With hooks its much easier
wrap your root with store
import {Provider} from 'react-redux';
const App = () => {
return (
<Provider store={store}>
<Yourcomponent/>
</Provider>
);
};
export default App;
Access your state in any component like this
import { useSelector } from "react-redux";
const state = useSelector(state => state)
I'm trying to setup a redux environment and I'm stumped as to why I am having this behavior.
I have my root state here on my reducer:
export type RootState = {
counter: number;
};
const initialState: RootState = { counter: 0 };
const staticLesson = (state = initialState, action: any) => {
switch (action.type) {
case "INCREMENT":
return state.counter + 1;
default:
return state;
}
};
export default staticLesson;
I then combine it here:
import staticLesson from "./staticLesson/index";
import { combineReducers } from "redux";
const rootReducer = combineReducers({
staticLesson,
});
export default rootReducer;
I wrap my App with the Provider:
import { createStore } from "redux";
import rootReducer from "./redux/reducers";
import { Provider } from "react-redux";
import { composeWithDevTools } from "redux-devtools-extension";
const store = createStore(rootReducer, composeWithDevTools());
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
Yet, when I try to log it in my component, the value is undefined.
const Component = () => {
const staticLesson = useSelector((state: RootState) => state);
const dispatch = useDispatch();
console.log("counter", staticLesson.counter); <-- logs undefined
return (
...
)
}
What I don't understand is why if I just log staticLesson, it appears as expected in my console. But if I try to access the value on the object, instead of getting the value 0 for my counter ... I get undefined. staticLesson is showing up in my devTools, it's there ... I'm stumped, what am i messing up?
I'm pretty sure you should be returning the staticLesson property from the state in your useSelector call. In useSelector you're returning the entire state. So it should be:
const staticLesson = useSelector((state: RootState) => state.staticLesson);
Solution(updated):
I thought any action would cause react-redux-connect to call the mapState functions but when an action doesn't change anything then this is not the case.
I have a localStorage module that dispatches actions but don't change state, instead thy will write to localStorage. The module has selectors that are used in the containers but they won't get called until the state actually changes so the UI would only show correctly after another action was dispatched that would change the state.
Problem
When I put the store on window (window.store=store), add a console.log in the mapStateToProps, then in the console I dispatch an action: store.dispatch({type:'some action'}) then the console.log of the mapStateToProps does not show.
I do memoize the result but the mapStateToProps should be called see here
Full code is here and running example here (you can open a console clicking on 'console' link in the right bottom of the screen).
package.json
store.js:
import { createStore } from 'redux';
export default (initialState, reducer) => {
const store = createStore(
reducer,
initialState,
window.__REDUX_DEVTOOLS_EXTENSION__ &&
window.__REDUX_DEVTOOLS_EXTENSION__()
);
window.store = store;
return store;
};
app.js
import React from 'react';
import { connect } from 'react-redux';
import './App.css';
import createStore from './store';
import { Provider } from 'react-redux';
import initCounter from './components/Counter';
import {
createWrapper,
memoize,
} from './components/#common';
const COUNTER = 'COUNTER';
const selectCounterState = state => state.myCounter;
const counter = initCounter({
actionWrapper: createWrapper(COUNTER, 'counter1'),
selectors: { myState: selectCounterState },
connect,
memoize,
});
const initialState = {
myCounter: counter.initialState,
};
const reducer = (state = initialState, action) => {
if (action.emittedBy === COUNTER) {
return {
...state,
myCounter: counter.reducer(
selectCounterState(state),
action.payload
),
};
}
return state;
};
const store = createStore(initialState, reducer);
const Counter = counter.container;
const App = () => (
<Provider store={store}>
<Counter id="counter1" parentId={[]} />
</Provider>
);
export default App;
component/Counter/index:
import component from './component';
const INCREASE = 'INCREASE';
const reducer = (state, action) => {
if (action.type === INCREASE) {
return { ...state, count: state.count + 1 };
}
return state;
};
const makeState = memoize =>
memoize((id, parentId, { count }) => ({
id: parentId.concat(id),
parentId,
count,
}));
const mapStateToProps = ({ myState }, memoize) => () => {
const newState = makeState(memoize);
return (state, ownProps) =>
console.log('in map state to props', new Date()) ||
newState(
ownProps.id,
ownProps.parentId,
myState(state)
);
};
export default ({
actionWrapper,
selectors,
connect,
memoize,
}) => {
const actions = {
increase: ({ id }) =>
actionWrapper({
type: INCREASE,
id,
}),
};
const container = connect(
mapStateToProps(selectors, memoize),
actions
)(component);
return {
container,
reducer,
initialState: { count: 0 },
};
};
components/counter/component.js:
import React from 'react';
export default props => (
<div>
<button onClick={() => props.increase(props)}>
add
</button>
{props.count}
</div>
);
This problem was caused because I had a localStorage module that did dispatch actions but did not change the state, instead it would write to localStorage.
The module had selectors that would get the right data and the containers would use them to build the correct state but since the dispatched action did not change the state in the redux store react-redux would skip calling my mapState functions (probably memoizing state in Provider).
The solution is to let the root reducer return a new state reference {...state} so any action would cause the mapState functions to be called.
Your example codepen works just fine, you just have to trigger an action that gets past your top level guard and is of the expected structure, as to not cause any followup errors:
Post this into the console of your codepen:
store.dispatch({emittedBy: "COUNTER", type: "COUNTER -> INCREASE", id: "counter1", payload: {type: "INCREASE", id: ["counter1"]}})
Why am I getting TypeError: notes.map is not a function in the following part of my Notes component? {notes.map((note) => (
components/Notes.js
import React, { Component } from "react"
import { connect } from "react-redux"
const mapStateToProps = (state) => {
return { notes: state.notes }
}
const NotesList = ({ notes }) => (
<ul className="notes_list">
{notes.map((note) => (
<li className="note_body" key={note.id}>
<div dangerouslySetInnerHTML={{ __html: note.body }}></div>
</li>
))}
</ul>
)
const Notes = connect(mapStateToProps)(NotesList);
export default Notes;
reducers/notes.js
import * as types from '../actions/actionTypes'
const initialState = {
notes: [{id: 1, body: "hey"}]
}
function notes(state = initialState, action) {
switch (action.type) {
...
default:
return state
}
}
export default notes
root reducer
import { combineReducers } from 'redux'
import notes from './notes'
import noteForm from './noteForm'
const rootReducer = combineReducers({
notes,
noteForm
})
export default rootReducer
app.js
import React, { Component } from 'react';
import Notes from './components/Notes'
import NoteForm from './components/NoteForm'
const App = () => (
<div className="App">
<NoteForm />
<Notes />
</div>
)
export default App
---upd
store
import { createStore, applyMiddleware } from 'redux'
import rootReducer from '../reducers'
import {ping} from './enhancers/ping'
import thunk from 'redux-thunk'
export default function configureStore(initialState) {
const store = createStore(rootReducer, initialState, applyMiddleware(thunk, ping))
return store
}
index.js
...
import configureStore from './store/configureStore'
const store = configureStore()
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root'));
Are you providing the connect function with a store? If so, everything looks fine to me -- it'd be useful to see your store initialization code.
Create a store with createStore from redux and wrap your App with a Provider from react-redux:
app.js
...
import notesReducer from './reducers/notes'
import { createStore } from 'redux'
const store = createStore(notesReducer) // use combineReducers when you add a 2nd reducer
const App = () => (
<Provider store={store}>
<div className="App">
<NoteForm />
<Notes />
</div>
</Provider>
)
If you already have a Provider somewhere else, check if everything's okay there.
Here's my fully working example - I copied your Notes.js file and wrote up the following App.js - no errors whatsoever (I bundled store creation and reducers all in one file for simplicity):
import React, { Component } from 'react';
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import Notes from './Notes'
const initialState = {
notes: [{
id: 1,
body: 'testing'
}]
}
function notes(state = initialState, action) {
switch (action.type) {
default:
return state
}
}
const store = createStore(notes)
export default () => (
<Provider store={store}>
<Notes />
</Provider>
)
Update for combineReducers
When using combineReducers, your reducers' initialState will already be namespaced in the store under the key which was used in the combineReducers call. Change your notes reducer's initialState to an array:
import * as types from '../actions/actionTypes'
// no need for { notes: [] } here, combineReducers({ notes }) will take care of that
const initialState = [{ id: 1, body: 'hey' }]
function notes(state = initialState, action) {
switch (action.type) {
...
default:
return state
}
}
export default notes
When you get map isn't a function that means you're not calling the data correctly.
I see in notes reducer page you're not calling the states correctly
function notes(state = initialState, action) {
switch (action.type) {
...
default:
return state
}
}
Change it to:
function notes(state = initialState.notes, action) {
switch (action.type) {
...
default:
return state
}
}
The regular way to do this is to not putting your states in an array
const initialState = {
id: 1,
body: "hey"
}
function notes(state = initialState, action) {
switch (action.type) {
...
default:
return state
}
}
This will works fine
since my root reducer has the following structure
const rootReducer = combineReducers({
notes
})
I can reach notes by state.notes.notes
const mapStateToProps = (state) => {
return { notes: state.notes.notes }
}
having the following initial state structure for notes
const initialState = {
notes: []
}
I tried simple react,redux,ajax working example and followed Reddit API tutorial, but I cannot create store and get error:
Uncaught Error: Expected the reducer to be a function.
index.jsx
...
import { createStore, applyMiddleware } from 'redux'
var thunkMiddleware = require('redux-thunk');
var createLogger = require('redux-logger');
var rootReducer = require('./reducers.js');
const loggerMiddleware = createLogger();
function configureStore(initialState) {
return createStore(
rootReducer,
initialState,
applyMiddleware(
thunkMiddleware,
loggerMiddleware
)
)
}
const store = configureStore();
...
rootReducer.js
import { combineReducers } from 'redux';
function products(state = {
isFetching: false,
didInvalidate: false,
items: []
}, action) {
switch (action.type) {
case 'REQUEST_PRODUCTS':
return Object.assign({}, state, {
isFetching: true,
didInvalidate: false
})
case 'RECEIVE_PRODUCTS':
return Object.assign({}, state, {
isFetching: false,
didInvalidate: false,
items: action.posts,
lastUpdated: action.receivedAt
})
default:
return state
}
}
function specialPosts(state = { }, action) {
switch (action.type) {
case RECEIVE_SPECPOSTS:
case REQUEST_SPECPOSTS:
return Object.assign({}, state, {
req: true
})
default:
return state
}
}
const rootReducer = combineReducers({
products,
specialPosts
});
export default rootReducer;
Type of rootReducer is object, but why? Should I change createStore function to rootReducer.default?
return createStore(
rootReducer.default,
initialState,
applyMiddleware(
thunkMiddleware,
loggerMiddleware
)
)
package.json
"redux-logger": "^2.6.1",
"react-redux": "^4.4.1",
"react-redux-provide": "^5.2.3",
"redux": "^3.3.1",
"redux-thunk": "^2.0.1",
const rootReducer = combineReducers({
products,
specialPosts
});
const store = createStore( rootReducer, applyMiddleware( thunkMiddleware ));
The initial state is then created automatically from the initial states returned by the individual reducer functions. These individual states can be accessed as state.products and state.specialPosts
The problem was due to rootReducer being imported by "require" (ES5):
var rootReducer = require('./reducers.js');
If you import it via the ES6 method, it will correctly save the rootReducer.js' default automatically into rootReducer as you expected:
import rootReducer from './reducers';
I see you are mixing ES5 (require) and ES6 (import) in that file...I was mixing in my project as well, which is why I ran into this problem. More information can be found here: https://medium.com/#kentcdodds/misunderstanding-es6-modules-upgrading-babel-tears-and-a-solution-ad2d5ab93ce0#.2x2p2dx3m
When you create store first argument must be function that means reduce(()=>[]) parameter
Error:
import {createStore,applyMiddleware} from 'redux';
export default function configureStore(initialState) {
return createStore(
[],
{},
applyMiddleware()
);
}
Solution:
import {createStore,applyMiddleware} from 'redux';
export default function configureStore(initialState) {
return createStore(
()=>[],
{},
applyMiddleware()
);
}
When you see the error:
Uncaught Error: Expected the reducer to be a function
It does not necessarily mean that the reducer is not a function. If you experience this error, you should try creating the store with just the reducer. So like this:
createStore(reducer)
Which might be working.
In my case, the second parameter was wrong, because I didn't call compose and applyMiddleware in the correct order. So this is a correct setup that seems to have worked in my case:
export const createDebugStore = () => {
const options: EnhancerOptions = {}
const composeFunc = (typeof window !== 'undefined' && composeWithDevTools(options)) || compose;
const enhancer = composeFunc(applyMiddleware());
return createStore((state: any) => state || {}, enhancer);
}
rootReducer
/* index.js */
import AppReducer from './reducers';
// import {AppReducer} from './reducers';
// bugs : must be a root reducer, can not be use as a module
/* reducers.js */
// initial state
const initialState = {
angular: 0,
react: 0,
vuejs: 0
};
// reducers update state
const AppReducers = (state = initialState, action) => {
switch (action.type) {
case 'VOTE_ANGULAR':
console.log("Vote Angular!");
return (
Object.assign(
{},
state,
{
angular: state.angular + 1
}
)
);
case 'VOTE_REACT':
console.log("Vote React!");
return (
Object.assign(
{},
state,
{
react: state.react + 1
}
)
);
case 'VOTE_VUEJS':
console.log("Vote Vue.jsc!");
return (
Object.assign(
{},
state,
{
vuejs: state.vuejs + 1
}
)
);
default:
return state;
}
};
export {AppReducers};
export default AppReducers;
I use VS Code Insiders and sometimes changes are not saved correctly. So if you followed all of the above and the error is still there go to each file and press STRG+S. It solved the issue for me.
i have faced the same problem and it is solved by
const redux = require('redux');
const store = redux.createStore(reducer);
and my reducer is
const reducer = (state = initailState, action)=>{
if(action.type === 'BUY_CAKE'){
return {...state, numOfCake : state.numOfCake + 1}
}
if(action.type === BUY_ICECREAM){
return {...state, numOfIceCream : state.numOfIceCream + 1}
}
}