React - constructor() and componentDidMount - reactjs

I am using Redux to create my pagination. My problem is, in constructor I ran a method that will parse the url and check if there is anything about pagination. Then it runs an action that will put the data in my store. It all runs smoothly.
Then, I have the componentDidMount method when I run another action - fetching data. And there I use those props I have previously pushed. Unfortunately, the store is at its initial state.
My (simplified) code:
class NewsList extends React.Component {
constructor(props) {
super(props);
this.profile = document.getElementById('articles').getAttribute('data-profile');
this.parseHash();
}
parseHash() {
/* Method for parsing navigation hash
---------------------------------------- */
const hash = window.location.hash.replace('#', '').split('&');
const data = {
page: 1,
offset: 0
};
// hash parsing
this.props.setPagination(data);
}
componentDidMount() {
this.loadNews();
// If I use
// setTimeout(() => { this.loadNews(); })
// it works, but I feel this is hack-ish
}
loadNews() {
this.props.update({
current: {page: this.props.NewsListReducer.current.page, offset: this.props.NewsListReducer.current.offset},
next: {page: this.props.NewsListReducer.next.page, offset: this.props.NewsListReducer.next.offset}
});
}
}
When I console.log(this.props.NewsListReducer), I am getting undefined for both current and next object, but when I use Redux DevTools, the data is there. What can I do?

It seems like there's some asynchronicity somewhere in there. You're probably using react-redux right? I think the asynchronicity comes from the connected component, as it uses setState when the store state has changed. setState schedules an asychronous state update.
Therefore this.props.NewsListReducer isn't up to date in componentDidMount().
I guess this.props.update is an action that will fetch the news, right? Why is it necessary that you provide the paging information from the component to it? E.g. with redux-thunk you can access the store state before dispatching an action. This could be your chance for reading the (up to date) paging information.
E.g.
export function fetchNews() {
return (dispatch, getState) => {
getState().NewsListReducer.current.page; // Up to date
...
fetch(...).then(() =>
dispatch({...}));
}
}
Btw. it could be a good idea to not call your state *Reducer. It's the reducer managing the state, but the reducer isn't part of the state in that manner.

Related

How to properly dispatch the same action twice in a single React Component

I am trying to understand the best approach to a data fetching challenge I am facing with redux in my react app.
In short, I need to dispatch the same fetch (in this case, fetchPlayerSeasonStats) twice, and save both fetches of data. The first fetch grabs statistics for a single player (via the fetches optional 1st paramter thisPlayerId), and the 2nd fetch omits the paramter and fetches a much larger dataset.
What I've attempted to do below is the following:
(a) fetch playerSeasonStats the first time
(b) in componentDidUpdate(), check that the first fetch was completed (the if condition checking array legnths).
(c) if condition met, use state variable thisPlayersSeasonStats to store the original fetch of data.
(d) then, refetch the larger dataset with another dispatched action.
... other than the warning I'm receiving saying "do not update state in componentDidMount", in general I'm not sure if this approach is correct or if it is an "anti-pattern" / bad React/Redux coding style. I'd like to make sure I'm doing this right so any review of the code below (in particular the componentDidUpdate() function) would be greatly appreciated!
Thanks!
// Import React Components
import React, { Component } from 'react';
import { connect } from 'react-redux';
// Import Fetches
import { fetchPlayerSeasonStats } from '../../../actions/...';
// Create The Component
class MyComponentHere extends Component {
constructor(props) {
super(props);
this.state = {
thisPlayerSeasonStats: []
};
}
componentDidMount() {
let thisPlayerId = this.props.playerInfo._id;
this.props.dispatch(fetchPlayerSeasonStats(thisPlayerId, this.props.appSeason.value));
}
componentDidUpdate(prevProps) {
console.log('prevProps: ', prevProps);
if (this.props.appSeason !== prevProps.appSeason) { this.refetchTeamsStats(); }
if (prevProps.playerSeasonStats.length === 0 && this.props.playerSeasonStats.length === 1) {
this.setState({ thisPlayerSeasonStats: this.props.playerSeasonStats });
this.props.dispatch(fetchPlayerSeasonStats(null, this.props.appSeason.value));
}
}
render() {
// Handle Initial Loading Of Data
if (this.state.thisPlayerSeasonStats.length === 0) { return <LoadingSpinner />; }
// The Return
return (
<div> Return Dont Matter For here </div>
);
}
}
function mapStateToProps(reduxState) {
return {
playerSeasonStats: reduxState.playerSeasonStatsReducer.sportsData,
loading: (reduxState.playerSeasonStatsReducer.loading),
error1: reduxState.playerSeasonStatsReducer.error
};
}
export default connect(mapStateToProps)(MyComponentHere);
The answer is simple.
Lets look at how redux-thunk works.
Redux Thunk middleware allows you to write action creators that return a function instead of an action
I think this is what fetchPlayerSeasonStats essentially do. It returns some async function that fetch players. Redux-thunk helps dispatch it (I think you use Redux-thunk. In case of you use some other async middleware, it should work essentially the same).
So we can write action creator that will return function (as fetchPlayerSeasonStats) but inside will dispatch not actions but another function. So we'll have function dispatching function which will dispatch action :-)
For example
fetchAllPlayerStats (thisPlayerId, appSeasonValue) => dispatch => {
dispatch(fetchPlayerSeasonStats(thisPlayerId, appSeasonValue));
dispatch(fetchPlayerSeasonStats(null, appSeasonValue));
}
Then you can use this.props.dispatch(fetchAllPlayerStats(thisPlayerId, this.props.appSeason.value)) from componentWillMount to fetch all data at once.
Tip. Current implementation of fetchAllPlayerStats will get all data at once. If you add async/await keywords you'll get firstly data for a single player and then larger data set. Modified version will look like
fetchAllPlayerStats (thisPlayerId, appSeasonValue) => async dispatch => {
await dispatch(fetchPlayerSeasonStats(thisPlayerId, appSeasonValue));
await dispatch(fetchPlayerSeasonStats(null, appSeasonValue));
}
Here is simple example to showcase logic

