React Redux Dispatch - reactjs

What is the best way to call a dispatch to get initial data on a React component. My understanding is that ComponentWillMount is called before render. So in theory if I call dispatch on ComponentWillMount, by the time I hit render and then ComponentDidMount I should have my data in the component's props, right? I'm not seeing that.
I'm seeing that render gets called twice and that on the first go when the component is being initialized, I cannot access the data in props. It also seems like dispatch does not actually get called until the second render. I'm basically looking to have some light shed on the best way to call a dispatch when initially setting up a component. I'm essentially trying to do something like the following where I use a container component to get my data from dispatch and then pass it to a child component as props. But I also want to initialize some state variables in the ContainerComponent and then pass them to the ChildComponent as props. The thing is that the state variables I want to initialize depend on the data returned from dispatch and ideally I would do the initialization in ComponentWillMount or ComponentDidMount.
import React from 'react';
import axios from 'axios';
import { connect } from 'react-redux';
import ChildComponent from './ChildComponent.js';
import { getTransactionsAll } from '../actions/actions.js';
class ContainerComponent extends React.Component {
constructor() {
super();
this.state = {
acctList:[],
acctChecked:[],
categoryList:[]
}
}
componentWillMount() {
console.log("componentWillMount entered");
this.props.get_data();
console.log(this.props.searchProps.transactions_all);//this is undefined meaning the dispatch has not assigned the data yet...??
}
componentDidMount() {
console.log("componentDidMount entered");
console.log(this.props.searchProps.transactions_all);//this is undefined meaning the dispatch has not assigned the data yet...??
}
render() {
console.log("TransactionManagerContainer render entered");
console.log(this.props.searchProps.transactions_all);//this is undefined the first time around meaning the dispatch has not assigned the data yet...??, but is defined on the second call to render after the dispatch has actually occurred...
return <ChildComponent
data={this.props.searchProps.data}/>;
}
const mapStateToProps = (state) => ({
searchProps: state.searchProps
});
export default connect(mapStateToProps, {getTransactionsAll})(TransactionManagerContainer);
Here is my reducer that assigns the state:
import { combineReducers } from 'redux'
import {GET_TRANSACTIONS } from '../actions/actions.js'
import {GET_TRANSACTIONS_ALL } from '../actions/actions.js'
const INITIAL_STATE = { defaultYear: 2016, transactions: []};
function get_transactions(state = INITIAL_STATE, action) {
// console.log("this is in the reducer: get_transactions");
// console.log(action);
switch(action.type) {
case GET_TRANSACTIONS:
// return { ...state, transactions: action.payload };
return Object.assign({}, state, {
transactions: action.payload,
selectedYear: action.selectedYear
})
default:
return state;
}
}
function get_transactions_all(state = INITIAL_STATE, action) {
console.log("this is the value of action in the reducer: get_transactions_all");
console.log(action);
switch(action.type) {
case GET_TRANSACTIONS_ALL:
// return { ...state, transactions: action.payload };
return Object.assign({}, state, {
transactions_all: action.payload
})
console.log("this is the value of state in the reducer after being set");
console.log(state);
default:
return state;
}
}
const rootReducer = combineReducers({
//stateProps: get_transactions,
searchProps: get_transactions_all
})
export default rootReducer
Here are my actions:
import axios from 'axios';
export const GET_TRANSACTIONS = 'GET_TRANSACTIONS';
export function getTransactions(year) {
return function(dispatch) {
axios.get(`http://localhost:3001/api/transfilter?year=${year}&grouping=2`)
.then(response => {
dispatch({
type: GET_TRANSACTIONS,
payload: response.data,
selectedYear: year
});
})
.catch((error) => {
console.log(error);
})
}
}
export const GET_TRANSACTIONS_ALL = 'GET_TRANSACTIONS_ALL';
export function getTransactionsAll(year) {
return function(dispatch) {
axios.get(`http://localhost:3001/api/trans?limit=20`)
.then(response => {
dispatch({
type: GET_TRANSACTIONS_ALL,
payload: response.data
});
})
.catch((error) => {
console.log(error);
})
}
}

I believe your main question is:
What is the best way to call a dispatch to get initial data on a React component?
Getting initial data requests (or any AJAX requests in general) should go in the componentDidMount lifecycle event.
There are a few reasons for this, here are two important:
Fiber, the next implementation of React’s reconciliation algorithm, will have the ability to start and stop rendering as needed for performance benefits. One of the trade-offs of this is that componentWillMount, the other lifecycle event where it might make sense to make an AJAX request, will be “non-deterministic”. What this means is that React may start calling componentWillMount at various times whenever it feels like it needs to. This would obviously be a bad formula for AJAX requests.
You can’t guarantee the AJAX request won’t resolve before the component mounts. If it did, that would mean that you’d be trying to setState on an unmounted component, which not only won’t work, but React will yell at you for. Doing AJAX in componentDidMount will guarantee that there’s a component to update.
Credits: I learned that from here, there is also a discussion here.
Then, there are a lot of smaller question you've raised and it will be hard for me to answer all, but I'll try to cover most:
After reading the above, you now should understand why your data is undefined in componentWillMount and componentDidMount. That's simply because the data has not arrived yet;
It's normal that your data is undefined during the first render of the component. Initial render happens before data arrival;
It's normal that the data is defined during the second render. The dispatch triggers asynchronous data fetch. Right after data comes, a reducer is hit and component gets re-rendered (that's the second re-render).
If the child components in your main component require the data - check in the parent render method if data exists pass internal components conditionally, only if data is present. Like so:
class ContainerComponent extends React.Component {
// ... omitted for brevity
render() {
return (
{ this.props.searchProps.data ?
<ChildComponent
data={this.props.searchProps.data} />
: <p>Loading</p>
}
);
}
}

Related

React-Redux: how to set the state?

I am trying to understand someone else their code but have difficulty understand the interaction between Redux and React.
On a React page, I invoke a Redux action called getSubscriptionPlan. Inside that Redux action, I see it is able to load the correct data (point 1 below). This uses a reducer, in which I can again confirm the correct data is there (point 2 below).
Then the logic returns to the React page (point 3 below). I now would expect to be able to find somewhere in the Redux store the previously mentioned data. However, I can't find that data listed anywhere... not in this.state (where I would expect it), nor in this.props. Did the reducer perhaps not update the store state...?
What am I doing wrong and how can I get the data to point 3 below?
React page:
import { connect } from "react-redux";
import { getSubscriptionPlan } from "../../../appRedux/actions/planAction";
async componentDidMount() {
let { planId } = this.state;
await this.props.getSubscriptionPlan(planId);
// 3. I can't find the data anywhere here: not inside this.state and not inside this.props.
this.setState({plan: this.state.plan});
}
componentDidUpdate(prevProps, prevState) {
if (prevProps.payment.paymentData !== this.props.payment.paymentData) {
this.setState({
checkout: this.props.payment.paymentData,
plan: this.props.payment.paymentData.plan,
});
}
}
const mapStateToProps = (state) => {
return {
plan: state.plan,
};
};
const mapDispatchToProps = (dispatch) => {
return bindActionCreators(
{ getSubscriptionPlan }, dispatch
);
};
export default withRouter(
connect(mapStateToProps, mapDispatchToProps)(Checkout)
);
Redux action:
export const getSubscriptionPlan = (id) => {
let token = getAuthToken();
return (dispatch) => {
axios
.get(`${url}/getSubscriptionPlan/${id}`, {
headers: { Authorization: `${token}` },
})
.then((res) => {
if (res.status === 200) {
// 1. From console.log(res.data) I know res.data correctly now contains the data
return dispatch({
type: GET_PLAN_SUCCESS,
payload: res.data,
});
})
};
};
Reducer:
export default function planReducer(state = initial_state, action) {
switch (action.type) {
case GET_PLAN_SUCCESS:
// 2. I know action.payload, at this point contains the correct data.
return { ...state, plan: action.payload };
default:
return state;
}
}
You are getting tripped up on how Redux works.
Redux does not use react component state. It manages state separately, and passes that state to components as props. When you call getSubscriptionPlan, you asynchronously dispatch an event to Redux, which handles the event and updates store state in the reducer. This state is the passed to the connected components mapStateToProps function, mapped to props, and then passed as props to your component. Passing new props triggers a componentDidUpdate and a rerender of the component.
A few key things here.
Redux does not interact with component state unless you explicitly set state with props passed from Redux.
Redux is asynchronous. That means that when you make a change to state via dispatch, the change is not immediately available in the component, but only available when new props are passed. It's event driven, not data binding. As a result, in your code you woun't see the plan prop in componentDidMount because at the time componentDidMount the call to getSubscriptionPlan hasn't happened.
You should see the prop populated in this.props in componentDidUpdate and in render before the didUpdate.
When working with react, it's best to think of components as basically functions of props with some extra lifecycle methods attached.

Redux state has changed, why doesn't it trigger a re-render? (Redux-saga)

I'm using react + redux + redux saga
I'm facing the issue that when I'm rendering the page (GET call)
The calls should be like:
constructor
render()
componentDidMount
render()
But I'm just reaching up to componentDidMount, mapDispatchToProps is dispatching the action, API call is working by which getting the response from the server and the data is updated into the state.
BUT somewhere it gets lost and my component is not even re rendering.
Up to the reducer, I'm getting the data where I'm returning action.items.
itemReducer.js
const itemReducer = (state = initialState, action) => {
switch (action.type) {
case types.GET_ALL_ITEMS_SUCCESS:
console.log("itemReducer-----", action.items); //getting the data over here
return action.items;
default:
return state;
}
};
itemPage.js (component)
class ItemsPage extends React.Component {
componentDidMount() {
this.props.loadItems();
}
render() {
const { items } = this.props; // not even it renders, so not getting data
...
return (<div>...</div>);
}
}
const mapStateToProps = (state) => {
return {
items: state.items,
};
};
const mapDispatchToProps = (dispatch) => {
return {
loadItems: () => dispatch(loadAllItemsAction()),
};
};
export default connect(mapStateToProps, mapDispatchToProps)(ItemsPage);
Please give some suggestions, Thanks in advance :D
There isn't issue in the code that you posted. To make sure I pretty much copy pasted the code you shared, filled in the missing parts and it works just fine:
https://codesandbox.io/s/3tuxv?file=/src/index.js
My guess what might be wrong is that your items are not stored in state.items but under some different path or you might be missing the react-redux Provider, but it is impossible to say for sure without seeing more of your code.
You need to understand that the calls of render/componentDidMount are not so linear as it could be expected. componentDidMount fires when all children were mount. And it doesn't means that render() was finished already.
Read here for more info:
https://github.com/facebook/react/issues/2659

How middleware in react life cycle works?

I am new in react js. I have started doing a small product with react-redux. I am using saga middle-ware.
What i have done is as under.
This is the component
//all import work
import { activateAuthLayout, onLoad } from '../../../store/actions';
class EcommerceProductEdit extends Component {
constructor(props) {
super(props);
this.state = {
checked: false,
unselected_lists: [],
main_checked: false
}
//here I get the products props always null
console.log(this.props);
}
componentDidMount() {
this.props.activateAuthLayout();
//dispatching an action to fetch data from api, done in midddleware
if (this.props.user !== null && this.props.user.shop_id)
this.props.onLoad({
payload: this.props.user
});
}
render() {
//here I get the products props
console.log(this.props);
return (
//jsx work
);
}
}
const mapStatetoProps = state => {
const { user, is_logged_in } = state.Common;
const { products, is_loading } = state.Products;
return { user, is_logged_in, products, is_loading };
}
export default withRouter(connect(mapStatetoProps, { activateAuthLayout, onLoad })(EcommerceProductEdit));
Action is
import { FETCH_PRODUCT, FETCH_PRODUCT_SUCCESS } from './actionTypes';
export const onLoad = (action) => {
return {
type: FETCH_PRODUCT,
payload: action.payload
}
}
export const productFetched = (action) => {
return {
type: FETCH_PRODUCT_SUCCESS,
payload: action.payload
}
}
Reducer is
import { FETCH_PRODUCT_SUCCESS } from './actionTypes';
const initialState = {
products: null,
is_loading: true
}
export default (state = initialState, action) => {
switch (action.type) {
case FETCH_PRODUCT_SUCCESS:
state = {
...state,
products: action.payload,
is_loading: false
}
break;
default:
state = { ...state };
break;
}
return state;
}
And saga is
import { takeEvery, put, call } from 'redux-saga/effects';
import { FETCH_PRODUCT } from './actionTypes';
import { productFetched } from './actions';
import agent from '../../agent';
function* fetchProduct(action) {
try {
let response = yield call(agent.Products.get, action.payload);
yield put(productFetched({ payload: response }));
} catch (error) {
if (error.message) {
console.log(error);
} else if (error.response.text === 'Unauthorized') {
console.log(error)
}
}
}
function* productSaga() {
yield takeEvery(FETCH_PRODUCT, fetchProduct)
}
export default productSaga;
I am being able to get the products props only in render function. How would i be able to get it it in constructor ?
I would be really grateful if anyone explained me about react life cycle a little bit more.
Thanks.
updated
a constructor is called during object instantiation. According to the docs "The constructor for a React component is called before it is mounted". So if the props passed to the component are being changed after the component has been mounted you can use componentWillReceiveProps life cycle methods.
componentWillReceiveProps is deprecated so you can use componentDidUpdate instead. Example from the docs.
componentDidUpdate(prevProps) {
// Typical usage (don't forget to compare props):
if (this.props.userID !== prevProps.userID) {
// update your component state from here.
this.fetchData(this.props.userID);
}
}
MiddleWare: Middleware just comes in between the flow after the action has been dispatched and before it reaches the reducers, like in your case once you fire onLoad action and before it reaches the reducers, its caught in Saga middleware which executes it according to code written in it
Lifecycle in your case goes the following way:
In your compoenentDidMount method, you dispatch an action of onLoad. The action type in such a case becomes "FETCH_PRODUCT" and same action is now caught in Saga.
Since this is async call, the code in your component continues executing while the Saga perform its action in parallel. It calls API through this line of code: yield call(agent.Products.get, action.payload); . Once API call is completed, it dispatches an action 'productfetched' through this line of code yield put(productFetched({ payload: response }));.
Now this action reaches reducer and modify the state of "products". Since the product state in your redux is modified, your component EcommerceProductEdit re-renders and you get your product list in render method. The point to be noted is that the flow must have already finished executing inside componentDidMount method by this time, so no chance of having products their
Solution to your problem:
Once an action is dispatched and which has become async due to Saga, you won't be able to get value in constructor, if you use Saga. You can just directly call upon the API using axios/fetch library in componentDidMount and await their (Making it synchronous). Once you get response, you may proceed further
In case you have functional component, then you may use Effect hook and bind the dependency to products state. You can write your code in this block, what you want to be executed after API call is made and product list modifies.
React.useEffect(
() => {
// You code goes here
},
[products]
);
You just have to console props rather than doing this.props. You should not reference props with this inside the constructor.
Do this instead:
console.log(props)
Middleware is not related to react lifecycle at all, other than it updates and connected components "react" to props updating.
Check the constructor docs
https://reactjs.org/docs/react-component.html#constructor
Question: why are you trying to log props in the constructor anyway? If you want to know what the props are, use one of the lifecycle functions, componentDidMount/componentDidUpdate, don't use the render function to do side-effects like make asynchronous calls or console log.
componentDidMount() {
console.log(this.props);
}
If you must log props in the constructor though, access the props object that was passed as the component won't have a this.props populated yet.
constructor(props) {
super(props);
...
console.log(props);
}

right way to POST data to a server and handle response with redux

I'm very new to react and redux.
Now I want to rewrite my post request with a redux process.
my current request looks like this:
_handleSubmit(event) {
axios
.post('/createUrl', {
url: this.state.url
})
.then((response) => {
this.setState({
shortenInfos: response.data
})
})
.catch((error) => {
console.log(error);
});
event.preventDefault()
}
now I created a store:
export default function url(state = 0, action) {
switch (action.type) {
case 'CREATE_URL':
// maybe axios request?!
return `${action.url}/test`
case 'CREATED_URL':
return `${action.url}/created`
default:
return state
}
}
so where I must use my store.dispatch()? Should I make my _handleSubmit something like this?
_handleSubmit(event) {
axios
.post('/createUrl', {
url: this.state.url
})
.then((response) => {
store.dispatch({
type: 'CREATED_URL',
url: response.data
})
})
.catch((error) => {
console.log(error);
});
event.preventDefault()
}
I think this is wrong? And where I must use mapStateToProps method? Or should I do the axios-request in my CREATE_URL in my reducer?
Introduction
Using React with Redux gives you high freedom on how you can do things. The downside of this is that it can be hard to find out how things should be done properly, mainly because there is no standard or comprehensive guide to the use of the many dependency you need for a properly implemented project. This answer will guide you through the basics with links to references that will help you to find out wheres next and how to deeper your knowledge.
Reducer
Reducers should be pure, meaning that they have no side effects (like making axios requests) and they should always return a new object/array/value instead of changing the previous state. It is also a good practice to use action types as constants. You can place action types wherever you want, but for simplicity I will put them into the reducer's file, but there are better ways to organize them like using ducks.
export const CREATED_URL = 'CREATE_URL';
export default const url = (state = '', action) => {
switch (action.type) {
case CREATED_URL:
return action.url;
default:
return state;
}
};
Asynchronous actions
Everything that causes a side effect should be an action, so XHR should happen there. Because XHR should be asynchronous it is recommended to use a middleware: redux-thunk and redux-saga are two popular solutions. I will go with thunk so install it first.
First (because const has temporal dead zone) you need an action that will "load" the result of the XHR to the store:
import { CREATED_URL } from './reducer';
const createdUrl = url => ({
type: CREATED_URL,
url, // ES6 trailing comma for prettier git diffs
});
Then you can create the action that will fire the XHR, wait for the response then load it to the store using the action created previously. We need to return a function that will receive dispatch as the parameter. This technique is used in functional programming and is called currying.
export const createUrl = url => dispatch => { // with only 1 parameter the parentheses can be omited
axios
.post('/createUrl', { url }) // ES6 Shorthand property name in { url }
.then(response => {
dispatch(createdUrl({
url: response.data,
})
})
.catch(error => {
// #TODO dispatch an action that will show a message
// notifying the user that the request failed
console.log(error);
});
}
Usage in the React component.
Preparation
For ease of use, you need to connect your React component with Redux. react-redux comes to the rescue. Read the API documentation and add the <Provider> component to the root of your React component tree.
Now, in the top of your React component's file, import all the necessary stuff:
import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { createUrl } from './reducer';
mapStateToProps and mapDispatchToProps
Then create the two helper functions for connect:
const mapStateToProps = store => ({ url: store.url })
const mapDispatchToProps = dispatch => bindActionCreators({ createUrl }, dispatch)
With the help of mapStateToProps you can subscribe to store updates and inject the important parts of the Redux store to your components props. mapStateToProps should return an object that will be merged to the component's props. Usually we just do something like store => store.url but because our example is so simple that the reducer returns a plain string instead of something more complex in an object, we need to wrap that string into an object over here.
mapDispatchToProps with the help of bindActionCreators will inject the passed actions to the component's props so we can call and pass them down to subcomponents with ease: this.props.createUrl().
The component itself
Now we can create the component itself. I will use an ES6 class to show an example with componentDidMount, but if you don't need that and you have a stateless component, you can go with a function too.
class Example extends React.Component {
componentDidMount() {
// If you need to load data from a remote endpoint place the action call here, like so:
// this.props.createUrl('your-url');
}
render() {
return (
<div>
<div>URL injected from the store, automatically refreshed on change: {this.props.url}</div>
<div onClick={event => {this.props.createUrl('your-url');}}>Click me to fetch URL</div>
</div>
)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Example)

Prop always null in react

I have a component which has to display the object details. This is instantiated from a TableComponent on click of a row.
The object id is passed:
<ObjectDetails objectID = {id}/>
This is my ObjectDetails component:
class ObjectDetails extends Component {
componentWillMount() {
this.props.dispatch(loadObjectDetails(this.props.objectID));
}
render() {
console.log("props------" + JSON.stringify(this.props));
....
}
let select = (state) => ({objectDetails: state.objectDetails});
export default connect(select)(ObjectDetails);
}
loadObjectDetails populates the store with objectDetails. I can see that the store does have the details.
But in render(), props always has objectDetails as null.
Not sure what I'm doing wrong, any help please?
Edit:
Adding few more details
export function loadObjectDetails(objectID) {
return function (dispatch) {
Rest.get('/rest/objects/' + objectID).end(
(err, res) => {
if(err) {
dispatch({ type: 'FETCH_OBJECT_DETAILS_FAILURE', error: res.body})
} else {
dispatch({ type: 'FETCH_OBJECT_DETAILS_SUCCESS', objectDetails: res.body})
}
}
)
}
}
export default function objectDetailsReducer(state={
objectDetails: null,
error: null,
}, action) {
switch (action.type) {
case "FETCH_OBJECT_DETAILS_SUCCESS": {
return {...state, objectDetails: action.objectDetails, error: null}
}
case "FETCH_OBJECT_DETAILS_FAILURE": {
return {...state, error: action.error }
}
}
return state
}
const middleware = applyMiddleware(promise(), thunk, logger())
export default compose(middleware)(createStore)(combineReducers({ reducer1, reducer2, objectDetailsReducer}))
The object inside your mapStateToProps function is what gets passed as props:
{objectDetails: state.objectDetails}
So in your component, you can access: this.props.objectDetails. And whatever properties are on that. If you just wish to pass the objectID, update your mapStateToProps function to change that.
if you want component get new props when store mutates, you have to use mapStateToProps can see many samples in docs: http://redux.js.org/docs/basics/UsageWithReact.html
In your sample code you have extra spaces around = sign:
<ObjectDetails objectID = {id}/>.
componentWillMount function is fired once when component will mount and that's it, so you dispatch this action, it updates the store, but you don't have mapStateToProps so this component has no reaction to store updates.
Component can react to store by mapStateToProps or you can leave this component presentational but then upper component has to have mapStateToProps.
You can read about presentational component on same page: http://redux.js.org/docs/basics/UsageWithReact.html
Component will try to re-render only when props or state is changed. If props/state is not changed - no re-render happens. Your props don't change. You don't use state on this component at all, so nothing changes too.
The render() method was throwing an error on the first render which is why it was not rendering on props change.
It is re-rendering after I fixed that error.
Sorry for wasting your time!

Resources