React/Redux—Individual state for each Instance of a Component - reactjs

If have a list of users and each Entry has a button »EDIT«. If the user clicks on it the following happens:
request the server for the form
Add the component <UserEditForm /> to the entry, what expands the entry
This works fine except one thing: If one clicks further buttons each Instance of the form receives the data of the last user form requested. That is because I have only only one userform property in the state.
So to solve this I want to exchange userform to userforms which should/could be an Object like that:
userforms: {
<id of first instance>: { … }, //formdata
<id of second instance>: { … },
…
}
But since I am new to React/Redux I do not really know how to do that, or what the »right« approach, or best practice is, to actually do it.
My Idea is to create a higher Order Component like so:
import React from 'react';
import {connect} from 'react-redux';
import {uuid} from '../../helpers/uuid';
export const formdatainstance = (FormInstance) => {
let id = null;
class FormDataMapper extends React.Component {
constructor (props) {
super(props);
id = uuid();
}
render () {
//extract the formdata from the state
//using the id
return <FormInstance { ...this.props } />
}
}
const mapStateToProps = (state) => {
console.log(id); //is null for one run
return {
userforms: state.userforms
};
};
return connect(mapStateToProps)(FormDataMapper);
}
So in the List component I can:
import UserEditForm from './UserEditForm';
import {formdatainstance} from './formdatainstance';
const MappedUserEditForm = formdatainstance(UserEditForm);
class List extends React.Component {
render(){
return (
{users.map(user => {
//more stuff
<MappedUserEditForm />
//more stuff
})}
);
}
}
So my Question: Is this a good Idea? If yes what would be the proper way to do the cleanup, so when in the life cycle of the component should I delete the data from the state? Is there another way to do that, which is easier?
Thanks for Help!

Here's what you can do...
import React from 'react';
import { compose } from 'redux';
import { connect } from 'react-redux';
import { reduxForm } from 'redux-form';
class UserEditForm extends Component {
...
render() {
return <form onSubmit={this.props.handleSubmit(this.props.onSubmit)}>
...form fields
</form>
}
}
const mapStateToProps = (state, ownProps) => {
return {
form: ownProps.formId
}
}
export default compose(
connect(mapStateToProps),
reduxForm({
//...other redux-form options
})
)(UserEditForm);
Your ListComponent
render() {
return <ul>
{this.props.users.map(user =>
<li key={user.id}>
...
<UserEditForm formId={'form-' + user.id} onSubmit={...} />
</li>
)}
</ul>
}
This allows you to have a dynamic form name.

Even if the answer of #jpdelatorre seems to be the best hit for me, since it also includes the link to redux-forms, what will probably help me a lot, I would like to post my working solution here, just in case somebody might find it useful. It just hit me over night, so needed to test if my thought were right, what I could finally proof.
I was not able to do the whole Mapping with a sole HOC and I needed to add/modify reducers too. Basically it works that way:
Data Mapping is done by ID,
the original action creators are wrapped, such that the used id is attached to the Object
the reducers are wrapped two and called by the »datamapped« reducer
So the code of the original reducers and action creators does not need to be changed, what makes the wrapping kind of easy to use. I first wanted to use uuid's which are created on the fly, but I discarded that, to make possible to save and restore the whole application state.
so the HOC code is that:
import React from 'react';
import {connect} from 'react-redux';
// The Component to wrap,
// all of its actions
// its default state
export const formdatainstance = (FormInstance, Actions, defaultState = {}) => {
const mapStateToProps = (state) => {
return {
mappedData: state.mappedData
};
};
class FormDataMapper extends React.Component {
static propTypes = {
id: React.PropTypes.string.isRequired
};
static contextTypes = {
store: React.PropTypes.object
};
//most of mapping happens here
render () {
//wrap the action creators
const actions = Object.keys(Actions).reduce((list, key) =>{
list[key] = (...args) => {
const action = Actions[key](...args);
//handle asyn operations as well
if('then' in action && typeof action['then'] == 'function') {
action.then(data => {
//attaching the id
this.props.dispatch({...data, id: this.props.id});
});
} else {
//attach the id
this.context.store.dispatch({...action, id: this.props.id });
}
};
return list;
}, {}),
//there wont be any data at first, so the default state is handed
//over
mappedProps = this.props.mappedData.hasOwnProperty(this.props.id) ?
this.props.mappedData[this.props.id] : defaultState;
//merge the hotchpotch
let props = Object.assign({}, mappedProps, this.props, actions);
//clean up
delete props.id;
delete props.mappedData;
return <FormInstance { ...props } />
}
}
return connect(mapStateToProps)(FormDataMapper);
};
the reducer code:
//hlper method
export const createTypesToReducerMap = (types, reducer) => {
return Object.keys(types).reduce((map, key) => {
map[types[key]] = reducer;
return map;
}, {});
}
export const createMappedReducer = (reducerMap, defaultState = {}) => {
const HANDLERS = reducerMap.reduce((handlers, typeMap) => {
return { ...handlers, ...typeMap };
},{});
return (state, action) => {
if (!action.hasOwnProperty('id')) {
if (state === undefined) return defaultState;
return state;
}
const reducer = HANDLERS.hasOwnProperty(action.type) ?
HANDLERS[action.type] : null;
let a = {...action};
delete a.id;
return reducer !== null ?
Object.assign({}, state, { [action.id]: reducer(state[action.id], a)}) :
state;
}
}
and finally the store:
const userEditTypeReducerMap = createTypesToReducerMap(userEditTypes, userFormReducer);
const reducer = combineReducers({
…
mappedData: createMappedReducer(
[userEditTypeReducerMap], {})
…
});
export default compose(
applyMiddleware(
thunk
)
)(createStore)(reducer, {});

