Skip to content

Latest commit

 

History

History
1597 lines (1308 loc) · 61.6 KB

front-end-engineer.md

File metadata and controls

1597 lines (1308 loc) · 61.6 KB

Front-end Engineer

By: Alan Nguyen

Table of Contents

1. Introduction

2. Web Development Fundamentals

3. Improved Styling with CSS

a. Intro

b. CSS: The Box Model

c. CSS: Display and Positioning

d. CSS: Documentation & Debugging

e. Color Theory

f. Color for UI

  • Having a neutral background (any color with low lightness or saturation) is a good base
  • a dark background with light colored text can be a reasonable alternative
  • Brand color is a hue that should dominate your color palette.
    • accounts for roughly 60% of the color used
  • Button color can determine a lot in the user’s eyes
    • hover state (a shade or tint of the accent color )
    • disabled state
  • Using a darker gray for text on a white background, or white text on a dark gray background,
  • Whitespace, or negative space, refers to the emptiness between elements.
  • Color blindness
    • red-green (difficult to differentiate between the red and green colors)
    • blue-yellow (blues tend to appear greener)
    • monochromatic (can’t see color at all)

Accessibility

Iterations: Define - Develop - Test - Research

Cloudflare This is a helpful tool to make it easier to work with color and build palettes.

g. Text Design

#HTML Headers

  • Differentiating important pieces of text, such as titles and subtitles
  • HTML Best Practice
    • <h1> — Used for titles. You should only have one header this style per page.
    • <h2> — Used for headings. This should be to identify major sections you want the user to immediately be drawn towards.
    • <h3> — Used for subheadings.

      This should be used for smaller focal points, such as articles, that you want the user to notice when scanning the page.

#Fonts

  • have most of your text in a serif font, and then create contrast with headings or subheadings in a sans-serif font
  • can contrast between different pieces of text in a number of ways:
    • style
    • size
    • weight
    • color

#Text Readability

  • Make sure that your text is big enough (over 16px).
  • Have a strong contrast between the foreground and the background.
  • Make sure there is enough space between lines and letters (remember, whitespace is king!)

How can we adjust whitespace within the text?

  • Line spacing (leading) - distance between two lines of text
  • Tracking - the space between the letters and the words
  • Kerning - the space between two letters

#Text Navigation

  • Stick to conventions about showing what is clickable
  • Never use blue as a general accent color for text
  • Having text on navigation buttons is important

If you organize the text into columns, the user’s eyes will likely not travel across the entire screen, they will travel to the end of a column and then down.

#Text length, columns, and line length

  • Text length - The average internet paragraph is only 1-3 sentences.
  • Line length - has a larger impact than you would expect on the reader

    Each time a reader gets to a new line the mind is slightly energized and encouraged (“Typographie”, E. Ruder).

    • users prefer shorter line lengths
    • columns contain roughly 50-75 characters per line

#What Content will the Users Notice and Remember?

  • Primacy and recency effects
    • People will notice and remember the first and last elements of a list or a page
  • Image pairing
    • Users eyes are easily drawn to images quicker than they are to text.
    • This pairing can be accomplished by grouping with card designs (putting them in a div together with a shared background color).

#F-Shaped skimming

  • You need to write and format your text with this skimmer in mind.
  • Users will scan content on the left of the screen before the right of the screen, and the top of the screen before the bottom of the screen. Our eyes jump back to the left of the screen whenever we finish reading a line.

Google Font Pairings

h. Links & Buttons

  • Showing interactivity and clickability through signifiers

  • Browser link styles

    • clicked (but not yet followed) links appear with red text,
    • previously visited links are styled with purple text.

#Tooltips and titles

<p>
  <a href="https://www.codecademy.com" title="Codecademy is an online learning platform">Codecademy</a> is the best place to learn to code!
</p>

#Link States Links have four main states:

  • normal (not clicked) - :link
  • hover - :hover
  • active (clicked) - :active
  • visited - :visited.

#Skeuomorphism and Flat Design

  • If users can draw a metaphor between a familiar real-life object and an interface element, they are more likely to know how to use it without training.
  • Flat design uses simplicity and lack of clutter for its UI elements.

#Buttons: Hover states

  • Users expect buttons to be clickable. Since buttons can consist of any number of total elements (rectangular/circular body, text, image(s)), all elements should be clickable

#Affordances

  • Objects afford the ability of users to interact with them in various ways.
  • Potentials for interaction are collectively called the affordances of an object.

#Signifiers

  • Signifiers are aspects of an object that a designer uses to indicate potential and intended affordances of an object.
  • The cup's handle is an example of a common user experience pattern.

#UX Patterns

  • User experience (UX) patterns establish reusable solutions to common problems.

#Affordances and Signifiers in Web Design

One common example of visual feedback is the cursor image itself

  • a pointing hand indicating that an interaction will occur
  • an i-beam shape indicating that text can be selected
  • a four-directional arrow showing that an element can be moved
  • and many more cursor styles and interactions.

A ubiquitous example is the styling of hyperlinks

Further Reading

Quiz

What is the proper cascade order for pseudo-classes so that they show all link states accurately?

  • :link, :visited, :hover, :active

i. Secondary Navigation

