I have been trying to figure out how to use combineReducers on the server side following the official document.
Here are two of the reducers I'm trying to combine, but no success:
ListingReducer:
import ActionType from '../ActionType'
export default function ListingReducer ( state = Immutable.List.of(), action){
switch(action.type) {
case ActionType.ADD:
return [
...state,
action.item
];
case ActionType.DELETE:
return state.filter(function(cacheItem){
return cacheItem.id !== action.item.id;
});
default:
return state
}
}
DialogShowHideReducer:
import ActionType from '../ActionType'
export default function DialogShowHideReducer ( state = false, action){
switch(action.type) {
case ActionType.DIALOG:
state = action.visible?false:true;
return state;
default:
return state;
}
}
Store.js (I need to pass some initial data to the listing reducer in order to dynamically add or remove items):
import {createStore} from 'redux';
import { combineReducers } from 'redux';
import ListingReducer from '../reducer/ListingReducer';
import DialogReducer from '../reducer/DialogShowHideReducer';
export default function (initData){
let listingStore = ListingReducer(initData.item,{});
let dialogStore = DialogShowHideReducer(false,{'type':'default'});
// !!!!!!No reducers coming out of this function!!!!!!!!!!
let combineReducer = combineReducers({
listing:listingStore,
dialog:dialogStore
});
return createStore(combineReducer)
}
homepage_app.js
import store from './store/Store'
import CustomComponent from './custom_component';
export default class HomePage extends React.Component {
render() {
<Provider store={store(this.props)}>
<CustomComponent/>
</Provider>
}
}
But what is this reducer failure error about on page load on the client side?
Store does not have a valid reducer.
Make sure the argument passed to combineReducers
is an object whose values are reducers.
The major difference between the official guide and my exmaple is that I pass the initial state to some reducer before passing them to combineReducers.
The problem is that you're actually not passing functions to your combineReducers function. You're passing the result of your reducer functions, when you do something like let listingStore = ListingReducer(initData.item,{});. This sets listingStore equal to the state returned from the reducer function, instead of the reducer function itself.
If you need to pass initial state to your reducers dynamically (i.e. not hard code them into the reducer), Redux provides a preloadedState argument for the createStore function.
So instead of what you did, you'll want to do something like this:
...
let combineReducer = combineReducers({
listing: ListingReducer //function
dialog: DialogShowHideReducer //function
});
let initialState = ... // your initial state here
return createStore(combineReducer, initialState);
...
Related
I am creating redux app using createSlice(), but get the error:
Error: The slice reducer for key "pageName" returned undefined during
initialization. If the state passed to the reducer is undefined, you
must explicitly return the initial state. The initial state may not be
undefined. If you don't want to set a value for this reducer, you can
use null instead of undefined.
Here is the minimal example for this error.
pageNameSlice.js:
import { createSlice } from "#reduxjs/toolkit";
const pageNameSlice = createSlice({
name: 'pageName',
initalState: "",
// The `reducers` field lets us define reducers and generate associated actions
reducers: {
setPageName: (state, newName) => {
state.pageName = newName
}
}
});
export default pageNameSlice.reducer;
store.js:
import { configureStore } from '#reduxjs/toolkit';
import pageNameReducer from '../features/pageNameSlice';
export const store = configureStore({
reducer: {
pageName: pageNameReducer
},
});
You have a typo there - initalState should be initialState. (This is more common than you might think ^^)
I just wanted to integrate a new Container in my React App, wired it up with Redux and just wanted to see it's all working. It's not however. accessing the reducer via this.props.selection gives me undefined. I don't know why. It does work in other containers, and the reducer has some well-defined initial state. - I'm not sure I see what the difference is here? Am I missing something trivial?
import React, { Component } from 'react'
import { connect } from 'react-redux';
import {bindActionCreators} from 'redux';
export class AudioPlayer extends Component {
constructor(props) {
super(props);
this.state = { someComponentState : true }
}
onLog() {
console.log("Logging:");
console.log(this.props.selection); // gives me: undefined
}
render() {
return (
<div>
<button onClick={()=> this.onLog()}>LOG</button>
</div>
)
}
}
function mapStateToProps (state) {
return {
selection: state.selection
};
}
export default connect(mapStateToProps)(AudioPlayer);
PS: I've simplified this component somewhat, but I think it should still reflect the problem.
edit: reducer example
people have asked to see the reducer, however, I've tried this with several reducers that are already implemented in the app and are working in other containers, so I don't think this is where the problem lies - but who knows:
import { SELECT_ITEM } from '../actions/types';
export default function(state = {}, action) {
switch(action.type) {
case SELECT_ITEM:
return {...state, error:'', selected: true};
}
return state;
}
edit2: mapStateToProps does not seem to be called at all
I just tried to do a console.log in mapStateToProps, to see if it's called, and seems that it never is. Nothing is ever logged. What could be the reason for this?
function mapStateToProps (state) {
console.log("In map function");
console.log(state);
return {
selection: state.selection, //both return
auth: state.auth // undefined
};
}
I also added another reducer (auth) which works elsewhere in the app, but here returns undefined.
edit3: My Root Reducer
import { combineReducers } from 'redux';
import { reducer as form } from 'redux-form';
//reducer imports
import authReducer from './auth_reducer';
import articlesReducer from './articles_reducer';
import userReducer from './user_reducer';
import currentSelectionReducer from './currentSelection_reducer';
const rootReducer = combineReducers({
auth: authReducer,
user: userReducer,
articles: articlesReducer,
selection: currentSelectionReducer,
});
export default rootReducer;
Can you try removing 'export' from 'export class AudioPlayer extends Component'
you can also check this: mapStateToProps not getting called at all
your component code is fine.
In your reducer it should be
export default function(state = { selected: false }, action) {
Further reading:
https://redux.js.org/recipes/structuringreducers/initializingstate
https://stackoverflow.com/a/37823335/2477619
1) In your debugging please check it enters the exact case in the reducer, that it understands the action.type == SELECT_ITEM, and returns the new state.
2) Also notice selection is an object, which contain the 'selected' inside it.
Your 'selection' reducer contains: {...state, error:'', selected: true}
maybe there is a confusion about this?
I am currently following this tutorial. I've hit a bit of a snag involving mapStateToProps in the following code:
import React from 'react';
import Voting from './voting';
import {connect} from 'react-redux';
const mapStateToProps = (state) => {
return {
pair: state.getIn(['vote','pair']),
winner: state.get('winner')
};
}
const VotingContainer = connect(mapStateToProps)(Voting);
export default VotingContainer;
Here is the Voting component that's imported:
import React from 'react';
import Vote from './Vote';
import Winner from './winner';
const Voting = ({pair,vote,hasVoted,winner}) =>
<div>
{winner ? <Winner winner={winner}/> :
<Vote pair={pair} vote={vote} hasVoted={hasVoted}/>
}
</div>
export default Voting;
It is supposed to render two buttons from the pair prop. The vote prop is a function that will be executed on click, hasVoted disables buttons when true and winner only renders the winner component as shown.
The state is expected to be an immutableJS map that looks like this:
Map({
vote:{
pair:List.of('Movie A','Movie B')
}
});
Instead I am getting an error saying that state is undefined in the state.getIn line.
The code setting the state is in index:
const store = createStore(reducer);
const socket = io(document.location.protocol + '//' + document.location.hostname + ':8090');
socket.on('state', state => store.dispatch({
type: 'SET_STATE',
state
}));
I have logged store.getState()after setting and it is as expected but undefined in mapStateToProps. I also logged the state variable in above context and it's also as expected.
I also set the state normally and it surprisingly works!:
store.dispatch({
type: 'SET_STATE',
state: {
vote: {
pair: ['Movie A', 'Movie B']
}
}
});
The value of state above is exactly what is received from the server
Lastly here's what my reducer looks like:
import React from 'react';
import {Map, fromJS} from 'immutable';
const reducer = (state = Map(), action) => {
switch (action.type) {
case 'SET_STATE':
return state.merge(action.state);
}
}
export default reducer;
What am I doing wrong?
EDIT: I realised that mapStateToProps is not being called after the store.dispatch(). I went through the docs for the possible reasons mapStateToProps is not being called and it's not one of them.
You reducer doesn't have a default action in switch statement. Which is why even though you mentioned the initial state in reducer params, undefined is returned as store initial state
import React from 'react';
import {Map,fromJS} from 'immutable';
const reducer = (state = Map() ,action) => {
switch(action.type){
case 'SET_STATE': return state.merge(action.state);
default:
return state;
}
}
export default reducer;
Adding the default statement will fix the issue :)
I ended up here too because I had failed to pass my rootreducer function to my createStore method. I had:
const store = createStore(applyMiddleware(...middlewares));
I needed:
const store = createStore(rootReducer(), applyMiddleware(...middlewares));
I have a simple reducer example:
import { fromJS } from 'immutable'
import {
REQUEST_SUCCESS
} from './constants'
const initialState = fromJS({
badges: []
})
function badgesReducer (state = initialState, action) {
switch (action.type) {
case REQUEST_SUCCESS:
return state
.set('badges', action.payload.badges || [])
default:
return state
}
}
export default badgesReducer
Idea here is to set badges equal to array returned by api response that is within action.payload.badges however I believe I am doing something wrong as I get following error returned:
warning.js:44 Warning: Failed prop type: Invalid prop badges of type
object supplied to Badges, expected array
The error is occurring because react is expecting an array but you're returning an Immutable.List. And React renders the component twice because the first render happens with reducer initialState and then the second render is caused due to props change (after the dispatched action).
Your props are inconsistent since you are passing Immutable.List in initialState & array in REQUEST_SUCCESS action.
If you want to stick with Immutable.js everywhere, then declare propTypes like this.
Component
import React, { PropTypes } from 'react'
import { List } from 'immutable'
Component.propTypes = {
badges: PropTypes.instanceOf(List)
}
Reducer
import { fromJS, List } from 'immutable'
function badgesReducer (state = initialState, action) {
switch (action.type) {
case REQUEST_SUCCESS:
return state
.set('badges', fromJS(action.payload.badges) || List())
default:
return state
}
}
And if you want to always pass an array to the component, remove fromJS from the initialState declaration
I am using immutable.JS to manage my stores via redux-immutablejs. I would now like to use the redux-form library but I am having an issue combining reducers.
Redux-immutable provides a combineReducers function that will check if all the reducers it is passed return immutable objects.
Redux itself provides a combineReducers function that performs no such checking.
Redux-form requires that you include their reducers but I cannot do so using Redux immutable's combineReducers as it will fail.
So what I'm trying to do is basically combine the outputs of these two functions like so:
import { combineReducers } from 'redux';
import { combineReducers as combineReducersUtils } from 'redux-utils';
import {reducer as formReducer} from 'redux-form';
const mainReducers = combineReducersUtils({
devices, alarms
});
const extraReducers = combineReducers({
form: formReducer
});
export default (mainReducers + extraReducers);
The last line obviously doesn't work but illustrates basically what I'm after.
Thanks for taking the time to read this.
Maybe something like this?
function rootReducer(state, action) {
const newState = combineReducersUtils({devices, alarms})(state, action);
return combineReducers({form: formReducer})(newState, action);
}
It should work because the return value of combineReducers is just a reducer:
(Function): A reducer that invokes every reducer inside the reducers object, and constructs a state object with the same shape.
https://github.com/rackt/redux/blob/master/docs/api/combineReducers.md
Updated to not create a new function on every dispatch:
const mainReducers = combineReducersUtils({devices, alarms});
const formReducers = combineReducers({form: formReducer});
function rootReducer(state, action) {
return formReducers(mainReducers(state, action), action);
}