New to react and redux so playing around with some very simple code to see how it all works.
When I try passing in a combineReducers method to a redux store then I get an error. If I remove the combinedReducers and pass the reducer in directly to the store all works fine.
let store = createStore(rootReducer);
Error
Uncaught Error: Objects are not valid as a React child (found: object
with keys {reducer}). If you meant to render a collection of children,
use an array instead or wrap the object using createFragment(object)
from the React add-ons. Check the render method of App.
Why do I get an error when I use combineReducers ? What if I wanted to add more reducers I presume thats what combineReducers is there for ?
main.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, combineReducers } from 'redux';
import App from './components/app';
let reducer = (state=0, action) => {
switch (action.type) {
case 'INCREASE':
return state+1
case 'DECREASE':
return state-1
default: return state
}
}
const rootReducer = combineReducers({
reducer:reducer
});
let store = createStore(rootReducer);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>
, document.querySelector('.container'));
//app.js
import React, { Component } from 'react';
import {connect} from 'react-redux';
class App extends Component {
render() {
let {number, increase, decrease} = this.props
return(
<div>
<div>{number}</div>
<button onClick={e=>increase()}>+</button>
<button onClick={e=>decrease()}> - </button>
</div>
);
}
}
let mapStateToProps = state => ({
number: state
})
let mapDispatchToProps = dispatch => ({
increase: () => dispatch({type: 'INCREASE'}),
decrease: () => dispatch({type: 'DECREASE'})
});
export default connect(mapStateToProps, mapDispatchToProps)(App);
Combine reducers takes a hash of reducers and returns a reducer. The resulting reducer represents an object of the same shape as the hash.
So, a call like this:
combineReducers({ name: nameReducer})
Would produce a state object that might look something like this:
{ name: 'Joe Shmo' }
In your example, you are producing a global state tree that looks like this:
{ reducer: 0 }
But you are trying to pull a property called number out of this in your mapStateToProps.
If you change your reducer declaration to look like this:
const number = (state=0, action) => {
switch (action.type) {
case 'INCREASE':
return state+1
case 'DECREASE':
return state-1
default: return state
}
}
const rootReducer = combineReducers({
number
});
Then change your mapStateToProps to look like this:
const mapStateToProps = ({number}) => ({number});
Your code should start working.
https://redux.js.org/docs/basics/Reducers.html
import { combineReducers } from 'redux'
const todoApp = combineReducers({
visibilityFilter,
todos
})
export default todoApp
Note that this is equivalent to:
export default function todoApp(state = {}, action) {
return {
visibilityFilter: visibilityFilter(state.visibilityFilter, action),
todos: todos(state.todos, action)
}
}
You could also give them different keys, or call functions differently. These two ways to write a combined reducer are equivalent:
const reducer = combineReducers({
a: doSomethingWithA,
b: processB,
c: c
})
function reducer(state = {}, action) {
return {
a: doSomethingWithA(state.a, action),
b: processB(state.b, action),
c: c(state.c, action)
}
}
And don't forget connect each parts
#connect(state => ({
reducerName: state[partStoreName]
}))
I believe you could have also done:
const INITIAL_STATE = { number: 0 };
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case 'INCREASE':
return state+1
case 'DECREASE':
return state-1
default: return state
}
};
Related
I just tried make simply reducer in react redux but it never called. After a lot trial i have no idea why it's not working. console.log in action is showing but reducer never is called.
import React from "react";
import { connect } from "react-redux";
import * as actions from "store/actions";
function Login(props) {
const login = (e) => {
e.preventDefault();
props.login();
};
return (
<form onSubmit={login}>
<button> login </button>
</form>
);
}
const mapDispatchToProps = (dispatch) => {
return {
login: () => dispatch(actions.login),
};
};
export default connect(null, mapDispatchToProps)(Login);
actions file- i'm here console.log is showing correctly
import * as actionsTypes from "./actionTypes";
export const logout = () => {
return {
type: actionsTypes.AUTH_LOGOUT,
};
};
export const login = () => {
console.log("i'm here")
return {
type: actionsTypes.AUTH_LOGIN,
};
};
reducer
import * as actionTypes from "../actions/actionTypes";
const initialState = {
isLogged: false,
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case actionTypes.AUTH_LOGIN:
return {
...state,
isLogged: true,
};
case actionTypes.AUTH_LOGOUT:
return {
...state,
isLogged: false,
};
default:
return state;
}
};
export default reducer;
many thanks for help
Probably, you forget to make a configuration of the store itself? :)
Something like that:
// at configureStore.js
import { createStore } from 'redux';
import reducer from '../path/to/your/root/reducer'; // assuming that you use combineReducer function to gather all reducers in one place
export default createStore(reducer);
Then in your app root you need to wrap entry component with the store provider:
import store from './store/configureStore';
import { Provider } from 'react-redux';
export default () => (
<Provider store={store}>
<AppRootComponent />
</Provider>
);
AppRootComponent -> your initial app component
For reference - how to configure store
UPD:
Looks like you were trying to pass the action creator to the dispatch function, instead of invoking it actually. Just make a call of that creator in the dispatch:
login: () => dispatch(actions.login()),
BTW, here is the working example of your case
In my project, I am trying to combine two reducers but whenever I try to combine them using combineReducers({}) my props become undefined and my state ( from store.getState() ) turns out to be two objects name after my reducers. See my reducer setup
root/reducers.js
import { combineReducers } from 'redux'
import dashboardReducer from '../views/DashboardPage/redux/reducers'
import formReducer from '../views/FormPage/redux/reducers'
export default combineReducers({
dashboardReducer,
formReducer
})
configureStore.js
import { createStore, compose } from "redux";
import rootReducer from "./reducers";
const storeEnhancer = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(rootReducer);
export default store;
dashboard/reducer.js
import {ADD_FIELD} from "./types"
const initialState = {
fields: [{title: "bla", text: "jorge", id: 1}],
};
const dashboardReducer = (state = initialState, action) => {
switch (action.type) {
case ADD_FIELD:
return {
...state,
fields: state.fields.concat(action.payload)
};
default:
return state;
}
};
export default dashboardReducer;
forms/reducer.js
const formReducer = (state = 0, action) => {
switch (action.type) {
case 'INCREMENT':
return state + 1;
case 'DECREMENT':
return state - 1;
default:
return state
}
};
export default formReducer;
calling console.log on "store.getState()" and "this.props" (after mapping state to props) returns the following, respectively:
console.log's
if it matters, I am using react-router
That is correct behaviour and it sounds like you need to modify your mapStateToProps to handle it correctly, so you might want to add your mapping function to your question.
export default combineReducers({
dashboardReducer,
formReducer
})
Using combineReducers means that you will manage different slices of your state with the different reducers and these slices will be named after the keys in the object you provide. You probably want to change that to be:
import dashboard from '../views/DashboardPage/redux/reducers'
import form from '../views/FormPage/redux/reducers'
export default combineReducers({
dashboard,
form
})
this will result in your state having the shape:
{
dashboard: { dash: "things" },
form: {}
}
Your dashboard reducer will be called with state set to
{ dash: "things" }
and your mapStateToProps will need to read the state accordingly
return {
fields: state.dashboard.fields
};
I have a left Sidebar that I want to share State of throughout my application so if user clicks on slider's button, user can make it hidden or show. Initially I plan to have it shown. I was following a Redux course and in the coure they did an example of fetching posts from API which is entirely different thant what I need so I am puzzled here...
So far, I created a folder called actions with 2 files,
sliderActions.js
import { Slider } from "./types"
export function sliderUpdate() {
return function(dispatch) {
dispatch({
status: "hidden"
})
}
}
and types.js
export const Slider = "Slider";
reducers folder has two files
index.js
import { combineReducers } from "redux"
import postReducer from "./postReducer"
export default combineReducers({
posts: postReducer
})
and postReducer.js
import { Slider } from "./../actions/types"
const initialState = {
Slider: "hide"
}
export default function(state = initialState, action) {
switch(action.type) {
default:
return state;
}
}
store.js file
import { createStore, applyMiddleware } from "redux"
import { thunk } from "redux-thunk"
import rootReducer from "./reducers"
const initialState = {};
const middleware = [thunk];
const store = createStore(
rootReducer,
initialState,
applyMiddleware(...middleware)
)
export default store
and lastly
I imported below two to the App.js
import { Provider } from "react-redux"
import { store } from "./store"
and wrapped my whole code inside return statement of App with a <Provider store={store}> and </Provider>
I am entirely new to the redux and don't know how to get this working, any help would be appreciated!
Actions must have a type property that indicates the type of action being performed. Types should typically be defined as string constants.
So your actions file should be something like this
import { SLIDER } from "../constant/slide";
export function sliderUpdate() {
return function(dispatch) {
dispatch({
type: SLIDER,
status: "hidden"
});
};
}
Where the constant SLIDER
export const SLIDER = "SLIDER";
And the reducer should look like this
import { SLIDER } from "../constant/slide";
const initialState = {
Slider: "hide"
};
export default function(state = initialState, action) {
switch (action.type) {
case SLIDER:
return Object.assign({}, state, action.data);
default:
return state;
}
}
We don't mutate the state.So We create a copy with Object.assign() where it will contain the new data.
Documentation says: Actions may not have an undefined "type" property.
Change your action in your sliderUpdate function and add 'type' key.
For example:
dispatch({
type: "SLIDER_BUTTON_CLICKED",
status: "hidden",
});
and now you want to change your postReducer to:
const initialState = {
slider: "opened"
}
export default function(state = initialState, action) {
switch(action.type) {
case "SLIDER_BUTTON_CLICKED": {
return {...state, slider: action.status}
}
default:
return state;
}
}
I have this error:
TypeError: Cannot read property 'map' of undefined
And don't know how can this.props.movies be undefined while I have initialState declared as [] and then it must straightly go to render?
So there are files:
Reducer.js
import * as types from "./ActionTypes.js";
const initialState = { movies: [] };
export const reducer = (state = initialState, action) => {
console.log(state.movies);
switch (action.type) {
case types.GET_MOVIES:
return {
...state,
movies: action.value
};
default:
return state;
}
};
Movies.js
import React, { Component } from "react";
import { connect } from "react-redux";
import MovieItem from "./MovieItem";
const mapStateToProps = state => ({
movies: state.movies
});
class Movies extends Component {
render() {
let movieItems = this.props.movies.map(movie => {
return <MovieItem movie={movie} />;
});
return <div className="Movies">{movieItems}</div>;
}
}
export default connect(mapStateToProps, null)(Movies);
And even if I put if-statement like
if (this.props.movies){
...
}else return 1;
it never rerenders
It should be:
const mapStateToProps = state => ({
movies: state.reducer.movies
});
Because your initial state is a object:
initialState = { movies: [] };
And you are using combineReducers like this:
const rootReducer = combineReducers({ reducer: reducer });
Now all the initialState of that reducer will be accessible by state.reducer.propertyName.
Note: Instead of using reducer, better to use some intuitive name like moviesReducer. Later in future if you will add more reducers then it will help to identify the the data.
I've just started implementing Redux in a React application, and it's the first time i try to, so please bear with me.
My problem is that i can't access the data in my component this this.props.questions
I have a simple action which is supposed to async fetch some data
export function fetchQuestions(url) {
const request = axios.get('url');
return (dispatch) => {
request.then(({data}) => {
dispatch({ type: 'FETCH_QUESTIONS', payload: data });
console.log(data);
});
};
}
Which is picked up my reducer questions_reducer
export default function(state = [], action) {
switch(action.type) {
case 'FETCH_QUESTIONS':
console.log('Getting here');
return state.concat([action.payload.data]);
console.log('But not here');
}
return state;
}
My index reducer looks like this:
import { combineReducers } from 'redux';
import fetchQuestions from './question_reducer';
const rootReducer = combineReducers({
questions: fetchQuestions
});
export default rootReducer;
I pass it to my store where i apply the thunk middleware and finally into <Provider store={store}> which wraps my app, but the prop just returns undefined in my React component
configureStore:
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from '../reducers';
export default function configureStore(initialState) {
return createStore(
rootReducer,
initialState,
applyMiddleware(thunk)
);
}
I don't know if the console.log is to be trusted but it logs from my questions_reducer before the data is returned from the dispatch in my action
EDIT (Component)
class QuestionsRoute extends Component {
constructor(props) {
super(props);
this.state = {
};
}
componentDidMount() {
this.props.fetch('someUrl);
setTimeout(function(){ console.log(this.props.questions) },
1500);
}
render() {
{console.log(this.props.questions)}
return (
<div>
<1>Hello</1>
{this.props.questions !== undefined ?
<p>We like props</p>: <p>or not</p>
}
</div>
);
}
};
const mapStateToProps = (state) => {
return {
questions: state.questions,
};
};
const mapDispatchToProps = (dispatch) => {
return {
fetch: () => dispatch(fetchQuestions())
};
};
export default connect(mapStateToProps, mapDispatchToProps)(QuestionsRoute);
In your reducer
export default function(state = [], action) {
switch(action.type) {
case 'FETCH_QUESTIONS':
return state.concat([action.payload.data]);
}
return state;
}
You should probably instead have return state.concat([action.payload]);
Since from dispatch({ type: 'FETCH_QUESTIONS', payload: data }); we see that payload is data, it doesn't contain it.
Update: I'd recommend setting up redux-devtools / redux-devtools-extension / react-native-debugger so you can visually see your actions and store state live - makes things like this a lot easier to debug!