Connect
The data-fetching decorator for Lore
The data-fetching decorator for Lore
This section describes how to use connect
, a decorator that greatly simplifies the process of requesting data from a REST API. It is designed to fetch data from the local cache if it exists, or automatically fetch it from the API if it doesn't.
The connect
decorator is used throughout the Quickstart and looks like this:
import { connect } from 'lore-hook-connect';
connect(function(getState, props) {
return {
tweets: getState('tweet.find')
}
})(
createReactClass({
//...
})
)
Whenever you create a model in Lore, the conventions will automatically create a set of actions and reducers to support basic CRUD operations. The actions
know how to fetch data from the API, and the reducers
know how to store it.
The problem(s) connect
solves for lie in the orchestration of when to fetch data from the API.
Applications that consume REST APIs often cache data on the client-side in order to avoid making excessive API calls. This means the application follows a process like "only fetch data if you don't already have it".
Managing this logic means every component that needs data also needs to follow the steps below before requesting it:
At first, that may not seem very complex to manage, but there's another check it gets a little trickier when we recall that React will re-render the application everytime data changes. And At first that may not seem very complex to manage, but consider the following scenario:
This scenario highlights an issue; if guards aren't in place to know when data has already been requested, then there's the potential to make an API request every time a component renders, which can result in multiple API requests for the same data.
Additionally, depending on the number of requests and the rate that components are updated, this also has the potential to not only severely degrade the usability of your application, but to also apply unnecessary load to the API, which may in turn have negative side-effects like hitting rate limits or increased hosting costs for the application.
So something else we need include in our check is that actions should only be invoked if the API call to fetch that data is not already in-flight.
The connect
decorator not only automatically performs this check and applies this guard, but also provides a means of breaking through it, for times when you need specialized control over the fetching logic, such as if you wanted to force data to be re-fetched every time a component was mounted.
Whenever you create a model in Lore, the conventions will automatically create a set of actions and reducers to support basic CRUD operations for fetching and storing data from the server.
For example, if you create a model called tweet
, the framework creates the following actions:
And it also creates the following reducers:
What the connect
decorator needs next is a map that defines, when a component requests data, which reducer it should look in for that data, and also what action should be invoked if it doesn't find the data (so that we can retrieve it).
That map looks like this:
getState | reducer | action |
---|---|---|
tweet.find | tweet.find | tweet.find |
tweet.byId | tweet.byId | tweet.get |
To better understand the map, let's look at a typical usage example. Here we want to retrieve a list of tweets from the server.
import { connect } from 'lore-hook-connect';
connect(function(getState, props) {
return {
tweets: getState('tweet.find')
}
})(createReactClass({ /*...*/ }))
The first thing we need is a way to describe the data we want. We do this using the getState
function, and our description is the string we pass as the first argument; tweet.find
. This string is the first column in the table (getState
).
Next, the getState
function needs to know which reducer to look for the data in. That's the second colum: reducer
. From the table, we can see passing tweet.find
to getState
will cause it to look in the tweet.find
reducer for the data (which in this case represents a query that includes no filter or pagination information).
Next, if no data exist in the reducer, we need to know which action to invoke to fetch it. That's what the third column is; the name of the action
that should be invoked. In this case, if the data we want isn't found in the tweet.find
reducer, then the tweet.find
action will be invoked to fetch it.
Now while that example does use the same name for the getState
, reducer
, andaction
, that's not always the case. If we were to request a specific tweet for example, we would use the follow getState
call:
import { connect } from 'lore-hook-connect';
connect(function(getState, props) {
return {
tweets: getState('tweet.byId', {
id: 1
})
}
})(createReactClass({ /*...*/ }))
In this example (and referencing the table) the tweet.byId
string will cause the getState
method to inspect the tweet.byId
reducer, and invoke the tweet.get
action that tweet has not yet been fetched.