#Intro

  • The primary navigation system typically contains the most important links and buttons that need to be displayed on every single page of the site.
  • Secondary(breadcrumbs) navigation consists of a clickable list of pages or attributes that led to the current page.

Benefit of using breadcrumbs

  • gives an idea of where they're on the website
  • hints at the extent of the site.
  • provides a way for a user to quickly jump backward in their navigation of the site

#Breadcumb styles

<ul class="breadcrumb">
  <li><a href="">Asia</a></li>
  <li><a href="">Singapore</a></li>
  <li><a href="">Tourism</a></li>
  <li><a href="">Hotels</a></li>
</ul>
.breadcrumb {
  text-align: left;
}
.breadcrumb li {
  float: left;
}

.breadcrumb a {
  color: #fff;
  background: darkcyan;
  text-decoration: none;
  position: relative;
  height: 30px;
  line-height: 30px;
  text-align: center;
  margin-right: 15px;
  padding: 0 5px;
}

.breadcrumb a::before,
.breadcrumb a::after {
  content: "";
  position: absolute;
  border-color: darkcyan;
  border-style: solid;
  border-width: 15px 5px;
}

.breadcrumb a::before {
  left: -10px;
  border-left-color: transparent;
}
.breadcrumb a::after {
  left: 100%;
}

.breadcrumb a::after {
  left: 100%;
  border-color: transparent;
  border-left-color: darkcyan;
}

.breadcrumb a:hover {
  background-color: blue;
}
.breadcrumb a:hover::before {
  border-color: blue;
  border-left-color: transparent;
}
.breadcrumb a:hover::after {
  border-left-color: blue;
}

#Breadcrumb Types

There are three major types of breadcrumbs:

  • Location
  • Attribute
  • Path

In general, the rule of not adding anything extraneous to the design

j. Build a Website Design System

4. Making a Website Responsive

a. Responsive design

b. Layout with Flexbox

c. Grids & Spacing

d. Grid

e. Wireframing

f. Company Home Page

g. Responsive Club Website

5. JavaScript Syntax - Part 1

a. JS in Web Development

b. Running JS

c. Syntax: Intro

d. Syntax: Conditionals

e. Syntax: Functions

f. Syntax: Scope

g. Number Guesser

6. JavaScript Syntax - Part 2

a. Syntax: Arrays

b. Syntax: Loops

c. Syntax: Objects

d. Syntax: Iterators

e. Syntax: Errors and Debugging

f. Syntax: Credit Card Checker

g. Mysterious Organism

7. Building Interactive Websites

a. JS interactive website

b. JS and the DOM

c. DOM Events with JS

d. HTML Forms

8. Making a Website Accessible

9. CSS Transitions and Animation

a. Transitions and Tranforms

#CSS Transitions

CSS transitions allow us to control the timing of visual state changes. We can control the following four aspects of an element’s transition:

  • Which CSS properties transition
  • How long a transition lasts
  • How much time there is before a transition begins
  • How a transition accelerates

#Duration

To create a simple transition in CSS, we must specify two of the four aspects:

The property that we want to transition.
The duration of the transition.
a {
  transition-property: color;
  transition-duration: 1s;
}

Resource - all animated properties

#Delay

Much like duration, transition-delay's value is an amount of time. Delay specifies the time to wait before starting the transition.

transition-property: width;
transition-duration: 750ms;
transition-delay: 250ms;

#Timing Function

transition-timing-function describes the pace of the transition.

The default value is ease, which starts the transition slowly, speeds up in the middle, and slows down again at the end.

Other valid values include:

  • ease-in — starts slow, accelerates quickly, stops abruptly
  • ease-out — begins abruptly, slows down, and ends slowly
  • ease-in-out — starts slow, gets fast in the middle, and ends slowly
  • linear — constant speed throughout

#Shorthand

/* property duration timing-function delay */
transition: color 1.5s linear 0.5s;
  • Leaving out one of the properties causes the default value for that property to be applied.
  • There is one exception: You must set duration if you want to define delay. Since both are time values, the browser will always interpret the first time value it sees as duration.

#Combination

  • can describe unique transitions for multiple properties, and combine them.
.button span,
.button div,
.button i {
 transition: width 750ms ease-in 200ms, left 500ms ease-out 450ms, font-size 950ms linear;
}

#All

  • It is common to use the same duration, timing function, and delay for multiple properties.transition-property value to all will apply the same values to all properties.
  • all means every value that changes will be transitioned in the same way. You can use all with the separate transition properties, or the shorthand syntax.
transition: all 1.5s linear 0.5s;

Resources

b. Animations

#Additional resources:

10. Command Line - Git - Github

a. Web Hostings

b. Command line for building websites

c. Intro to Git

d. Intro to GitHub

e. Markdown

11. HTML-CSS-JS Portfolio Project

12. JavaScript Syntax - Part 3

a. Syntax: Classes

b. Syntax: Modules

c. Syntax: Error Handling

d. Practice Classes

e. Find Your Hat

13. TDD Fundamentals

a. Why Test?

b. Write Good Tests with Mocha

c. Learn TDD with Mocha

14. Async JavaScript and HTTP Requests

a. Basics of Asynchronous JS

b. Syntax: Promises

c. Syntax: Async-Await

d. APIs and HTTP Requests

e. JS: Requests

f. Securing your applications

#Authentication and OAuth Introduction

  • Authentication is the process used by applications to determine and confirm identities of users. It ensures that the correct content is shown to users. More importantly, it ensures that incorrect content is secured and unavailable to unauthorized users.

Password authentication

  • most common
  • application's server checks the supplied credentials
  • upon a successful login, the application will respond with an authentication token (or auth token) for the client to use for additional HTTP requests. This token is then stored on the user’s computer, preventing the need for users to continuously log in.

API keys

  • Many apps expose interfaces to their information in the form of an API (application program interface)
  • When your application makes a request, API key is sent along with it. The API can then verify that your application is allowed access and provide the correct response based on the permission level of your application.
  • The API can track what type and frequency of requests each application is making. This data can be used to throttle requests from a specific application to a pre-defined level of service. This prevents applications from spamming an endpoint or abusing user data.

OAuth

  • OAuth is an open standard and is commonly used to grant permission for applications to access user information without forcing users to give away their passwords.
  • An open standard is a publicly available definition of how some functionality should work. However, the standard does not actually build out that functionality.

Generic OAuth Flow

  • After selecting the service, the user will be redirected to the service to login. This login confirms the user’s identity and typically provides the user with a list of permissions the originating application is attempting to gain on the user’s account.
  • If the user confirms they want to allow this access, they will be redirected back to the original site, along with an access token. This access token is then saved by the originating application

OAuth 2

  • OAuth 2 allows for different authentication flows depending on the specific application requesting access and the level of access being requested.

Below, we’ll discuss a few of the common OAuth 2 flows and how they are used.

  • Client Credentials Grant
    • This type of grant is used to access application-level data (similar to the developer API key above) and the end user does not participate in this flow.
    • Instead of an API key, a client ID and a client secret (strings provided to the application when it was authorized to use the API) are exchanged for an access token (and sometimes a refresh token).
    • It is essential to ensure the client secret does not become public information, just like a password.
    • Developers should be careful not to accidentally commit this information to a public git repository.
    • To ensure integrity of the secret key, it should not be exposed on the client-side and all requests containing it should be sent server-side.
    • This access token is often short-lived, expiring frequently. Upon expiration, a new access token can be obtained by re-sending the client credentials or, preferably, a refresh token.
    • Refresh tokens are an important feature of the OAuth 2 updates, encouraging access tokens to expire often and, as a result, be continuously changed
  • Authorization Code Grant
    • This flow is one of the most common implementations of OAuth (e.g. Facebook, Google)
    • It is similar to the OAuth flow described earlier with an added step linking the requesting application to the authentication.
    • A user is redirected to the authenticating site, verifies the application requesting access and permissions, and is redirected back to the referring site with an authorization code.
    • The requesting application then takes this code and submits it to the authenticating API, along with the application’s client ID and client secret to receive an access token and a refresh token.
    • To avoid exposing the client ID and secret, this step of the flow should be done on the server side of the requesting application.
    • Since tokens are tied both to users and requesting applications, the API has a great deal of control over limiting access based on user behavior, application behavior, or both.
  • Implicit Grant
    • The Implicit Grant OAuth flow was designed for applications which may need to access an OAuth API but don’t have the necessary server-side capabilities to keep this information secure.
    • This flow prompts the user through similar authorization steps as the Authorization Code flow, but does not involve the exchange of the client secret.
    • The result of this interaction is an access token, and typically no refresh token. The access token is then used by the application to make additional requests to the service, but is not sent to the server side of the requesting application.
    • This flow allows applications to use OAuth APIs without fear of potentially exposing long-term access to a user or application’s information.

OAuth provides powerful access to a diverse set of sites and information. By using it correctly, you can reduce sign-up friction and enrich user experience in your applications.

#CORS

The web pages make frequent requests to load assets like images, fonts, and more, from many different places across the Internet. If these requests for assets go unchecked, the security of your browser may be at risk.

  • For example, your browser may be subject to hijacking, or your browser might blindly download malicious code.
  • Many modern browsers follow security policies to mitigate such risks.

What is security policy?

  • Security policies on servers mitigate the risks associated with requesting assets hosted on different server.
  • same-origin security policy
    • is very restrictive. Under this policy, a document (i.e., like a web page) hosted on server A can only interact with other documents that are also on server A.
    • the same-origin policy enforces that documents that interact with each other have the same origin.
    • An origin is made up of the following three parts:
      • protocol
      • host
      • port number
    • not having a security policy can be risky, but a security policy like same-origin is a bit too restrictive.
    • there are security policies that strike a mix of both, like cross-origin, which has evolved into the cross-origin resource sharing standard (CORS)

What is CORS?

  • A request for a resource (like an image or a font) outside of the origin is known as a cross-origin request. CORS (cross-origin resource sharing) manages cross-origin requests.
  • Allowing cross-origin requests is helpful, as many websites today load resources from different places on the Internet (stylesheets, scripts, images, and more).
  • Cross-origin requests mean that servers must implement ways to handle requests from origins outside of their own. CORS allows servers to specify who (i.e., which origins) can access the assets on the server, among many other things.

