Redux state with local state concerning API calls - reactjs

I came across interesting article (Link at the end of post). Author of the article states, that they treat redux store as client-side database and UI logic does not fit there (If it's not needed for unrelated components), even for data fetching purposes. For example we want to show some loading spinner when fetching some data:
async componentDidMount() {
this.setState({isLoading: true});
await this.props.fetchSomeData();
this.setState({isLoading: false});
}
We fire async thunk action that fetches some data that is needed for more than one component or we want to cache that data, even when that component is unmounted.
The only component that is concerned about loading state is the one in which we fire thunk action, other components are not concerned about state of loading. But I always see redux examples with async actions creators which fire REQUEST/SUCCESS/FAILURE actions types and reducers bloated with loading states even if they're are used in one component.
I can see some cons of this code, that some state lives in component and some in redux, but pros are that redux store is not bloated with state that is not needed for other components, also we can avoid the verbosity of redux.
So my question would be, what are cons of this state separation regarding this particular example ?
Article: https://dev.bleacherreport.com/3-things-i-learned-about-working-with-data-in-redux-5fa0d5f89c8b
(Also interesting discussion in article comments)

IMO, some of the cons of your state separation approach is that:
If there is complex API fetching behind the scene ( fetch A then fetch B then fetch C) redux-thunk would be inadequate and you will have to use redux-saga or redux-observable and together with them, the REQUEST/SUCCESS/FAILURE styles.
If, in the future, there are other components need to listen to the 'loading' state or worse need the same data but there is no guarantee that the order of display them will be the same ( A mount and fetch data and then B mount before the data is fetched, and B fetch again ), you will have to refactor all the structures. And that doesn't count the case when you need to CANCEL a fetch.
If your component unmount before the data is fetched, the "this.setState" call will result in a null reference error.
In general, I agree that UI state should be put in the component, but global state must always be stored in the Redux store and your isLoading is not totally a UI state but a state of the request that your component are listening to.

Related

What is the recommended approach for large react app with react-query?

I've recently started using react using functional components and react-query and it has been working fine except that I'm not clear about how to organize components properly.
the way I'm used to designing components is by having a top level component that performs all data access and passes data to it's child components through props. it also passes various callback handlers to child components so that when action is required, the top level component would update the data and passes the new data to child component. so in my case all calls to useQuery(), useMutation() reside in top level component but it's making the code extremely messy. but it's much like a page containing various child components that only display data or help user interact with data.
function Page(){
const [page, setPage] = useState(1)
const [size, setSize] = useState(10)
const persons = useQuery('persons', async ()=> await getPersons(page, size))
const addPerson = useMutation(async (args)=> {
const {id, name, desc} = args
await addPerson(id, name, description)
})
const person = useQuery('persons', async ()=> await getOnePerson(page, size), { enabled : false })
const addPersonCB = (id: number, name: string, desc: string)=> {
addPerson.mutate({id, name, desc})
}
// complex if/else logic to choose child components
the second approach is to disperse react useQuery() and useMutation throughout the components where it's need. and to further simplify things, if rendering logic is complex, each component would have a parent component that would perform the action and passes data as prop.
function PersonCard(props: PersonCardPropsType){
const {data, isLoading, isError, error} = useQuery(`personQuery${props.id}`, getPerson)
if(isLoading)
return <Wait />
if(isError)
return <Error reason={error} />
const record = data as PersonModel
return ( <PersonCardUI person={record} />)
}
and there are may compoenents for grid, form and etc each one in form of pair like
<PersonEditor />, <PersonEditorUI />, <PersonGrid />, <PersonGridUI />
in this case the calls are dispersed everywhere in the code. I want to know
For large projects, which approach is recommended and why?
Is the mix-match of Redux & react-Query okay? like for instance a grid has page size and page number which should go in redux, maybe?
Is it okay to use pure axios/fetch at some places with redux/react-query it's considered a frowned upon way of doing things?
It is generally considered a best practice to use useQuery where you need it. The separation into container / presentational components, while still possible, has been largely deprecated since hooks came around. With redux connect / mapStateToProps, it was best practice. Now, even in redux, you just call useSelector and useDispatch close to where you need it. This is no different in react-query.
There is a great talk on this subject from Mark Erikson: Hooks, HOCs and tradeoffs that I can totally recommend watching.
Using react-query hooks where they are needed not only avoids prop drilling, it also makes it easier for react-query to keep your data up-to-date, because more observers (=components that call useQuery) are mounting. This is also why it's best to just set a staleTime when you want to customize refetching behaviour. I've written about this in detail in React Query as a State Manager.
Is the mix-match of Redux & react-Query okay? like for instance a grid has page size and page number which should go in redux, maybe?
Totally, as long as you don't sync server state to redux. page number and page size are considered "client state" because the client is control over that state. The user selects the page, and the server responds with the data depending on it. I also like to abstract that away together in custom hooks:
const useData = () => {
const pageNumber = useSelector(state => state.pageNumber)
return useQuery(["data", pageNumber], () => fetchData(pageNumber))
}
that way, you have a hook you can use wherever you want (without passing anything to it), and it re-fetches data automatically if the pageNumber changes.
Is it okay to use pure axios/fetch at some places with redux/react-query it's considered a frowned upon way of doing things?
If you don't need caching / loading states managed for you etc then sure. The only thing that comes to my mind where I don't want a query / mutation might be file downloads or so :)
Let’s step back for a minute and see what each of these abstractions help us achieve & then it makes it easier to see how one should architect an application.
Very broadly, you have
useQuery useSwr
Redux or any other global state management tool
concept of lift state up
Context api
Saving state in url (filters say or link to say a product/item page)
useQuery useSwr are responsible for managing remote state and provide a snapshot of your data that resides behind a remote API. They help with fetching data, caching, error handling, showing loading spinners. They give us additional features such as refetch after a certain interval or refetch on focus, refetch a certain # of times on error etc. Whether we then decide to call these individually in each component or a parent component is a matter of design i.e. implementation detail.
Redux and other global state management tool help with managing local state, globally throughout your client application. A great example of that would be your auth’ed user. That information probably is required globally so redux sounds like a great place to have that information. Shopping cart is another example that might make sense in a redux store.
Lift state up when you want to share information with siblings. This stackoverflow question is a perfect example of lifting state up. DataTableComponent now becomes a controlled component or what you might call a presentation component.
If lifting state up becomes too cumbersome then look at context api or perhaps redux.
So, taking shopping cart as an example, you might decide that context api makes better sense or perhaps lifting state up makes more sense rather than having it in a redux store. My point being that there isn't one way of doing this and it will be a judgement call.
Lastly, you might have a page with filters say, and you may want to give your users an ability to send a link/Url & you might want the recipients to see the same information as the sender. So, now you must save state in your url via say query strings.
Going back to my comment above, there is no one way of doing things. So, you may start off by lifting state but then realize it's too cumbersome so you may switch to context api or even redux.
But each of these abstractions usually do have a place in your application & I have used all the above abstractions in conjunction with each other quite successfully.

React and Redux: Proper Way To Store Loading State in Redux

I have one file loading ui that I call whenever my app's components fetch data from the backend so the frontend can show loading...
The issue is when one component is fetching data, I dispatch loadingData() which causes the other components to showing loading... as well. I know this is happening because I have one action for loading that I dispatch. My question is, should I have separate loading actions for each component? If no, how can I go about fixing this? Thank you.
//Loading action
export const LOADING_DATA = '[ui] LOADING DATA';
export const LOADING_DATA_COMPLETE = '[ui] LOADING DATA COMPLETE';
export const loadingData = () => ({
type: LOADING_DATA
});
The answer is you shouldn't have a loadingData() Redux action in the first place. Loading or not is, as you correctly pointed out, every component's "local" state, so you should store it appropriately - inside each component's "normal" state.
Redux store is designed for storing the data that is mutual to several components. And whether some component is ready or not is certainly NOT that.
It is perfectly fine to handle a loading state either in local component state, the part of your redux state where you will finally store the data, or a completely different part.
There is no "one size fits all" solution and different applications handle it differently.
If you want to track that state globally, it is a fairly common pattern to have a yourApi/pending action followed either by a yourApi/fulfilled or yourApi/rejected action - this is how createAsyncThunk of the official redux toolkit handles it.
But of course, if you have two components sharing the same data, then they also share the same loading state. Maybe you should check if the data is already present and fetch it only when it is not already present, because why fetch it twice in the first place?
Or, if the loading state is really describing a different endpoint, really split that up into multiple loading state.
There is good practice that you have loading for each subject you're calling a backend api, for example a loading for calling books api, a loading for calling movies api and so on.
I recommend you create a loadings object in your state and fill it with different loadings that you need like this:
loadings: {
books_loading,
movie_loading
}
so in your components, you wouldn't call a general loading state which affects a lot of components, only those who need the specific loading will use it and you will solve the problem you have

Making API Calls with React for fetch data and render it

I am new to React and want to understand the difference from classic MVC.
I want to create a simple components that loads some data initially and renders let say a grid.
On some state or prop change it will reload the data and re-render.
What is the best approach in react from below two options?
using the lifecycle events to load the data, update some state and render while in another event will show some loading opacity.
Work with redux and react-redux? but in all example I cant see API calls.
Is this the role of a middleware (Thunk?)?
Will appropriate an explanation.
Both the approaches are correct. It depends on your use case. If you can avoid using redux in your app, use the lifecycle methods to make API calls (also called subscriptions in react documentation). If you think your app has many components and different components needs to share a state, then use redux.
You should also look at React hooks https://reactjs.org/docs/hooks-reference.html
You can use Effect Hook https://reactjs.org/docs/hooks-effect.html to make API calls and update your component's state.
Update:
Both Thunk and Sage are used to manage side effects in your application (making API calls from here). I've used saga, I don't know much about thunk.
How you would use redux-saga:
Say if you want to get some data for display on a button click, this is how it works:
On button click you dispatch an action, say GET_DATA
Your redux reducer will change some state on this particular action, say isLoading=true
You can use isLoading in your component to show a spinner/overlay
At the same time saga will listen to GET_DATA action and make the API call
If success, from Saga you'll dispatch an action, say GET_DATA_SUCCESS with the data from API call
Reducer will change isLoading=false and set the data in state, say apiData = { ... }
If failure, from Saga you'll dispatch an action, say GET_DATA_FAILED with the error code/message
Reducer will change isLoading=false and set the error in state, say apiError = { ... }
You can now use isLoading=false in you component to remove spinner and display the data (apiData) or error (apiError) in you component.
You can go through this tutorial to learn more https://redux-saga.js.org/docs/introduction/BeginnerTutorial.html

Initialising State in React Redux app with data in server

I am working on my first React Redux project.
The project depends on a server to get its data and therefore there is also a server API that needs to be called in order to get those data.
My question is about initialising state.
There are 2 ways I can think of initialising state:
1.) First pass an empty object {} as the initialised state of the Redux store then inside componentDidMount that is where I call the API to access the data in the server and then update the state of the store/app then.
2.) In the reducer of the Redux app call all relevant server method (e.g. getCategories(), getPosts(), etc.) then compose a object with all of the data e.g. {categories: categories, posts: posts} then pass this object to the reducer as the initial state.
Which way is the recommended way for Redux when dealing with data stored in the server?
If there is a 3rd or 4th way that is the Redux way or the recommended way then please share your knowledge. Thank you.
The way I work with data coming from the back end, and as far as I know is also the suggested way, is this:
In the componentDidMount method call a thunk action creator. you can read more about them in this link.
Inside that thunk action creator you first dispatch a REQUEST action, then perform a fetch to access the back end and when the response arrives you handle it on either the success or error handlers. Depending on the handler executed you dispatch either a SUCCESS or an ERROR action (and you attach to it all the relevant information that is coming from the back end so the reducer can access it).
In your reducer you write code to handle all the above actions (REQUEST, SUCCESS and ERROR). Each handler will transition your state, for instance the REQUEST can set an isFetching flag to true that will let you show a spinner in the UI, and the SUCCESS can set that flag to false and populate the state with the data coming from the back end and passed to the reducer using the action dispatched.
Once your reducer is updated, you will access that updated state from the UI, for instance using the connect react-redux function.
Regarding the initial state, it should represent a default state. for instance, it will have the isFetching flag in false and, if you are fetching a list of foods from the back end, then that list could be an empty list in your initial state. This is just a for instance of course. You need to set an initial state that makes sense to your app.
I hope this helps you.
The first way is the better way of initializing state. Your component makes the necessary API calls in componentDidMount and passes the data to Redux as payload of actions which the reducers use to update the state of your application.
The second way is not advisable. According to the Redux docs:
The reducer is a pure function that takes the previous state and an action, and returns the next state.
(previousState, action) => newState
...
It's very important that the reducer stays pure. Things you should never do inside a reducer:
Mutate its arguments;
Perform side effects like API calls and routing transitions;
Call non-pure functions, e.g. Date.now() or Math.random().
Edit:
You can also use thunk middleware and async actions to do API calls as explained in the Redux docs and #DiegoTArg's answer.

Correct way to use API with React/Redux

I'm a bit confused about the component lifecycle in React. Consider a component that loads its initial state by dispatching the action, getTodo() and updating the state of the entire app
componentDidMount() {
var _this = this;
axios.get('/api/todo').then(function(response) {
_this.props.actions.getTodo(response.data);
}).catch(function(err) {
console.log(err);
});
}
What can I use in componentWillUnmount() to prevent the memory leak? If I choose to do this, how will it be possible to update the state when I come back to the page from another page with this method?
Also, is it a better to just store the props that I need for the page as the state and updating the state instead? My concern with this approach is that it just doesn't update the state of the entire app and other components that might need the same props will have to go through the same unnecessary process, which could be avoided by using the first approach.
You should avoid doing api call in a component. React offers an interface "nothing" more.
You should dispatch an action. In fact an action creator can be used (check redux-thunk or a redux-api-middleware). Your state app must be hold by redux store, not in your interface.
You probably have a "top" component will is mount only once, it can dispatch this action (action which is able to get initial state)
I hope it will help

Resources