Paul Miranda
Paul Miranda

Reputation: 748

How to use a reducer for multiple actions in Redux?

I'm new using Redux, and I'm trying to integrate React with Redux. What I want is to put all my actions in one reducer. Actually my reducer looks like this:

import {GET_ALL_CONNECTIONS, DELETE_CONNECTION, POST_CONNECTION} from '../actions';

const initialState = {

}

export default (state = initialState, { type, payload }) => {
    switch (type) {

    case GET_ALL_CONNECTIONS:
        return payload

    case POST_CONNECTION:
        return {...state, ...payload}

    case DELETE_CONNECTION:
        return {...state, ...payload}

    default:
        return state
    }
}

The problem is when I call the action corresponding to the GET_ALL_CONNECTIONS type:

export const getAllConnections = () => {
    return async (dispatch) =>{
        const response = await Conexiones.get('/db/myConnections');
        dispatch({type: GET_ALL_CONNECTIONS, payload: response.data});
    }
}

When I call this function in a React component is supposed to get multiple connections from an API, and save the array of object resultant of the API call in the state. The problem is when I want to save the connections array in the state, to later map that state and generate options with each one of the connection inside a select element. When I render the component it throws me the next error:

TypeError: this.props.conexiones.map is not a function

The file where I combine all reducers looks like this:

import {combineReducers} from 'redux';
import {reducer as formReducer } from 'redux-form';

    import postUser from './postUser';
    import postConnection from './postConnection';
    import getAllConnections from './getAllConnections';
    import ConnectionsReducer from './ConnectionsReducer';

    export default combineReducers({
        newUser: postUser,
        form: formReducer,
        conexiones: ConnectionsReducer
    });

And the component where I do the call looks like this:

import React, { Component } from 'react';
import { Grid, Container, Select, Button, withStyles, FormControl, InputLabel, MenuItem } from '@material-ui/core';
import {connect} from 'react-redux';
import {reduxForm, Field} from 'redux-form';


import {deleteConnection, getAllConnections} from '../actions';

const styles = theme => ({
    root: {
        display: 'flex',
        flexWrap: 'wrap',
    },
    formControl: {
        margin: theme.spacing(1),
        minWidth: 120,
    },
    selectEmpty: {
        marginTop: theme.spacing(2),
    },
});

class BorrarConexion extends Component {

    componentDidMount() {
        this.props.getAllConnections();
    }

    handleSubmit = ({conexionId}) => {
        this.props.deleteConnection(conexionId);
    }

    renderConexiones = () => {
        return this.props.conexiones.map(conexion =>{
            return (<MenuItem key={conexion.id} value={conexion.id}>{conexion.connectionUrl}</MenuItem>);
        });
    }

    renderSelectField = ({input,label,meta: { touched, error },children,...custom}) =>{
        return (
        <FormControl>
            <InputLabel>Seleccione la URL que desea eliminar</InputLabel>
            <Select {...input} {...custom}>
                {this.renderConexiones()}
            </Select>
        </FormControl>
        )
    }

    render() { 
        return (
            <Container>
                <Grid container direction="column">
                    <Field name="conexionId" component={this.renderSelectField} label="Favorite Color"/>
                    <Button onClick={this.props.handleSubmit(this.handleSubmit)}>Eliminar</Button>
                </Grid>
            </Container>
        );
    }
}

const mapStateToProps = (state) => {
    return {conexiones: state.conexiones}  
}

const BorraConexionEstilizado = withStyles(styles)(BorrarConexion);
const formWrapped = reduxForm({form: 'delete_connection'})(BorraConexionEstilizado);

export default connect(mapStateToProps, {getAllConnections, deleteConnection})(formWrapped);

When I do this with a separate reducer called getAllConnections and replace the conexiones: ConnectionsReducers with conexiones: getAllConnections it works. The getAllConnections reducer looks like this:

export default (state = [], { type, payload }) => {
    switch (type) {

    case 'GET_ALL_CONNECTIONS':
        return payload

    default:
        return state
    }
}

I want to know how to do this work with one reducer receiving all my actions instead of a individual reducer for each action.

Upvotes: 1

Views: 7885

Answers (3)

Paul Miranda
Paul Miranda

Reputation: 748

I solved this issue by surrounding the conexion:state.conexion in the mapStateToProps method with Object.values() like this:

conexion: Object.values(state.conexion)

Upvotes: 0

Logan Murphy
Logan Murphy

Reputation: 6230

This issue is related to the fact that you are likely returning different structures from your reducer. It looks like the reducer is meant to handle objects as state but for this one case you return an array. Objects do not have a map function. You need to determine the structure of your state for this reducer and stick with it, you cannot change from array to object and back again, that is undeterministic behavior that redux is not built for.

I do not know the implementation details of your APIs but this is the main area in need of updating

  const initialState = []

  export default (state = initialState, { type, payload }) => {
      switch (type) {

      case GET_ALL_CONNECTIONS:
          return payload //hoping that your api gave an array here

      case POST_CONNECTION:
          //return {...state, ...payload} //this is clearly returning an object
          return [...state, payload] //now it returns an array with a new item

      case DELETE_CONNECTION:
          //return {...state, ...payload} //this is clearly returning an object
          return state.filter(item => item.id !== payload.id) //now it returns a filtered array

      default:
          return state
      }
  }

The following code should recieve states as arrays and return updated states as arrays.

Upvotes: 1

Noman Gul
Noman Gul

Reputation: 397

You have to set initial state... right now, your state is an empty object

Upvotes: 0

Related Questions