Using nested objects in redux initial state and reducers - reactjs

I have dashboard that have a sections which is separated by react-router.
Sections are:
Categories
Exams
Reports
Lets take a look at Exam section. we have this state as the initial state.
{
ID: null,
RandomizeQuestions: false,
RandomizeAnswers: false,
Pages: [
{
ID: 1,
Title: 'Page one',
Blocks: [
{
ID: 1,
Title: 'Block One',
Questions: [
{
ID: 1,
Title: "Quesiton title is here",
Answers: [
{
ID: 1,
Title: "Answer 1"
},
{
ID: 2,
Title: "Answer 2"
}
]
}
]
}
]
}
]
}
I know that the state should be flatten and normalized when I use redux. but that is my exact problem. if i try to normalize my state it should be like this :
{
Exam: {
ID: null,
RandomizeQuestions: false,
RandomizeAnswers: false
},
ExamPages: [
{
ExamId: 1,
ID: 1,
Title: 'Page One'
}
],
ExamBlocks: [
{
ID: 1,
Title: 'The New Block',
Questions: []
}
],
ExamQuestions: [],
ExamAnswers: []
}
lets assume that its ok and I wont have problem with it. but when happen to the other sections ?
Categories have its own states which is big too and report too. ( there are other section that I didn't mention )
and then my combineReducer would be looks like this :
import {combineReducers} from 'redux'
import Exam from './redc.exams.jsx'
import ExamBlocks from './redc.blocks.jsx'
const rootReducer = combineReducers({
Exam,
ExamBlocks
})
export default rootReducer
and of course I should have a reducer for every array in that object.
so my main question is how can I at lest have this structure:
const initialState = {
Exams: {
Blocks: [],
Questions: [],
Answers: []
},
Categories: {
Menus: [],
Users: []
},
Report: {
MainReports: [],
SideReports: []
}
}
remember that I want to have separate reducers for each of them.
a reducer for Blocks , reducer for Questions and so on...
UPDATE #1
I tried #Tom Fenech said in the answers but cannot combine reducers before final combineReducers.
right now my root reducers is looks like this:
import {combineReducers} from 'redux'
//Exams
import Exams from './ExamsReducers/redc.exams.jsx'
import Blocks from './ExamsReducers/redc.blocks.jsx'
//Categories
import Categories from './CategoriesReducers/redc.categories.jsx'
const rootReducer = combineReducers({
Exams,
Categories,
Blocks,
})
export default rootReducer
I tried with what he says and here is my code after the changes:
import {combineReducers} from 'redux'
//Exams
import Exams from './ExamsReducers/redc.exams.jsx'
import Blocks from './ExamsReducers/redc.blocks.jsx'
//Categories
import Categories from './CategoriesReducers/redc.categories.jsx'
const examRedc = combineReducers(Exams,Blocks )
const catsRedc = combineReducers(Categories)
const rootReducer = combineReducers(examRedc, catsRedc)
export default rootReducer
i get this error :
Store does not have a valid reducer. Make sure the argument passed to combineReducers is an object whose values are reducers.
and when i log the state in one of the components like this
var mapDispatchToProps = function (dispatch) {
return {
AddBlock: function () {
dispatch(addBlock());
}
}
};
const mapStateToProps = function (state) {
console.log(state); //////HERE
return {Blocks: state.Blocks};
}
export default connect(mapStateToProps, mapDispatchToProps)(Blocks)
I get this state which is not what I want finally.

I don't think there's any need to have Blocks, Questions and Answers inside of Exams. I would suggest something like this at the top level:
{
Exams: {
[ID]: {
ID: string,
RandomizeQuestions: boolean,
RandomizeAnswers: boolean,
Blocks: string[] // array of block IDs
}
}
Blocks: {
[ID]: {
ID: string,
Title: string,
Questions: string[] // array of question IDs
}
},
Questions: // etc.
}
Now, instead of storing the nested objects, we just store their ID to keep track of ownership.
This state would be created with:
state = combineReducers({
examsReducer,
blocksReducer,
// ...more reducers
})
If you want to split the overall state into sections, you could use something like this:
const examStuff = combineReducers({
examsReducer,
blocksReducer,
// ...
});
const categoryStuff = combineReducers({
categoriesReducer,
menusReducer,
// ...
});
const state = combineReducers({
examStuff,
categoryStuff
});
Then state would be:
{
examStuff: {} // the object defined at the top of question,
categoryStuff: {},
otherStuff: {}
}

You can have nested reducer structure like
// block_reducer.js
export const reducer = (state = [], action) => {
switch (action.type) {
case BLOCK_ACTION:
return [{a:1}, {b:2}]
}
return state
}
// exam_reducer.js
import {combineReducers} from 'redux'
import {reducer as BlockReducer} from './block_reducer'
export const reducer = combineReducers({
Blocks: BlockReducer,
Sections: .....,
Answers: .......
})
// root_reducer.js
import {combineReducers} from 'redux'
import {reducer as examReducers} from './exam_reducers'
export const reducer = combineReducers({
Exams: examReducers,
Categories: .....,
Report: ......
})

Related

Redux -- can two containers share state?

I am super new to both React and Redux. I am at the point where I am trying to bring several containers into a parent container, a form.
I started by creating a Select component that is reused by using Redux.
The Select component has one set of actions. But there are unique reducers for each of the instances of the Select component. All come together by creating a container for each instance of the Select. (See code below).
The Select containers work perfectly. And the coexist on the same page without a problem.
So now I want to "wire them" into a form. The form needs to know what value the user has selected. So I thought I could do that by mapping properties for the form to the appropriate bits of state associated with the Select containers. That works for initialization but when I select an item from one of the Select containers the new value is not reflected in the form container.
Can this be done?
/reducers/base_reducers/SingleSelect.js
import { FETCH_ITEMS, ADD_ITEM, SET_VALUE, SHOW_LOADING, SHOW_ERROR } from "../../constants";
// Defines STATE for the SingleSelect component and initial defaults. Any key with a value of 'DEFINE'
// must be INITIALIZED in the model reducer. Remaining items can also be modified or left as initialized here.
export const base_reducer_single_select_state = {
namespace: 'DEFINE',
componentId: 'DEFINE',
items: ['DEFINE',],
defaultValue: 'DEFINE',
selectItem: '0',
allowAdd: true,
validationRegEx: /^[a-zA-Z ]*$/,
regExDescription: 'letters and spaces.',
errorMessage: '',
isError: false,
isLoading: false,
apiRoute: 'DEFINE',
};
// Processes state changes from actions for the Simple Select component.
export function base_reducer_single_select(state, action) {
switch (action.type) {
case `${state.namespace}/${FETCH_ITEMS}`:
action.items.unshift(state.defaultValue);
return {...state, "items": action.items};
case `${state.namespace}/${ADD_ITEM}`:
return {...state, "selectItem": action.itemValue};
case `${state.namespace}/${SET_VALUE}`:
return {...state, "selectItem": action.newValue};
case `${state.namespace}/${SHOW_ERROR}`:
return {...state,
"isError": action.trueFalse,
"errorMessage": action.message};
case `${state.namespace}/${SHOW_LOADING}`:
return {...state, "isLoading": action.value};
default:
return state;
}
}
And here are a couple of the reducers that implement the generic SimpleSelect reducer:
/reducers/City.js
import { base_reducer_single_select, base_reducer_single_select_state } from './base_reducers/SingleSelect';
import { getNamespace } from '../helpers';
const defaultValue = {id: 0, name: "- Select city -"};
const initialState = {
...base_reducer_single_select_state,
namespace: getNamespace(),
componentId: 'City',
items: [
defaultValue,
],
defaultValue: defaultValue,
apiRoute: '/api/cities/',
};
export default function city(state=initialState, action) {
return base_reducer_single_select(state, action);
}
/reducers/State.js
import { base_reducer_single_select, base_reducer_single_select_state } from './base_reducers/SingleSelect';
import { getNamespace } from '../helpers';
const defaultValue = {id: 0, name: '- Select state -'};
const initialState = {
...base_reducer_single_select_state,
namespace: getNamespace(),
componentId: 'State',
items: [
defaultValue,
],
defaultValue: defaultValue,
apiRoute: '/api/states/',
};
export default function state_name(state=initialState, action) {
return base_reducer_single_select(state, action);
}
Their containers:
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import SingleSelectComponent from '../components/SingleSelectComponent';
import * as CityActions from '../actions/SingleSelect';
const mapStateToProps = state => {
return {
namespace: state.city.namespace,
componentId: state.city.componentId,
items: state.city.items,
selectItem: state.city.selectItem,
allowAdd: state.city.allowAdd,
validationRegEx: state.city.validationRegEx,
regExDescription: state.city.regExDescription,
errorMessage: state.city.errorMessage,
isError: state.city.isError,
isLoading: state.city.isLoading,
apiRoute: state.city.apiRoute
};
};
function mapDispatchToProps(dispatch) {
return {actions: bindActionCreators(CityActions, dispatch)};
}
const CityContainer = connect(
mapStateToProps,
mapDispatchToProps
)(SingleSelectComponent);
export default CityContainer;
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import SingleSelectComponent from '../components/SingleSelectComponent';
import * as StateActions from '../actions/SingleSelect';
const mapStateToProps = state => {
return {
namespace: state.state_name.namespace,
componentId: state.state_name.componentId,
items: state.state_name.items,
selectItem: state.state_name.selectItem,
allowAdd: state.state_name.allowAdd,
validationRegEx: state.state_name.validationRegEx,
regExDescription: state.state_name.regExDescription,
errorMessage: state.state_name.errorMessage,
isError: state.state_name.isError,
isLoading: state.state_name.isLoading,
apiRoute: state.state_name.apiRoute
};
};
function mapDispatchToProps(dispatch) {
return {actions: bindActionCreators(StateActions, dispatch)};
}
const StateNameContainer = connect(
mapStateToProps,
mapDispatchToProps
)(SingleSelectComponent);
export default StateNameContainer;
And the "form" container (which is not a form yet as I'm just doing the initial testing)
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import CompanyInfoComponent from '../components/CompanyInfoComponent';
import * as CompanyInfoActions from '../actions/CompanyInfo';
const mapStateToProps = state => {
return {
namespace: state.company_info.namespace,
componentId: state.company_info.componentId,
childCityValue: state.city.selectItem,
childStateValue: state.state_name.selectItem,
childCountryValue: state.country.selectItem,
validationRegEx: state.company_info.validationRegEx,
regExDescription: state.company_info.regExDescription,
errorMessage: state.company_info.errorMessage,
isError: state.company_info.isError,
isLoading: state.company_info.isLoading,
apiRoute: state.company_info.apiRoute
};
};
function mapDispatchToProps(dispatch) {
return {actions: bindActionCreators(CompanyInfoActions, dispatch)};
}
const CompanyInfoContainer = connect(
mapStateToProps,
mapDispatchToProps
)(CompanyInfoComponent);
export default CompanyInfoContainer;
Again, the initial values are showing up correctly. But when state changes, meaning for instance, when I select a City, the state is changing when I examine state associated with the city (state.city.selectItem) but that is not being reflected in the CompanyInfo container.
Thanks in advance for your time reading all that code and any insight/help you can provide.

Redux Store and nested JSON from Axios API

I tried every possible variation of this code, but I don't really manage to get whatever the API fetched into my data store. I am absolutely stuck and would appreciate some help.
I think I just don't get the essential part of this construct and I would really like to understand how it works properly.
The data looks like this - it's basically a simple JSON (from a django restframework API) with some nested elements:
EDIT 2 (changed JSON to screenshot of axios API/ Redux action)
My Redux action - works perfectly fine. console.log pulls exactly the data from above (with correct inputs) :
// ./action/plan.js
import axios from 'axios';
export function fetchBudgets(){
return function(dispatch){
axios.get("/api/budgets/")
.then((response) => {
console.log(response)
dispatch({ type: "FETCH_BUDGETS", budgets: response.data})
})
.catch((err) => {
dispatch({type: "FETCH_DATA_REJECTED", budgets: err})
})
}
}
So until now, everything seems fine. The problems starts with the reducer - as I am not sure how to model the reducer to use the nested data.
My reducer:
// ./reducer/plan.js
const initialState = {}
export default function budgets(state=initialState, action) {
switch (action.type) {
case 'FETCH_BUDGETS':
console.log(action)
return {
...state,
id: action.budgets.id,
value_jan: action.budgets.value_jan,
value_feb: action.budgets.value_feb,
value_mar: action.budgets.value_mar,
value_apr: action.budgets.value_apr,
value_may: action.budgets.value_may,
value_jun: action.budgets.value_jun,
value_jul: action.budgets.value_jul,
value_aug: action.budgets.value_aug,
value_sep: action.budgets.value_sep,
value_oct: action.budgets.value_oct,
value_nov: action.budgets.value_nov,
value_dec: action.budgets.value_dec,
p_version: action.budgets.p_version,
entry_time: action.budgets.entry_time,
campaign: {
...state.campaign, ...action.budgets.campaign
},
segment: {
...state.segment, ...action.budgets.segment
},
touch_point: {
...state.touch_point, ...action.budgets.touch_point
},
year: {
...state.year, ...action.budgets.year
},
user: {
...state.user, ...action.budgets.user
}
}
default:
return state
}
}
I already cannot display data in here - so this.props.fetchBudgets() doesn't seem to fetch any data.
My .jsx App
//./container/PlanContainer.jsx
import React, { Component } from 'react';
import {connect} from 'react-redux';
import BootstrapTable from 'react-bootstrap-table-next';
import cellEditFactory from 'react-bootstrap-table2-editor';
import 'jquery';
import 'popper.js'
import 'bootstrap';
import 'underscore'
import _ from 'lodash'
import {plan} from "../actions";
const columns = [
{ dataField: 'id', text: 'ID', hidden: true},
{ dataField: 'year', text: 'Year', editable: false},
{ dataField: 'segment', text: 'Segment', editable: false},
{ dataField: 'campaign.name',text: 'Campaign', editable: false},
{ dataField: 'touch_point',text: 'Touchpoint', editable: false},
{ dataField: 'value_jan',text: 'Jan'},
{ dataField: 'value_feb',text: 'Feb'},
{ dataField: 'value_mar',text: 'Mar'},
{ dataField: 'value_apr',text: 'Apr'},
{ dataField: 'value_may',text: 'May'},
{ dataField: 'value_jun',text: 'Jun'},
{ dataField: 'value_jul',text: 'Jul'},
{ dataField: 'value_aug',text: 'Aug'},
{ dataField: 'value_sep',text: 'Sep'},
{ dataField: 'value_oct',text: 'Oct'},
{ dataField: 'value_nov',text: 'Nov'},
{ dataField: 'value_dec',text: 'Dec'},
{ dataField: 'user',text: 'User'},
];
const RemoteCellEdit = (props) => {
const { columns, data, keyField } = props
const cellEdit = {
mode: 'click',
errorMessage: props.errorMessage,
blurToSave: true
};
return (
<div>
<BootstrapTable
remote={ { cellEdit: true } }
keyField = { keyField }
data={ data }
columns={ columns }
/>
</div>
);
};
class PlanContainer extends React.Component {
componentDidMount() {
this.props.fetchBudgets();
console.log(this.props.fetchBudgets())
}
render() {
return (
<div>
<RemoteCellEdit
data={ this.props.budgets }
columns = { columns }
keyField = 'id'
/>
</div>
);
}
}
const mapStateToProps = state => {
return {
budgets: state.budgets,
}
}
const mapDispatchToProps = dispatch => {
return {
fetchBudgets: () => {
dispatch(plan.fetchBudgets());
},
}
}
export default connect(mapStateToProps, mapDispatchToProps)(PlanContainer);
Finally, my store - according to the console.log nothing is beeing passed:
// .Planning.jsx
import React from "react"
import { hot } from 'react-hot-loader'
import { render } from "react-dom"
import {
createStore,
compose,
applyMiddleware,
combineReducers,
} from "redux"
import { Provider } from "react-redux"
import thunk from "redux-thunk"
import PlanContainer from "./containers/PlanContainer"
import reducerApp from "./reducers";
import Sidebar from "./components/Sidebar"
import axios from 'axios';
import axiosMiddleware from 'redux-axios-middleware';
let store = createStore(reducerApp, applyMiddleware(thunk, axiosMiddleware(axios)));
console.log(store)
class Planning extends React.Component {
render() {
return (
<Sidebar>
<Provider store={store}>
<PlanContainer />
</Provider>
</Sidebar>
)
}
}
render(<Planning />, document.getElementById('Planning'))
Again, I would appreciate as I've been stuck on this issue for quite some time and I really want to understand how to do this properly.
Edit:
Here's a screenshot of my browser: 1st element is the store, second in the .jsx app, 3rd of the action (that looks perfectly fine) and 4th of the action in the reducer.
PlanContainer is messed up. Here's how:
componentDidMount() {
this.budgets = this.props.fetchBudgets();
}
this.budgets is pointing to the value returned by this.props.fetchBudgets() which, in this case, is a Promise, and not the actual data.
state = {
data: this.budgets
};
state now holds the promise, not the data.
render() {
return (
<div>
<RemoteCellEdit
data={ this.state.data }
...
}
So data here is not the actual data but the promise.
The confusion is happening because you are mixing redux state with react state. Use one or the other, not both (there are expcetions to this but not in this particular scenario).
There are some more issues with PlanContainer which are not clear as to whether they are real issues, or just a result of code ommission in OP.
See annotations below:
class PlanContainer extends React.Component {
componentDidMount() {
this.props.fetchBudgets();
}
constructor(props) {
... // removed for brevity, use the same code as you have right now
}
render() {
return (
<div>
<RemoteCellEdit
data={ this.props.budgets}
columns = { this.columns }
keyField = 'id'
errorMessage={ /* should come from props.data or similar - it's not in state */ }
/>
<tbody>
{this.props.budgets} /* not sure what this is for - I assumed RemoteCellEdit is the one rendering the data */
</tbody>
</div>
);
}
}
Fixing these should set you on the correct course. Good luck!

Adding item to array in child object of a child object via reducer

I apologize if my terminology is incorrect. I am very new to reactjs and react-redux (couple days).
The challenge I am trying to overcome is adding an item in store.order.cart.items. My store structure is as such:
{
order:
cart:
items: [
{name: "product a"},
{name: "product b"}
]
}
My reducer is as follows:
import ApplicationState from '../application-state';
import { ADD_PRODUCT } from "../constants/action-types";
const initialState = ApplicationState;
const rootReducer = (state = initialState, action) => {
switch (action.type) {
case ADD_PRODUCT:
return {
...state, order: {
...state.order, cart: {
...state.cart, items: [
...state.items, action.payload
]
}
}
};
default:
return state;
}
};
export default rootReducer;
My action for ADD_PRODUCT is as follows:
import { ADD_PRODUCT } from "../constants/action-types"
export const addProduct = product => ({ type: ADD_PRODUCT, payload: product });
And my action type constants (constants/action-types):
export const ADD_PRODUCT = "ADD_PRODUCT";
When I dispatch my addProduct action (testing this via the browser console: store.dispatch( addProduct({ name: "product c" }) )
I get the following error:
app.js:36995 Uncaught TypeError: Cannot convert undefined or null to object
at Function.from (/native)
at _toConsumableArray (app.js:36995)
at rootReducer (app.js:37009)
at Object.dispatch (app.js:13839)
at <anonymous>:1:7
What am I doing wrong? What is the correct way to get the new item added to the order.cart.items array?
items: [
...state.items, action.payload
]
In this excerpt above, you have attempted to spread ...state.items into the items array, but items is actually at state.order.cart.items;
Similarly:
cart: {
...state.cart,
}
cart is actually at state.order.cart
Deeply nested situations like this are why it's best to flatten your state where possible and use combineReducers(). Alternately you could look at an immutability helper to help with immutably setting deeply nested state

How to re-render child component In this situation?

Problem
If I click a number, the number should increase but not increase.
As you can see, child components are not re-rendered. (If I change the key of the 'li' element to Math.random() it works fine.)
How can I solve this situation?
An example was posted on https://codesandbox.io/s/p5q30rxk47
Thanks for reading.
Source code
The source code is roughly as follows.
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import Parent from './Parent';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import reducer from './redux';
const body = document.querySelector('body'),
store = createStore(reducer);
ReactDOM.render(<Provider store={store}><Parent/></Provider>, body);
Parent.js
import React from 'react';
import { connect } from 'react-redux';
import Child from './Child';
class Parent extends React.PureComponent {
get_numbers () {
return this.props.numbers.map((number) => (
<li key={number.id}>
<span>number : </span><br/>
<Child number={number} />
</li>
));
}
render () {
return (
<ul>
{this.get_numbers()}
</ul>
);
}
}
function mapStateToProps(state) {
return { numbers: state.numbers };
}
Parent = connect(mapStateToProps)(Parent);
export default Parent;
Child.js
import React from 'react';
import { connect } from 'react-redux';
import { increase_number } from './redux';
class Child extends React.PureComponent {
render() {
return (
<span onClick={() => this.props.increase_number(this.props.number)}>{this.props.number.value}</span>
);
}
}
function mapDispatchToProps(dispatch) {
return {
increase_number: (number) => dispatch(increase_number({ number }))
};
}
Child = connect(undefined, mapDispatchToProps)(Child);
export default Child;
redux.js
import { createAction, handleActions } from 'redux-actions';
export const increase_number = createAction('increase_number');
const initial_state = {
numbers: [
{ id: 1, value: 1 },
{ id: 2, value: 2 },
{ id: 3, value: 3 }
]
};
export default handleActions({
increase_number: (state, action) => {
// console.log(action.payload.number.value);
action.payload.number.value++;
// console.log(action.payload.number.value);
return { ...state, numbers: [...state.numbers] }
}
}, initial_state);
This is because keys helps you to figure if that element
has changed when it gets compared in the virtual Dom,
so if the id is same for the elements,
ie : 1 for first li, 2 for second li,
the dom will never know since the element updates
based on change in virtual dom , even though the values
are changing
possible solution could be to use id's different from 1, 2, 3 and update the id along with the value so that the Dom is able to figure out the change.
one Possible hack could be
increase_number: (state, action) => {
//console.log(action.payload.number.value);
action.payload.number.value++;
action.payload.number.id--;
//console.log(action.payload.number.value);
return { ...state, numbers: [...state.numbers] }
}
now the key will update everytime with the value but should
not increase since it will be same key of the 2nd li
and the dom will give you an error
so everytime 1 goes to 2 its id will go -- ie 1-1 =0
here is the working Sample
https://codesandbox.io/s/mz6zy5rq28
You need to get a deep copy of numbers array and then increase the value of the number that is passed in payload, like this:
export default handleActions({
increase_number: (state, action) => {
let numberToModify = action.payload.number;
// make a new array with new objects
let newNumbers = state.numbers.map(n => ({ id: n.id, value: n.value }));
// find the one that needs to be modified
let existingNumber = newNumbers.find((number) => (number.id === numberToModify.id));
if (existingNumber) {
existingNumber.value++;
}
return { ...state, numbers: newNumbers };
}
}, initial_state);
Made a working example here.
You need to modify the handleAction as below
export default handleActions({
increase_number: (state, action) => {
action.payload.number.value++
const currentNum = action.payload.number;
let newLst = state.numbers.map((num) => {
return num.id === currentNum.id ? {...action.payload.number} : num
})
return { ...state, numbers: newLst }
}
}, initial_state);
Here is the working sample
https://codesandbox.io/s/4jor55xz5w
Slightly modified you handleActions
export default handleActions({
increase_number: (state, action) => {
action.payload.number.value++;
const currNumber = action.payload.number;
const numbers = state.numbers.map((num) => {
return num.id === currNumber.id ? { ...action.payload.number } : num
});
return { ...state, numbers }
}
}, initial_state);
For increasing the value by one you should make changes in your redux.js file like below:
import { createAction, handleActions } from 'redux-actions';
export const increase_number = createAction('increase_number');
const initial_state = {
numbers: [
{ id: 1, value: 1 },
{ id: 2, value: 2 },
{ id: 3, value: 3 }
]
};
export default handleActions({
increase_number: (state, action) => {
console.log(action.payload.number.value);
// action.payload.numbers.value++;
const numbers = [...state.numbers];
numbers.push({
id: action.payload.number.value++,
value: action.payload.number.value++
})
// console.log(action.payload.number.value);
return { ...state, numbers: numbers }
}
}, initial_state);`

Javascript map method doesn't work in reactjs project

I am completely new at react and I am following a tutorial. For this tutorial I have the following component called user-list.js:
import React, {Component} from 'react';
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
class UserList extends Component {
createListItems() {
return this.props.users.map((user) => {
return (
<li>{user.first}</li>
);
});
}
render() {
return (
<ul>
{this.createListItems()}
</ul>
);
}
}
function mapStateToProps(state) {
return {
users: state.users
};
}
export default connect(mapStateToProps)(UserList);
And here is my reducer-users.js
export default function() {
return [
{
id: 1,
first: 'Bucky',
last: 'Roberts',
age: 71,
description: 'Bucky is a React developer anbd Youtuber',
thumbnail: 'http://i.imgur.com/7yUvePI.jpg'
},
{
id: 2,
first: 'Joby',
last: 'Wasilenko',
age: 27,
description: 'Joby loves the Packers, cheese and turtles',
thumbnail: 'http://i.imgur.com/52xRlm8.jpg'
},
{
id: 3,
first: 'Madison',
last: 'Williams',
age: 24,
description: 'Madi likes her dog but it is really annoying.',
thumbnail: 'http://i.imgur.com/4EMtxHB.jpg'
}
]
}
And now I am getting an error in the console:
Uncaught TypeError: Cannot read property 'map' of undefined
I don't understand what I am doing wrong, I removed the map function and returned any other data and it works fine, except when it tries to map the data.
When using the ES6 syntax for React Components, in your constructor function you need to bind any custom bethods you define on your class.
add the following to your UserList definition
constructor(props) {
super(props);
this.createListItems = this.createListItems.bind(this);
}
and you should be good to go. If you don't like doing this, you can also revert to the React.createClass({}) method for creating your component class.
you should have something like this for your code to work correctly.
// user reducer file
const initialState = [
{
id: 1,
first: 'Bucky',
last: 'Roberts',
age: 71,
description: 'Bucky is a React developer anbd Youtuber',
thumbnail: 'http://i.imgur.com/7yUvePI.jpg'
},
{
id: 2,
first: 'Joby',
last: 'Wasilenko',
age: 27,
description: 'Joby loves the Packers, cheese and turtles',
thumbnail: 'http://i.imgur.com/52xRlm8.jpg'
},
{
id: 3,
first: 'Madison',
last: 'Williams',
age: 24,
description: 'Madi likes her dog but it is really annoying.',
thumbnail: 'http://i.imgur.com/4EMtxHB.jpg'
}
];
export default function usersReducer(state, action) {
return state;
}
// root reducer file
import { combineReducers } from 'redux';
import usersReducer from 'your path to users reducer file'
export default function rootReducer() {
return combineReducers({
users: usersReducer,
});
}
// store file
import { createStore } from 'redux';
import rootReducer from 'path to root reducer file';
const store = createStore(rootReducer());
export default store;

Resources