Why is my reducer returning an empty array in react/redux? - javascript

In my reducer, it returns an array of objects that i got from an api. I do a console.log on the list and I'm able to see the array, but when I get access to the reducer in my react class, it shows up as an empty array, why is that so?
Inside the render() function in my react file, it does print for some odd reason, but I have a function where I'm trying to render seperate divs using that data from the reducer and the array shows up empty.
getList() {
let arr = [];
if(this.props.popular){
arr = this.props.popular.map(item => {
return (
<div key={item.id} className="movie">
<img
src={`https://image.tmdb.org/t/p/w300${item.poster_path}`}
//onClick={() => this.displayModal(item)}
/>
</div>)
})
}
// console.log(arr)
// this.props.updateCurrentShowList(arr);
return arr;
}
I use this.props.popular from the mapstatetoprops function i have below.
import { FETCH_POPULAR, RESET_POPULAR } from "../Actions/types";
let initialList = [];
export default function(state = initialList, action){
switch(action.type){
case FETCH_POPULAR:
//return action.payload || false;
initialList = initialList.concat(...action.payload);
//console.log(initialList);
return initialList;
case RESET_POPULAR:
initialList = action.payload;
return initialList;
default:
return state;
}
}
Here the initialList is printed and works and i then return it.
This is my mapStateToProps function that i have in my other file where I want to get access to the array. I used combinereducers in one of my reducers file.
function mapStateToProps(state) {
return {
popular: state.popular
};
}
Why does this.props.popular print correctly when i do it in render(), but whenever i use it anywhere else, it doesnt?
action function
export const fetchPopular = (searchTypeFormat, page) => async (dispatch) => {
let url = `https://api.themoviedb.org/3/discover/${searchTypeFormat}?api_key=${APIKEY}&language=en-US&sort_by=popularity.desc&include_adult=false&include_video=false&page=${page}`;
//console.log(url);
const res = await axios.get(url);
//console.log(res.data.results)
dispatch({type: FETCH_POPULAR, payload: res.data.results});
};
my store creation
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import reducers from './Reducers/index';
import reduxThunk from 'redux-thunk';
const store = createStore(reducers, {}, applyMiddleware(reduxThunk));
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root'));
I combined my reducers this way
import { combineReducers } from 'redux';
import authReducer from './authReducer';
import popularReducer from './popularReducer';
import genreListReducer from './genreListReducer';
import searchByGenreReducer from './searchByGenreReducer';
import { reducer as formReducer } from 'redux-form';
import modalReducer from './modalReducer';
import detailsReducer from './moreDetailReducer';
import userDisplayList from './userDisplayList';
export default combineReducers({
auth: authReducer,
form: formReducer,
popular: popularReducer,
genreList: genreListReducer,
searchByGenre: searchByGenreReducer,
modalData: modalReducer,
details: detailsReducer,
displayList: userDisplayList
})
the whole component
import React, { Component } from 'react';
import { withRouter } from "react-router-dom";
import { connect } from 'react-redux';
import * as actions from '../Actions';
class SearchPopular extends Component {
constructor(props) {
super(props);
this.state = {
list: [],
page: 1
}
this.getList = this.getList.bind(this);
}
componentWillMount() {
//console.log(this.props.match.params.format)
this.props.fetchPopular(this.props.match.params.format, this.state.page);
console.log(this.props.popular)
console.log(this.getList());
}
getList() {
let arr = [];
if(this.props.popular){
arr = this.props.popular.map(item => {
return (
<div key={item.id} className="movie">
<img
src={`https://image.tmdb.org/t/p/w300${item.poster_path}`}
//onClick={() => this.displayModal(item)}
/>
</div>)
})
}
//console.log(arr)
// this.props.updateCurrentShowList(arr);
return arr;
}
render() {
console.log(this.props.popular);
return (
<div>
</div>
);
}
}
function mapStateToProps(state) {
return {
popular: state.popular,
updatedList: state.displayList
};
}
export default withRouter(connect(mapStateToProps, actions)(SearchPopular));