Related

Redux store updates in asynchronous function, but container does not get props changes

I' trying to make a real time application with react, redux and redux-thunk, that gets the objects from back-end through socket with STOMP over sockJS, and update redux store every time an object comes and finally updates the container when redux store updates.
My connect class through stomp over sockjs is this;
class SearcButtons extends Component {
render() {
return (
<div className="searchbuttons">
<RaisedButton className="bttn" label="Start" onClick={() => this.start_twitter_stream()} />
<RaisedButton className="bttn" label="Start" onClick={() => this.stop_twitter_stream()} />
</div>
);
}
start_twitter_stream() {
let stompClient = null;
var that = this;
let socket = new SockJS('http://localhost:3001/twitterStream');
stompClient = Stomp.over(socket);
stompClient.debug = null;
stompClient.connect({}, function () {
stompClient.subscribe('/topic/fetchTwitterStream', function (tokenizedTweet) {
let tweet = JSON.parse(tokenizedTweet.body);
let payload = {
data: {
tweets: that.props.state.reducer.tweets,
}
}
payload.data.tweets.push(
{
"username": tweet.username,
"tweet": tweet.tweet,
}
);
that.props.actions.update_tweets_data(payload);
});
stompClient.send("/app/manageTwitterStream", {}, JSON.stringify({ 'command': 'start', 'message': that.props.state.reducer.keyword }));
let payload = {
data: {
socketConnection: stompClient
}
}
that.props.actions.start_twitter_stream(payload);
});
}
stop_twitter_stream() {
var socketConnection = this.props.state.reducer.socketConnection;
socketConnection.send("/app/manageTwitterStream", {}, JSON.stringify({ 'command': 'stop', 'message': null }));
socketConnection.disconnect();
let payload = {
data: {
socketConnection: null
}
}
return this.props.actions.stop_twitter_stream(payload);
}
}
SearcButtons.propTypes = {
actions: PropTypes.object,
initialState: PropTypes.object
};
function mapStateToProps(state) {
return { state: state };
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(actions, dispatch)
};
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(SearcButtons);
I'm calling tweet panel container inside App.js
import TweetPanel from './containers/TweetPanel';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
class App extends Component {
render() {
return (
<MuiThemeProvider>
<div className="main">
<TweetPanel />
</div>
</MuiThemeProvider>
);
}
}
export default App;
My container that listens redux-store is this;
class TweetPanel extends Component {
const TABLE_COLUMNS = [
{
key: 'username',
label: 'Username',
}, {
key: 'tweet',
label: 'Tweet',
},
];
render() {
console.log(this.props);
return (
<DataTables
height={'auto'}
selectable={false}
showRowHover={true}
columns={TABLE_COLUMNS}
data={
(typeof (this.props.state.reducer.tweets) !== "undefined" ) ?this.props.state.reducer.tweets : []
}
showCheckboxes={false}
onCellClick={this.handleCellClick}
onCellDoubleClick={this.handleCellDoubleClick}
onFilterValueChange={this.handleFilterValueChange}
onSortOrderChange={this.handleSortOrderChange}
page={1}
count={100}
/>
);
}
}
TweetPanel.propTypes = {
actions: PropTypes.object,
initialState: PropTypes.object
};
function mapStateToProps(state) {
return { state: state };
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(actions, dispatch)
};
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(TweetPanel);
My actions;
import {
BUILD_TWITTER_STREAM,
START_TWITTER_STREAM,
UPDATE_TWEETS_DATA,
} from '../actions/action_types';
export function build_twitter_stream(state) {
return {
type: BUILD_TWITTER_STREAM,
payload: state
};
}
export function start_twitter_stream(state) {
return {
type: START_TWITTER_STREAM,
payload: state
};
}
export function update_tweets_data(state) {
return {
type: UPDATE_TWEETS_DATA,
payload: state
};
}
My reducer;
import update from 'immutability-helper';
let initialState = {
socketConnection : null,
tweets : [ ]
}
export default function reducer(state = initialState, action) {
switch (action.type) {
case BUILD_TWITTER_STREAM:
return update(
state, {
socketConnection: { $set: action.payload.data.socketConnection }
}
);
case START_TWITTER_STREAM:
return update(
state, {
socketConnection: { $set: action.payload.data.socketConnection }
}
);
case UPDATE_TWEETS_DATA:
return update(
state, {
tweets: { $merge: action.payload.data.tweets }
}
);
default:
return state;
}
}
My observations are when I try to connect to socket through stomp over Sockjs, I need to pass the context as named "that" variable which you can see the first code block above and update redux store with that context in stompClient's connect function's callback, which means I update store in an asynchronou function, redux store updates very well when I look to Chrome' s extension of Redux devtools, but container doesn't update unless I press to the stop button which triggers an action which is not asynchronous.
Thanks in advance, your help is much appreciated :)
I can offer another approach, function delegate approach, since I have struggled by similar issiues. I create props in components, like your RaisedButton. For example I create BindStore props, such as:
<RaisedButton BindStore={(thatContext)=>{thatContext.state = AppStore.getState();}}... />
Also I can add subscriber props, such as:
<RaisedButton SubscribeStore={(thatContext) => {AppStore.subscribe(()=>{thatContext.setState(AppStore.getState())})}} ... />
At the RaisedButton.js, I can give thatContext easily:
...
constructor(props){
super(props);
if(this.props.BindStore){
this.props.BindStore(this);
}
if(this.props.SubscribeStore){
this.props.SubscribeStore(this);
}
}
...
Also by doing so, means by using props, one RaisedButton may not have BindingStore ability or SubscribeStore ability. Also by props I can call store dispatchers, such as:
in parent.js:
<RaisedButton PropsClick={(thatContext, thatValue) => {AppStore.dispacth(()=>
{type:"ActionType", payload:{...}})}} ... />
in RaisedButton.js
//as an example I used here dropDown, which is:
import { Dropdown } from 'react-native-material-dropdown';
//react-native-material-dropdown package has issues but the solutions are in internet :)
...
render(){
return(
<View>
<Dropdown onChangeText={(value) => {this.props.PropsClick(this, value);}} />
</View>
)
}
...
In many examples, for instance your parent is SearchButtons, the parent must be rendered, the parent must subscribe the store so when any child changes the store all component cluster is rerendered. But, by this approach, children components are subscribed and bound to the store. Even one child may dispatch an action and after that, other same type children subscribed function is called back and only the subscribed children is rerendered. Also, you will connect only one component to the redux store, the parent.
//parent.js
import { connect } from 'react-redux';
import AppStore from '../CustomReducer';
...
//you will not need to set your parent's state to store state.
I did not investigate very much about this approach but I do not use mapping functions. However, the child components will access all store datas, but also in mappings all store datas are also accessible.

