OSU eCampus CS467 Capstone review and recap

This post is part of an ongoing series recapping my experience in Oregon State University’s eCampus (online) post-baccalaureate Computer Science degree program. You can learn more about the program here.

Six-word summary: One big project, make it count!

You group up with two other students and spend the entire quarter making a project together.

CS467 Review

CS476 is one quarter-long project with a 3-person team of your choosing. If you can’t find groupmates, you can volunteer to be assigned a team. I looked for my groupmates a quarter ahead of time and I think it’s how I ended up with such a high performing group. My team was great.

You can pick one of the instructor-provided projects (which is what my team did) or you can come up with something of your own design, but you’ll have to “sell it” to the instructor or a TA to get approval to make it.

There were a good 20 or so instructor-provided projects to pick from and about half of them seemed to involve developing some kind of mobile app, so if you aren’t keen on developing an app (which some people in the class weren’t) this might rub you the wrong way. I wanted to build a React website and, luckily, there were a couple suitable prompts.

There is very little (if any) instructor/TA feedback on what you make. You won’t get any feedback on your code or what you make, or at least no one on my team did.

Every week you record a 3-4 minute video demonstrating the progress you personally made, and at the end of the quarter someone on your team will put together a poster that summarizes the project.

The class doesn’t really “teach” anything – there’s no lectures or worksheets or tests, it’s just there to make sure you make progress each week. We had to make a presentation poster at the end, but I have no idea who actually saw it (our project showcase was held virtually in the midst of COVID-19 and I couldn’t attend due to my kids’ daycare closing).

If I have any complaint it’s that I had to spend $2000 to just… do a project. I can do that by myself for free. (And I have done that: see OSU Course Explorer, my collection of WordPress plugins, Scene Together, Amazin’ Link Checker). But my group was solid and we made something cool, so at least it was a positive experience overall.

Time commitment

The class’s instructions tell everyone to spend 10 hours a week on it but they also lay out a list of requirements that, in my opinion, could not be achieved if everyone on the team just shut their laptop at the 10-hour mark. I put in around 20-25 hours most weeks.

Tech stack

Since everyone in the team either already worked in web development (or aspired to), choosing React for the project felt relevant and meaningful.

We used:

  • React 16.8
  • TypeScript
  • Google Firebase and Firestore
  • Web speech API and Azure Speech Services
  • Node.js
  • Heroku

My contributions

Just to give you a feel for what an individual might contribute to a quarter-long project, here’s a list of my major contributions:

  • Project setup, structure, naming conventions
  • Early working examples of routes, components, and features to serve as a template/guide for the rest of the project
  • User account creation and management (using Firebase Authentication API)
  • User schema in Firebase to hold additional user account data
  • All of the user-facing forms
  • Account linking system, whereby one user can “invite” another to follow and that other account either accepts or declines the invitation
  • Settings page where account links are viewed, deleted, and account profile details are changed
  • Researching, prototyping, and implementing the “voice to text” feature which required access to the on-device microphone and camera
  • Prototype work for the “photo reply” feature
  • “Quality of life” improvements, such as being able to refresh app and stay on the page you were on, the header collapsing into a drawer for mobile users, form validation, supported browser notification text
  • Responsive app UI works on desktop, iPad, and mobile (in both vertical and horizontal layout)
  • CSS-based solution to create “envelope” graphic that has open/closed states
  • Created art assets and icons for the project
  • App “look and feel”
  • “Sent” animation
  • Heroku pipeline owner
  • Contributed to fixing bugs found by teammates

My teammates had similarly long lists of accomplishments. We arranged the workload such that each of us owned a full “slice” of the app, so they took on the creation, sending, and display of messages between users. Everyone owned the “full stack” of their features, so (hopefully) everyone finished the project feeling like they owned a section of it top to bottom.

What we made

We called our app “Hola, Oma!” and it was based on a prompt provided by the instructor. We built a messaging app wherein the interface is simplified for the “grandparent” user but more complex for the “post creator” user. The user can send text, photos, or videos, and the recipient can respond with text or a selfie. We implemented “voice to text” to make replying with a message simpler for a less tech-savvy user.

Here’s our poster:

Main screen for the “grandparent” user:

User log-in flow and “link up” screen on mobile (for “post creator” type user):

Post-creator type user’s home screen (mobile and desktop):

Post creation (mobile and desktop):

By the end of the quarter the app felt pretty robust! We completed all of the goals we set out to achieve, and even had time for some nice extras. I think it helped a lot that everyone on my team had either an internship or an industry job already, so it was truly a team of professionals.

You can view our project’s GitHub repo here.

In conclusion

