Programming Assignment 4

Initial Due Date: 2024-10-10 11:59PM Final Due Date: 2024-10-25 4:00PM

Github Classroom Gradescope
In this assignment, you will continue your implementation of Simplepedia by incorporating a REST server.
Learning Goals:
  • Learn how to incorporate REST server interaction into your app
  • Practice "agile" development when requirements change over time
Submission: Successfully submitting your assignment is an ordered two-step process:
  1. Commit and push your changes to GitHub
  2. Submit your repository to the Gradescope assignment

Prerequisites

This assignment builds on the work of assignment 3. As such, you should not start it until you have passed all of the tests for assignment 3.

  1. Click the GitHub classroom link and then clone the repository GitHub classroom creates to your local computer (in your shell, execute đź’» git clone followed by the address of the repository).
  2. Update the package.json file with your name and e-mail
  3. Install the package dependencies with đź’» npm install
  4. Practice good software development habits and create a new feature branch for your work

Once you have the dependencies installed you can start the development server with đź’» npm run dev.

Background

This assignment builds on the the REST practical in which you incorporated an API into the color picker. You will be transitioning Simplepedia over to server-maintained data management.

The server

You will be communicating with a server integrated into your application with NextJS API routes. The server is already implemented for you. Check out the files in the src/pages/api directory. (Note that this may not be the way you would implement this type of site with Next.JS. In subsequent classes we will talk about other methods for implementing server functionality within Next.JS.)

The server provides the following API (:id indicates a parameter that should be replaced with a valid article id, while :section indicates a parameter that should be replaced with a valid section):

Endpoint Method Action
/api/sections GET Fetch a JSON array of all sections in alphabetical order
/api/articles GET Fetch the entire article collection as an array
/api/articles?section=:section GET Fetch all articles in the corresponding section
/api/articles?section=:section&titlesOnly GET Fetch all articles (but id and title fields only) in the corresponding section
/api/articles POST Add a new article to the collection (the new article should be provided as the JSON-encoded request body)
/api/articles/:id GET Get article with id of :id
/api/articles/:id PUT Update the article with id of :id (entire updated article, including id should be provided as the JSON-encoded request body)

In the case of both PUT and POST, the server will send back the new or updated article.

The server implements some server-side validations and will respond with an error (HTTP status code 4** or 5**) if your request or data is ill-formed or otherwise invalid. An example of the former is a missing or mismatched id field in PUT request body (it should match the URL). An example of the latter is creating an article with a duplicate title. I suggest keeping the browser developer tools open while working on the assignment to monitor your requests and the corresponding responses. Your application should handle errors “gracefully”, i.e. “catch” any errors that arise in your promise chain and not make any state updates if a fetch returns an error code (which manifests as a rejected Promise).

The server is using a local database, implemented with SQLite, for storing the articles during both development and testing. As a result the articles should persist during development.

End-to-end testing

To enable end-to-end testing, we are running the embedded API server during testing. You will notice your package.json file has a slightly different test command (which builds the production version of your application then starts the server and runs the tests). This testing approach may not be appropriate for other applications, but here it enables us to test our front-end application without needing to mock fetch (like we did in the practical) or create a fake server.

This approach does introduce some limitations:

  • You cannot run the development server and the tests at the same time as they use the same port

Assignment

Part 0: Port over Assignment 3 and prepare the database

Update the Assignment 4 skeleton with the code you wrote for assignment 3. Be a little cautious as you do this, there are some changes and you don’t want to just replace the files with your old ones. Some specific notes:

  1. collection has been removed from the main component (_app.js) and its children in anticipation of fetching the data from the server. Note that due to this changes not all of our previous code will work until more of the refactoring is complete
  2. The skeleton includes a new next.config.js file that disables linting during production builds.

To prepare the database for use, execute the following. You can recreate the database at any time by re-running these commands.

đź’» npx knex migrate:latest
đź’» npx knex seed:run

Part 1: Refactoring IndexBar

I suggest tackling this one piece at a time. So, start with displaying the correct sections with database fetched data. Then turn to the titles. Note that the tests assume that thefetches will occur in IndexBar, not in its child components.

Sections

Instead of obtaining the sections from the articles in the collection, we will obtain them from the server (that way we don’t need to have all the articles available locally). Since the sections are no longer derived from the collection we will need to create a piece of state, e.g, sections, to store that information. Start by creating the sections state (initialized with an empty array) and then implementing a useEffect hook that will populate that state with data fetched from the server. Since we only want this hook to execute once, when component first mounts, set the dependencies to be the empty array. Your newly created state can then be passed as the sections prop to the SectionsView component.

Titles

We similarly refactor the input to the TitlesView component. Instead of filtering articles from collection, we will obtain only the ids and titles of articles that belong to the relevant section from the server. That is, for efficiency reasons we only want to fetch the information we need, the article ids and titles. Notice that the API query parameters described above enable you to efficiently fetch just the data you need. As with the sections, we will need to create a new piece of state, e.g., titles, to store these articles. Start by creating the state, and then implementing another useEffect hook that will fetch the data from the server. Pay close attention to the dependencies of your hook. When will you want to perform a new fetch operation?

