简体   繁体   中英

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

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM