Github API
In this step-by-step tutorial we are going to build a component that asks for a Github username and then fetches all the repositories for that user, using the Github API.
To follow along, you must have a good understanding of React and you should have gone through the quickstart or core concepts pages at least once.
The final result will look like this:
Search for a github user
#
1. Creating the React Componentnote
To code along, use this codesandbox.
Now that you have seen the end result, let's build it, piece by piece.
The first thing we need is a React component, which has 1) one input field for the username
and 2) a place to show the results.
Written with React hooks, it would look something like this:
Search for a github user
username
in Kea#
2. Save the That's great, but this isn't a tutorial on React hooks. ๐
Let's refer back to this illustration from the What is Kea page:
In Kea, everything starts with an action. Every button press, every change in a textfield, every network request and every response starts with an action.
In this case we need one action called setUsername
, which takes one parameter, username
.
This is how you would write a logic with such an action:
To store data you use a reducer. Reducers are functions that react to actions and change their state if needed.
We need just one reducer, username
, that reacts to the setUsername
action and stores
its payload. This is how that looks like:
Finally, we need a way to read the username
value and call the setUsername
action in our
logic.
For this we use the useValues
and useActions
hooks:
Putting it all together, we end up with a component like this:
Live demo:
Search for a github user
Obviously for examples this simple, adding Kea feels like a lot of boilerplate compared to Hooks.
Luckily it won't stay this way for long!
setUsername
action#
3. Listen for the The next step is to use a listener to listen for the setUsername
action and run some code
whenever it has been dispatched.
This is how that's written in Kea:
#
4. Trigger the actual callNext we must make a request to the Github API and ask for data about this user.
For this we'll make a simple window.fetch
call:
#
5. Store the response of the callNow that we got the list of repositories, what to do with them?
The answer: we store them in a few new reducers.
We're interested in 3 things:
- Whether we're currently fetching any data:
isLoading
- The repositories that we have fetched:
repositories
- Any error that might have occurred:
error
Because of the way Kea is set up (any reducer can react to any action), we can achieve all
of this by just adding two new actions (in addition to setUsername
):
- One to set the repositories:
setRepositories
- One to set the error message:
setFetchError
Hooking them up gives the following result:
Try to follow along and "connect the dots" to see what gets stored in which reducer when each of these three actions is called.
The final step is to add setRepositories
and setFetchError
into the listener:
#
6. Display the resultFinally, we also want to display the repositories to the user.
We fetch the new values (isLoading
, repositories
, error
) with the same useValues
hook and put them into our JSX accordingly.
Here is one way to do it:
Giving us the following result:
Search for a github user
It almost works! Almost. Try changing the value in the textfield.
There are two issues we must still fix. One of them is rather obvious, the other one less so.
#
7. Fetch the repositories on first loadTo load the repositories on page load, we can hook into the afterMount
event
and run the setUsername
action when the logic is mounted.
We pass the current value
of username
to the action as a bit of a cheat (we're setting the username
to what it already is), but it gets the job done:
This is the result:
This example is halted to show the afterMount
effect.
#
8. Add breakpointsThere's still the second and less obvious problem to solve.
What if we type "microsoft" to the username field?
Well, if you open up your network inspector panel in Chrome's devtools (or Firefox... or Edge... or Safari... or Lynx?) you would see 9 different requests being made:
- "m"
- "mi"
- "mic"
- "micr"
- "micro"
- "micros"
- "microso"
- "microsof"
- "microsoft"
What a waste. We only need the last one!
What's more, Github's API has a rate limit. If we do this long enough, we'll just get banned.
Luckily listeners come with one very cool feature: breakpoints.
You might have noticed the second argument in the listener function is called breakpoint
:
The breakpoint
function takes one argument: a number of milliseconds to wait.
But what is that exactly?
In essence, a breakpoint
inside the setUsername
listener tells your browser the following:
"in case another setUsername
listener was started while I was waiting, stop now".
In practical terms, you can use it to debounce calls.
Calling await breakpoint(300)
as the first thing in the setUsername
listener pauses the request to
Github by 300 milliseconds. If another setUsername
call was made in that time, the first one
terminates and only the second one starts, waiting another 300ms in case it too would be terminated:
That is so simple and so effective. With this code, when you type microsoft
and you're fast enough,
only the last t
will trigger the API call.
There is, however, one more and slightly subtler issue still in the code.
Suppose you type micro
, pause for a second and then follow it up with soft
.
We will make two API calls. One for micro
and one for microsoft
. In an ideal world with
unlimited fiber optic connections, both calls will complete in 11ms and feel instantaneous.
What if, however, on your spotty 3G the call for micro
takes three seconds to complete,
but the call to microsoft
comes back immediately.
Remember, we paused for just a second.
Well, in this case the username
textfield will show microsoft
, but the list of repositories
will show the ones for the user micro
.
How do we prevent this from happening?
With another breakpoint of course! This time we don't need an async
in front of it:
This will make the app much nicer to use:
Search for a github user
#
9. Finishing touchesThere are two final things to make this example complete.
First, it would be nice to sort the list of repositories by the number of stars.
For this we can either 1) sort the list in the React component before rendering, 2) sort the list
in the listener before handing it over to setRepositories
... or 3) use a
selector to sort it dynamically and automatically.
Obviously we'll do the latter. ๐คช
Selectors take any number of reducers and other selectors as input and return a combined or modified output.
The cool thing about selectors is that they are only recalculated when their input changes. This way every new list of repositories is sorted only once.
Here's how you would create a selector sortedRepositories
that takes repositories
as an input
and returns a sorted array:
Please note that the Array.sort
function mutates the array it sorts. Since we should never modify the input in a selector,
we use [...repositories]
to create a copy of the array before sorting it.
Now it's just a matter of replacing repositories
in the component with sortedRepositories
.
The second thing that would make this example complete has to do with network errors.
Basically, what if the following code:
... throws an Error
?
In this case the listener will be abruptly terminated, setRepositories
and setFetchError
will
never be called and the page will be isLoading
forever.
To prevent this, we must wrap our fetch call in a try / catch
block:
note
We could have wrapped the entire listener in a try / catch
block, but that would have
added an extra complication: under the hood breakpoints also just throw an error and we should
then use the isBreakpoint
function to figure out wha type of error was just caught.
I opted to avoid it in the example above. See the listeners
docs for more details.
#
10. Final resultAdding the finishing touches gives us this final masterpiece:
Search for a github user
With this code:
There's still one thing that's broken:
If a github user or organisation has more than 100 repositories, only the first 100 results will be
returned. Github's API provides a way to ask for the next 100 results
(the Link
headers, but resolving this is
outside the scope of this guide.
This will be the left as an exercise for the ambitious reader. That's you, right? ๐
Next Steps
Read the Core Concepts guide to get an in-depth understanding of how Kea works and why it works the way it does.