You are doing to state update in a wrong way. What you have done is it will always take empty array initially and then append into it.
case 'FETCH_POPULAR':
return [...state, ...action.payload];
Try this in your reducer.
****To your main issue
You are trying to fetch store.popular but you donot have popular in store
const composeEnhancer = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const configureStore = () => {
const store = createStore(
combineReducers({
popular: Your reducer here
}),
composeEnhancer(applyMiddleware(thunk))
);
return store;
}
**** New update
I think that's the issue of function loosing the reference of this.
This is why we are using this.getList.bind(this) in the constructor
So when we call this.getList the function gets the reference of this and can use it. so when you are calling it directly from any other function then use this.getList.bind(this)
componentWillMount() {
//console.log(this.props.match.params.format)
this.props.fetchPopular(this.props.match.params.format, this.state.page);
console.log(this.props.popular)
console.log(this.getList.bind(this));
}

Don't mutate variables in Redux reducers! You'll get lots of weird effects and race conditions. You want to always return fresh new objects from a reducer, unless no action matches in the default case, then return the current state.
So firstly, don't define your initial state with a let and then mutate it in your reducers, that's completely wrong.
Secondly, if you want to return new state based on the previous state, as in your FETCH_POPULAR action, then use the state argument (that's what it's for).
Rewrite like this,
export default function(state = [], action){
switch(action.type){
case FETCH_POPULAR:
return [...state, ...action.payload];
case RESET_POPULAR:
return [];
default:
return state;
}
}

Related

I can't fetch the data from reducer to component

I'm trying pass the data from reducer to component and receive as props.
But the data return UNDEFİNED, so I have tried console the data on reducer and action, but it's okey. There isn't any problem with the data coming from the API, but it always return to component undefined. Where is my fault?
Action
export default ProfileTab;
import axios from 'axios';
import { BASE, API_KEY } from '../config/env';
export const FETCHED_MOVIES = 'FETCHED_MOVIES';
export function fetchMovies() {
return (dispatch) => {
axios
.get(`${BASE}s=pokemon&apikey=${API_KEY}`)
.then((result) => result.data)
.then((data) =>
dispatch({
type: FETCHED_MOVIES,
payload: data.Search,
}),
);
};
}
Reducer
import { FETCHED_MOVIES } from '../actions/movies';
const initialState = {
fetching: false,
fetched: false,
movies: [],
error: {},
};
export default (state = initialState, action) => {
switch (action.type) {
case 'FETCHED_MOVIES':
return {
...state,
movies: action.payload,
};
default:
return state;
}
};
Component
import React, { Component } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { fetchMovies } from '../../actions/movies';
class Case extends Component {
static propTypes = {
movies: PropTypes.object.isRequired,
};
constructor(props) {
super(props);
}
componentDidMount() {
this.props.fetchMovies();
}
onChangeHandler = (e) => {
this.setState({
input: e.target.value,
});
};
render() {
console.log(this.props.movies);
return (
<div>
<div className="movies-root">
<div className="movies-wrapper">
<div className="movies-container safe-area">
<h1>mert</h1>
</div>
</div>
</div>
</div>
);
}
}
const mapStateToProps = (state) => {
return {
movies: state.movies,
};
};
const mapDispatchToProps = {
fetchMovies,
};
export default connect(mapStateToProps, mapDispatchToProps)(Case);
Do this in the connect statement:
export default connect(mapStateToProps,{fetchMovies})(Case);
And remove the mapDispatchToProps function from your code.
Dispatching props as an object is quite incorrect. Try this, and it should work.
That's because your mapDispatchToProps function should return an object and take dispatch as parameter. Each field in your returned object should contain a function that dispatches your action.
So try something like this:
const mapDispatchToProps = dispatch => {
return {
fetchMovies: () => dispatch(fetchMovies())
}
}
Although there's already an accepted answer, I'm not sure how correct it is, as it's completely valid to pass mapDispatchToProps the way you did with the latest react (16.13.1) and react-redux (7.2.1) versions (I'm not sure about earlier versions).
Now, assuming your question contains the whole code, there are two important things missing:
Creating the store:
import { createStore } from "redux";
const store = createStore(reducer);
and passing it to the Provider component:
<Provider store={store}>
If you go ahead and do as above, you'll see that this.props.fetchMovies emits the following error:
Actions must be plain objects. Use custom middleware for async actions.
To fix it, do as it says and add a middleware, e.g. thunk:
import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
const store = createStore(rootReducer, applyMiddleware(thunk));
What follows is the full code. Note that I "split" fetchMovies into two functions: sync and async, for illustrating the difference usage between the two. I also modified your code (made is shorter, mostly) for this answer's readability. You can also see a live demo here:
File app.js
import React, { Component } from "react";
import { connect } from "react-redux";
import { fetchMoviesSync, fetchMoviesAsyncMock } from "./api";
class App extends Component {
componentDidMount() {
this.props.fetchMoviesSync();
this.props.fetchMoviesAsyncMock();
}
render() {
return (
<div>
<div className="movies-root">
<div className="movies-wrapper">
<div className="movies-container safe-area">
{this.props.movies.join("\n")}
</div>
</div>
</div>
</div>
);
}
}
const mapStateToProps = (state) => ({ movies: state.movies });
const mapDispatchToProps = {
fetchMoviesSync,
fetchMoviesAsyncMock
};
export default connect(mapStateToProps, mapDispatchToProps)(App);
File api.js
export const FETCHED_MOVIES = "FETCHED_MOVIES";
export const fetchMoviesSync = () => ({
type: FETCHED_MOVIES,
payload: ["movie1", "movie2", "movie3", "movie4"]
});
export const fetchMoviesAsyncMock = () => (dispatch) => {
dispatch({
type: FETCHED_MOVIES,
payload: ["movie5", "movie6", "movie7", "movie8"]
});
};
File reducer.js
const initialState = {
movies: [],
};
export default (state = initialState, action) => {
switch (action.type) {
case "FETCHED_MOVIES":
return {
...state,
movies: state.movies.concat(action.payload)
};
default:
return state;
}
};
File index.js
import React from "react";
import ReactDOM from "react-dom";
import Case from "./app";
import reducer from "./reducer";
import { createStore, applyMiddleware } from "redux";
import { Provider } from "react-redux";
import thunk from "redux-thunk";
let store = createStore(reducer, applyMiddleware(thunk));
ReactDOM.render(
<Provider store={store}>
<Case />
</Provider>,
document.getElementById("container")
);
File index.html
<body>
<div id="container"></div>
</body>

React Redux not re-rendering when Store changes

So I have been trying to figure this out for a day now.
I think I have set up everything correctly, however, the view does not re-render nor the prop updates. However, I can see the change in Redux Developer tools. I know there are other questions like this on Stackoverflow but none of them really helps me.
Am I not seeing something?
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import './index.css';
import App from './App';
import Store from './store';
import * as serviceWorker from './serviceWorker';
const store = createStore(Store, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__())
ReactDOM.render(
<Provider store={store} >
<App />
</Provider>
,
document.getElementById('root'));
//actions.js
const initPurchases = (payload) => {
return {
type: "INITILIZE_PURCHASES",
payload
}
}
module.exports = {
initPurchases,
}
// store.js
const initalState = {
inventory: [],
}
const rootReducer = (state = initalState, action) => {
switch(action.type) {
case "INITILIZE_PURCHASES":
state.purchases = [...action.payload];
break;
default:
return state;
}
return state;
}
export default rootReducer
import React from 'react';
import { connect } from 'react-redux';
import actions from './actions';
class App extends React.Component {
state = {}
componentDidMount = () => {
this.getPurchases();
}
getPurchases = async () => {
// call to api which returns t
this.props.initPurchases(t)
}
render() {
console.log(this.props.purchases) // Returns empty array []
return (
<div className="App">
// Some view
</div>
);
}
}
export default connect(
(state) => {return {purchases: state.purchases}},
actions,
)(App);
Logs from React Redux Developer Tools
Can somebody please help me? I can't figure out what's wrong here. I ommited most of the things that i are not related to my problem (at least I do not think they are). I can upload the entire repo to github to see the bigger context
Your reducer needs to return the new state, otherwise the state remains unchanged:
const rootReducer = (state = initalState, action) => {
switch(action.type) {
case "INITILIZE_PURCHASES":
return { ...state, purchases: [...action.payload] };
break;
default:
return state;
}
return state;
}
I think you need to implement something like:
import actions from './actions'
...
class App extends React.Component {
...
componentDidMount = () => {
this.props.initPurchases();
}
render() {
...
}
}
const mapDispatchToApp = (dispatch) => (
{
initPurchases: () => (
dispatch(actions.initPurchases())
),
}
)
...
export default connect(
(state) => {return {purchases: state.purchases}},
mapDispatchToApp,
)(App);
This is because you need to dispatch actions to the store

Why state returned from my reducer is an empty object??(React-Redux)

I have a problem in my redux reducer, it does not return expected state after dispatching FETCH_BOOKS action, it returns an empty object instead of an object of state which is books that is fetched by AJAX request,
the reducer returns correct data when storing my state in array instead of object, this is so confusing, why does this happen??
These are my components
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import './index.css';
import App from './App';
import * as BooksAPI from './BooksAPI';
import registerServiceWorker from './registerServiceWorker';
import { createStore, applyMiddleware } from 'redux';
import { bookReducer } from './reducers/BookReducer';
import thunk from 'redux-thunk';
import {BrowserRouter as Router} from 'react-router-dom';
const middleware = [thunk];
const initialState = {};
const store = createStore(bookReducer, initialState, applyMiddleware(...middleware));
ReactDOM.render(
<Provider store={store}>
<Router>
<App />
</Router>
</Provider>,
document.getElementById('root')
);
registerServiceWorker();
App.js
import React, { Component } from 'react';
import { connect } from 'react-redux'
import BookShelf from './components/BookShelf'
import AllShelves from './components/AllShelves'
import Header from './components/Header';
import SearchPage from './components/SearchPage';
import * as BooksAPI from './BooksAPI';
import { Route, withRouter } from 'react-router-dom';
import './App.css';
class App extends Component {
componentWillMount() {
this.props.fetchBooks();
}
render() {
console.log(this.props.books)
return (
<div className="App">
<Header />
<Route exact path="/" component={AllShelves} />
<Route path="/search" component={SearchPage} />
</div>
);
}
}
const mapStateToProps = (state) => {
return {
books: state.books
}
}
const mapDispatchToProps = (dispatch) => {
return {
fetchBooks: () => {
BooksAPI.getAll().then(books => dispatch({
type: 'FETCH_BOOKS',
books
}))
},
}
}
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(App))
reducer that doesn't works
import { FETCH_BOOKS } from '../actions/Types.js';
import * as BooksAPI from '../BooksAPI'
const initialState = {
books: [],
query: ''
}
export const bookReducer = (state = initialState, action) => {
switch(action.type) {
case 'FETCH_BOOKS':
return {
...state,
books: action.books,
}
default:
return state;
}
}
The reducer that work
export const bookReducer = (state = [], action) => {
switch(action.type) {
case 'FETCH_BOOKS':
return action.books
default:
return state;
}
}
So why storing state in object doen't work and it works perfectly with array, i don't want to store my state in array, as books is not the only data i need to manage in my state!!!
I've checked all your codes and I think the problem possibly come from the redux store setup:
const initialState = {};
const store = createStore(bookReducer, initialState, applyMiddleware(...middleware));
I suggest removing the initialState:
const initialState = {}; // remove this line cuz we don't need it
const store = createStore(bookReducer, applyMiddleware(...middleware)); //fixed like this
In addition, I think you should fetch your books in the componentDidMount() lifecycle hook instead of componentWillMount(), like this:
componentDidMount() {
this.props.fetchBooks();
}
In the second example, you are fetching the value in the reducer as action.books, instead it should be action.payload because that's the key dispatched in action.
After any Action is dispatched you have to return a new state to the store so you have to return new state object for that you have to get the state now and change the books and return the new state so following code doing that
export const bookReducer = (state = initialState, action) => {
switch(action.type) {
case 'FETCH_BOOKS':
return {
...state,
books: action.books,
}
default:
return state;
}
}
but from the other reducer you return only the books array that coming from the action that is wrong way to do that

Redux - application state has the name of reducer as key

Could someone please help me with this problem?
I've started to learn React and Redux but I'm stuck from a couple of days on configuring redux.
I'm assuming that when something triggers an action, redux through the reducers stack of functions should return an object that represents my application state.
Unfortunately, It returns an object with { reducerName => reducer result } basically means that if I've 4 reducers, the function store.getState() returns something like
{
'reducerOne': entireApplicationState
'reducerTwo': entireApplicationState
'reducerThree': entireApplicationState
'reducerFour': entireApplicationState
}
I'll really appreciate if someone can help me because I've finished all the ideas :)
This is my application.js:
import React from 'react';
import ReactDom from 'react-dom';
import HomePage from 'root_views/home';
import {store} from 'root_services/redux/store';
class Application extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<HomePage/>
)
}
}
var Provider = React.createClass({
childContextTypes: {
store: React.PropTypes.object.isRequired
},
getChildContext: function () {
return {store: this.props.store}
},
render: function () {
return this.props.children;
}
});
ReactDom.render(
<Provider store={store}>
<Application/>
</Provider>,
document.getElementById('application')
);
My store.js
import { createStore } from 'redux';
import {rootReducer} from './reducers/container';
export const store = createStore(
rootReducer,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);
My container.js that basically contains all my reducers
import {combineReducers} from 'redux';
// This is just the action label
import {DATA_EXCHANGE_LOAD} from 'root_services/redux/actions/container'
const initialState = {
data_exchange: {},
}
function dataExchange(state = {}, action) {
switch (action.type) {
case DATA_EXCHANGE_LOAD:
return Object.assign({}, state, {
data_exchange:{'reducerOne':'dataExchange'}
});
break;
default:
return initialState;
break;
}
};
function testReducer(state = {}, action) {
switch (action.type) {
case DATA_EXCHANGE_LOAD:
return Object.assign({}, state, {
data_exchange:{'reducerTwo':'testReducer'}
});
break;
default:
return initialState;
break;
}
};
// Export the combined reducers
export const rootReducer = combineReducers({
dataExchange,
testReducer
});
This is the action that triggers the event:
export function dataExchangeLoad(){
return {
type: DATA_EXCHANGE_LOAD,
}
};
This is my component where the action is triggered:
import React from 'react'
import "../components/layouts/header/header.less";
import {dataExchangeLoad} from "root_services/redux/actions/container"
export default class HomePage extends React.Component {
constructor(props, {store}) {
super(props);
store.dispatch(dataExchangeLoad());
console.log(store.getState());
}
render() {
return (
<div>
<h1>test</h1>
</div>
)
}
};
HomePage.contextTypes = {
store: React.PropTypes.object,
}
This is the result:
Object {dataExchange: Object, testReducer: Object}
As was already answered in comments combineReducers indeed works that way. In case you want to chain reducers so that action will go through all of them sequentially updating state in each one you can use reduce-reducers. Using this helper function it's possible to do something like that (looks like that is what you want to achieve):
import reduceReducers from 'reduce-reducers';
const reducer1 = (state = {}, action) => {
if (action.type === 'foo') {
return ({
...state,
touchedBy: ['reducer1'],
})
}
return state;
};
const reducer2 = (state = {}, action) => {
if (action.type === 'foo') {
return ({
...state,
touchedBy: state.touchedBy.concat('reducer2'),
})
}
return state;
};
const reducer = reduceReducers(reducer1, reducer2);
expect(reducer({}, { type: 'foo' }))
.toMatchObject({ touchedBy: ['reducer1', 'reducer2'] });
In case anyone is looking, the link provided above in the comments is broken. This link works and explains well how to rename the state coming from your reducers. If you don't want to read, rename your reducer import or rename it inside your combineReducer.
Example1:
import billReducer as billState from "./reducers";
Example2:
const rootReducer = combineReducer({billState: billReducer});
Using combineReducers

Can´t access props after using CombineReducer

My previous React-Redux implementation was working, but after I tried to implement the combineReducer function with seperated files, an error is thrown that I don´t really understand. Hope some of you can help me!
ERROR: Uncaught TypeError: this.props.todos.map is not a function
My Reference for that Code was the Async Example of the Redux-Doc´s. But I stated with another example and the change from each examples are not documented in the doc´s.
The first code I will show, is that I had (working):
MyStore
import { createStore, applyMiddleware } from 'redux'
import thunkMiddleware from 'redux-thunk'
import createLogger from 'redux-logger'
import addItem from '../reducers/addItem'
export default function configureStore(preloadedState) {
const store = createStore(
addItem,
preloadedState,
applyMiddleware(thunkMiddleware, createLogger())
)
if (module.hot) {
// Enable Webpack hot module replacement for reducers
module.hot.accept('../reducers', () => {
const nextRootReducer = require('../reducers').default
store.replaceReducer(nextRootReducer)
})
}
return store
}
My Reducer
export default (state = ['Test'], action) => {
switch (action.type){
case 'ADD_ITEM':
//return action.item
return [
...state,
{
id: action.id,
text: action.item
}
]
default:
return state
}
}
Actions
export default function addItem(item){
console.log("addTOdo")
return {
type: 'ADD_ITEM',
id: nextTodoId++,
item
}
}
And the subComponent where the input is finally rendered
import React, { Component, PropTypes } from 'react'
import { connect } from 'react-redux'
export default class TodoList extends Component {
render() {
const posts = this.props
const isEmpty = posts.length === 0
return (
<div>
<h3>Meine Aufgaben</h3>
<ul>
{isEmpty
? <h3>Sie haben noch keinen Todo´s angelegt</h3>
: <h3>Ihre Ergebnisse</h3>
}
{this.props.todos.map((todo, i) => <li key={i}>{todo.text} </li>)}
</ul>
</div>
)
}
}
const mapStateToProp = state => ({todos: state})
export default connect (mapStateToProp)(TodoList)
What I have change:
First, I created another Reducers File, called Index where I imported the addItem Reducer and exported the rootReducer:
import {combineReducers} from 'redux'
import addItem from './addItem'
import getItem from './getItem'
const rootReducer = combineReducers({
addItem,
getItem
})
export default rootReducer
After that, I changed the Store to import the rootReducer and put it´s reference in the Store (just the changes to configureStore):
import rootReducer from '../reducers/index'
const store = createStore(
rootReducer,
preloadedState,
applyMiddleware(thunkMiddleware, createLogger())
)
I don´t know if that Information is also required, but here is my Container Component:
import React, { Component, PropTypes } from 'react'
import AddTodo from '../components/AddTodo'
import TodoList from '../components/TodoList'
import { connect } from 'react-redux'
import addItem from '../actions/addItem'
import getItems from '../actions/getItems'
class App extends Component {
constructor(props) {
super(props)
this.handleClick = this.handleClick.bind(this)
this.state = {text: ''}
}
handleClick(e){
console.log(e);
const {dispatch} = this.props
dispatch(addItem(e));
}
componentDidMount(){
console.log("COMPONENT MOUNT");
const {dispatch} = this.props
// dispatch(getItems())
}
componentWillReceiveProps(nextProps) {
console.log("GETTT IT");
console.log(nextProps)
}
render() {
return (
<div>
< h1 > Hallo </h1>
<AddTodo handleAddItem={this.handleClick}/>
<TodoList/>
</div>
)
}
}
App.propTypes = {
dispatch: PropTypes.func.isRequired
}
function mapStateToProps(state){
return {
AddTodo
}
}
export default connect (mapStateToProps)(App)
I hope this issue is not to basic and someone can help me. Thanks in advance!
If you inspect your redux state you will see that the following code sets up 2 more keys in the state (addItem and getItem):
const rootReducer = combineReducers({
addItem,
getItem
})
So, now to connect todos you need to one of the 2 new keys. If todos is not defined on those, then you need to add the reducer of todos to the combineReducers call.
So this needs to map to a valid location in state:
const mapStateToProp = state => ({todos: state})

Categories