Skip to content

feat: Make the starred repositories list paged #750

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Mar 26, 2018

Conversation

machour
Copy link
Member

@machour machour commented Mar 25, 2018

Question Response
Version? v1.4.1
Devices tested? iPhone 7
Bug fix? no
New feature? yes
Includes tests? no
All Tests pass? yes

Description

In order to further explain the new API, this PR demonstrate how we turn a limited list of repositories (50), into a paged list displaying all repositories. I did that on the starred repositories list displayed on a user profile.

By chance, the file changes are well ordered, so I'll break down the migration steps in comments.

Once merged, we will have two screens using the new pagination API (events & starred repos), so next PR will be about a little refactoring:

  • Adding a new <PaginatedFlatList /> component and using it in both screens.
  • Adding a getPagination() helper to reduce the amount code needed in mapStateToProps

@coveralls
Copy link

coveralls commented Mar 25, 2018

Coverage Status

Coverage increased (+0.01%) to 43.4% when pulling 4a98a8b on machour:feat/paged-starred-repos into 7f82b48 on gitpoint:master.

Copy link
Member Author

@machour machour left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added walk through.

@@ -3,3 +3,6 @@ import { createPaginationActionSet } from 'utils';
export const ACTIVITY_GET_EVENTS_RECEIVED = createPaginationActionSet(
'ACTIVITY_GET_EVENTS_RECEIVED'
);
export const ACTIVITY_GET_STARRED_REPOS_FOR_USER = createPaginationActionSet(
'ACTIVITY_GET_STARRED_REPOS_FOR_USER'
);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First, we create our new actions. A "pagination action" in a object composed of the 3 usual sub-actions (pending, success, error) and an additional one: reset.

nextPageUrl: this.getNextPageUrl(response),
schema: Schemas.REPO_ARRAY,
}));
},
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Second, we implement our new API call: client.activity.getStarredReposForUser(userId)

The schema key tells which kind of objects the call to GitHub will be returning: An array of repositories.

PS: I'm following @oktokit/rest for the choice of namespace/method names.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Outside the scope of this PR, but curious

Where is nextPageUrl being used? Is it part of the pagination flow within proxy to define a nextPageUrl property so it knows how to continue moving past pages?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In our FlatList onEndReached, we call client.activity.getStarredReposForUser(login, { loadMore: true})

When the proxy sees this parameter, it overrides the GitHub URL that will be called with nextPageUrl: https://github.com/gitpoint/git-point/blob/master/src/api/proxies/create-dispatch-proxy.js#L54

@@ -78,4 +78,7 @@ const paginate = types => {
// Updates the pagination data for different actions.
export const pagination = combineReducers({
ACTIVITY_GET_EVENTS_RECEIVED: paginate(Actions.ACTIVITY_GET_EVENTS_RECEIVED),
ACTIVITY_GET_STARRED_REPOS_FOR_USER: paginate(
Actions.ACTIVITY_GET_STARRED_REPOS_FOR_USER
),
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We tell our pagination reducer that it should take care of this action set.

{
idAttribute: repo => repo.full_name,
}
);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is where we define our Repository schema for Normalizr.

For now, we simply tell it to use the repository full name as an identifier.

Copy link
Member Author

@machour machour Mar 25, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Above is everything that is required to get going. All the logic will happen under the hood:

When client.activity.getStarredReposForUser('machour') is called, the dispatch proxy does this:

  1. Analyzes the call and extract the following information:
  • namespace: activity
  • method: getStarredReposForUser
  • parameters: ['machour']
  1. Automatically guess the action set to be used from 'namespace' and 'method' : ACTIVITY_GET_STARRED_REPOS_FOR_USER !

  2. Checks if this is a paginated call, by checking if STORE.pagination.ACTIVITY_GET_STARRED_REPOS_FOR_USER exists. It does since we added it to the paginate() reducer.

  3. Checks if we already have data inside STORE.pagination.ACTIVITY_GET_STARRED_REPOS_FOR_USER['machour']

'machour' is a '-' separated string of our parameters.

So client.namespace.method("foo", "bar", 42) would have checked inside:
STORE.pagination.NAMESPACE_METHOD['foo-bar-42']

  1. No data, "ACTIVITY_GET_STARRED_REPOS_FOR_USER.pending" is dispatched, the API call is performed, and the returned json is treated as an array of repositories (as we said in the schema key)

  2. After "success" is dispatched, we end up with this in our store:

screen shot 2018-03-25 at 5 18 30 pm

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah this makes so much sense, and it makes things so so clean by abstracting it all away 👏 👏

starredRepos,
userId,
};
};
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mapStateToProps now create two props:

  • starredReposPagination: STORE.pagination.ACTIVITY_GET_STARRED_REPOS_FOR_USER['machour'] or and empty pagination
  • starredRepos: An array of repositories picked from STORE.entities.repos, filtered to contain only those whose id is present in my pagination.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is starredReposPagination just an object that returns nextPageUrl and an isFetching bool?