componentWillReceiveProps not called after redux dispatch

I'm building a react native app and using redux to handle the state. I am running into a situation where one of my containers is not updating immediately when the redux state is changed.
Container:
...
class ContainerClass extends Component<Props, State> {
...
componentWillReceiveProps(nextProps: Object) {
console.log('WILL RECEIVE PROPS:', nextProps);
}
...
render() {
const { data } = this.props;
return <SubComponent data={data} />
}
}
const mapStateToProps = (state) => ({
data: state.data
};
export default connect(mapStateToProps)(ContainerClass);
Reducer:
...
export default function reducer(state = initalState, action) => {
switch(action.type) {
case getType(actions.actionOne):
console.log('SETTING THE STATE');
return { ...state, data: action.payload };
...
...
...
In a different random component, I am dispatching a call with the actionOne action, which I confirm prints out the relevant console.log. However, the console.log in the componentWillReceiveProps in the container is not printed.
The component that dispatches the call is a modal that has appeared over the Container, and closes automatically after the call is dispatched and the state is updated. What is weird is that although the Container isn't updated immediately, if I navigate to a different page and then back to the Container page, the state is in fact updated.
EDIT: Initial state is:
const initialState: Store = {
data: []
}
And the way I dispatch is in a different component which gets called as a new modal (using react-native-navigation) from Container:
fnc() {
...
setData(data.concat(newDatum));
...
}
Where setData and data are the redux dispatch action and the part of the store respectively that is passed in on props from the Container (which has setData and data through mapStateToProps shown above and a mapDispatchToProps which I didn't show).
I solved my problem by updating from react-native v0.56 to v0.57. Apparently there was a problem with react-redux v6 working properly in the react-native v0.56 environment.
Assuming you're using a recent version of React, componentWillReceiveProps is actually deprecated:
Using this lifecycle method often leads to bugs and inconsistencies
You can't really rely on that lifecycle hook in a number of situations. You may want to look at a slightly different approach with componentDidUpdate instead.
I think more important is to get the value after changing in state of redux rather than in which lifecycle you are getting the value . so for getting the value you can use subscribe method of redux in componentDidMount
store.subscribe( ()=> {
var updatedStoreState = store.getState();
})
I believe that getDerivedStateForProps would solve your problem.
static getDerivedStateFromProps(nextProps, prevState) {
if(nextProps.data !== prevState.data) {
//Do something
} else {
//Do something else
}
}
You would check the state from the redux against the state from your component and then act accordingly.
Also, some info from the documentation that you might consider before using this method:
1. getDerivedStateFromProps is invoked right before calling the render method, both on the initial mount and on subsequent updates.
2. This method exists for rare use cases where the state depends on changes in props over time.
3. If you need to perform a side effect (for example, data fetching or an animation) in response to a change in props, use componentDidUpdate lifecycle instead.
You can read more at: https://reactjs.org/docs/react-component.html#static-getderivedstatefromprops

When to use Dispatcher in React Application

I am facing some issue while using Dispatcher in ReactJS. So, I try to remove this dispatcher from the store and still store works well. Store properly hold my data and change event works well.
Now I am bit confusing to use dispatcher in our application.
Here is the code
MenuList is my component in which I call MenuStore.getMenuFromAPI() and after I also added onChange event of MenuStore.
class MenuList extends React.Component{
constructor(){
super();
this.state = {open:false, menuList:"",numbering:-1}
}
componentWillMount(){
var that = this;
MenuStore.getMenuFromAPI();
MenuStore.on("change", ()=> {
that.setState({menuList:MenuStore.getMenu()});
})
}
componentWillReceiveProps(nextProps){
if(nextProps.show!=this.props.show){
this.setState({open:nextProps.show});
}
}
render(){
const { classes } = this.props;
return (
<div>My MEnu</div>
)
}
}
MenuStore
class MenuStore extends EventEmitter {
constructor() {
super();
this.menu = null;
}
getMenu(){
return this.menu;
}
getMenuFromAPI(){
var that = this;
$.ajax({
type: "POST",
url: LinkConstants.GETMENU,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
data: "",
dataType :"json",
success: function(response) {
that.menu =response;
that.emit("change");
}.bind(this),
error: function(xhr, status, err) {
console.log(err);
}.bind(this)
});
}
// handleAction(action) {
// switch (action.type) {
// case ActionTypes.MENU: {
// this.getMenuFromAPI();
// break;
// }
// }
// }
}
const menuStore = new MenuStore;
//Dispatcher.register(menuStore.handleAction.bind(menuStore));
export default menuStore;
As you can see I commented out Dispatcher.register line and handleAction function.
Above code works properly fine but I wanted to know why to use Dispatcher over here ?
If I want to just store my data in the MenuStore and get it back from MenuStore on any of the component in the application. So it is necessary to use dispatchers and action or to just work with stores only.
Please clarify my doubts with proper example or case scenario (if possible) when to use dispatchers and action or when to work with stores only.
In your example your are not using Redux at all, you've just created a class that is used as a simple storage for the fetched data but your are not using any of Redux capabilities.
Redux is all about one store which is just a plain object which represents your application state tree. In order to change this state you dispatch actions. Actions are just simple objects which describe what happened. Each action has a type field which describes the action. Actions are treated by reducers which are functions that gets the current state and the dispatched action and decide on the next state of the application. This is Redux in few sentences.
Redux store has a method named dispatch which is used to dispatch actions. As mentioned in Redux documentation, this is the only way to trigger a state change.
Lets say we have a TODO list application. Our store may be represented as an array of strings (todo items).
To add a new item to the list we will define a simple action:
const addItemAction = (item = '') => ({
type: 'ADD_ITEM',
data: item,
});
Dispatching this action can be done from within one of your component's methods which will be attached to some keyboard/mouse event:
class TodoList extends React.Component {
...
// addNewItem is called with a value from a text field
addNewItem(item) {
store.dispatch(addItemAction(item));
}
...
}
As I mentioned above, state is changed by a reducer function. The reducer decides if to change the state and how to change it. If dispatched action shouldn't change the state it can just return the received state:
function todoReducer(state = [], action) {
switch(action.type) {
case 'ADD_ITEM':
return [...state, action.data];
break;
default:
return state;
}
}
Reducer is passed to createStore method:
import { createStore } from 'redux'
const store = createStore(todoReducer);
In the TodoList component you can subscribe to the store using store.subscribe method which accepts a callback function that will be called each time the store state changes. When detecting a change you can call setState of your component to set the list on the component state and to cause the component to rerender:
class TodoList extends React.Component {
....
componentDidMount() {
this.storeSubscription = store.subscribe((state) => {
// For the example I'm just setting the state (list of todos)
// without checking if it changed or not
this.setState({
todos: state,
});
});
}
render() {
return this.state.todos.map(todo => <div>{todo}</div>);
}
....
}
This is an almost complete example of using Redux. We used action to describe an event in our application, we used the store's dispatch method to dispatch the action to the store, Redux will invoke the reducer when it gets new actions, the reducer computes the new state and our component detects the change by using the store's subscribe method.
There are more things to consider and to take care of in a more complex application. You will probably have a more complex state tree so you will need additional reducers to take care of computing the state. Additionally in some step you would need to consider working with some helpers to reduce overhead of subscribing to state change and detecting changes.
In a more complex application you would probably connect your component to the store by a binding library such as react-redux so your component will receive the relevant parts of the store by props which will save the overhead of subscribing to the store changes and deciding on when to rerender the component.
I would recommend watching "Getting started with Redux" by Dan Abramov to get some more understanding of what is Redux and how to work with it.
Getting started with Redux

ComponentDidMount not getting called after redux state update?

I think I'm missing a concept here about React and Redux. I'm trying to work with objects stored in redux, and I'm having trouble.
REDUX:
I have an action fetchItems, that gets all items from the database. This action works successfully.
REACT:
I have a container, UserProfile, that calls fetchItems in componentDidMount.
class UserProfile extends Component {
componentWillMount() {
console.log('------------ USER PROFILE -------------------');
}
componentDidMount() {
console.log('[ComponentDidMount]: Items: ', this.props.items);
this.props.fetchItems();
}
render() {
let profile = null;
console.log('[Render]: Items: ', this.props.items);
return <Auxillary>{profile}</Auxillary>;
}
}
const mapStateToProps = state => {
return {
items: state.items.items
};
};
const mapDispatchToProps = dispatch => {
return {
fetchItems: () => dispatch(actions.fetchItems())
};
};
export default connect(mapStateToProps, mapDispatchToProps)(UserProfile);
The problem I'm seeing is that this.props.items is always null (even though fetchItems is successful). The only way I can detect that items were stored in redux store is if I use componentWillRecieveProps(nextProps). Here, I successfully see the items in nextProps. I feel like using componentWillReceiveProps might be too "messy" though. I guess what I'm asking is, what is the standard way of dealing with updates to redux states in react?
Aseel
The cycle will be :
constructor()
componentWillMount() (will be soon deprecated by the way : https://medium.com/#baphemot/whats-new-in-react-16-3-d2c9b7b6193b)
render() => first render (this.props.items, coming from mapStateToProps will be undefined)
componentDidMount() => launching fetchItems() => changing redux state => changing the this.props.items => launching the second render() where this.props.items will be set.
So :
you should have two console.log('[Render]: Items: ', this.props.items);
you should deal with a "loading" state when the this.props.items is null
If the second console.log is still null, Try to add log in your reducer, in the mapStateToProps, ... perhaps it's not state.items.items ...
In react, we have something called state. if the state of a component is changed the component will re-render. Having said that we can use this.setState() inside componentWillRecieveProps to update the state which in turn will rerender the component. So your code will look like this which is the standard way to handle Redux level state changes in react.
class UserProfile extends Component {
constructor(props) {
super(props);
this.state = {
items: props.items
}
}
componentWillMount() {
console.log('------------ USER PROFILE -------------------');
}
componentWillRecieveProps({ items }) {
this.setState({ items });
}
componentDidMount() {
console.log('[ComponentDidMount]: Items: ', this.state.items);
this.props.fetchItems();
}
render() {
let profile = null;
console.log('[Render]: Items: ', this.state.items);
return <Auxillary>{profile}</Auxillary>;
}
}
const mapStateToProps = state => {
return {
items: state.items.items
};
};
const mapDispatchToProps = dispatch => {
return {
fetchItems: () => dispatch(actions.fetchItems())
};
};
export default connect(mapStateToProps, mapDispatchToProps)(UserProfile);
P.S Just making the API call inside componentWillMount will not help either as API call is async and can take up some time to resolve and till then react will finish rendering the component. so you'll still have to use componentWillRecieveProps
Standard practice is to call this.props.fetchItems() in your constructor or componentWillMount().
componentDidMount is called after render which is why your items do not render - they do not exist until after the initial render.
There are certain ways you can resolve this.
The very first time when render() gets called it was subscribed to the initial props/state that was initialise in redux store through redux connect method. In your case items was null.
Always initialise your redux store with some meaningful data.
In your case if items will be array you can initialise with empty array.
When you dispatch action your store will get updated and the component which was subscribed to items will be re rendered and in this way you donot have to use setState inside componentWillReceiveProps and you can avoid using it.
You need to handle certain cases in render like if array is empty and data is still loading then show some kind of loader and once data is fetched then display it.

React | Redux | Thunk - How data should be loaded from within a component?

I've got a component that uses componentWillMount to make an API call through redux to get data and update the state.
Before calling this method, I need to go to the DB and get a property upon which I'll decide if the data retrieval (from the 1st paragraph) should happen.
What I was thinking of doing (using promises) -
Fetch the property (from paragraph 2)
then, if data is needed, dispatch the normal flow (paragraph 1).
if data is not needed, carry on.
My question is WHERE should it go in your opinon.
On the one hand, it feels like a mega overkill to do it through the store. On the other hand, any chance I'll encounter side effect problems.
In addition, I could implement the logic in the component or in the action creator. What do you think is best?
Additional info:
1. I'm using redux-thunk. Changing to sagas is out of the question.
2. The property that I'm checking is in 1 reducer, while the data that needs to be fetched is in another reducer (dunno, might be problematic for some solutions.).
Option 1:
import {getData} from '....../dataActions';
import {getToken} from '......../userActions';
const MegaComponent extends React.Component {
componentWillMount() {
getToken(uid)
.then(shouldUpdate => {
if (shouldUpdate) {
getData(uid);
} else {
console.log('no need to get data');
}
})
}
}
funciton mapStateToProps(state, ownProps) {
return {
user: state.user
}
}
function mapDispatchToProps(dispatch) {
return {
getToken: (uid) => dispatch(getToken(uid)),
getData: (uid) => dispatch(getData(uid))
};
}
export default connect(mapStateToProps, mapDispatchToProps)(MegaComponent);
Option 2 (what I think should be done)
export function getToken(uid) {
return dispatch => {
return api.getToken(uid)
.then(token => {
if (token === 'what it should') {
return {type: 'NO_DATA_CHANGE_NEEDED', action: null};
} else {
// the action that handle getting data is in a different action creator.
// I either import it here and call it, or there's a better way.
}
})
}
}
UPDATE
This might come in handy for future visiotrs - getting state inside an action creator
Hey this is a very broad question, but I want to give a short advice on how I would solve it although I am not quite sure if i got it totally right.
So I would start by separating the concerns of point 1. (database call) and 2. (everything else)
You could start by writing a Component-Wrapper (HoC) that only does the database call to get the prop you need for further processing.
Once you fetched the data from db you can render the InnerComponent with the data as prop and do everything else you need there just by checking the prop you injected and trigger additional actions.
I wouldn't let my component deal with logic. keep your components all about View. Think of a component as a visual representation of a point-in-time state.
In componentWillMount you can check if the data exists, if it's not then you call your action which will fetch the data through an API and pass the data to the reducer. the reducer will 'update' the state.
the dispatch calls should be invoked from actions.js and not from your component.
So, my feeling was right - option 2 it is. Much like #guruPitka suggested.
In my action creator (that is being called by the component), I get the current app state, make an API call, compare the tokens and act accordingly.
export function getToken(uid) {
return (dispatch, getState) => {
const currentToken = getState().user.token;
return api.lastAppLaunch(uid)
.then(newToken => {
if (newToken === currentToken) {
// All is good, carry on.
} else {
dispatch(getData(uid)); //getData is from a different action creator.
}
});
}
}
And the call in the component -
this.props.getToken(uid);

Resources