You can think of these interactions as a building with a security entrance. For example, if you need to borrow a ladder, you could ask a neighbor in the building who has one. The building’s security would likely not have a problem with this request (i.e., same-origin). If you needed a particular tool, however, and you ordered it from an outside source like an online marketplace (i.e., cross-origin), the security at the entrance may request that the delivery person provide identification when your tool arrives.

Why is CORS necessary?

  • allows servers to specify not only who can access the assets, but also how they can be accessed.
  • With CORS, a server can specify who can access its assets and which HTTP request methods are allowed from external resources.

How does CORS manage requests from external resources?

  • The CORS standard manages cross-origin requests by adding new HTTP headers to the standard list of headers.

  • The following are the new HTTP headers added by the CORS standard:

    • Access-Control-Allow-Origin
    • Access-Control-Allow-Credentials
    • Access-Control-Allow-Headers
    • Access-Control-Allow-Methods
    • Access-Control-Expose-Headers
    • Access-Control-Max-Age
    • Access-Control-Request-Headers
    • Access-Control-Request-Method
    • Origin
  • The Access-Control-Allow-Origin header allows servers to specify how their resources are shared with external domains. When a GET request is made to access a resource on Server A, Server A will respond with a value for the Access-Control-Allow-Origin header. Many times, this value will be *, meaning that Server A will share the requested resources with any domain on the Internet. Other times, the value of this header may be set to a particular domain (or list of domains), meaning that Server A will share its resources with that specific domain (or list of domains). The Access-Control-Allow-Origin header is critical to resource security.

Pre-flight Requests

  • When a request is made using any of the following HTTP request methods, a standard preflight request will be made before the original request.
    • PUT
    • DELETE
    • CONNECT
    • OPTIONS
    • TRACE
    • PATCH
  • Preflight requests use the OPTIONS header. The preflight request is sent before the original request, hence the term “preflight.” The purpose of the preflight request is to determine whether or not the original request is safe (for example, a DELETE request). The server will respond to the preflight request and indicate whether or not the original request is safe. If the server specifies that the original request is safe, it will allow the original request. Otherwise, it will block the original request.
  • The request methods above aren’t the only thing that will trigger a preflight request. If any of the headers that are automatically set by your browser (i.e., user agent) are modified, that will also trigger a preflight request.

How do I implement CORS?

  • Implementing the request headers to set up CORS correctly depends on the language and framework of the backend.

  • Node, you can use setHeader(), as shown below:

    response.setHeader('Content-Type', 'text/html');
  • Express, you can use CORS middleware

    • $ npm install cors
    var express = require('express');
    var cors = require('cors');
    var app = express();
    
    app.use(cors());
    
    app.get('/hello/:id', function (req, res, next) {
      res.json({msg: 'Hello world, we are CORS-enabled!'});
    });
    
    app.listen(80, function () {
      console.log('CORS-enabled web server is listening on port 80');
    });

Additional resources:

15. Web Apps

16. React - Part 1

a. Review ES6 Syntax

b. Review Functional JS

c. Intro to React.js

d. The Virtual DOM

e. JSX

f. React Components

17. React - Part 2

a. Components and Props

b. React State

c. React Development Tools

d. Stateless Components from Statefull Components

e. Lifecycle Methods

f. Jammming

g. Deploying React Apps with Netlify

h. Function Components and Hooks

#Components

#The State Hook

Array in States

  • JavaScript arrays are the best data model for managing and rendering JSX lists.
    • options is an array that contains the names of all of the pizza toppings available
    • selected is an array representing the selected toppings for our personal pizza
  • The options array contains static data, meaning that it does not change. We like to define static data models outside of our function components
  • The selected array contains dynamic data, meaning that it changes, usually based on a user’s actions. We initialize selected as an empty array. When a button is clicked, the toggleTopping event handler is called.
import React, { useState } from "react";
 
const options = ["Bell Pepper", "Sausage", "Pepperoni", "Pineapple"];
 
export default function PersonalPizza() {
  const [selected, setSelected] = useState([]);
 
  const toggleTopping = ({target}) => {
    const clickedTopping = target.value;
    setSelected((prev) => {
     // check if clicked topping is already selected
      if (prev.includes(clickedTopping)) {
        // filter the clicked topping out of state
        return prev.filter(t => t !== clickedTopping);
      } else {
        // add the clicked topping to our state
        return [clickedTopping, ...prev];
      }
    });
  };
 
  return (
    <div>
      {options.map(option => (
        <button value={option} onClick={toggleTopping} key={option}>
          {selected.includes(option) ? "Remove " : "Add "}
          {option}
        </button>
      ))}
      <p>Order a {selected.join(", ")} pizza</p>
    </div>
  );
}

Objects in State

  • When we work with a set of related variables, it can be very helpful to group them in an object.
export default function Login() {
  const [formState, setFormState] = useState({});
 
  const handleChange = ({ target }) => {
    const { name, value } = target;
    setFormState((prev) => ({
      ...prev,
      [name]: value // [name] is computed property 
    }));
  };
 
  return (
    <form>
      <input
        value={formState.firstName}
        onChange={handleChange}
        name="firstName"
        type="text"
      />
      <input
        value={formState.password}
        onChange={handleChange}
        type="password"
        name="password"
      />
    </form>
  );
}