Copy link
Member Author

@machour machour Mar 26, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and pageCount and ids.

It's literally this in our case:

screen shot 2018-03-26 at 1 03 57 am

bindActionCreators({ getStarredRepositories }, dispatch);
const mapDispatchToProps = {
getStarredReposForUser: RestClient.activity.getStarredReposForUser,
};
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this.props.getStarredReposForUser() is mapped to our API call.

this.props.getStarredReposForUser(userId, {
forceRefresh: true,
})
}
Copy link
Member Author

@machour machour Mar 25, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remember how our action set contains the 3 usual pending,success,error and an additional 'reset' thingy?
'reset' is dispatched when 'forceRefresh' is set to true, and it will simply wipe out STORE.pagination.ACTIVITY_GET_STARRED_REPOS_FOR_USER['machour'], so that fresh information is refetched from the GitHub endpoint

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was just about to ask about why we have reset. You've thought this through mate :)

}
refreshing={!starredRepos && starredReposPagination.isFetching}
onEndReached={() =>
this.props.getStarredReposForUser(userId, { loadMore: true })
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

loadMore tells the proxy to use nextPageUrl as endpoint for the next API call, instead of the original endpoint, and the new results are appended to the pagination.

@@ -12,7 +12,6 @@ import {
GET_IS_FOLLOWING,
GET_IS_FOLLOWER,
GET_REPOSITORIES,
GET_STARRED_REPOSITORIES,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And here, we removed our old boring actions, types, reducers. So long!

Copy link
Member

@housseindjirdeh housseindjirdeh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WOW this is so good. So so good.

The number of screens we have that literally follow the exact same logic and can be simplified and we cana easily have paginated logic throughout the app :)

Thanks a million. I've left a few comments just asking questions but feel free to merge this right in.

To fully digest how to use it, I'm going to incorporate this logic to a separate screen and use this PR as reference 👏

nextPageUrl: this.getNextPageUrl(response),
schema: Schemas.REPO_ARRAY,
}));
},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Outside the scope of this PR, but curious

Where is nextPageUrl being used? Is it part of the pagination flow within proxy to define a nextPageUrl property so it knows how to continue moving past pages?

{
idAttribute: repo => repo.full_name,
}
);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah this makes so much sense, and it makes things so so clean by abstracting it all away 👏 👏

this.props.getStarredReposForUser(userId, {
forceRefresh: true,
})
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was just about to ask about why we have reset. You've thought this through mate :)

starredRepos,
userId,
};
};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is starredReposPagination just an object that returns nextPageUrl and an isFetching bool?

@machour
Copy link
Member Author

machour commented Mar 26, 2018

Made a tiny change: the repo id (full name) is now stored in lower case.

This make things more consistent internally and will save us a headache if for some reason, one endpoint respond with a lowercased name.

@machour
Copy link
Member Author

machour commented Mar 26, 2018

Sadely, last change made an important comment outdated, reposting it:

Here's all the logic that will happen under the hood:

When client.activity.getStarredReposForUser('machour') is called, the dispatch proxy does this:

  1. Analyzes the call and extract the following information:
  • namespace: activity
  • method: getStarredReposForUser
  • parameters: ['machour']
  1. Automatically guess the action set to be used from 'namespace' and 'method' : ACTIVITY_GET_STARRED_REPOS_FOR_USER !

  2. Checks if this is a paginated call, by checking if STORE.pagination.ACTIVITY_GET_STARRED_REPOS_FOR_USER exists. It does since we added it to the paginate() reducer.

  3. Checks if we already have data inside STORE.pagination.ACTIVITY_GET_STARRED_REPOS_FOR_USER['machour']

'machour' is a '-' separated string of our parameters.

So client.namespace.method("foo", "bar", 42) would have checked inside:
STORE.pagination.NAMESPACE_METHOD['foo-bar-42']

  1. No data, "ACTIVITY_GET_STARRED_REPOS_FOR_USER.pending" is dispatched, the API call is performed, and the returned json is treated as an array of repositories (as we said in the schema key)

  2. After "success" is dispatched, we end up with this in our store:

screen shot 2018-03-25 at 5 18 30 pm

@machour machour merged commit 22add17 into gitpoint:master Mar 26, 2018
@machour machour deleted the feat/paged-starred-repos branch March 31, 2018 11:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants