teaching machines

CS 347: Lab 20 – GitHub Search, Really

November 10, 2020 by . Filed under fall-2020, labs, webdev.

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;