A few things to notice:

  • We use a state setter callback function to update state based on the previous value
  • The spread syntax is the same for objects as for arrays: { ...oldObject, newKey: newValue }
  • We reuse our event handler across multiple inputs by using the input tag’s name attribute to identify which input the change event came from

Seperate Hooks for Separate States

  • Managing dynamic data with separate state variables has many advantages, like making our code more simple to write, read, test, and reuse across components.
function Subject() {
  const [currentGrade, setGrade] = useState('B');
  const [classmates, setClassmates] = useState(['Hasan', 'Sam', 'Emma']);
  const [classDetails, setClassDetails] = useState({topic: 'Math', teacher: 'Ms. Barry', room: 201});
  const [exams, setExams] = useState([{unit: 1, score: 91}, {unit: 2, score: 88}]);
  // ...
}

#The Effect Hook

Why use useEffect?

A few reasons why we may want to run some code after each render:

  • fetch data from a backend service
  • subscribe to a stream of data
  • manage timers and intervals
  • read from and make changes to the DOM

useEffect() is a function that we’ll use to execute some code after the first render, after each re-render, and after the last render of a function component.

Clean up Effects

useEffect(()=>{
  document.addEventListener('keydown', handleKeyPress);
  return () => {
    document.removeEventListener('keydown', handleKeyPress);
  };
})

Control when effects are called

  • If we want to only call our effect after the first render, we pass an empty array [] to useEffect() as the second argument.
useEffect(() => {
  alert("component rendered for the first time");
  return () => {
    alert("component is being removed from the DOM");
  };
}, []); 

Fetch Data

  • An empty dependency array signals to the Effect Hook that our effect never needs to be re-run, that it doesn’t depend on anything. Specifying zero dependencies means that the result of running that effect won’t change and calling our effect once is enough.
  • A dependency array that is not empty signals to the Effect Hook that it can skip calling our effect after re-renders unless the value of one of the variables in our dependency array has changed. If the value of a dependency has changed, then the Effect Hook will call our effect again!
useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); // Only re-run the effect if the value stored by count changes

Rules of Hooks

There are two main rules to keep in mind when using Hooks:

  • only call Hooks at the top level
  • only call Hooks from React functions

React keeps track of the data and functions that we are managing with Hooks based on their order in the function component’s definition. For this reason, we always call our Hooks at the top level; we never call hooks inside of loops, conditions, or nested functions.

Instead of confusing React with code like this:

if (userName !== '') {
  useEffect(() => {
    localStorage.setItem('savedUserName', userName);
  });
}

We can accomplish the same goal, while consistently calling our Hook every time:

useEffect(() => {
  if (userName !== '') {
    localStorage.setItem('savedUserName', userName);
  }
});

Separate Hooks for Separate Effects

  • It's a good idea to separate concerns by managing different data with different Hooks.

Compare the complexity here, where data is bundled up into a single object:

const [data, setData] = useState({ position: { x: 0, y: 0 } });
useEffect(() => {
  get('/menu').then((response) => {
    setData((prev) => ({ ...prev, menuItems: response.data }));
  });
  const handleMove = (event) =>
    setData((prev) => ({
      ...prev,
      position: { x: event.clientX, y: event.clientY }
    }));
  window.addEventListener('mousemove', handleMove);
  return () => window.removeEventListener('mousemove', handleMove);
}, []);

To the simplicity here, where we have separated concerns:

const [menuItems, setMenuItems] = useState(null);
useEffect(() => {
  get('/menu').then((response) => setMenuItems(response.data));
}, []);
 
const [position, setPosition] = useState({ x: 0, y: 0 });
useEffect(() => {
  const handleMove = (event) =>
    setPosition({ x: event.clientX, y: event.clientY });
  window.addEventListener('mousemove', handleMove);
  return () => window.removeEventListener('mousemove', handleMove);
}, []);

#Review

In this lesson, we learned how to write effects that manage timers, manipulate the DOM, and fetch data from a server. In earlier versions of React, we could only have executed this type of code in the lifecycle methods of class components, but we can now perform these types of actions in function components as well!

Let’s review the main concepts from this lesson:

  • useEffect() - we can import this function from the ‘react’ library and call it in our function components
  • effect - refers to a function that we pass as the first argument of the useEffect() function. By default, the Effect Hook calls this effect after each render
  • cleanup function - the function that is optionally returned by the effect. If the effect does anything that needs to be cleaned up to prevent memory leaks, then the effect returns a cleanup function, then the Effect Hook will call this cleanup function before calling the effect again as well as when the component is being unmounted
  • dependency array - this is the optional second argument that the useEffect() function can be called with in order to prevent repeatedly calling the effect when this is not needed. This array should consist of all variables that the effect depends on.

The Effect Hook is all about scheduling when our effect’s code gets executed. We can use the dependency array to configure when our effect is called in the following ways:

Dependency Array Effect called after first render & …
undefined every re-render
Empty array no re-renders
Non-empty array when any value in the dependency array changes

Hooks gives us the flexibility to organize our code in different ways, grouping related data as well as separating concerns to keep code simple, error-free, reusable, and testable!

i. Advanced React

j. React Testing with Jest

18. Redux

a. Why Redux?

b. Intro to Redux

c. Advanced Redux

d. Testing Redux

19. Advanced Concepts in TDD

a. Advanced Testing: Mocking

Running packages

  • Using Yarn: yarn create-react-app my-app
  • Using npm: npm create-react-app my-app

Adding dependencies

  • Using Yarn: yarn add enzyme enzyme-adapter-react-16 --dev
  • Using npm: npm install enzyme enzyme-adapter-react-16 --save-dev

Running package.json scripts

  • Using Yarn: yarn run test
  • Using npm: npm run test

b. Advanced Testing: Browser Automation

20. React & Redux Porfolio Project

21. Advanced Web Development

a. Styling Applications

b. SEO

c. Build Tools

d. Optimizing Application

e. Adding Realtime Connectivity

22. Linear Data Structures

a. Intro to Data Structures

#Why Data Structure?

#Data Structure API

b. Nodes

c. Singly Linked Lists

#Conceptual

Intro

  • The list is comprised of a series of nodes
  • The head node is the node at the beginning of the list.
  • Each node contains data and a link (or pointer) to the next node in the list.
  • The list is terminated when a node’s link is null. This is called the tail node.
  • Since the nodes use links to denote the next node in the sequence, the nodes are not required to be sequentially located in memory.
  • allow for quick insertion and removal of nodes
  • Can be unidirectional or bidirectional
  • Common operations on a linked list may include:
    • adding nodes
    • removing nodes
    • finding a node
    • traversing (or travelling through) the linked list

Adding a new node

  • Adding a new node to the beginning of the list requires you to link your new node to the current head node.
  • This way, you maintain your connection with the following nodes in the list.

Removing a node

  • If you accidentally remove the single link to a node, that node’s data and any following nodes could be lost to your application, leaving you with orphaned nodes.
  • To properly maintain the list when removing a node from the middle of a linked list, you need to be sure to adjust the link on the previous node so that it points to the following node.
  • Depending on the language, nodes which are not referenced are removed automatically. “Removing” a node is equivalent to removing all references to the node.

#JS Code

Constructor and Adding to head

const Node = require('./Node');

class LinkedList {
  constructor() {
    this.head = null;
  }

  addToHead(data) {
    const newHead = new Node(data);
    const currentHead = this.head;
    this.head = newHead;

    // check if there is a current head to the list, set the list’s head’s next node to currentHead
    if (currentHead) {
      this.head.setNextNode(currentHead);
    }
  }
}

module.exports = LinkedList;

Adding to tail

addToTail(data) {
  let tail = this.head;

  // check if there's no head, add the node to the head of the list
  if(!tail) {
    this.head = new Node(data);
  } else { // iterate through the list until we find the last node
    while (tail.getNextNode()) {
      tail = tail.getNextNode(data);
    }
    // add a pointer from the last node to new tail
    tail.setNextNode(new Node(data));
  }
}

Removing the Head

removeHead() {
    const removedHead = this.head;

    // check if there is a head
    if (!removedHead) {
      return 
    } 
    //  if there is a head, remove it by setting the list’s head = to the original head’s next node
    if (removedHead.getNextNode() !== null) {
      this.head = removedHead.getNextNode();
    }
    // return original head
    return removedHead.data
  }

Printing list

printList() {
    let currentNode = this.head;
    // This string will holds the data from every node in the list, start at the list's head
    let output = '<head> ';

    // iterate through the list, adding to the string as we go
    while (currentNode !== null) {
      output += currentNode.data + ' ';
      currentNode = currentNode.getNextNode();
    }
    output += '<tail>';
    console.log(output);
  }

Using the Linked list

const LinkedList = require('./LinkedList');

const seasons = new LinkedList();
seasons.printList();

seasons.addToHead('summer');
seasons.addToHead('spring');
seasons.printList();

seasons.addToTail('fall');
seasons.addToTail('winter');
seasons.printList();

seasons.removeHead();
seasons.printList();

Additional Resources

#Swapping elements in a linked list

Given an input of a linked list, data1, and data2, the general steps for doing so is as follows:

  1. Iterate through the list looking for the node that matches data1 to be swapped (node1), keeping track of the node’s previous node as you iterate (node1Prev)
  2. Repeat step 1 looking for the node that matches data2 (giving you node2 and node2Prev)
  3. If node1Prev is null, node1 was the head of the list, so set the list’s head to node2
  4. Otherwise, set node1Prev‘s next node to node2
  5. If node2Prev is null, set the list’s head to node1
  6. Otherwise, set node2Prev‘s next node to node1
  7. Set node1‘s next node to node2‘s next node
  8. Set node2‘s next node to node1‘s next node
const LinkedList = require('./LinkedList.js')

const testList = new LinkedList();
for (let i = 0; i <= 10; i++) {
  testList.addToTail(i);
}

testList.printList();
swapNodes(testList, 2, 5);
testList.printList();

