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

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

Related

What is the meaning of actions and action types in React-redux?

Now if i want to change value in store i should do following steps:
Go to constants/actionTypes file, create a line with action type
Go to actions and create action function
In each component where i use it i should create a function for mapDispatchToProps
In reducer i should write a logic of changing
Whats the point of such complexity?
Will it be wrong if i will do just one file with actions which will change the state? For example:
// actions.js
export const setCategories = (payload, setState, currentState) => setState({ categories: payload })
export const addCategory = (payload, setState, currentState) => setState({ categories: [...currentState.category, payload] })
To make it work i can create just couple of universal functions for all projects
1) getActions, which authomaticly collects all exports from actions.js and provide them to mapDispatchToProps, so in all components we could just write
const mapDispatchToProps = getActions
code of it can be something like
// actionsDispatcher.js
import * as actions from 'actions'
const getActions = (dispatch, ownProps) => {
Object.keys(actions).forEach(actionName => {
const fn = actions[actionName]
actions[actionName] = payload => dispatch({ action: fn, payload, type: _.toSnakeCase(actionName) })
}
return actions
}
which means we pass to dispatch the function of action from actions.js
2) setState which will work similary to react function, but for redux state
then in reducer function we just right
function rootReducer(state = initialState, action) {
if (action.action) {
action.action(action.payload, setState, state)
}
// here we make it extandable for ordinary way if required
if (action.type === '...') {
// ...
}
}
and nothing else...
So the question is whats wrong in such approach that will require for coder just write a function in one file 'actions.js' and call it from any component as props.someActionName(someParams) instead of changing 4 differents files?
Thank you
Redux is supposed to make complex requirements easier to implement but if you have simple requirements then it makes implementing these requirements more complicated.
The motivation mentions CQRS(Command Query Responsibility Segregation) that separates how you read from store (in redux with selectors and I'm a big fan of reselect) with how you write to it (with action and reducers).
The actions and reducers are the command (write) part of CQRS and is event sourcing, redux is sometimes referred to as an event store. This enables you to add or remove handlers (reducers or middle ware) for your events (actions) that can update the store, dispatch other events (=actions), do asynchronous stuff, write to local storage.
If you need to do all these things in one function (async fetch, write to local storage, call other functions (dispatch other actions),...) then that function becomes unmanageable.
Even if the function only calls other functions then it still needs to know the entire process of certain action. But if (for example) you had a local storage middleware that would write to storage on certain actions then no other code needs to know how or when it's called. So when logic of writing to local storage changes it is limited to the local storage middle ware.
This is the advantage of handlers (reducers, middleware) listening to events (actions), the handler only needs to know about a small portion of the process, not the entire process.
With event resourcing we also know why the state has a certain value instead of only knowing what the state is, the article states:
However there are times when we don't just want to see where we are, we also want to know how we got there.
Another big advantage of an event store is that you can re create the data by playing back the events. All this is excellently done with redux def tools.
Here is a great book on React with Redux.
Conventional-redux is a:
Library for small and medium react applications, it wraps the react-redux and provides API based on convention over configuration pattern without breaking redux compatibility.
You simply define an interactor:
class CounterInteractor {
// initial state
defaultState() {
return 0;
}
// actions:
doubleAsync() {
setTimeout(() => { this.dispatch('counter:double') }, 500)
}
// reduce methods:
onIncrement() {
return this.state + 1;
}
onDouble() {
return this.state * 2;
}
}
And dispatch actions to that interactor from your connected component component. That's it!

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

How to get the updated props immediately after service call in reactjs?

In component I want to get the data immediately in props after calling
webapi service call and do some operation,but issue is that it is not
updating the props immediately because as we know that call will be
async, So what will be the solution? My codes in component are like
this:-
openPreviewClick=(event) => {
this.props.GetReport();
console.log(this.props.reportData);
}
function mapStateToProps (allReducers) {
return {reportData: allReducers.reportData
}}
const matchDispatchToProps = (dispatch) => ({
GetReport: () => dispatch(LoadReportData())
})
export default connect(mapStateToProps, matchDispatchToProps)(MyContainer)
Now I have to open a pdf for this I have tried with two solution:-
Handling life cycle of the page
componentWillReceiveProps(nextProps) {
if(nextProps.reportPath!=undefined){
window.open(nextProps.reportPath,"thePop","menubar=1,resizable=1,scrollbars=1,status=1,top=280,width=850,height=600");
}
Writing the code in render
render () {
if(this.props.reportPath!=undefined && this.props.reportPath!=""){}
window.open(this.props.reportPath,"thePop","menubar=1,resizable=1,scrollbars=1,status=1,top=280,width=850,height=600");
}
openPreviewClick is my button click on which I want to access the
props named as reportData.But console.log(this.props.reportData); is
giving me the null value for the first time,second time if I will
click then we are getting the data.How we can manage this? I already tried above two solution but it is not working.
Simple answer, you don't ^1
If this is truely an async request, there is no guarantee when the data will come back, so your component needs to "understand" that is can exist in a "without data" state.
Simplest form of this is:
render() {
if( ! this.props.reportData) return null;
// normal render code, at this point we have data
return <div>{this.props.reportData.map(foo, ...)}</div>
}
A better form, would be something like:
render() {
if( ! this.props.reportData) {
return <div><img src="loading.gif" /></div>;
}
// normal render code, at this point we have data
return <div>{this.props.reportData.map(foo, ...)}</div>
}
^1 Note: You could technically use async functions, but I feel that would complicate the problem, especially without a fundamental understanding of what is already going on.
In your main file where you create the store you can dispatch action and set initial value just like
import configureStore from './store/configureStore;
import {LoadReportData} from './actions/LoadReportData';
const store = configureStore();
store.dispatch(LoadReportData());

React - constructor() and componentDidMount

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.

Pass values as parameters state from the component or access the status in the action creator?

In my project I have action creator that depend on values ​​that are in the state of the application to generate a new value or to decide what action to dispatch. My question is to know which is the right way to do it. I thought of two ways. Access those values ​​within the action creator:
export const changePreviousPage = () => {
return (dispatch, getState) => {
let pagination = getState().appReducers.availability.pagination;
let previousPage = pagination.actualPage != 1 ? pagination.actualPage - 1 : pagination.actualPage;
dispatch({
type: types.CHANGE_PREVIOUS_PAGE,
previousPage
});
}
};
The other option I thought was to pass the value from the component to the action creator:
In my component
class Pagination extends Component {
...
handlePreviousPage() {
const {pagination} = this.props;
this.props.changePreviousPage(pagination);
}
...
}
In my action creator
export const changePreviousPage = pagination => {
let previousPage = pagination.actualPage != 1 ? pagination.actualPage - 1 : pagination.actualPage;
return{
type: types.CHANGE_PREVIOUS_PAGE,
previousPage
}
};
What is the best way to address it ?
In my opinion always use/retrieve the state at the closest time to execution, here the action creator (or rather more specifically the thunk you are returning that would then execute).
Remember that dispatch may have any number of middleware running before the actual store.dispatch call. This can include async middleware, so the state may have changed in between calling the dispatch and the store.dispatch call it will ultimately run.
Another one to consider is you may be dispatching multiple things in an action creator which change the state and invalidate what you passed into the action creator at the top. Also a reason why I consider let state = getState() at the top of an action creator a bad idea unless you are very sure nothing is going to change during your processing (as soon as you involve any API calls I would always use getState() again instead of using a stored variable).
Also putting data from state into props (using a redux container and connect helper method) will cause a rerender every time this changes, which could have a performance impact in some cases.
My personal coding preference is also to keep things as simple as possible in mapDispatchToProps (assuming that is where you're passing in your handlers like handlePreviousPage) and avoid any data processing (in your example it's not much, but you can easily see how that may get out of hand if you're preparing data for your action creator).

Resources