CS 347: Lab 20 – GitHub Search, Really
Welcome to lab, which is a replay of the last lab that got nixed by technical difficults with NPM. By now you have already watched the lecture videos on your own. Now we will apply the ideas from those videos in an exercise, which you will complete in small groups.
Within your breakout room, designate one of your team to be the host. Host, claim your group’s task on Crowdsource. Make sure to enter every group member’s JMU eID so that they receive credit. Your group will be assigned a task number.
Team, complete the assigned task below. Host, be careful not to dominate. All members should contribute ideas.
Task
Your task is to write a React/Redux app that shows GitHub repositories matching a query. The matches will be provided by GitHub’s REST API. The end product will look something like this:
Setup
Host, follow these steps:
The next three sections can be done in parallel. Split them up between team members.
Store
Create store.js
that looks just like the one made in the videos. It uses the reducer (defined elsewhere) and the thunk
middleware.
Provider
In index.js
, add a Provider
component and pass your store as a prop as demonstrated in the videos.
Reducer
Create reducer.js
with a similar structure to the one made in the videos. Use this initial state:
const initialState = {
matches: [
{
full_name: 'twodee/fiction',
description: 'This repository does not exist.',
html_url: 'https://github.com/twodee/fiction',
},
],
};
The reducer should handle just the default case for the moment. Whatever the action, return the unmodified state.
Repository
Define a Repository
component in Repository.js
. It receives a prop named repository
, which is an object shaped like the example from the initial state shown above, having keys full_name
, description
, and html_url
. Display the repository’s full name in an anchor tag and the repository’s description.
App
Your next task is to reach into the global store and turn its matches into viewable components. Complete the following steps in App.js
.
Actions
Now that you’ve got the initial state showing, it’s time to add some actions to pull down new state from GitHub’s REST API. Complete the following steps.
Submission
Screen sharer, when your group is done or when time is up, submit just your group’s actions.js
on Crowdsource.
Reference Implementation
App.css
.App {
margin: 20px;
}
h3 {
margin-bottom: 5px;
}
App.js
import {useState} from 'react';
import './App.css';
import {useSelector, useDispatch} from 'react-redux';
import {Repository} from './Repository';
import {startSearching} from './actions';
function App() {
const matches = useSelector(state => state.matches);
const [query, setQuery] = useState('');
const dispatch = useDispatch();
const onSearch = () => {
dispatch(startSearching(query));
};
return (
<div className="App">
<input type="text" value={query} onInput={e => setQuery(e.target.value)} />
<button onClick={onSearch}>search</button>
{matches.map(match => <Repository key={match.full_name} repository={match} />)}
</div>
);
}
export default App;
Repository.js
import React from 'react';
export function Repository(props) {
const {repository} = props;
return (
<div className="Repository">
<h3><a href={repository.html_url}>{repository.full_name}</a></h3>
{repository.description}
</div>
);
}
actions.js
export const Action = Object.freeze({
LoadMatches: 'LoadMatches',
})
export function loadMatches(matches) {
return {
type: Action.LoadMatches,
payload: matches,
};
}
export function startSearching(query) {
return dispatch => {
fetch(`https://api.github.com/search/repositories?q=${query}`)
.then(response => response.json())
.then(data => {
console.log(data);
dispatch(loadMatches(data.items));
})
};
}
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import {Provider} from 'react-redux';
import store from './store';
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>,
document.getElementById('root')
);
reducer.js
import {Action} from './actions';
const initialState = {
matches: [
{
full_name: 'twodee/fiction',
description: 'This repository does not exist.',
html_url: 'https://github.com/twodee/fiction',
},
],
};
export function reducer(state = initialState, action) {
switch (action.type) {
case Action.LoadMatches:
return {
...state,
matches: action.payload,
};
default:
return state;
}
}
export default reducer;
store.js
import {createStore, applyMiddleware} from 'redux';
import thunk from 'redux-thunk';
import reducer from './reducer';
const store = createStore(reducer, applyMiddleware(thunk));
export default store;