function swapNodes(list, data1, data2) {
  console.log(`Swapping ${data1} and ${data2}:`);
  
  let node1Prev = null;
  let node2Prev = null;
  let node1 = list.head;
  let node2 = list.head;

  if (data1 === data2) {
    console.log('Elements are the same - no swap to be made');
    return;
  }
  
  while (node1 !== null) {
    if (node1.data === data1) { 
      break;
    }
    node1Prev = node1;
    node1 = node1.getNextNode();
  }
  
  while (node2 !== null) {
    if (node2.data === data2) {
      break;
    }
    node2Prev = node2;
    node2 = node2.getNextNode();
  }
  
  if (node1 === null || node2 === null) {
    console.log('Swap not possible - one or more element is not in the list');
    return;
  }

  if (node1Prev === null) {
    list.head = node2;
  } else {
    node1Prev.setNextNode(node2);
  }

  if (node2Prev === null) { 
    list.head = node1;
  } else {
node2Prev.setNextNode(node1);
  }
  
  let temp = node1.getNextNode();
  node1.setNextNode(node2.getNextNode());
  node2.setNextNode(temp); 
}

Time and Space Complexity

  • The worst case for time complexity in swapNodes() is if both while loops must iterate all the way through to the end (either if there are no matching nodes, or if the matching node is the tail). This means that it has a linear big O runtime of O(n), since each while loop has a O(n) runtime, and constants are dropped.
  • There are four new variables created in the function regardless of the input, which means that it has a constant space complexity of O(1).

#Two-Pointer Linked List Techniques

#Practice: Singly Linked Lists

d. Doubly Linked Lists

#Conceptual

Intro

  • Like a singly linked list, a doubly linked list is comprised of a series of nodes.

  • Each node contains data and two links (or pointers) to the next and previous nodes in the list.

  • Common operations on a doubly linked list may include:

    • adding nodes to both ends of the list
    • removing nodes from both ends of the list
    • finding, and removing, a node from anywhere in the list
    • traversing (or traveling through) the list

Adding to the Head

  • When adding to the head of the doubly linked list, we first need to check if there is a current head to the list. If there isn’t, then the list is empty, and we can simply make our new node both the head and tail of the list and set both pointers to null. If the list is not empty, then we will:

    1. Set the current head’s previous pointer to our new head
    2. Set the new head’s next pointer to the current head
    3. Set the new head’s previous pointer to null

Adding to the Tail

  • Similarly, there are two cases when adding a node to the tail of a doubly linked list. If the list is empty, then we make the new node the head and tail of the list and set the pointers to null. If the list is not empty, then we:

    1. Set the current tail’s next pointer to the new tail
    2. Set the new tail’s previous pointer to the current tail
    3. Set the new tail’s next pointer to null

Removing the Head

  • Removing the head involves updating the pointer at the beginning of the list. We will set the previous pointer of the new head (the element directly after the current head) to null, and update the head property of the list. If the head was also the tail, the tail removal process will occur as well.

Removing the Tail

  • Similarly, removing the tail involves updating the pointer at the end of the list. We will set the next pointer of the new tail (the element directly before the tail) to null, and update the tail property of the list. If the tail was also the head, the head removal process will occur as well.

Removing from the Middle of the List

  • It is also possible to remove a node from the middle of the list. Since that node is neither the head nor the tail of the list, there are two pointers that must be updated:

    • We must set the removed node’s preceding node’s next pointer to its following node
    • We must set the removed node’s following node’s previous pointer to its preceding node
  • There is no need to change the pointers of the removed node, as updating the pointers of its neighboring nodes will remove it from the list. If no nodes in the list are pointing to it, the node is orphaned.

alt Removing from the middle of a list

#JavaScript implementing

Node

class Node {
  constructor(data) {
    this.data = data;
    this.next = null;
    this.previous = null;
  }

  setNextNode(node) {
    if (node instanceof Node || node === null) {
      this.next = node;
    } else {
      throw new Error('Next node must be a member of the Node class')
    }
  }

  setPreviousNode(node) {
    if (node instanceof Node || node === null) {
      this.previous = node;
    } else {
      throw new Error('Previous node must be a member of the Node class')
    }
  }

  getNextNode() {
    return this.next;
  }

  getPreviousNode() {
    return this.previous;
  }
}

module.exports = Node;

Doubly linked lists

const Node = require('./Node');

class DoublyLinkedList {
  constructor() {
    this.head = null;
    this.tail = null;
  }
  
  // Add to head
  addToHead(data) {
    const newHead = new Node(data);
    const currentHead = this.head;

    // check if there is a head, update previous and next node of the current head
    if (currentHead) {
      currentHead.setPreviousNode(newHead);
      newHead.setNextNode(currentHead);
    }

    // set the list's head to the new head
    this.head = newHead;

    // if the list has no tail, set its tail to the new head
    if (!this.tail) {
      this.tail = newHead;
    }
  }

  // Add to tail
  addToTail(data) {
    const newTail = new Node(data);
    const currentTail = this.tail;
    if (currentTail) {
      currentTail.setNextNode(newTail);
      newTail.setPreviousNode(currentTail);
    }
    this.tail = newTail;
    if (!this.head) {
      this.head = newTail;
    }
  }

  // remove head
  removeHead() {
    const removedHead = this.head;

    // if there is no head, nothing to remove
    if (!removedHead) {
      return 
    }
    // set the list's head to the next node of the removed head node
    this.head = removedHead.getNextNode();

    // if the head has value, set the head's previous node to null
    if (this.head) {
      this.head.setPreviousNode(null);
    }
    // if the removed head was also the tail of the list
    if (removedHead === this.tail) {
      this.removeTail();
    }
    // return the old head
    return removedHead.data;
  }
  