With this refactoring complete, you should be able to display sections and their corresponding titles using only data obtained from the server. Note that because TitlesView will be receiving “incomplete” articles, you will likely need to refactor is PropTypes to allow for that. Instead of an array of full ArticleShapes, it will now receive an array of the following (i.e., objects with just id and title properties).

PropTypes.shape({
   id: PropTypes.number.isRequired,
   title: PropTypes.string.isRequired,
})

Part 2: Refactoring MainApp

When we had all the data available, we could make currentArticle a variable, as opposed to a piece of state. Now we will need state to maintain that information over time. Replace your currentArticle variable with currentArticle state (you will likely need to rename your existing setCurrentArticle callback function to something else to avoid naming conflicts with the state setter). With that state in place, create a useEffect hook that will set the currentArticle state based on the current value for id (as extracted from the query object in the router). When id is not defined, we want to clear the current article. When id is defined we want to fetch the corresponding article (and that article alone) from the server.

To try to reduce the number of article requests, we will only fetch an article when it is not already available locally. That is we want to fetch the article when id is defined, and either currentArticle is not defined or id from the router (from the URL) doesn’t match currentArticle.id (i.e., we have picked a different article). This implies that currentArticle will need to be dependency of your useEffect hook. To ensure that we always get the most up-to-date article, whenever we are switching articles we want to clear the currentArticle so that we fetch the latest version of the article from the server (e.g., if we just created or edited the article). Note it would be even more efficient to use a complete article if available locally, but for simplicity we will not try to implement that optimization as part of this assignment (i.e., we will just re-fetch any newly created or modified articles).

Recall that depending on the route router.query.id might be a string containing a single integer, an array of one element or undefined. To most effectively utilize useEffect’s dependency tracking, we want to normalize the the value of id outside of the useEffect hook, i.e., in the component function body, so that when id switches from "42" to ["42"] (or vice versa), we don’t trigger updates. That is we will use the now normalized integer as the hook dependency. Observe that the unary + operator will convert strings and arrays of strings to integers, e.g.,

> +"42"
42
> +["42"]
42

Part 3: Refactoring SimplepediaCreator and SimplepediaEditor

These two components differ in their implementation of their callback methods. Update the callbacks to create or update an article by making the appropriate requests to the server (POST to “/articles” to create a new article, PUT to “/articles/:id” to update an article).

fetch takes a second argument that specializes the behavior of fetch. With that argument we will want to specify the method, the body, and any headers. For example the following specifies a POST request, with JSON encoded data that is expecting a JSON response. Note that we need to manually encode the JavaScript objects as JSON with JSON.stringify.

fetch(`/url`, {
   method: "POST",
   body: JSON.stringify(data),
   headers: new Headers({
      Accept: "application/json",
      "Content-Type": "application/json",
   }),
});

The server is responsible for assigning a unique id to each article (that is the only way to ensure the ids are unique). The POST request will send back the newly created article with its id.

You do not need to integrate the returned article into the titles or other state. Instead when the Simplepedia component and its children are remounted after editing, they will fetch updated sections, the titles in that section, etc. Similar to the note above, we could imagine several possible optimizations to reduce the the amount of data fetched. For simplicity, we will not try to implement those optimizations as part of this assignment.

Unlike previous assignments, the server enforces constraints on the articles, specifically, that the titles must be unique. It will rejects POST and PUT requests with a duplicate title. You are not expected to handle these errors. Those constraints may impact your development approach, e.g., you can’t try adding an article with the title “Test” twice.

Finishing up

Your submission should not have ESLint warnings or errors when run with 💻 npm run lint. Remember than you can fix many errors automatically with 💻 npm run lint -- --fix (although since ESLint can sometimes introduce errors during this process, we suggest committing your code before running “fix” so you can rollback any changes). The assignment skeleton includes the Prettier package and associated hooks to automatically reformat your code to a consistent standard when you commit. Thus do not be surprised if your code looks slightly different after a commit.

Submit your assignment by pushing all of your committed changes to the GitHub classroom via đź’» git push --all origin and then submitting your repository to Gradescope as described here. You can submit (push to GitHub and submit to Gradescope) multiple times.

Portions of your assignment will undergo automated grading. Make sure to follow the specifications exactly, otherwise the tests will fail (even if your code generally works as intended). Use the provided test suite (run with đź’» npm test) to get immediate feedback on whether your code follows the specification. Because of the increased complexity of a React application, Gradescope can take minutes to run all of the tests. Thus you will be more efficient testing locally and only submitting to Gradescope when you are confident your application meets the specification.

Grading

Assessment Requirements
Revision needed Some but not all tests as passing.
Meets Expectations All tests pass, including linter analysis (without excessive deactivations).
Exemplary All requirements for Meets Expectations and your implementation is clear, concise, readily understood, and maintainable.

FAQ

Do I need to implement unit testing?

For this assignment you are not expected to implement any of your own unit tests. The skeleton includes some unit tests to assist you in your development and to ensure that the grading scripts can automatically test your submission.

What if the tests and assignment specification appear to be in conflict?

Please post to Ed so that we can resolve any conflict or confusion ASAP.


© Laura Biester, Michael Linderman, and Christopher Andrews 2019-2024.