Accessing Reducer in container returns undefined - javascript

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?

Related

Array gets undefined - React Redux [duplicate]

This question already has answers here:
JavaScript: Difference between .forEach() and .map()
(17 answers)
Closed 25 days ago.
i am new to React and Redux. Trying to understand the basics and do some simple examples, but i am stuck in this problem for more than one day i can't find the sollution. I imagine that my mistake is a dumb mistake.
The problem is that i can't print the array of users. When debugging, the variable users is loading with all the corrected ids and users, but after executing the <li key={id}>{name}</li> for three times, it comes back to the forEach and gives me this exception: Uncaught TypeError: Cannot read property 'forEach' of undefined, where users is undefined. And i also get an error corresponding to the PropTypes: Invalid prop user of type array supplied to HomePage, expected object
Here is the code:
store/configureStore.js
import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from '../reducers/index';
const initialState = {};
const middleware = [thunk];
const store = createStore(rootReducer, initialState, compose(
applyMiddleware(...middleware),
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
));
export default store;
reducers/index.js
import { combineReducers } from 'redux';
import userReducer from './userReducer';
//import groupReducer from './groupReducer';
export default combineReducers({
user: userReducer
});
reducers/userReducer.js
import { GET_USERS, ADD_USER, DELETE_USER } from '../actions/types';
const initialState = {
users: [
{ id: 1, name: 'brunao'},
{ id: 2, name: 'flavio'},
{ id: 3, name: 'dudu'}
]
};
export default function(state = initialState, action) {
switch (action.type) {
case GET_USERS:
return [
...state
];
default:
return state;
}
}
actions/usersAction.js
import { GET_USERS, ADD_USER, DELETE_USER } from './types';
export const getUsers = () => {
return {
type: GET_USERS
};
};
components/HomePage.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { getUsers } from '../actions/usersActions';
import PropTypes from 'prop-types';
class HomePage extends Component {
componentDidMount() {
this.props.getUsers();
}
render() {
const { users } = this.props.user;
return(
<div>
<h3>Users</h3>
<ul>
{users.forEach(({id, name}) => (
<li key={id}>{name}</li>
))}
</ul>
</div>
);
}
}
HomePage.propTypes = {
getUsers: PropTypes.func.isRequired,
user: PropTypes.object.isRequired
}
const mapStateToProps = (state) => ({
user: state.user
});
export default connect(mapStateToProps, { getUsers })(HomePage);
You are returning a wrong shape of state in your reducer. Your related code:
export default function(state = initialState, action) {
switch (action.type) {
case GET_USERS:
return [
...state
];
default:
return state;
}
}
Here, state is an object but you are returning it in an array by spreading it. So, your state gets broken.
Try it like that:
case GET_USERS:
return state;
As #Idan Dagan pointed out in his answer, actually we do not mutate state in our reducers. I just gave this suggestion since you are just playing around to learn Redux and we are returning the original state here, nothing more. But, this is a suitable and better way to return the state:
case GET_USERS:
return { ...state };
Here is a working code: https://codesandbox.io/s/k5ymxwknpv
I also changed forEach with map again as #Idan Dagan suggested. I haden't realized that. forEach is not the suitable method here since actually it does not return anything. You want to map through your arrays in React and render them.
Also, your state name is confusing :) user.users is a little bit weird, you can think a better one maybe.
Edit after comments
Your GET_USERS action actually is being hit, but you are checking it wrong in your code. You are doing:
export default function(state = initialState, action) {
switch (action.type) {
case GET_USERS:
return Object.assign({}, state);
default:
return {
users: [
{ id: 1, name: "TEST" },
{ id: 2, name: "TEST1" },
{ id: 3, name: "TEST2" }
]
};
}
}
What happens here? First action of Redux is INIT. This is the initialization of your state. Now, since there is no certain action, your reducer hits the default case and returns the TEST one. Now, your state becomes this TEST data. Then your GET_USERS is hit and you return a new object which merges the state which is the TEST one. Here is the steps:
First, state is the `initialState` -> `INIT` runs and hits the default case
State is now TEST one -> GET_USERS hit and returns the `state` which is TEST one
You see the TEST one.
How can you test your actions? Just put a console.log in your reducer:
export default function(state = initialState, action) {
console.log("state",state);
console.log("action",action);
.....
and see GET_USERS actually is being hit. The other option is instead of returning the merged object with state, try to merge it with initialState or with spread operator return a new object by using initialState:
return return Object.assign({}, initialState);
or
return {...initialState}
Last option provides you a little bit more understanding how my first explanation works. Try to return this for your GET_USERS:
return {...state, users:[...state.users, {id:4, name: "foo"}]};
You will see a users list with TEST data but the last one will be your foo. This explains how you loose your initialState if you return anything beside state in your default case.
Last suggestion, you can debug your Redux development with Redux Dev Tools. It is a great tool and does much more than debugging. You can easily track all your operations for Redux.
I'm not sure what are you trying to do with the new state.
But there is couple of changes that you need to do:
Use map instead of forEach (because you want to return a new array).
At the reducer you can return the state like this:
export default function(state = initialState, action) {
switch (action.type) {
case GET_USERS:
return Object.assign({}, state);
default:
return state;
}
}
as mention in redux docs:
We don't mutate the state. We create a copy with Object.assign().

Redux Fetch JSON Data

my Redux fetch is returning empty..
It does not break but it just returns me empty object.
Here is the code for my action (newsActions.js):
import axios from 'axios';
import kickstarterData from '../server/kickstarter-october.json';
export const FETCH_KICKSTARTER = 'FETCH_KICKSTARTER';
export function fetchKickstarter() {
return {
type: FETCH_KICKSTARTER,
payload: {
data: kickstarterData
}
};
}
Here is my Reducer:
import { FETCH_KICKSTARTER } from '../actions/kickstarterActions';
export default function(state = [], action) {
switch (action.type) {
case FETCH_KICKSTARTER:
debugger;
return [action.payload.data, ...state];
}
return state;
};
https://stackoverflow.com/questions/ask#
Here is my index.js that combines all the reducers:
import { combineReducers } from 'redux';
import NewsReducer from './reducer_news';
import KickstarterReducer from './reducer_kickstarter';
const rootReducer = combineReducers({
news: NewsReducer,
kickstarters: KickstarterReducer
});
export default rootReducer;
Finally, inside my app.js I have the following code:
const mapStateToProps = (state) => ({
news: state.news,
kickstarters: state.kickstarters
});
export default connect(mapStateToProps, {...newsActions, ...kickstarterActions})(App);
Could anyone tell me why this is breaking?
Also, could anyone suggest me a better/cleaner way of writing these codes?
Thank you
I have a hunch that in your reducer
return [action.payload.data, ...state];
should be
return [...action.payload.data, ...state];
Frankly it should be just
return [...action.payload.data ];
Since i don't have any idea about your biz logic, but later seems more correct to me (why do you need to merge it with old state).
you need to spread the action.payload.data in the state.

Redux state is undefined in mapStateToProps

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));

