Upgrading From Version 2
If you are coming from version 1, please read this first for instructions.
Version 3.0.0 comes with some new features and performance improvements. We've also changed some state management details and made some tweaks to how async updates are performed. All of these are described briefly in this document, and more extensively in the relevant sections.
Please note that most of these changes involve inline updating, which is not used in the majority of cases. If all you're doing is paginating and filtering, a great deal of this information will not apply to you.
New / Improved Features
Better Performance for VioletDataTable
Two now mechanisms were introduced to make inline updating more performant: the tabulateLean()
decorator and the DataRow
component.
These are both utilized by VioletDataTable
so that when an async update is being performed on a row, only that row will re-render,
with the others being completely unaffected.
Rather than injecting the current page of results, tabulateLean()
injects only the ids
, and prevents re-renders unless those ids
have changed (e.g. if a different page has been requested). You can then iterate over the ids
and pass each into a DataRow
component
to render the rows. The source code for VioletDataTable
is an excellent reference to see these both in action. You can also read more
about them here.
Augmenting Reducers!
You might want to manage some additional state that's relevant to your paginated list. In previous versions, you would have to
keep a separate reducer that's tied to your pagination list to accomplish this. Not anymore. You can now add custom properties
and reducer handlers to your pagination reducers. You can do this during your createReducer
call by adding additional properties
to the initialSettings
object, and then declaring an augmentedWith
object that contains your reducer handlers. This small unit
test demonstrates this capability pretty well:
describe('pagination reducer', () => {
context('when augmented', () => {
it('responds to given actions', () => {
const config = {
listId: 'customized',
initialSettings: {
customField: undefined
},
augmentWith: {
CUSTOM_ACTION: (state, action) => state.set('customField', action.value)
}
}
const reducer = createPaginator(config)
const action = {
type: 'CUSTOM_ACTION',
value: 'someVal'
}
expect(reducer(undefined, action)).toEqual(defaultPaginator.set('customField', action.value))
})
})
This is considered advanced usage and usually shouldn't be necessary. It's also worth noting that you will be working with Immutable objects in your augmented reducer handlers. While we do try to avoid making you think about Immutable when injecting props into your components, in this case it simply cannot be avoided.
For more information, see the section on augmenting reducers.
Better tracking of updates in progress
Previous versions were very naive when attempting to indicate that an item is being updated on the server. As soon as the server responded,
the reducer would clear the updating flag for the item being updated, even if there are other requests in progress. Version 3 fixes this by
channeling all individual item updates through a counting semaphore. You can now let your users mash buttons as much as they like. Leave that
decision to your UX engineer, we have you covered. As long as you use updateAsync
to perform asynchronous updates, the isUpdating
selector
should always be accurate for a given record.
Breaking Changes
Serialized component props
In previous versions, decorated components would receive the results
and other pieces of state as Immutable objects. In version 3
we will serialize these to plain javascript objects and arrays. violet-paginator
will still manage its state behind the scenes with
Immutable, but we don't want to force you to learn this library just so that you can use ours.
Error state gone!
We used to keep some reducer state to indicate if an item had any errors. For example, if a list allows inline editing and a server request to update an item returns an error response, we would store this in our reducer. This is discontinued in version 3. We will no longer attempt to keep any error state pertaining to individual items. If you want to keep error state in version 3, you can augment your pagination reducer, which is a new feature that will be described earlier in this document.
updateAsync
Server Responses
The updateAsync
action will no longer attempt to make use of your server response. This feature got in the way more than it helped.
If users still want to apply an update using the server response, they can do so by attaching a promise handler to updateAsync
:
export function toggleActive(recipe) {
const data = {
active: !recipe.get('active')
}
return dispatch =>
dispatch(pageActions.updateAsync(
recipe.get('id'),
data,
api.recipes.update(data)
)).then(resp =>
dispatch(pageActions.updateItem(recipe.get('id'), resp.data))
)
}
Server Errors
In versions 1.x and 2.x, updateAsync
would handle server errors by flagging the item as errored, resetting the record state, and then
swallowing the exception. However, as mentioned above, in version 3, violet-paginator
is no longer keeping any record of an item's errors
in the reducer. Instead, after resetting the record state, we will throw the exception for you to handle as you see fit, e.g:
export function toggleActive(recipe) {
const data = {
active: !recipe.get('active')
}
return pageActions.updateAsync(
recipe.get('id'),
data,
api.recipes.update(data)
).catch(resp =>
dispatch(toastr.error(resp.error.message))
)
}
See the Updating Items documentation for more information.
removeAsync
In previous versions, removeAsync
would remove an item from the list after the request completed. This causes a bit of a UX problem.
Imagine you have 30 records and are viewing 15 per page, and on the first page, you delete the 15th record. Page 1 now displays records
1-14. Now if I click to go to the second page, the server will return records 17-30, because the deletion actually causes record 16 to
appear on the first page. So we effectively skip over it.
To address this, the default behavior for removeAsync
is now to expire the list when the request completes, causing it to refresh.
However, removeAsync
now accepts a third argument named expire
which defaults to true
, so if you still would like to just remove
the item instead of refresh, you can pass in false
as the third argument to this method.
`
isUpdating
The isUpdating
function is still the correct function to determine if an item is currently being updating. However, the internal
implementation of that function has changed. If you are reading directly from the reducer to determine whether or not an item
is updating, your logic may no longer be correct. You should switch to isUpdating
. Here is an example usage that also demonstrates
some other handy selectors.
import { getItem, isUpdating, isRemoving } from 'violet-paginator'
export default connect(
(state, ownProps) => {
const { listId, itemId } = ownProps
return {
record: getItem(state, listId, itemId).toJS(),
updating: isUpdating(state, listId, itemId),
removing: isRemoving(state, listId, itemId)
}
}
)(Component)
Component markup and names
Links are now buttons in version 3. This was done mainly for accessibility reasons. If you are using any of our premade components that use pagination links or sort links, you will probably have to restyle them.
Some components were also renamed:
Old | New |
---|---|
SortLink |
ColumnHeader |
PageLink |
PageNumber |