How to reuse reducer with same action using redux-subspace

Viewed 800

I'm building a small app using React, semantic-ui-react, redux-subspace. I have many different tables and when the user clicks on one of the cells, the value supposed to come out on the console but the result is undefined when it clicked. I'm trying to reuse reducer. Same action with different instances. I appreciate any comments that guide me to right direction.

PartA.js

This component renders Tables and wrapped with <SubspaceProvider>.

<Segment inverted color='black'>
<h1>Age </h1>
{ this.state.toggle ?
<SubspaceProvider mapState={state => state.withSpouseAge} namespace="withSpouseAge">
    <TableForm 
        headers={spouse_ageHeaders} 
        rows={spouse_ageData}  
        namespace={'withSpouseAge'}
    />
</SubspaceProvider> :
<SubspaceProvider mapState={state => state.withoutSpouseAge} namespace="withoutSpouseAge">
    <TableForm 
        headers={withoutSpouse_ageHeader} 
        rows={withoutSpouse_ageData}
        namespace={'withoutSpouseAge'}
    />
</SubspaceProvider> }

TableForm.js

This component return Table with the Data and this is where I want to implement onClick method.

import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Table } from 'semantic-ui-react';
import { select } from '../actions';

const shortid = require('shortid');

class TableForm extends Component {
    constructor(props){
        super(props);
        this.state = {
            activeIndex: 0,
        }
        this.handleOnClick = this.handleOnClick.bind(this);
        this.isCellActive = this.isCellActive.bind(this);
    };

    isCellActive(index) {
        this.setState({ activeIndex: index });
    }

    handleOnClick(index, point) {
        this.isCellActive(index);
        this.props.onSelect(point);
    };

    tableForm = ({ headers, rows }) => {
        const customRenderRow = ({ factor, point, point2 }, index ) => ({
            key: shortid.generate(),
            cells: [
                <Table.Cell content={factor || 'N/A'} />,
                <Table.Cell 
                    content={point}
                    active={index === this.state.activeIndex}
                    textAlign={'center'} 
                    selectable 
                    onClick={() => this.handleOnClick(index, point)}
                />,
                <Table.Cell 
                    content={point2}
                    textAlign={'center'} 
                    selectable
                />
            ],
        });
        return (
            <Table
                size='large'
                padded
                striped
                celled
                verticalAlign={'middle'} 
                headerRow={this.props.headers} 
                renderBodyRow={customRenderRow} 
                tableData={this.props.rows}
            />
        )
    };

    render() {
        console.log(this.props.withSpouseAgePoint);
        const { headers, rows } = this.props;
        return (
            <div>
                {this.tableForm(headers, rows)}
            </div>
        );  
    }
};

const mapDispatchToProps = (dispatch) => {
    return {
        onSelect: (point) => {dispatch(select(point))},
    }
}

const mapStateToProps = state => {
    return {
        withSpouseAgePoint: state.withSpouseAge,
        withSpouseLoePoint: state.withSpouseLoe,
    }
}

export default connect(mapStateToProps, mapDispatchToProps)(TableForm);

Action

import {
    SELECT,
} from './types';

export const select = (points) => ({
    type: 'SELECT',
    points,
});

Reducer.js

import { SELECT } from '../actions/types';

const INITIAL_STATE = {
    point: 0,
};

const selectionReducer = (state = INITIAL_STATE, action) => {
    switch (action.type) {
        case 'SELECT':
            return { ...state, point: state.point + action.points };
        default:
            return state;
    }
};

export default selectionReducer;

Reducer index.js

import { createStore, combineReducers } from 'redux';
import { subspace, namespaced } from 'redux-subspace';
import selectionReducer from './selectionReducer';
import toggleReducer from './toggleReducer';

const reducers = combineReducers({
    withSpouseAge: namespaced('withSpouseAge')(selectionReducer),
    withSpouseLoe: namespaced('withSpouseLoe')(selectionReducer),
    withSpouseOlp: namespaced('withSpouseOlp')(selectionReducer),
    withSpouseOlp2: namespaced('withSpouseOlp2')(selectionReducer),
    withSpouseExp: namespaced('withSpouseExp')(selectionReducer),    
    withoutSpouseAge: namespaced('withoutSpouseAge')(selectionReducer),
    withoutSpouseLoe: namespaced('withoutSpouseLoe')(selectionReducer),
    withoutSpouseOlp: namespaced('withoutSpouseOlp')(selectionReducer),
    withoutSpouseOlp2: namespaced('withoutSpouseOlp2')(selectionReducer),
    withoutSpouseExp: namespaced('withoutSpouseExp')(selectionReducer),
    toggle: toggleReducer,
});

Update

I added below TableForm component

const mapDispatchToProps = (dispatch) => {
    return {
        onSelect: (point) => {dispatch(select(point))},
    }
}

const mapStateToProps = state => {
    return {
        withSpouseAgePoint: state.withSpouseAge,
        withSpouseLoePoint: state.withSpouseLoe,
    }
}

export default connect(mapStateToProps, mapDispatchToProps)(TableForm);

implement this.props.onSelect(point) on handleOnClick. It still shows me the same result undefined. I checked store states by getState(). consloe.log. I think my implementation of redux-subspace is wrong. I uploaded whole TableForm component and also updated reducer. Please help me out!

update 2

I replaced mapStateToProps and it worked like a magic. Thank you again @JustinTRoss. but there is another problem, all the states are coming out with the same value when I clicked on the cell. console.log(props). my plan is each state has their own value stored.

const mapStateToProps = state => {
    return {
        withSpouseAgePoint: state,
        withoutSpouseAge: state,
    }
}
1 Answers

You have already namespaced your component to withSpouseAge and mapped state to state.withSpouseAge in your SubspaceProvider. Thus, you're calling the equivalent of state.withSpouseAge.withSpouseAge (undefined).

Another potential issue is the signature with which you are calling connect. From the snippet you provided, there's no way to be sure of the value of 'select'. Typically, connect is called with 2 functions, often named mapStateToProps and mapDispatchToProps. You are calling connect with a function and an object. Here's an example from http://www.sohamkamani.com/blog/2017/03/31/react-redux-connect-explained/#connect :

import {connect} from 'react-redux'

const TodoItem = ({todo, destroyTodo}) => {
  return (
    <div>
      {todo.text}
      <span onClick={destroyTodo}> x </span>
    </div>
  )
}

const mapStateToProps = state => {
  return {
    todo : state.todos[0]
  }
}

const mapDispatchToProps = dispatch => {
  return {
    destroyTodo : () => dispatch({
      type : 'DESTROY_TODO'
    })
  }
}

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(TodoItem)

Additionally, there's one other issue, although it isn't affecting you yet: You're calling this.tableForm with 2 arguments (headers and rows), while you defined the this.tableForm function to take a single argument and destructure out 'headers' and 'rows' properties.

Related