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;
Related
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.
I have a app in which users can build a "Quote". So far I've been adhering to "idiomatic" redux (or something close to it) and it's been working out well.
However, I'm struggling to deal with a fairly simple scenario:
When the page is first opened, I fire an async LOAD event which retrieves info from the server needed to build the quote (products, inventory, previously saved line items, etc).
I need some way to be able to automatically add a specific line item(s) to the quote first it's first opened.
I've added a defaultLineItems property to my LOAD payload, but to fire the addLineItem(product, inventory, options) action, I need data from the productReducer, inventoryReducer, optionsReducer. The lineItemReducer could look at these defaultLineItems and try to set it's state appropriately, but that would require having to rewrite a lot of BL typically handled by the actions using data aggregated from reducer memorized "selectors" (EG: defaulting price, quantity, currency translation, etc)
I can think of a couple ways to achieve this, but they all seem somewhat hack-ish (IE storing a flag in the reducer that says I need to fire an action and then running it when my root component props update). It seems like a react component should not be responsible for this type thing.
What is the proper way to do something like this?
Seems there are a couple different ways this can be accomplished but for me the most balanced approach between simplicity and design was to use store.subscribe in conjunction with a reducer to track the last action(s).
At it's simplest, this would look something like this.
store.subscribe(function() {
let state = store.getState();
if(state.lastAction.type === ActionKeys.LOAD){
console.log('load action fired!');
}
})
Please be aware that firing an action from store.subscribe will cause recursion so you need to be selective.
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;
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.
TL;DR edit in retrospect years later: there's no solution that's not gross as long as it's just state data - you'll need to also get it into a separate store somewhere somehow and can do whatever you want at that point. But read the question and the answer and the back-and-forth if you want some more background.
I have a table of two sections, each with various input values. Let's say that it is a survey. Feeding data into this is straightforward; I have the typical model:
{ "sections": [ { "name": "a", values: { "A": 1, "B": 2, "C": 1, ... } }, ... ], ... }
And a component hierarchy like:
<Survey>
<Section> (for each section)
<ValueRow> (for each value)
I put the model into a prop on the survey and the right information is trickled down into the subcomponents. Each ValueRow has a text field and its ephemeral value reflected back into its own state. This works fine "on the way down", in the one way flow that React is built for.
However, I also wish to show progress on the Section level and for the entire Survey, both simple things like number of fields filled out and statistical data needing the entire data set - what's the average across sections, how many "1" answers do I have in total, what's my grade (calculated from all the answers) and so on. Essentially, I'd also want to have:
<Survey>
<SurveyWideStats>
<Section> (for each section)
<SectionWideStats>
<ValueRow> (for each value)
This turns into a reduction of the current state instead of the model data. What's the best way of doing this in React? Flux and Actions and Stores all seem to deal with how to handle the data once it has been committed to the model. What I want to do is to pluck all the state data and do something with it, but it also seems terribly gross for the SurveyWideStats element, for example, to go poking through the garbagestate of its sibling element's children.
My current solution is to pass around an accumulation object and provide enough state to each component that it can keep calling that whenever something changes. This seems clear and divided enough, but it means that I have to have two passes and have to be careful not to start fiddling with state during rendering (at least since that's when I call the accumulation object - I suppose there may be a better point during the lifecycle where I could call that). And in addition, it seems like this would be an obstacle to "pick up from" server side rendering.
What's the best way? Is there an established pattern for this - preferably one where these things don't have to be so custom and really tailored to the data all the time?
Two ways to do this:
Pass the entire table as a prop to the highest component .
Inside survey's render function, calculate the stats, then pass them to the component as props, followed by the foreach loops over the table for the other children components. That way, your stats component is a pure component, does not need state and does not need to poke in siblings.
Create a stats function in a store, and have the component call this to get the stats. NB best not to save the stats in a store, since it is clearly derived data. Unless for performance reasons.
Hope this helps!
UPDATE:
To handle changes by the user when they change an input value, you have two options, depending on your preference:
(Option 1 describes a pure component).
(When you use flux pattern): Put the value of the input control in props. And whenever the user makes a change, fire an action to update a store, and have the store pass down updated props. So the (top) component notices a change event and rerenders. This creates more or less 'live' updates, e.g. when a user types a single character in an input field, the page title is updated immediately. The component with the input control does not have (and does not need) setState. This setup may become slow in really large component trees (because with each character, the entire tree is rerendered). But react is superfast and smartly only renders changes in de tree.
Put the initial prop value in state (in getInitialState() and put the input value in state also. Typical example: user types a character in an input field, the change triggers a setState() and the component is rendered again. Only when the user clicks some save or commit button, an action is fired to save the value in a store.
UPDATE:
As a bonus, below the flow for updating stores and components.