Capstone was the final course in my OSU CS studies and it was great to end on a high note. You can read my reviews of other OSU CS courses here.

React – Fixing a flickering video that gets redrawn every time something on the page changes

In this post: Fixing a video preview that gets restarted and redrawn every time React redraws the page.

Key words: React, HTML video flicker, DOM redraw React, useMemo

First, an example of the problem:

(This is painful to look at. That’s why we’re going to fix it!)

This is a form that allows the user to upload a video and add a message. But every keystroke was causing the video preview to redraw!

Yikes indeed.

Originally, the <video> tag was part of the React component’s return, like this. On every state change (in this case, the contents of the text area changing), the whole DOM was getting redrawn, video and all.

return (
  ...
  // lots of stuff omitted for brevity 
  ...
  {fileType === 'video' &&
    <Row justify="center">
      <video src={videoURL}
        className="photo"
        preload="auto"
        controls
        style={{height: '95%', width: '95%'}}
      />
    </Row>
  }
  ...
)

I started Googling things like “React video flickers when updating a form” and “React redraws video preview too often” and eventually landed on this helpful tutorial, Getting the heck out of React, which helped me understand the problem better, but the author’s examples were written for an older (2018) version of React and I got lost trying to adapt them to my project. (My React journey only began a few months ago and it seems a lot has changed since the “older” tutorials were written).

I knew there had to be something, but I couldn’t find it by describing my problem to Google, so I explained it to a friend who has more React experience and he pointed me at the useMemo hook, which the docs describe as follows:

Pass a “create” function and an array of dependencies. useMemo will only recompute the memoized value when one of the dependencies has changed. This optimization helps to avoid expensive calculations on every render.

reactjs.org

Okay, that sounded promising. But I still didn’t know how to use it, and the method signature example just exists in a vacuum.

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

The method signature sets it to the value of a const, but I didn’t know where to put this const or what to have it return, so I went looking for more examples of useMemo.

This tutorial (written in April 2020) was fresh and full of examples. My key takeaways about useMemo were:

  • useMemo doesn’t have to return a function that takes parameters (maybe I’m just a newbie, but seeing the (a, b) in the signature made me think it was going to act like a .sort() callback)
  • useMemo can return JSX instead of a function (JSX being HTML created by JavaScript or TypeScript), which means it can also return React components
  • useMemo has a dependency array like useEffect, and it will run the function it’s attached to if something in that dependency array changes
  • If the dependency array is present but empty, it will compute once on page load and then never again (which isn’t what I want, since the video preview is meant to be set/updated by the user)
  • If there is no dependency argument at all (no array), it will compute every render (which would defeat the purpose of useMemo, so I knew my use case would have to including something in the dependency array)

For my use case, I created a new const, renderVideo, that uses useMemo and placed it above the return (...) but still within the component that makes up the page. The function called by useMemo returns the markup (containing the <video /> tag) that should only redraw when selectedFile changes. Then, where the <video /> markup used to be, I replaced it with a call to renderVideo instead.

The code explains it best:

    const renderVideo = useMemo(() => (
        <Row justify="center">
            <video src={getImageAsUrl()}
                className="photo"
                preload="auto"
                controls
                style={{height: '95%', width: '95%'}}
            />
        </Row>
    ), [selectedFile])

    return (
      ...
      {fileType === 'video' &&
        renderVideo
      }
      ...
    )

(Note: no parenthesis or curly brackets are needed around renderVideo)

It was that simple!

I think I ran in circles for a little while on this one because:

  1. I didn’t know about useMemo, and once I did,
  2. I didn’t realize useMemo could be used for just returning some boring old JSX (HTML markup, basically) that simply has to remain static until a state variable changes – no comparisons or logic calculations needed

Anyway, I hope this helps someone else fix a flickering video or avoid an expensive recalculation on every React redraw!

Additional notes

Notes and observations that didn’t fit elsewhere…

Note #1: the reference to the const can’t be wrapped in any other HTML tags

I had some initial trouble with this technique because I had additional HTML tags around renderVideo. The call to the useMemo const has to be the only thing after the &&.

For example, it does NOT work if you do this:

  // bad idea, does not work!

  {fileType === 'video' &&
    <div className="videoStyling">
      renderVideo
    </div>
  }

Note #2: Using this technique with a component. I later refactored the video preview stuff into its own component, which looks like this:

VideoPreview.tsx

import React from 'react';
import Row from 'shared/components/Row/Row';

interface IVideoPreview {
  videoSrc: string
}