Change in state do not propagate in props

I have the "classic" issue with the React redux about not propagating the change in state into the props when I try to access it in the component.
Here I have read that
99.9% of the time, this is because you are accidentally mutating data, usually in your reducer
Can you tell me what am I doing wrong? Is this the good way how to do the deep copy of the property of the specified object in array?
note: in the reducer in the return statement the state is clearly changed correctly (debugged it)
reducer:
case 'TOGGLE_SELECTED_TAG':
const toggledTagId = action.payload;
const index = findItemById(state.tags, toggledTagId);
const newTags = state.tags.slice(0);
if(index >= 0)
{
newTags[index] = Object.assign(
state.tags[index],
{selected: !state.tags[index].selected});
state.tags = newTags;
}
return Object.assign({}, state);
component:
import React from 'react';
import { Button, FormControl, Table, Modal } from 'react-bootstrap';
import { connect } from 'react-redux';
import axios from 'axios';
import {selectTagAction} from '../../actions/actions'
#connect((store) => {
return {
tags: store.TagsReducer.tags,
}
})
export default class AssignTag extends React.Component {
constructor(props) {
super(props);
this.handleTagClick = this.handleTagClick.bind(this);
}
handleTagClick(element) {
debugger;
this.props.dispatch(selectTagAction(element));
}
render() {
const tags = this.props.tags;
console.log(tags);
const mappedTags = tags.map(tag => {
return (
<div className="col-sm-12" key={tag.id} onClick={() => this.handleTagClick(tag.id)}
style={{background: this.getBackgroundColor(tag.selected)}}>
<span>{tag.name}</span>
</div>
)
})
// code continues
}
}
You are indeed mutating the state. Try this:
case 'TOGGLE_SELECTED_TAG':
const toggledTagId = action.payload;
const index = findItemById(state.tags, toggledTagId);
let newTags = state;
if( index >= 0 )
{
newTags[index] = Object.assign(
{},
state.tags[index],
{ selected: !state.tags[index].selected }
);
//state.tags = newTags; This line essentially mutates the state
return Object.assign( {}, state, { tags: newTags });
}
return state;
Another workaround to avoiding mutation of state is to use the ES6 shorthand in your reducer:
.... return { ...state, tags : newTags };

How to reuse reducer with same action using redux-subspace

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.
. my plan is each state has their own value stored.
const mapStateToProps = state => {
return {
withSpouseAgePoint: state,
withoutSpouseAge: state,
}
}
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.

blueprintjs Table with redux

I have troubles with a blueprintjs table with redux. Here I am trying to maintain the row selection thru redux. I am fairly new to react/redux so maybe I am missing something?
I tried the same component without redux (i.e. the selection modifies the state of the component) and it works fine, but I would prefer to use redux.
Here is a minimal example (jsx) with redux:
import React from "react"
import {Cell, Column, Table, SelectionModes, Regions, RegionLayer} from "#blueprintjs/table"
import ReactDOM from 'react-dom';
import {Provider, connect} from 'react-redux'
import {applyMiddleware, createStore} from 'redux';
import createLogger from 'redux-logger';
export class TableTestRedux extends React.Component {
constructor(props) {
super(props)
this.state={}
}
componentDidUpdate() {
console.log("TableTestRedux:componentDidUpdate props:", this.props)
}
onSelection(e) {
console.log("TableTestRedux:onSelection e:", e)
if (e.length > 0) {
var selectedRow = e[0].rows[0]
this.props.onSelectedEdgeOverrideIdx(selectedRow)
}
}
getSelectedRegion() {
var region = this.props.selectedEdgeOverrideIdx != null ? [Regions.row(this.props.selectedEdgeOverrideIdx)] : []
console.log('TableTestRedux:getSelectedRegion returns ', region);
return region
}
render() {
console.log('TableTestRedux:render props:', this.props);
const renderCell = (rowIndex) => <Cell>{this.props.edgeOverrides[rowIndex]}</Cell>;
return (
<div>
<Table ref="table" numRows={this.props.edgeOverrides.length} onSelection={(e) => this.onSelection(e)}
allowMultipleSelection={false}
selectionModes={SelectionModes.ROWS_ONLY}
selectedRegions={this.getSelectedRegion()}>
<Column name="Description" renderCell={renderCell}/>
</Table>
</div>
)
}
}
// --- redux container/smart component
const mapStateToProps = (state) => {
console.log("TableTestRedux:mapStateToProps ", state);
return {
edgeOverrides: state.edgeOverrides ? state.edgeOverrides : [],
selectedEdgeOverrideIdx: state.selectedEdgeOverrideIdx
}
}
const mapDispatchToProps = (dispatch) => {
console.log("TableTestRedux:mapDispatchToProps ");
return {
onSelectedEdgeOverrideIdx: (selectedEdgeOverrideIdx) => {
dispatch(OverrideEntryActions.selectEdgeOverrideIdx(selectedEdgeOverrideIdx))
}
}
}
export const TableTestReduxCC = connect(mapStateToProps, mapDispatchToProps)(TableTestRedux)
// --- redux action and reducer
export class OverrideEntryActions {
static selectEdgeOverrideIdx(selectedEdgeOverrideIdx) {
return {
type: 'SELECT_EDGE_OVERRIDE_IDX',
selectedEdgeOverrideIdx: selectedEdgeOverrideIdx
}
}
}
const initialOverrideEntryState = {
selectedEdgeOverrideIdx: null,
edgeOverrides: ["bla", "blabla", "more blabla"]
}
export const overrideEntryReducer = (state = initialOverrideEntryState, action) => {
console.log("overrideEntryReducer: action:", action, " state:", state)
switch (action.type) {
case 'SELECT_EDGE_OVERRIDE_IDX':
return {selectedEdgeOverrideIdx: action.selectedEdgeOverrideIdx}
default:
return state
}
}
// --- launch
var store = createStore(overrideEntryReducer, applyMiddleware(createLogger()))
ReactDOM.render((
<Provider store={store}>
<TableTestReduxCC/>
</Provider>
), document.getElementById('app'))
When I click on a row header, TableTestRedux.render() is called and this causes all the table cells to be blank. The log shows:
Warning: NaN is an invalid value for the height css style property. Check the render method of RegionLayer.
The problem was on my side in overrideEntryReducer, I was erasing the edgeOverrides property of the state. Here is the fix:
case 'SELECT_EDGE_OVERRIDE_IDX':
return {...state,selectedEdgeOverrideIdx: action.selectedEdgeOverrideIdx}

React/Redux Connect issues with mapStateToProps

I have the following React Component
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import _ from 'lodash';
import Product from './product';
import { openPaymentModal } from '../../../state/modalActions';
import { fetchAccountProducts } from '../../../lib/ecApi';
import { fetchChargifyCallById } from '../../../lib/chargifyApi';
import { filterProductsForUser, prepProducts } from '../../../_helpers';
class Products extends Component {
constructor () {
super();
this.state = {
products: [],
currentProduct: '',
showSuccess: false,
}
}
componentDidMount() {
const { location, user } = this.props;
fetchAccountProducts()
.then(this.addBasicProduct)
.then(this.filterProducts(user));
this.checkChargifyCall(location.query, user);
}
addBasicProduct(products) {
return prepProducts(products);
}
filterProducts(user) {
return products => {
this.setState({products: filterProductsForUser(products, user)});
}
}
checkChargifyCall (query, user) {
if (_.isEmpty(query)) {
const currentProduct = this.determineProduct(user);
this.setState({currentProduct});
return;
}
fetchChargifyCallById(query.call_id).done(data => {
const { product } = data.response.signup;
const { errors } = data.response.meta;
if (query && query.status_code !== '200') {
this.props.dispatch(openPaymentModal(
product.handle,
errors,
));
} else {
this.setState({
currentProduct: product.handle,
showSuccess: true
});
}
});
}
determineProduct(user) {
const subscription = user.chargifySubscriptions[0];
if (subscription && subscription.product) {
return subscription.product.handle;
}
return this.state.currentProduct;
}
render () {
let calloutEl = (
<div className='callout success'>Success!</div>
);
return (
<div className="row medium-up-2 large-up-3 products-row">
{this.state.showSuccess && calloutEl}
{this.state.products.map((object, i) => {
return <div className="column"><Product
price={object.price}
name={object.name}
interval={object.interval}
intervalUnit={object.interval_unit}
isPaid={object.require_credit_card}
description={object.description}
handle={object.handle}
key={i}
currentProduct={this.state.currentProduct} /></div>;
})}
</div>
);
}
}
const mapStateToProps = state => ({user: state.user});
export default connect(mapStateToProps)(Products);
The problem I am having is that if I console.log(this.props.user) in my componentDidMount method, it is the initial state from the reducer vs the fully propagated user state. Any reasons why that might be happening? I'm fairly new to React/Redux, so my apologies for ignorance
Answer: it is the initial state from the reducer.
reason reducer represents a piece of state. and please your a promise middle to handle your data fetching.
The problem I am having is that if I console.log(this.props.user) in my componentDidMount method, it is the initial state from the reducer vs the fully propagated user state. Any reasons why that might be happening? I'm fairly new to React/Redux, so my apologies for ignorance.
connect is a high order component that passes data to your container component. in your case Products component receives state as props from connect
const mapStateToProps = state => ({user: state.user}); //the state you want
export default connect(mapStateToProps)(Products); //user as state to you products component.

Resources