  // remove tail
  removeTail() {
    const removedTail = this.tail;

    if (!removedTail) {
      return;
    }
    this.tail = removedTail.getPreviousNode();
    if (this.tail) {
      this.tail.setNextNode(null);
    }
    if (removedTail === this.head) {
      this.removeHead();
    }
    return removedTail.data;
  }

  // Remove node by data
  removeByData(data) {
    let nodeToRemove;
    let currentNode = this.head;
    while (currentNode) {
      // check if current node's data matches data
      if (currentNode.data === data) {
        nodeToRemove = currentNode;
        break;
      } 
      currentNode = currentNode.getNextNode();
    }
    // if there was no matching node in the list
    if (!nodeToRemove) {
      return null
    }

    // Resetting pointers around the node
    if (nodeToRemove === this.head) {
      this.removeHead();
    } else if (nodeToRemove === this.tail) {
      this.removeTail();
    } else { // not head or tail
      const nextNode = nodeToRemove.getNextNode();
      const previousNode = nodeToRemove.getPreviousNode();

      /* remove the pointers to and from 
      nodeToRemove and have nextNode and 
      previousNode point to each other
      */
      nextNode.setPreviousNode(previousNode);
      previousNode.setNextNode(nextNode);
    }

    return nodeToRemove
  }


  printList() {
    let currentNode = this.head;
    let output = '<head> ';
    while (currentNode !== null) {
      output += currentNode.data + ' ';
      currentNode = currentNode.getNextNode();
    }
    output += '<tail>';
    console.log(output);
  }
}

module.exports = DoublyLinkedList;

Using Doubly linked List

const DoublyLinkedList = require('./DoublyLinkedList.js');

const subway = new DoublyLinkedList();

subway.addToHead('TimesSquare');
subway.addToHead('GrandCentral');
subway.addToHead('CentralPark');
subway.printList();

subway.addToTail('PennStation');
subway.addToTail('WallStreet');
subway.addToTail('BrooklynBridge');
subway.printList();

subway.removeHead();
subway.removeTail();
subway.printList();

subway.removeByData('TimesSquare');
subway.printList();

#Additional resources

e. Queues

#Conceptual

#JavaScript implementing

#Additional resources

  • Video: Stacks & Queues
  • Interactive: Queues
  • Github Cheat sheet: Queues

f. Stacks

#Conceptual

#JavaScript implementing

#Additional resources

  • Interactive: Stacks
  • Github Cheat sheet: Stacks

23. Complex Data Structures

a. Hash Maps

b. Trees

c. Heaps

d. Graphs

24. Algorithms

a. Recursion

b. Asymptotic Notation

c. Bubble Sort

d. Merge Sort

e. Quicksort

25. Search & Graph Search Algorithms

a. Binary Search & Search Trees

b. Graph Traversals

26. Intervew Skills

a. Whiteboarding

b. JS algorithms Practice

c. Soft skills

d. Real World Interview Problems

Additional Resources:

  • dailycodinproblem.com
  • leetcode.com

27. Final Front-end Portfolio Project

Overview

For this project, you will build an application using everything you’ve learned. Unlike the previous projects, what you build is up to you. We will provide some ideas to get started but we want this project to be something that you are passionate about building.

Think about:

  • An application that you wish exists but doesn’t
  • What you can build to solve a problem that you, your family, or your friends have

Here are some starting ideas to inspire you:

These ideas require you to learn a few additional technologies on your own:

  • A single/multiplayer game using Canvas or Phaser
  • A mobile application using React Native
  • A data visualization using D3.js
  • A virtual reality application using React 360
  • An interactive 3D visualization using Three.js

Project Requirements:

  • Build the application using React and Redux
  • Version control your application with Git and host the repository on GitHub
  • Use a project management tool (GitHub Projects, Trello, etc.) to plan your work
  • Write a README (using Markdown) that documents your project including:
    • Wireframes
    • Technologies used
    • Features
    • Future work
  • Write unit tests for your components
  • Write end-to-end tests for your application
  • Users can use the application on any device (desktop to mobile)
  • Users can use the application on any modern browser
  • Users can access your application at a URL
    • This means your application should be hosted online
  • Users are delighted with a cohesive design system
  • Users are delighted with animations and transitions
  • Users are able to leave an error states
    • Think about bad API calls, network failures. When an event like that happens, your app shouldn’t crash but provide a user a means to get back to a working state (retry button, go back button, etc.)
  • Get 90+ scores on Lighthouse
  • OPTIONAL: Get a custom domain name and use it for your application
  • OPTIONAL: Set up a CI/CD workflow to automatically deploy your application when the master branch in the repository changes
  • OPTIONAL: Make your application a progressive web app

28. Next Steps in Your Front-end engineer jouney

Mobile Applications

If you’d like to build apps for iOS or Android, try React Native. It uses the same component hierarchy as React, which you already know!

TypeScript

JavaScript is powerful and available on every browser, but it lacks one major feature: types. TypeScript is an extension of JavaScript that adds types, which can save you time catching errors and provide fixes before you run code.

Static Site Generators

If you want to use React for a large-scale application, try Gatsby, an ecosystem of tools that optimizes everything around your application, making it faster, safer, scalable, and accessible.

GraphQL

If you plan on using or building APIs, look into GraphQL, a query language that makes it easier to work with APIs.