Store does not have a valid reducer with combineReducer

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);
...

React Redux - changes aren't reflected in component

I created a simple React component for generating menu, and I wanted to replace it's visibility toggling with Redux (instead of state).
My component looks like this:
class SiteMenu extends React.Component {
constructor(props) {
super(props);
}
toggle(force = false) {
let active = !active;
store.dispatch({
type: 'TOGGLE',
active
});
}
render() {
const wrapperClass = this.props.active ? `${this.props.className}__wrapper ${this.props.className}__wrapper--active` : `${this.props.className}__wrapper`;
return (
<nav className={this.props.className} ref="nav">
<button className={`${this.props.className}__trigger`} onClick={this.toggle.bind(this)}>
{this.props.active}
</button>
<ul className={wrapperClass}>
</ul>
</nav>
);
}
}
I added mapStateToProps:
const mapStateToProps = (store) => {
return {
active: store.menuState.active
}
};
and connect
connect(mapStateToProps)(SiteMenu);
My store:
import { createStore } from 'redux';
import reducers from './reducers/index.js';
const store = createStore(reducers, window.devToolsExtension && window.devToolsExtension());
export default store;
and reducers:
import { combineReducers } from 'redux';
import menuReducer from './menu-reducer';
const reducers = combineReducers({
menuState: menuReducer
});
export default reducers;
const initialMenuState = {
active: false
};
const menuReducer = (state = initialMenuState, action) => {
switch(action.type) {
case 'TOGGLE':
console.log(action);
return Object.assign({}, state, { active: action.active });
}
return state;
};
export default menuReducer;
When I check my Redux DevTools, state is changing. What should I do?
Code in the repo: https://github.com/tomekbuszewski/react-redux
to use connect func , also you should add Provider from react-redux
render(<Provider store={store}><Parent /></Provider>, app);
then you should add wrapped component to Parent component
const SiteMenuWrapped = connect(mapStateToProps)(SiteMenu);
///in Parent component
<Header className="site-header">
<SiteMenuWrapped
className="site-navigation"
content={this.state.sections}
/>
</Header>
Few issues:
connect returns a higher order component, so best advice is to split out each component to a separate file and export the result of connect. See the examples at e.g. http://redux.js.org/docs/basics/ExampleTodoList.html. This means that your mapStateToProps function is never being called, hence why this.props.active is always undefined.
Your store registration is incorrect, the second parameter to createStore is the initial state. To register the Chrome Redux dev tools see https://github.com/zalmoxisus/redux-devtools-extension
You should use <Provider> to make the store available to all components. See the Redux docs.
You can dispatch actions through this.props.dispatch or use mapDispatchToProps, see https://github.com/reactjs/react-redux/blob/master/docs/api.md

Categories