Programming Assignment 4
Initial Due Date: 2024-10-10 11:59PM Final Due Date: 2024-10-25 4:00PMGithub 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
- Commit and push your changes to GitHub
- 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.
- 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). - Update the
package.json
file with your name and e-mail - Install the package dependencies with
đź’» npm install
- 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:
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- 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 thefetch
es 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?
Conditionals in useEffect hooks
When we use a variable as a dependency of an effect hook, it will trigger that hook whenever the value changes. That is not always the same as when we want to perform the action in the effect, e.g., fetch new data from the server. That is, it is quite common to have additional conditionals inside the effect to limit when we perform the actions.
For example, if we have an effect with a boolean dependency shouldUpdate
, it will “fire” when that variable transitions from false to true, but also when it transitions true to false. But as its name suggests, we only want to do the action when shouldUpdate
is true. Thus we include a conditional within the effect.
useEffect(() => {
// Only do action when shouldUpdate is true
if (shouldUpdate) {
// Do action
}
}, [shouldUpdate]); // Fire effect when shouldUpdate changes
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 ArticleShape
s, 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.
When I edit an article, it isn’t updating
See Ed for a potential fix.