Last updated: Sun Nov 14 08:13:20 PST 2021

CS 124-Lab 2

In this lab, you'll take the interface you designed for Lab 1, and actually implement it using React. As you are doing the implementation, you may find you want to change parts of the design. That's OK, but the changes and rationale should be documented in design.md.

Your work should be done in a branch named lab2.

cd ~/cs124/lab
git checkout lab1 	
git checkout -b lab2 	# creates a new branch named lab2 based on lab1 and checks it out
mkdir docs # make a documentation directory
mv design.md imagefile1..imagefilen docs # move all documentation to docs directory
rm *.html # rm all .html files that are no longer needed in this branch
git commit -a # commit all the changes that have just been made
git push -u origin lab2 # pushes the new branch to the origin

cd ~/cs124/lab
git pull
git checkout lab2 # switches to the new lab2 branch.
Note that like in any source code control system, in Git, you don't want to checkin files that can be reproduced from other files that are checked in.

When using React, all the installed Node.js packages are installed in a node_modules directory. However, the contents of that directory can be recreated by doing an npm install, so shouldn't be checked in.

Similarly, the build directory contains the built output of your React app, npm run build will recreate that directory.

So that git doesn't bug you about adding those directories, create a .gitignore at the top-level of your git repository. Initialize it with:

echo node_modules >> .gitignore
echo build >> .gitigore
git commit .gitignore -m "Add .gitignore to ignore build and node_modules"

Data

For the moment, you won't do long-term storage of any data. Instead, create a global variable (named initialData perhaps) that contains initial values of data. Your components, of course, should be unaware that the data is stored in a global. They should, as always, be displaying data that comes in via props, and updating that data by making calls to update methods passed in via props.

When the user loads the page, they'll see items based on the initial value of the global. They can then make changes which'll be stored in memory and will be displayed in the application.

However, those changes will only be transitory. If the app is reloaded, all those changes will be lost.

Suggested approach

Here are the steps I'd take (based, in a large part, on the Thinking in React page in the React docs).
  1. Create a global variable initialData in index.js that contains your initial data. You get to decide the format of the data, but it's likely it'll be an array of objects.
  2. Break the UI into a component hierarchy. That is, decide what parts of the UI should be displayed by what components. In addition, decide the hierarchical relationship between these components.

    Although in theory you could implement your entire application in single App component, it'd be a mix of different concerns and hard to reason about. Separating into different components allow each componenet to have its own responsibilities and hide some complexity from other components.

  3. Implement a static version of your app that renders using only props. You will implement the components you identified in the previous step. Each component will render based on the props it receives.

    Put each component in its own .js file (and associated .css file). That way, each component is independent from other components.

    For example, the code for component Foo should be in a file Foo.js, and the associated CSS should be in a file Foo.css.

    At the end of Foo.js, you should add export default Foo;. A component Bar can then have access to component Foo by adding import Foo from './Foo'; at the beginning of Bar.js.

    In a future lab, you'll be storing user data permanently in the cloud. For this lab, however, you'll be storing data in memory. I recommend having a component whose sole purpose is managing this data. Then, in future labs, you can swap out that component (that stores in memory) with another component that stores in the cloud.

    Thus, I'd create an InMemoryApp component that will sit between index.js and the App component. index.js will initialize the InMemoryApp component with something like:

    <InMemoryApp initialData={initialData}/>
    

    For the time being, InMemoryApp can be quite simple:

    function InMemoryApp(props) {
        return <App data={props.initialData}/>
    }
    

    Your goal is to have the components render the same HTML that you used for your UI design in Lab 1. You can take the CSS from Lab 1 and extract it into the .css files for your various components.

  4. At this point, you can see how your app behaves with various forms of data (that is, change the contents of the global data array and see how your app behaves).
  5. Identify the needed state information.

    You need to identify what state information you'll need to maintain. For example, depending on your UI design, you may need to maintain the state of:

    Good rules of thumb from the React docs: So, examples of things that probably shouldn't be state:
  6. Identify where your state should live and create it.
    1. Now you need to decide which component owns the piece of state you identifed in the last step.

      From the React docs:

      For each piece of state in your application:

      • Identify every component that renders something based on that state.
      • Find a common owner component (a single component above all the components that need the state in the hierarchy).
      • Either the common owner or another component higher up in the hierarchy should own the state.
      • If you can’t find a component where it makes sense to own the state, create a new component solely for holding the state and add it somewhere in the hierarchy above the common owner component.
    2. Once you've identifed what state is owned by which component:
      1. Add the state to that component (with useState and an appropriate initial value).
      2. Pass that state on to child components that need it. As with all other information passed to a child component, you'll pass it as a JSX attribute that'll be packaged up into a props object.
      3. Utilize this new information in the child components.
    3. At this point, you can change the initial value of the state (the parameter to useState) in order to see how your app renders with various state configurations.

      For example, you could change the state of whether completed items are shown, or whether a confirmation dialog should be shown, or whether an item is being edited.

      In all these cases, the HTML that is rendered should match your existing UI you'd designed for this state configuration.

  7. Now, you've verified that your React application can render properly based on settings of props and state, it's time to actually handle updating state based on user actions.

    Some updates are fairly straightforward. For example, assume:

    To update the state, you'll add an onClick={() => setShowCompletedItems(!showCompletedItems)} attribute to the button.

    In other cases, you'll need to work with child components. Let's modify the previous example. Assume

    To update the state, you'd add:

    In order to handle changes to the underlying data, your InMemoryApp will want to have state containing the data:

    const [data, setData] = useState(props.initialData);
    

    I suggest you add several callback attributes to the App which will be handled by the InMemoryApp:

Hand-In Procedure

You will turn in your labs using git and GitHub.

$ git commit -am "my last commit"
[lab1 c2e3c8b] my last commit
 2 files changed, 18 insertions(+), 2 deletions(-)

$ git push

Deploy your app to GitHub Pages so that https://{GITHUBUSERNAME}.github.io/cs124/ shows your running application.

You will then need to go to GitHub.com and create a pull request whose base is lab1 and whose compare is lab2. That is, you'll be submitting the changes between lab1 and lab2. This pull request should show, for example, changes between the lab1 and lab2 design.md rather than showing the entirety of the design.md as new.

Deliverables

You'll submit a GitHub branch named lab2 containing, at the top level,: Before creating your pull request, go to GitHub and verify that: