How to manage data in a React/Redux application - reactjs

I having some issues on how to conceptualize a react application with Redux being my Flux library of choice.
So I am taking a few assumptions from my readings, correct me if I am wrong,
How does one manage data fetching?
Let's say this, I have a application that needs to fetch some data specific for the current logged in user, I assume this user data should be stored in the Redux Store.
But now the problem ensues, if all my state data is stored in a store, do I have for example a array of messages in the store for this user, and make my component fetch the information from the store? Or should I Fetch the data on the componentWillMount or similar method? I get that when I need to fetch data the first time, I will send an action to the store to fetch the data from the server, which will trigger a change event that I can catch on the component and update the state, is that a correct?
I feel like I am missing a point somewhere and can't make the connection on how the app is supposed to be structured and manage the data, since it seems the store will be bloated with tons of smaller "state" objects that will be used across the other routes/components.

In Redux you have a single store with many reducers. Each reducer changes specific part of the state.
For example you have an state like this:
{
messages: [...],
currentUser: {...},
notifications: [...],
...
}
In this case you will have a reducer to change messages part of state. Another reducer to change notifications part.

Related

Is it a good practice to allocate a Redux state dedicated to entity creation with a 'fire-and-forget' API?

In several blogs video and so on, there's a CRUD tutorial with Redux.
None of them (AFAIK after surfing) deal with fully async API on servers, like a fire-and-forget behavior.
Main commands in a CQRS environment deal frequently with those kind of fire-and-forget.
Let's take a fictive example of Twitter to easily get the idea:
Basically, in a context of a synchronous CRUD API, you likely have:
Redux Action: POST_TWEET
Server API: returning the entire created tweet in the response data.
State: TweetReducer exploring and storing the created tweet's data from the response.
UI: listening to the new tweet from the Tweet state directly.
The TweetReducer, besides the classical fetching APIs, can fully handle the POST_TWEET action since it can encompass the new tweet directly.
However, in a context of fire-and-forget API on the server:
Redux Action: POST_TWEET
Server API: returning only the tweet's id (e.g Location) in the response.
State: TweetReducer does not handle the creation since the tweet is not available at the time of the success action triggering.
Thus a new Redux state dedicated to handle tweet creation labeled TweetCreation typically owning those properties: (data: {id: string}, inProgress: boolean, errors: []).
It would then grab the newly created tweet's id in the data and allow UI to listen to this state (TweetCreation).
UI: listening to TweetCreation state and hence displays that the tweet is sent or even tries to fetch the server at some time interval to get the full tweet.
Is it a good practice some people experiment to add another state on the Redux store to deal with fire-and-forget APIs?
Is it "conventional" in the community or is there another clever way?
1. Creating a separate state for pending tweets
For a start, you'd need to change your TweetCreation to an array in case the user makes a second tweet before the first is confirmed.
So your shape would look like this: { pendingTweets: [], confirmedTweets: [] }.
In POST_TWEET, you append the new tweet to pendingTweets.
In SET_TWEET_ID, you remove the matching tweet from pendingTweets and push it to confirmedTweets.
In your component, you probably do something like confirmedTweets.concat(pendingTweets).map(...).
2. Use the same state for pending tweets
The shape will just be { tweets: [] }.
In POST_TWEET, you append the new tweet to tweets.
In SET_TWEET_ID, you update the matching tweet in tweets.
In your component, you do tweets.map(...).
Conclusion
Using the same state for pending tweets seems like a simpler (and therefore better) approach.
Additional considerations (for both approaches)
I left out details about avoiding direct state mutations when updating since that's very basic.
You probably need to do something like generating a temporary id for the pending tweet and sending it back from the server so that you can find the matching tweet in SET_TWEET_ID.
The temporary id can use a different object key (or use an additional flag) so that you can distinguish between a pending and a confirmed tweet in the component (eg. to render a loading icon beside pending tweets).
Replacing [] with {} using id as object key might be better (depending on the exact requirements) but that's not the focus of this question.

Update Redux store for every character entered in a input field?

I have a sequential data collection app built with React and Redux. Right now, I have internal state for each page which has a form and when user clicks "Submit" button, I am dispatching the data collected in the state and updating my Redux store. Should I be dispatching an action on every key entered(debouncing) or should I store it in a local state and update Redux store at once?
Will there be a performance issue if I dispatch action to Redux store on every keypress since it is not an asynchronous call.
Well, the answer is: it depends.
Some argue that we loose some of the advantages of using a central store (like Redux) if you also have an internal state in the components. Like, if all your application state is just the redux store, storing the store, or the sequence of actions that lead to that store, will give you a complete picture of what was happening before maybe an error occurs.
I believe that is excessive. It is completely fine to store little stuff in your component's internal state, but there should be a well defined line.
In case of a form, you'd have controlled input elements, and you'll want a mechanism to submit the form with an action. It'd be necessary to have the contents of the form in the redux store.
One way would be to have the contents in an internal state, and only put them into redux form after a little debounce. But that has some edge cases. What if the user submits the form in the little debounce time? The form in the store is incomplete. Is thinking about all the edge cases worth it?
I believe just having the form elements controlled from the redux form is the simplest solution. The performance issue one would imagine is not really an issue. This is also what redux-form does (you can look at how that is implemented for inspiration).

React+Redux - managing state for autocomplete inputs

I have a large app with some autocomplete text-inputs that retrieve search suggestions from the backend on every key stroke.
I want to save recent search query results to avoid multiple backend calls for the same query if the user deletes characters. I also want to expire these queries after some period of time to keep search results fresh.
I also want to have a loading indicator for the current text input if the backend call isn't complete yet for that exact value.
The question is - where to manage this state and the actions associated with this state (below I have a sample state shape I need).
Managing it in the main Redux store looks like an overkill - this state is kind of temporary-intermediate state, it's very short-lived (queries should be expired after a while), there may be many instances of the same Component present on the screen, and different instances might use different backend calls.
Managing the recent search-queries and search-results in the local React Component state object - looks like a fine solution.
But now we have the backend calls which I don't want to fire from within the component, but go through the full-blown Flux process, with proper actions and reducers and passing the results (or errors) into props through the Store Connector.
So eventually, things here don't fit properly with each other - I don't want to manage the state in the main Redux store but I do want the backend-calls (results of which are the main part of that state) to go through the main reducers+store lifecycle.
Any advice for a robust, maintainable, and easy-to-figure-out-without-docs architecture is appreciated.
The state I need for every instance of this component looks something like:
(let's say I typed in dog, and the last result didn't come yet):
{
currentSearchInput: 'dog',
recentQueries: [
{
input: 'd',
isLoading: false,
results: [...]
},
{
input: 'do',
isLoading: false,
results: [...]
},
{
input: 'dog',
isLoading: true,
results: null
},
]
}
In my opinion, using the object structure noted above with the components internal state is just fine. Write a function that will accept the results of the HTTP Request that come back through Redux, and update the results field of the appropriate object in the array. Personally, I would store the promise in the results field. Each time componentWillReceiveProps gets called (due to your reducers returning new redux state) use a setState to update your recentQueries.
I'd also set a limit. 5 recent query max or something like that.
That being said, I use the google places API to return address/establishment suggestions and the data is so small it wasn't worth it to do this. Most of the results are under 10kb.
EDIT:
Based on our discussions in the comments, here is what I would suggest doing.
In the onChange handler for the input.
Pass the search criteria to the action as you normally would
In the action creator, return the results of the API call AND the search criteria
In the reducer
Check to see the length of the array that holds your search calls (the redux store value). If it is less than five, simply concat the current values and the new values and return. If its value, overwrite the 0th position of the array with the new content. e.g.
state[0] = action.newApiResult; return state;

React Redux - state undefined on first render

For my client I'm creating something like quiz web app in react with redux based on websockets (socket.io) with a huge, very unique data. No user interaction, just presentation layer. It works like this: I get websocket event with url to my layout and payload data, and then I render given url and fire redux action with data as argument, which becomes app's state. Simple as that. BUT I noticed that on first render initial state is loading, not given from websocket as argument to action. As I said data I get is huge and unique so I didn't want declare in reducer something like this:
pageData: {
assets: [],
questions: [],
details: []
And so on. It's much more complicated btw it's just an example. Instead of this I made something like this:
pageData: {}
And I was hoping that on view (using connect) I can get this data like this:
this.props.view.pageData.questions
But then it turned out that I can not get this because it's undefined on first render. So my questions are:
Is there a way to access to this data on first render without
declaring whole structure?
If not, should I reconstruct given data in reducer?
Should I then create reducers for each page (there are like over 20 views
with unique data)
Of course I can declare everything in reducers but I feel it's very hard to maintain so much data.
But you know, maybe I'm just too lazy and I should declare initial state for each page and this question does not have sense ;).
I think you may have a few options here:
Define fallback data in your components if undefined
Don't render your page (or components) until you have received the data
Define your initialState explicitly as you already suggested
All or most your components expect or should expect data of a certain kind and in a certain format. For this reason, laying out the structure beforehand (#3) seems to be most appropriate. Ask yourself this: would my app still display correctly if the format of the web socket event data changes?
To answer your questions specifically:
Is there a way to access to this data on first render without
declaring whole structure?
Yes, you could use the || operator in your bindings to fall back (#1) to an empty array or object or value. Example <MyComponent listOfItems={this.props.items || []}. This effectively creates an empty state, however, IMO this should be standardized in the reducer/store with initialState.
Should I then create reducers for each page[?]
Not necessarily a reducer for each page, but a store with all pertinent data to your application. It is hard to say for sure without knowing more about the architecture of your app, but keeping small, well defined chunks of information is generally easier than one big blob.
I strongly advocate defining your data beforehand. It might sound cumbersome at first, but it will pay off greatly and helps others understand what the app might look like with live data.
that's because you haven't added default case in reducer
default:
return state;

Reducer/state organization for large objects

I have a react native app that uses many screens to manage the data in a large object.
For example, each of the following is a screen to manage a portion of 1 object in this app: address, contacts, images, notes, and a few more.
Is there any issue that would conflict with best practices or performance to use a single reducer which passes back the entire object in state?
or should my reduces and state be more screen oriented, passing back only the properties of the object that each screen is handling?
Straight forward answer. It depends
Depends on the data stored by each reducer.(state maintained by each)
If it's complex data better to create a separate reducer.
If each screen's state is just a flat JSON object, keep all of them in single reducer.
At the end combineReducers() will make them all available for us at any place we want by simple connect() method of redux.
Generally we'll maintain a reducer for container where container holds the components which are hydrated by reducer of this container.
It's not an issue even if we maintain a 'reducer' for each component.
And one more good practice is to keep related state say:
cart => cartReducer which in turn accepts all actions on the state of cart like ADD_TO_CART, RESET_CART, CHECKOUT_CART, DELETE_ITEM etc.
And in your case if address, contacts, images etc are related to a single entity, better to maintain a single reducer.

Resources