zappee
zappee

Reputation: 22746

react-redux store mapping issue between components

I am working on my first 'big' react-redux application. I am trying to map react-redux state between components but it seems that I missed something.

Everything works like a charm except one thing: my menu with react-route navigation works fine, my components are rendered, button onClick events are okay, my rest api is called and I get back http 200 with the proper json data which is added to the redux store (I guess).

The only one thing which does not work is that this.state is null in TableRenderer.js.

The error what I get is related to redux state mapping:

Uncaught TypeError: Cannot read property 'json' of null

App.js (main class)

const store = createStore(
    combineReducers({
        ...reducers,
        routing: routerReducer
    }),
    applyMiddleware(thunk)
)

const history = syncHistoryWithStore(browserHistory, store)

ReactDom.render(
    <Provider store={store}>
        <Router history={history}>
            <Route path='/' component={MainLayout}>
                <Route path='about' component={About}/>

                <Route path="list">
                    <Route path="people" component={PeopleList}/>
                </Route>

                <Route path='*' component={NotFound}/>
            </Route>
        </Router>
    </Provider>,
    document.getElementById('root')
);

PeopleList.js (my main component)

export default class PeopleList extends React.Component {
    render() {
        return (
            <TableRenderer title='My 1st people list' />
        );
    }
}

TableRenderer.js (reads data from redux store and render it)

export default class TableRenderer extends React.Component {
    render() {
    return (
        <div>
        <p style={STYLE.title}>{this.props.title}</p>
        <ActionBar />
        <table style={STYLE.table}>
            <thead style={STYLE.tableHead}>
            <tr>
                <th style={STYLE.td}>id</th>
                <th style={STYLE.td}>field 1</th>
                <th style={STYLE.td}>field 2</th>
                <th style={STYLE.td}>field 3</th>
            </tr>
            </thead>
            <tbody style={STYLE.tableBody}>
            {this.state.json.map(row => {
                return <RowRenderer key={row.id} row={row} />
            })}
            </tbody>
        </table>
        <ActionBar />
        </div>
    );
    }
}

ActionBar.js (it holds the buttons and dispatch actions)

class ActionBar extends React.Component {
    render() {
        return (
            <div style={STYLE.actionBar}>
                <Button bsSize="xsmall"
                        onClick={() => this.props.doRefresh()}>
                    Refresh
                </Button>
                <Button bsSize="xsmall">Clear all from database</Button>
            </div>
        );
    }
}

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

const mapDispatchToProps = (dispatch) => {
    return {
        doRefresh: () => dispatch(fetchData())
    };
};

export default connect(mapStateToProps, mapDispatchToProps)(ActionBar)

TableAction.js (my action class)

const loadDataAction = (json) => {
    return {
        type: ActionType.GET_DATA,
        json: json
    }
};

export function fetchData() {
    return (dispatch) => {
        dispatch(loadDataAction(''));

        axios({
            baseURL: UrlConstant.SERVICE_ROOT_URL,
            url: 'list/people',
            method: 'get'
        })
            .then((response) => {
                if (response.status == 200) {
                    dispatch(loadDataAction(response.data));
                }
            })
            .catch((error) => {
                if (error.response) {
                    dispatch(loadDataAction(''));
                }
            });
    }
}

Reducers.js

const initialState = {
    json: ''
};

export default (state = initialState, action) => {
    return Object.assign({}, state, {
        json: action.json
    });
}

UPDATE: Thank you for Max Sindwani's help this issue is solved. There was many things to fix.

App.js (main class) My store definition was not correct

const store = createStore(
    combineReducers({
        response: reducer,
        routing: routerReducer
    }),
    applyMiddleware(thunk)
)

TableRenderer.js

{this.props.json} needs to be used instead of {this.state.json}

A connect staff was missing from this class. It bounds data between redux store and class locale props (if I am correct):

class TableRenderer extends React.Component {
    render() {
        return (
            <div>
                ...
            </div>
        );
    }
}

const mapStateToProps = (state) => {
    return {
        json: state.response.json
    };
};

export default connect(mapStateToProps)(TableRender)

Reducers.js

My reducer was also wrong because without switch statement, during the initial phase the store was initialized on a wrong way by redux. And the type of json needs to be array because it holds multiply items.

const initialState = {
    json: []
};

export default (state = initialState, action) => {
    switch (action.type) {
        case ActionType.GET_DATA:
            return Object.assign({}, state, {
                json: action.json
            });
        default:
            return state;
    }
};

export default reduces;

That's it :)

Upvotes: 1

Views: 1182

Answers (1)

Max Sindwani
Max Sindwani

Reputation: 1267

The error seems to relate to the fact that state will be null (since there is no initial local state defined). Redux is meant to provide uni-directional data flow from the provider component. You'll need to pass props down or connect from the component (although it is recommended that you connect only top-level components to avoid losing track of where data is coming from). Any time the reducer returns a new/updated state, the provider passes down props to its children again and causes them to re-render. Try connecting the TableRenderer. Something like this should work:

class TableRenderer extends React.Component {
    render() {
    return (
        <div>
        <p style={STYLE.title}>{this.props.title}</p>
        <ActionBar />
        <table style={STYLE.table}>
            <thead style={STYLE.tableHead}>
            <tr>
                <th style={STYLE.td}>id</th>
                <th style={STYLE.td}>field 1</th>
                <th style={STYLE.td}>field 2</th>
                <th style={STYLE.td}>field 3</th>
            </tr>
            </thead>
            <tbody style={STYLE.tableBody}>
            {this.props.json.map(row => {
                return <RowRenderer key={row.id} row={row} />
            })}
            </tbody>
        </table>
        <ActionBar />
        </div>
    );
    }
}

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

export default connect(mapStateToProps)(TableRenderer);

Notice that after connecting and mapping the state, the state exists as props in the component. Also note that you'll want to change json (if it isn't already) to an array if you use map, and keep the initial state as empty array.

Furthermore, check to make sure that your reducer is included. It doesn't look like you've associated a key with the json reducer (assuming routingReducer is from https://github.com/reactjs/react-router-redux). Try something like this -- https://jsfiddle.net/msindwan/bgto9c8c/

Upvotes: 1

Related Questions