const VideoPreview: React.FC<IVideoPreview> = ({videoSrc}) => {
  return (
    <Row justify="center">
        <video src={videoSrc}
            className="photo"
            preload="auto"
            controls
            style={{height: '95%', width: '95%'}}
        />
    </Row>
  )
}

export default VideoPreview;

I updated const renderVideo to use my new <VideoPreview /> component instead:

    const renderVideo = useMemo(() => (
        <VideoPreview videoSrc={getImageAsUrl()}/>
    ), [selectedFile])

React – replacing an asynchronous .forEach() with for…of (and why it’s better that way)

In this post: My investigation into why ESLint didn’t like my .forEach loop and why for...of is the better choice for an asynchronous loop.

Key words: React, TypeScript, loops, JavaScript array iterators, forEach, for let, asynchronous

So there I was, .forEach-ing through some post replies when I noticed the code was triggering an ES-Lint error, no-loop-func:

Line 73:28: Function declared in a loop contains unsafe references to variable(s) 'repliesTotal' no-loop-func

My code seemed to work fine as it was, though.

const processUnreadReplies = async (userPosts: Post[]) => {
  let repliesTotal = 0;
  for await (let post of userPosts) {
    const replyArray: Array<Reply> = await getRepliesToPost(post.pid);
      replyArray.forEach((reply: any) => {
        if (!reply.read) {
          post.unreadReplyCount = (post.unreadReplyCount ?? 0) + 1;
          repliesTotal++;
        }
      });
  };
  setUnreadRepliesTotal(repliesTotal);
}

In English, this is what this code is doing:

  1. It gets all of the user’s posts as userPosts (and they are of type Post)
  2. It loops through each post in userPosts, getting that post’s replies as replyArray
  3. For each reply in replyArray, it checks each one to see if it is read or not by waiting on an asychronous function call
  4. If the reply is not read (reply.read is false), then it increases the post’s unreadReplyCount
  5. When it’s done with the loop, it sets a state variable called unreadRepliesTotal to the total it tallied up during the loop

ES-Lint’s documentation for no-loop-func was informative, but the examples didn’t make it clear enough to me what I had done wrong. Their “don’t do this” examples were "let i = 0, i < n; i++" and do ... while style loops, and I had a .forEach. They also didn’t have anything on the topic of asynchronous loops.

(Could my code be so awful that they didn’t even consider including it as a “do not do this” example? :D )

I decided to investigate.

First, I had to rethink my assumption that .forEach was the preferred ES6 style – maybe it wasn’t always the case, and maybe my case was one of them.

Two helpful (ie: plain English) posts I read while researching this problem:

For...in iterates through the enumerable properties of an object or array, which means it steps through all the X in object[X] or array[X]. You can still use it to access the elements of an array like so:

for (const idx in cars) {
  console.log(cars[idx]);
}

But that a more roundabout way of accessing the array data, and I soon found a more direct approach:

For...of was the next thing I tried and it worked just as well as my .forEach, but the linter liked it better.

const processUnreadReplies = async (userPosts: Post[]) => {
  let repliesTotal = 0;
  for await (let post of userPosts) {
    const replyArray: Array<Reply> = await getRepliesToPost(post.pid);
      for (let reply of replyArray) {
        if (!reply.read) {
          post.unreadReplyCount = (post.unreadReplyCount ?? 0) + 1;
          repliesTotal++;
        }
      };
  };
  setUnreadRepliesTotal(repliesTotal);
}

But why? I went back to the MDN docs and discovered an important tidbit I overlooked earlier:

forEach expects a synchronous function

forEach does not wait for promises. Kindly make sure you are aware of the implications while using promises (or async functions) as forEach callback. 

from the MDN docs on forEach

As far as ESLint could tell from looking at the code, every loop run by .forEach was returning void. Furthermore, using await does not pause the .forEach (I didn’t expect it to pause it, and I don’t need the previous iteration’s result for the next one, but I wanted to make note of that important distinction anyway).

In any case, this seemed like one of those times where the thing appeared to be working to the user, but could be done better and for...of was the preferred approach here. (There are plenty of opinions and debates about this, though.)

In summary

  • Use for...of for asynchronous loops in JavaScript/TypeScript
  • let creates a block-scoped variable, so in the case of my code the let is creating a new “reply” instance for each iteration of the for...of and each “reply” will be an individual reply from the array
  • .forEach() does not wait for asynchronous code to complete, which may not be a problem in your specific use case but ESLint can’t tell that so it flags it as a potential problem
  • await suspends the current function until it is resolved
  • for...of creates an individual function call for each loop, whereas .forEach() counts as one function call for all iterations

So there we have it – a better, ESLint-approved, way of writing an asynchronous loop in TypeScript/JavaScript.

Related