React - Unable to set state from the response on initial render - reactjs

This is the response from redux store :
{
"newsletter": true,
"orderConfirmation": true,
"shippingInformation": true,
"orderEnquiryConfirmation": true,
}
This is the jsx file, where am trying to set state. The idea is setting the state from the response and add an onChange handle to each checkboxes.
But currently am receiving a correct response but I tried to set state in didUpdate, DidMount but no luck. I want to know the correct place to set state on initial render of the component.
import React from 'react';
import Component from '../../assets/js/app/component.jsx';
import { connect } from 'react-redux';
import * as actionCreators from '../../assets/js/app/some/actions';
import { bindActionCreators } from 'redux';
import Checkbox from '../checkbox/checkbox.jsx';
const mapStateToProps = (state, ownProps) => {
return {
...state.emailSubscriptions
}
}
const mapDispatchToProps = dispatch => {
return {
actions: bindActionCreators(actionCreators, dispatch)
}
}
#connect(mapStateToProps, mapDispatchToProps)
class EmailSubscriptions extends Component {
constructor(props) {
super(props);
this.state = {};
}
componentDidMount() {
this.props.actions.getEmailSubscriptions();
this.setState({ // Not setting state
notifications: [
newsletter = this.props.newsletter,
orderConfirmation = this.props.orderConfirmation,
shippingInformation = this.props.shippingInformation,
orderEnquiryConfirmation = this.props.orderEnquiryConfirmation
]
})
}
render() {
return (
<div>
Here I want to use loop through state to create checkboxes
{this.state.notifications&& this.state.notifications.map((item, index) => {
const checkboxProps = {
id: 'subscription' + index,
name: 'subscription',
checked: item.subscription ? true : false,
onChange: (e)=>{ return this.onChange(e, index)},
};
return <div key={index}>
<Checkbox {...checkboxProps} />
</div>
</div>
)
}
}
export default EmailSubscriptions;

I hope getEmailSubscriptions is an async action, so your setState won't update the state as you intended. add componentDidUpdate hook in your class component and your setState statement within an if statement that has an expression checking your props current and prev value.
You can do something like this.
componentDidMount() {
this.props.actions.getEmailSubscriptions();
}
componentDidUpdate(prevProps, prevState, snapshot){
if(this.props.<prop_name> != prevProps.<prop_name>){
this.setState({
notifications: [
newsletter = this.props.newsletter,
orderConfirmation = this.props.orderConfirmation,
shippingInformation = this.props.shippingInformation,
orderEnquiryConfirmation = this.props.orderEnquiryConfirmation
]
})
}
}

Related

this.setState not updating state

For some reason my this.state call is not updating the state in my roleClicked function. I can't seem to figure out what the problem is. Here is the code:
import React, { Component } from 'react'
import { Redirect } from 'react-router-dom';
import instance from '../../../config/axios';
import ls from 'local-storage';
import Sidenav from '../../layout/Sidenav'
import isLoggedIn from '../../../helpers/isLoggedIn';
import redirectToLogin from '../../../helpers/redirectToLogin';
import apiErrorHandler from '../../../helpers/apiErrorHandler';
import RolesHeader from './RolesHeader';
import '../../../App.css'
import { Table, Pagination, Input, Alert } from 'antd';
export default class ViewRoles extends Component {
state = {
loggedIn: true,
roleSelected: false,
roleSelectedId: null,
rolesTable: {
columns: null,
dataSource: null,
currentPageNumber: null,
currentPageSize: null,
total: null,
},
filters: {
roleName: null
},
displays: {
createRoleView: false,
roleCreatedAlert: false,
},
errors: null
}
componentDidMount = () => {
//here i make AJAX calls to set rolesTables
}
roleClicked = (record) => {
console.log("clicked")
this.setState({
roleSelected: true,
roleSelectedId: record.key
})
}
render() {
if(this.state.roleSelected) {
const roleUrl = "/roles/" + this.state.roleSelectedId
return <Redirect to={roleUrl}/>
}
return (
<React.Fragment>
<Sidenav activeLink="roles"/>
<Table
className="border"
columns={this.state.rolesTable.columns}
dataSource={this.state.rolesTable.dataSource}
pagination={false}
onRow={(record) => {
return {
onClick: () => this.roleClicked(record)
}}}
/>
<Pagination
className="m-3"
current={this.state.rolesTable.currentPageNumber}
pageSize={this.state.rolesTable.currentPageSize}
total={this.state.rolesTable.total}
onChange={this.loadRolesTable}
/>
</React.Fragment>)
}
}
The function runs as expected but the state is not updated. Any idea what the problem could be?
Everywhere else I use this.setState it works. The full code can be found on this link.
I updated roleClicked to this and it works. I still have no idea why the initial code didn't work.
viewRole = async (record) => {
var currentState = this.state;
currentState.roleSelected = true;
currentState.roleSelectedId = record.key;
await this.setState(currentState);
console.log(this.state)
}
Just changing it to Async didn't work. Only assigning the state to currentState and then updating currentState and using it in setState worked. And now it works both synchronously and asynchronously.
try something like this
this.setState(function(state, props) {
return {
...state,
roleSelected: true,
roleSelectedId: record.key
};
});

lifecycle react , redux, and redux-form apollo-graphql

I have a redux-form component and another container component that load apollo graphql data. Here below just some important parts of code.
FORM COMPONENT:
class Form extends Component {
constructor(props) {
super(props);
setTimeout(function() {
this.executeCode ( 'onChangeInput', { action: 'initForm' , props: this.props, formProps: this.props, formState: this.state });
}.bind(this), 1000);
}
render() {
(...)
}
}
const ComponentWithData = reduxForm({
form: nameForm,
validate,
})(Form);
function mapStateToProps(state, ownProps) {
const log = false;
const statesReturn = { myState: state };
let initialValues;
initialValues = processValues(ownProps, tableCrud, ownProps.data, 'toClient','view' );
statesReturn.initialValues = initialValues ;
return statesReturn;
}
const ComponentWithDataAndState = connect(
mapStateToProps,
null,
)(ComponentWithData);
export default ComponentWithDataAndState;
CONTAINER COMPONENT:
class FormContainer extends Component {
render() {
const { t, ...otherProps} = this.props;
let aElements = [];
let aQlFiltered = {"crud_view_payment":{"table":"payment"}};
const resultCheck = checkLoadCrud (aQlFiltered,this.props);
if (resultCheck.messageError) {
return <MsgError msg={resultCheck.messageError} t={this.props.t} />;
}
if (!resultCheck.globalLoading && !resultCheck.messageError) {
if (this.props['crud_view_'+tableCrud] && this.props['crud_view_'+tableCrud][tableCrud]) {
if (this.props['crud_view_'+tableCrud][tableCrud].deleted) {
aElements.push(<RecordHeadInfo
key="recordhead"
tableCrud={tableCrud}
{...this.props}
data={this.props['crud_view_'+tableCrud][tableCrud]}
/>);
}
}
}
if (!resultCheck.globalLoading && !resultCheck.messageError) {
aElements.push(<Form
crudAction="View"
key="mainform"
id={ this.props.match.params.id }
data={this.props['crud_view_'+tableCrud][tableCrud]}
onSubmit={this.handleSubmit}
containerPropsForm={this.props}
t={this.props.t}
/>);
}
}
return (
<div>
{aElements}
</div>
);
}
}
const withGraphqlandRouter = compose(
graphql(defQls.payment.View, {
name: 'crud_view_payment',
options: props => {
const optionsValues = { variables: {id: props.match.params.id, _qlType: 'View' }};
optionsValues.fetchPolicy = Tables[tableCrud].fetchPolicy ? Tables[tableCrud].fetchPolicy :'network-only';
return optionsValues;
},
}),
)(withRouter(FormContainer));
const mapStateToProps = (state) => {
return {
myState: state,
};
};
const mapDispatchToProps = (dispatch) => {
return bindActionCreators ({ appSubmitStart, appSubmitStop, showConfirm, initialize, dispatch }, dispatch ); // to set this.props.dispatch
};
const withState = connect(
mapStateToProps ,
mapDispatchToProps,
)(withGraphqlandRouter);
const ComponentFull = withState;
export default ComponentFull;
on Form Component I have a setTimeout that execute a code, because I need set disable or hidden field accord to data loaded. I can't do it directly on construct() neither componentDidMount() because if i try to retrieve data from redux: this.props.myState.form ( have no values). it's for that use a timeout, with 1000 is ok, and with 1 milisecond it's ok too, I see this.props.myState.form.myForm.values (with data retrivied from db trough apollo), i prefer 1 milisecond of course because i don't see blink fields that get disabled or dissapears, but i'm not sure that is a good practice, because in a slow computer or slow browser that can produce conflict with the render ?
It's not clear form the lifecycle mixing react, redux, apollo and redux-form; anyone has idea how i can order better my ideas to write better code here?

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 };

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