JavaScript

Useful Things

  • Math.random() always chooses a random number between 0 and 1. The Math.round() rounds the number to the nearest integer. Therefore, using Math.round(Math.random() * number) can get random numbers greater than 1. e.g. Math.round(Math.random() * 3) would return a random integer between 1 and 3.
  • Math.round() rounds to the nearest integer, and it has 2 variations: Math.ceil() and Math.floor() which round up and down, respectively.
  • There is also the .toFixed() function which lets you round to a specified decimal place.
  • When concatenating variables and strings, a quicker way to do it is to use template literals: ` and ${}. e.g. confirm(order.name + ": place the order for " + order.item + "?") could also be written as confirm(`${order.name}: place the order for ${order.item}?`)
  • When listing the values of an array or multiple values in one line, you can use .join() which lets you concatenate with a custom string that you put in between the brackets. e.g. console.log(arrayName.join(", ")) would list all the values of the array separated by ", ".

Data types and Operators

  • Using console.log(input) prints the output to the console. In a .html file, this can be accessed using chrome dev tools, and navigating to the Console tab. e.g. console.log("Hello World") or console.log(24 == 2*12)
  • Use var variableName = variable, e.g. var favouriteColour = "blue"
  • There are 4 basic data types: string, number, boolean and undefined. JavaScript is loosely typed, so the type of variable is tied to the value held in the variable not the variable iself.
    • Strings: var penColour = "black". Note that strings are alphanumeric - any text enclosed in (either single or double) quotes can be a string.
    • Numbers: var age = 18. Number variables can be both integers or floats/decimals.
    • Booleans: var status = true. Booleans are binary variables - either true or false.
    • Undefined: Undefined variables haven't been assigned values.
  • Using console.log(typeof age) (if the above variables were also stored in the JS file) would return number in the console.
  • Arithmetic operators combine with numbers to form an expression that returns a single number. Assume that var a = 10 and var b = 2.
    • console.log(a + b) returns 12
    • console.log(a * b) returns 20
    • console.log(a - b) returns 8
    • console.log(a / b) returns 5
    • There is also the modulus operator % which returns the remainder from a division. For example, console.log(4 % 15) would return 3.
  • Comparison operators can compare values, or values AND data types:
    • Use == for just comparing values, and === to check strict equality. Similarly, != and !== will check for just values, and both values and data type, respectively.
    • Use < or > for (strictly) greater than or less than; and <= and >= for greater than or equal to and less than or equal to.
  • Logical operators can take in two or more expressions and compare their results. You can store the output of an expression in a variable (e.g. var test1 = a * b) and then compare them using that method.
    • For an AND test, i.e. to check whether all expressions are true, use &&. The output of this: console.log(test1 && test2) would be a boolean value, either true or false. It would compare the values of 2 expressions, test1 and test2, then check to see if their values match, and print either true or false to the console.
    • For an OR test, i.e. to check whether any of the expressions are true, use ||. The ouput of this: console.log(test1 || test2) would also be a boolean value but if either test1 or test2 were true, the console would print true.

Arrays

  • An array can store any data type, but it stores a list of data inside one variable: var arrayName = [item1, item2, item3, etc.]
  • You can access the entire array by simply using the array's name: e.g. If there was an array as such: var names = ["Adam, "John", "James", "Leo"] then typing something that references "names", e.g. console.log(names) would return the entire array.
  • You can also return a single element of the array, by referencing the index number of the array. The first element is always 0. So running console.log(names[1]) would return John, (not Adam).
  • Indexing can also be used to replace data in an array. If we wanted to switch out the name Adam with Alex, running names[0] = "Alex" would redefine the 0 index (formerly Adam) with the string "Alex".
  • To find out how many elements are inside an array, you can use console.log(arrayName.length). Adding .array onto the end of an array name will return the number of items inside the array - it can be used outside of console.log() too.
    • Using this method, you can reference the last item of an array (without knowing it's length) by using console.log(arrayName[arrayName.length -1])
    • This can be shortened in JS to console.log(arrayName.at(-1)). Swapping -1 for any number can therefore find the nth last item.

Nested Data

  • Arrays and objects can nest inside of one another depending on what makes sense in the context, e.g.:
    var bankAccount = {

          user: {

                firstName: "Name",

                lastName: "Surname",

                address: {

                      houseNumber: "house number",

                      city: "city name",

                      postcode: "postcode",

                      nearestBranch: ["branch1", "branch2", "branch3"]

                }

          }

          balance:{

                primary: 10000,

                savings: 12093,

                investmentISA: 1023102

          }

    }
  • You can access nested objects in the same way as objects: i.e. the .keyName or [keyName]; and you can access arrays in the same way too: arrayName[i].

Scopes

  • Global Scope: A variable declared in global scope is accessible to all functions (and the rest of the .js file).
  • Local Scope: Variables declared in local scope (i.e. inside a function) is not accessible outside of the function, though.
  • Lexical Scope: var is considered lexical scoping.
  • Block Scope: const and let as used in loops or if statements, are considered "block" scoping. What this means is the variables set with those keywords will only exist inside that block of code. This allows for a greater precision of when the variable is used - it is even narrower than local scope.

Prompts

  • There are three types of prompt: the alert, the confirm and the prompt.
  • The format for an alert is: alert("Hello world")
  • Confirm works like this: confirm("Typically a question"). Because a confirm prompt requires a user input - clicking "Ok" returns true, and "Cancel" returns false - it can be used with variables to get user-defined variables easily. e.g. var variableName = confirm("question"). The answer chosen by the user will now be stored in variableName.
  • A prompt acts like a confirm, except instead of a boolean response ("Ok" or "Cancel"), the user can input any string response. prompt("Typically a question"). Again, using this with variables is an easy way to gather user input, e.g. var variableName = prompt("question") - and this time the answer can be anything the user types in the prompt input field. Even if the user types a number here, the result will always be a string.

Conditional Statements

  • Conditional statements (a.k.a. "if" statements) are formatted like this:
    if (condition) {

           code to run if condition is true

    }
    e.g.
    if (hungerLevel <= 20){

          console.log("I'm hungry!")

    }
  • It's often helpful to have an "else" statement as well:
    if (hungerLevel <= 20){

          console.log("I'm hungry!")

    } else {

          console.log("I'm full.")

    }
    Likewise you can have an else-if statement which allows for checking more than 1 condition:
    if (condition1){

          code to run if condition1 is true

    } else if (condition2) {

          code to run if condition2 is true

    } else if (condition3) {

          code to run if condition3 is true

    // Repeat as necessary... You can also include an "else" at the end as in a normal if statement too.
    } else {

          code to run if no conditions are met

    } 

Switch

  • A switch statement is essentially an alternative to an else-if statement that has a lot of else-if layers. Switch is a cleaner version of writing that out.
  • The switch statement can run a different block of code based on the value of the input (typically a variable).
  • The syntax is as below:
    switch (variableName) {

          case value1:

          code to run if the variable is value1;

          break;

          case value2:

          code to run if the variable is value2;

          break;

    // Repeat as necessary...
    }
  • To include what would be the "else" part to the above "else-if" alternative, you just add a default case to the code block. This will return only if no other conditions are met in the switch statement.
    ...
          code to run if the variable is value2;

          break;

    // Repeat as necessary...
          default:

          code to run if the variable is not any of the cases above

    }

"For" Loops

  • Iterative loops (a.k.a. "for" loops) are formatted like this:
    for (expression 1; expression 2; expression 3) {

           continue here

    }
    • Expression 1 is executed (one time) before the execution of the code block. It is used to initialise the for loop, i.e. set the starting variables. You can use multiple values here, seperated by commas.
    • The next expression, separated by a semicolon is: Expression 2, which defines the condition for executing the code block. The for loop will repeat until this condition is met. If expression 2 returns true, the loop will start over again. If it returns false, the loop will end.
    • Expression 3 is executed (every time) after the code block has been executed. This is how we change the variable/value we intialised in expression 1 so that it eventually fulfils expression 2 and stops the loop from running.
    e.g.
    for (var i = 0; i <= 20; i++){

          console.log("i is less than or equal to 20.")

    }
    In this example, the i in the iterator is just a variable, it starts at 0 (or the first item in an array). The stopping condition is when i is strictly greater than 20 (i.e. the for loop will run as long as i is less than or equal to 20), and the means of increasing the iterator is i++ which is shorthand for i = i + 1. This way every time the for loop runs, i becomes closer and closer to meeting the stopping condition.
  • If you use var inside a loop and a variable of the same name exists outside the loop, that variable will be redefined. If you use let the variable declared there will only exist inside that loop.
    • e.g.
      var i = 3

      for (var i = 0; i <= 20; i++){

            console.log("i is less than or equal to 20.")

      }
      In this example, the variable i would be 20 after the loop, as it was redefined as 0, then for as long as i was less than or equal to 20, it kept increasing by 1, until it reached 20 and the loop stopped.
    • e.g.
      var i = 7

      for (let i = 0; i <= 20; i++){

            console.log("i is less than or equal to 20.")

      }
      In this example, the variable i remains 7, even though it was redefined as 0 and increased to 20 - those actions happened only inside the for loop.
  • You can combine iterations with an array to apply an action to all items in an array:
    var arrayName = ["item0", "item1", "item2", "item3"];

    for (var i = 0; i < array length; i++){

          action to apply to each array item

    }
    e.g.
    var animals = ["chicken", "duck", "cat", "shark"];

    for (var i = 0; i < 4; i++){

          var currentAnimal = animals[i];
    // This sets the current array item as a variable so we can manipulate it easier.
          console.log(currentAnimal + "s")
    // This will print the current animal with an "s" appended, to make it the plural form of the word.
    }

Functions

  • The syntax for a function is function functionName(input parameter) {actions}. The functionName is any name, typically one that describes the action the function is taking. The input parameter is not necessary, but if used typically describes what type of data the function will run on, and must be used if the function has a functionInput to refer to that input. The actions are what actions you want the function to take.
    To call the function, you use functionName(functionInput). e.g.:
    var fruitsA = ["pears", "apples", "oranges"]

    var fruitsB = ["pineapple", "peach", "mango"]


    function listFruits (arr) {

    //"arr" is just a random name to indicate we intend to use this function on arrays. Some value must be used here though, as the function is being used on arrays.
          for (let i = 0; i < arr.length; i++) {

                console.log(arr[i])

          }

    }


    listFruits(fruitsA)

    listFruits(fruitsB)
    This lets us list out all of the values of both arrays, without making a separate "for" loop for each array.
  • Functions can be called again, and at any point not just at page load, which makes them much more flexible.
  • Functions can have multiple inputs, e.g. function functionName(input1, input2) {}
  • Functions can return values with the use of the return keyword - this is useful for getting outputs from functions.
    • The return keyword exits a function once it is read within the function. This can be used to avoid running through other lines of a function
  • Functions can also be written as expressions (as opposed to declarations, which is the above way): var functionName = function(inputParameters) {}. It can be called in the same manner.

Objects

  • An object can store a list of key:value pairs. The "key" basically replaces the 0, 1, 2, ... index - it acts the same way.
  • The object can store different data types, e.g.:
    var planet = {

          name: "Earth",

          age: "4.543 billion years",

          moons: 1,

          isPopulated: true,

          population: "8.045 billion",

    }
  • To reference a particular item within an object, you would use objectName.keyName or objectName["key Name"], if the keyname is a string. You can use the [] method for regular key names too, but you can't use strings with the . method.
  • A method is a function that is inside of an object, e.g. .log or .random. The only difference between a method an a function is that a method lives inside an object.
  • Use Object.keys(objectName) to list all the keys, Object.values(objectName) to list the values and Object.entries(objectName) to list all the entries (keys and values).
  • Converting these into arrays can then let us iterate through them with ease. For example, if I had a menu as below and wanted to find the total cost of all items, I could do something similar to this:
    var prices = Object.values(menu);

    console.log(prices);


    var totalprice = 0.00;


    for (let i = 0; i < prices.length; i++) {

          const price = prices[i];

          totalprice += price;

    }


    console.log(`The total price for all items is £${totalprice}`)

Object Methods

  • Objects can store more than primitive data types. Objects can embed functions into them as the value of a key-value pair. It can then be referenced the same way you would reference a key: objectName.keyName() BUT you must add () afterwards.
    var person = {

          name: "myName",

          age: 21,

          tellFunFact: function () {

                console.log(`My favourite number is ${1+2}`)

          }

    }


    person.tellFunFact()
    This would show "My favourite number is 3" in the console.
  • The this keyword references the outermost object. If you use console.log(this) the console will log the "Window" object, which keeps track of all the information and everything the user can see, e.g. viewport, HTML, etc. It is the outermost object we have to deal with when coding in the front-end in JavaScript.
    • The this keyword can more usefully be used inside our objects/methods though. It'll refer to the object it is contained inside.
    • var user = {

            name: "J",

            surname: "G",

            age: 21,

            displayUser: function() {

                  console.log(`The user is ${this.name} ${this.surname} and they are ${this.age} years old.`)

            }

      }


      user.displayUser()
    • The above example would display all the other values in a console log of The user is J G and they are 21 years old, despite the method itself being inside the object.

Manipulative Methods

  • Array Methods:
    • arrayName.sort() returns a sorted array in alphabetical order.
    • arrayName.push() adds elements to the end of an array. Can take more than one parameter.
      • Conversely, arrayName.unshift() adds elements to the beginning of the array. Can also take more than one input.
    • To log elements from one array into a new array, use arrayName.slice(start index, stop index). e.g. writing arrayName.slice(3,6) would start a new array with the 3rd index of the first array, and take the 4th and 5th indices, but stop before the 6th index.
    • arrayName.join(separator) joins all items of an array with the specified separator.
    • The splice method can add, remove or replace elements within an array: arrayName.splice(start index, number of items to delete, item(s) to add).. e.g. arrayName.splice(4,2,"apple","pear") would remove the 4th and 5th index (which are the 3rd and 4th items) of the arrayName array, and insert "apple" and "pear" into the array starting at the 4th index.
  • String Methods:
    • stringName.split(separator) separates a string into individual items of an array every time the separator appears. e.g. string.split(",") would split the original string into parts every time there was a ,. If the separator is left blank, the string splits at every character.
    • stringName.replace(characters to replace, characters to replace with) does what it says.

Iterative Methods

  • arrayName.forEach() iterates through each item of an array and applies an action (defined within the ()) to it. When used with a function, this removes the need to write a for loop, as you can just use arrayName.forEach(functionName) instead.
  • arrayName.map() puts all items of an array into a new array (when used with a new array name). Again, when used with a function, it allows you to map out a new array with ease. e.g. var newArray = oldArray.map(functionMultiply).
  • the for... of... loop can be used to iterate through an array. It should be used in preference to forEach.
  • This is useful when used with document.querySelectorAll(), for instance, which generates an array (of objects).

Filtering Methods

  • var filteredArray = arrayName.filter(functionName), where the function is the filtering method - actions you apply to first array to get the second array. e.g.:
    var nums = [1, 20, 30, 3, 5, 8]


    function lessthan10(num) {

          return num > 10

    }


    var smallNums = nums.filter(lessthan10)
    This would return 1, 3, 5 and 8.
  • You can also find a single result. It works just like the filter method in that it needs a function to work off of, but once it has found a result that matches that condition, it will exit the loop. Its syntax is var filteredArray = arrayName.find(functionName).

Web APIs

Useful Info

  • Web APIs are built into the web browser and contain methods that allow us to manipulate a webpage using JavaScript. We use Web APIs to create elements and add them to the browser or to add and remove styles and attributes via JavaScript.
  • The this attribute refers to the outermost object to where it is placed. Unless inside a custom function/method etc, it will refer to the window.
  • One way to navigate through the DOM/objects in the window is as below. You can use .children[index number] to navigate throught the children of an object too, and using console.log(document.body.children) would list the children of the body object, for example, as an array.
    • console.log(window) shows the window object and all its members.
    • console.log(window.document) shows the document object and all its members - this is similar to the HTML document.
    • console.log(element) shows the specified element and all its members.
    • console.log(document.head) shows the header tag and all its members, as in a HTML file.
  • The other way can be to access elements by their class or id. To do this, use document.querySelector(), which would find the first element that matches, or document.querySelectorAll(), which would provide an array of all elements that match.
    Inside the brackets you can refer to an element with CSS notation by its class or element, e.g. document.querySelector("#exampleID") or document.querySelector(".exampleClass"). You can also use tag names here. Besides querySelector, there are several ways to target more specifically:
    • document.getElementbyId()
    • document.getElementbyClassName()
    • document.getElementbyTagName()
    • These methods don't require the # or ., etc. as it already specifies what it is searching for.
  • Input fields by default collect the data the user enters into the field. To record this in JS, use the .value method in conjunction with a variable to store it, e.g. var recordedInput = document.querySelector("element").value
  • Appending .trim() removes any blank space before and after a value. e.g. javascript would be shortened to javascript

Setting Attributes

  • By accessing elements, you can set their styles in JavaScript with the .style method. You can also change other properties with other methods, e.g.:
    var textbox = document.querySelector("#setattribute > ul > li:nth-child(1)")

    // This defines a variable and assigns it the appropriate document object.
    textbox.style.color = "red"

    textbox.style.fontFamily = "monospace"

    //This sets the font color to red, and the font to "Cabinet Grotesk".
  • If you want to change multiple attributes at once, instead of typing out .style.attribute for each attribute, you can use .setAttribute("attributeName", "attributeValue(s)")e.g. document.querySelector("#customClass").setAttribute("style","color: yellow; font-size: 0.85rem; text-decoration: underline;") would set the first element with the class customClass with the style attributes listed above.
    NB. This could be re-written much neater with convenience variables, indentation, etc.
  • Combined with for loops and the document.querySelectorAll() (which generates an array of all elements that match the requirements), you can adjust multiple things quickly:
    var listEls = document.querySelectorAll("li")


    listEls.forEach(element => {

          element.setAttribute("style","color: yellow; text-decoration: underline;")

    });
  • You can set other attributes than style too, e.g. document.querySelector("img").setAttribute("src", "example.gif;")
    [ Currently there is no image source ]

Creating and Appending Elements

  • You can create a new element with document.createElement(element name).
  • The second step is to add this newly created element (which only exists in the JS file right now) to the DOM. To do this use document.body.the path where you want to insert the element.appendChild(element).
  • The next step is to add content to this new element. There are a few ways to do this, but for simple text-container elements (e.g. div, p, h1, title) you can use .textContent().
    var ele = document.createElement("li")

    ele.textContent = "This text was created and added to the DOM by JS."

    document.querySelector("#createappends > ul").appendChild(ele)

Event Listeners

  • Event listeners are the functionality of JavaScript to wait for a user interaction before enacting some code.
  • The syntax is targetElement.addEventListener("type", "function()). The targetElement is the element in the DOM (accessed via document.querySelector(), for instance); the type is the type of interaction to listen for: i.e. click or mouseover; and function() is the function that will run (can be written in place, or defined beforehand).
  • Note that the event parameter can also commonly be written as just e or omitted entirely if not used within the function.
    Event Listener Spinner
    Hover over the spinner to demo code.
    // evListenerObj is a predefined object.
    evListenerObj.addEventListener("mouseover", function(event) {

          event.preventDefault()

          console.log(event)

          alert("Hey :)")

     })
  • Sometimes event listeners will refresh the page, which can be an undesired behaviour. This cna be stopped with .preventDefault() within the function of the event listener. This is where the event or e parameter can be used, as you would write it out like: event.preventDefault(). This should be used by default.
  • Console logging out event.target will show what element triggered the event, even if that's the child within the parent which had the event listener.

Keyboard Events

  • Another common event listener is the change type. This listens for a change in the value of an object - for example, on a dropdown menu:

    This text will change based on the value of the dropdown menu above.

    var changekeybevent = document.querySelector("#changekeybevent");

    var ckbParent = document.querySelector("#keyboardevents > ul > li");

    var ckbChild = document.querySelector("#keyboardevents > ul > li > p");

    var newText;



    changekeybevent.addEventListener("change", function(event){

          event.preventDefault();

          newtext = changekeybevent.value;

          ckbChild.textContent = newtext;

    })
  • The keydown event type is also useful. It listens for any keypress (except backspace).


    var keydownevent = document.querySelector("#whatdausertypin")

    var textareakeydownevent = document.querySelector("#keydownevent")


    textareakeydownevent.addEventListener("keydown", function(event){

          var key = event.key.toLowerCase()

          var alphanumericchars = "abcdefghijklmnopqrstuvwxyz0123456789 ".split("")

          if (alphanumericchars.includes(key)) {

                keydownevent.textContent += event.key

          }

    })
  • For forms, the submit event type listens for when the Enter button is pressed inside a form input field.

Event Bubbling

  • When a child with an event listener is inside a parent with its own event listener (of the same type), both event listeners will trigger if the child is triggered. For example, if the child has a click event, and the parent does too, both events would trigger when the child is clicked.
  • This is a default behaviour but can be stopped with event.stopPropagation() rather than the event.preventDefault().
  • To demo behaviour: click this box, then middle box, then outer box.

Timers and Intervals

  • To create a timer, use setInterval(function, delay). The delay, in milliseconds, is the delay before the function is repeated.
  • The biggest pitfall of setInterval() is that multiple timers cannot run at the same time - so there needs to be a way to remove the timer or stop it before another one is run. This is done with clearInterval(), inside the brackets you put the variable name that you assigned the setInterval function to previously.
    var timerInterval = setInterval(function(){

          secondsLeft--;

    // A predefined variable
          timerbutton.textContent = `${secondsLeft} seconds left until timer is up.`;

    // Every time the function runs, this action will be repeated (timerbutton is also a predefined variable referring to an element in the DOM).

          if (secondsLeft === 0) {

                clearInterval(timerInterval);

                timesUp();

          }

    // Once the time has run out, the function
    timesUp() will be called, and the
    timerInterval will be cleared, preventing this timer from running further.

    }, 1000)

    // Every 1000 ms the function is re-run.

Data Attributes

  • Data Attributes allow for toggling of an element between different states with ease.
  • The simplest way to implement data attributes is by adding custom attributes to the element tag, as in the example below:
    <img

       src="https://techcrunch.com/wp-content/uploads/2015/10/14.gif?w=700&crop=1"

       data-state="gif1"

       data-gif1="https://techcrunch.com/wp-content/uploads/2015/10/14.gif?w=700&crop=1"

       data-gif2="https://techcrunch.com/wp-content/uploads/2015/10/22.gif?w=700&crop=1"

    />
  • To obtain the data attribute of an example element, you would run
    element
    .getAttribute("
    data-state
    ")
  • Another way to get the data state is to use
    element
    .dataset.state
    : the dataset is equivalent to the data from the HTML, and the .state is equivalent to the -state.
  • This information can then be used to switch the state of the image in an if loop.
  • Star Wars GIF thumbnail

Local Storage

  • Local Storage objects persist through page refreshes.
  • Counter

  • To store a value in local storage use: localStorage.setItem("
    key
    ", "
    value
    ")
    .
  • To obtain a value from local storage use: localStorage.getItem("
    key
    ")
    .
  • To remove a vlue from local storage use: localStorage.removeItem("
    key
    ")
  • For Arrays/Objects:
  • Items can be stored in local storage as an array/object too, which makes organising the key/value pairs much easier. To do this though, the items should be stored in a normal array/object first, and then that object can be set as an item in local storage.
  • The array/object must be converted to a string when it is moved to local storage with localStorage.setItem("
    key
    ", JSON.stringify(
    value
    ))
    .
  • user = {

          name: "john",

          surname: "doe",

          email: "john_doe@gmail.com",

          password: "password123",

    }


    localStorage.setItem("user", JSON.stringify(user))
  • Running localStorage.getItem("user") would retrieve the data but as a STRING not its initial data type.
  • Therefore, to get the item in a useful way (as an array/object), the data must be converted back from a string with: JSON.parse(localStorage.getItem("
    key
    "))
    .

3rd Party & Server APIs

jQuery Elements

  • jQuery is accessed via a CDN link. Minified version allows for slightly better performance since the indentation, spacing, etc. is reduced - but so is the readability.
  • The idea behind jQuery is that it compresses vanilla JS lines into a much slimmer version, e.g. document.getElementbyId("header") in vanilla JS would be $("#header") in jQuery.
  • To get a list of all the methods jQuery provides, run console.log($()).
  • Referencing elements on the page with jQuery is done with $("element tag /.element class / #element ID").
    • Note that for element tags no syntax is used (i.e. the <>) as that would create an element not reference it. However, when searching for classes/IDs . or # must be used.
  • Another way to reference an element is to use its tag and any attribute of the element within square brackets: e.g. $("input[name="firstname"]") would reference the following HTML element: <input type=text name=firstname>
  • Adding Elements:
  • To create an element in jQuery use $("<tag>"), e.g. $("<h1>") to do what vanilla JS does with document.createElement("h1").
  • Another way to add an element is to use .append("The element as it would appear in HTML"), e.g. .append("<p class="m-5" id="newEl">This element was created with .append</p>")
  • Altering Elements:
  • To replicate vanilla JS .textContent = "string", i.e. alter text content of elements use .text("string").
  • Use .append(element) to append elements to an object in the DOM (equivalent of vanilla JS .appendChild(element)).
  • Editing attributes works similarly: there is .attr("attribute", "value") or for specifically adding a class, you can use .addClass("class").
  • For adding styling specifically, you can use .css("style", "value").
    • For altering multiple CSS styles at a time, you can do it as if it were an object, e.g.:
      element.css({

            "background-color": "black",

            "color": "red",

            "font-size": "30rem"

      })
  • Removing Elements:
  • To remove an element use element.remove(). It removes all matching elements when searching by tag or class, but only the first if searching by ID.
  • Using element.hide() is a quick way to hide the element.
  • var placeToAddTheNewElement = $("#jqueryaddhere");

    var newBulletPoint = $("<li>");

    newBulletPoint.text("This element was created with jQuery.");

    newBulletPoint.css("color", "yellow");

    newBulletPoint.attr("id", "jqueryaddedelement");

    placeToAddTheNewElement.append(newBulletPoint)

    jQuery DOM

    • To access the children of an element, use element.children(). The children of an element can be manipulated by just chaining on arguments, e.g. element.children().css("color", "yellow"), which would change all the children to font colour yellow.
    • To target a specific child, use element.children().eq(index).
      var jqueryDOMsect = $("#jquerydom")

      // The element with ID #jquerydom is this card. So the child's child is the bullet point above.
      jqueryDOMsect.children().children().eq(1).addClass("yellowtext")
    • If you know the tag/class/ID, you can also use that with element.children(element) such as element.children("h3").
    • To access the parent of an element, use .parent().
    • To access the sibling of an element, you can use .siblings(), or .next() to find the next sibling and
      .prev()
      to find the previous one.

    jQuery Forms

    • Use event.preventDefault() with forms, as before, to prevent it from refreshing the page.
    • With checkbox forms, <input type=checkbox value=value>, the way to select all checked options is $("input:checked")
    • The $.each(array, function) is an equivalent to the "for" loop, allowing you to iterate through each item of an array and apply a function to them.
    • Within the $.each(), using $(this) references the current value the loop is iterating over.
    • Demo Form
      Apples
      Bananas
      Oranges
      Pears
      Grapes
      Submit
      function handleFormSubmit(){

            var checkedEl = $("input:checked")

            var selected = []

            $.each(checkedEl, function(){

                  selected.push($(this).val());

                  console.log(selected)

            })

            $("#checkboxes").text(`Fruits: ${selected.join(", ")}`)

            $("input[type='checkbox']").prop("checked", false)

      }


      $("#submit").on("click", handleFormSubmit)
    • For type=text forms, data can be submitted with the event type submit, as opposed to click.
      var formEl = $("#jquerytypetext")

      var textfield = $("#textfield")

      var putItemshere = $("#jquerytextformlist")


      function typetextformSubmit(event) {

            event.preventDefault();

            var newItem = (textfield.val());

            textfield.val("");


            if (!newItem) {return;}
      // Prevents blank text from being entered.

            var newItemEl = $("<li>");

            newItemEl.text(newItem);

            newItemEl.addClass("toremove")

            putItemshere.append(newItemEl);

      }


      formEl.on("submit", typetextformSubmit)

    jQuery Events

    • To add an event listener to jQuery use element.on("event type", function)- this is the equivalent to vanilla JS element.addEventListener("event type", function).
    • Besides the syntax change, the click events work the same as in vanilla JS.
    • Event Delegation:
    • Elements that are created with JS don't exist in the DOM on page load (they are loaded immediately after). This means that they can't be targeted with event listeners. The way around this is to use event delegation. This targets the parent element, with a secondary condition of specifying the element within it.
      EDparentEl.on("click", ".delete-item-btn", removeBtnED)

      // [1] The line above uses event delegation - it targets the parent element, with a selector of the class of the button we click, and a function that deletes the button. This works.
      DelItemBtn.on("click", removeBtnDefault)

      // [2] This line uses a regular function, attached directly to the newly-created button element. This does not work.

    Scoping

    • Same as JavaScript, a global variable is one set outside of a function/method, a local variable is one set inside a function. Also as before, a shadowed variable is when a variable is declared inside a function with the same name as a global variable.
    • The .this refers to its parent object/element. If it inside a nested function (a function inside another function) it'll return the window object as it cannot see a parent object - it defaults to the window.
    • Local scope variables take priority over global scope variables.

    jQuery UI Widgets

    • jQuery UI is a content library like bootstrap that gives useful things like dialogue boxes. It can be accessed at jqueryui.com.
    • This is an example of a jQuery UI widget:
      Date:
    • To use jQuery UI, you need the following tags, one CSS library two JS tags: one for jQuery and one of jQuery UI.
    • <link rel="stylesheet" href="//code.jquery.com/ui/1.13.2/themes/base/jquery-ui.css">

      <script src="https://code.jquery.com/ui/1.13.2/jquery-ui.js"></script>

      // The following tag is for jQuery, so don't include if it's already there.
      <script src="https://code.jquery.com/jquery-3.7.1.min.js" integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=" crossorigin="anonymous"></script>

    jQuery UI Interactions

    • NGL could not be bothered to make notes for this it's all in the documentation pls just read that 👍.
    • But the idea is that using the jQuery UI library's interactions set,
    • You can apply simple interactions to premade elements.
    • For example:
    • These bullet points have the .sortable() function applied to them,
    • So you can drag and drop them into a different order!

    Day.js Format

    • Day.js is another third party library (CDN available here) that deals with specifically enhancing or changing the way in which dates and times are displayed.
    • To create a timestamp for right now use: dayjs().format(format), where the format can be obtained here. Writing out dayjs().format(`[The time is] HH:mm [and the date is] DD-MM-YYYY`) would give: .
    • You can also parse dates in one format (e.g. 03 January 2024) and convert it into a new format (e.g. 03/01/2024): dayjs(inputDate).format("newFormat"). The following code will switch today's date from shorthand to fully written out.
    • You can calculate the difference between two dates using dayjs(date1).diff(dayjs(date2)), by default it returns the difference in milliseconds. You can also calculate in different units (e.g. days, weeks, months) by incuding it as the second argument. Note that the default behaviour does not include partial weeks in its calculation - this can be changed by including true as the third argument.
      today.diff(dayjs("2024-01-01"), "days")


      dayjs().diff(dayjs("2023-06-31"), "weeks", true)

    Fetch

    • Server APIs are ways of grabbing data from repositories hosted by other places. The syntax of a query URL will differ from provider to provider.
    • Using the .fetch(queryURL) method, the data can be retrieved from the server API. It will come in a .json file format (or an .xml).
      fetch(queryURL)

            .then(function (response) {

                  return response.json();

            }).then(function (data){

                  what you want to do with the data;

            });
    • Because fetch gets data from an external site, it is asynchronous. This means the response from fetch won't necessarily arrive before other lines of JavaScript are run.
      fetch("https://pokeapi.co/api/v2/pokemon/pikachu/")

      .then(function (response) {

            return response.json();

      }).then(function (data){

            pokename.text(data.species.name)

            pokeimg.attr("src", data.sprites.front_default)

      });
    Pokémon Name
    Move1
    Move2
    Move3
    Move4
    • Often, server APIs require an API key to validate the data. This would just go onto the end of the queryURL, e.g. fetch("https://api.giphy.com/v1/gifs/trending?api_key=api key").

    ES6 and Node.js

    Arrow Functions

    • A var functionName = function(parameter1, parameter2){expressions} can be swapped with var functionName = (parameter1, parameter2) => {expressions}.
    • If there is only one input parameter the () can be omitted like so: var functionName = parameter => {expressions}.
    • If there is only one expression inside the function, the {} can be omitted like so: var functionName = parameter => expression. If the purpose of the function is to just return a value, this can be taken one step further, and return can be removed altogther, e.g. var multiplyNums = (a,b) => a*b - here there is no need to type return a*b.
    • Arrow functions bind the this keyword to the object it's created inside of. As a result avoid using arrow functions for object methods and instead use a default function.
    • In regular functions the this keyword represented the object that called the function, which could be the window, the document, a button or whatever. With arrow functions, the this keyword always represents the object that defined the arrow function.
    • JavaScript also has a ternary operator ? which takes in three parameters: condition ? expression if condition is true : expression if condition is false, e.g. console.log(1 == 1 ? "True" : "False") would print True.

    "Let" & "Const"

    • A commonly accepted practice is to use const as much as possible, and let in the case of loops and reassignment. Generally, var can be avoided outside of legacy code.
    • If the const is an array, values can be pushed to it - it can be added onto, just not redefined. const means constant reference, not constant value.
    • Likewise, with objects, properties of the object can be redefined/updated - but the object itself cannot be redefined.
    • While arrays and objects defined with const can be modified, they cannot be reassigned.
    • Both const and let are always scoped to a block of code, they no longer exist outside of them.
    • As such, let will not leak outside of a loop or function.
    • An exception is that while using while loops, values created inside also exist outside of the loop.

    Functional Loops

    • The .foreach() loop is a functional way of iterating through an array without using a for loop, its syntax is arrayName.foreach((input element) => expression).
    • .filter() returns a new array based on the filter options. e.g. const newArrayName = arrayName.filter((input element) => expression). e.g.:
      const moviePatrons = [

            { name: "Tom", age: 16 },

            { name: "Ashley", age: 31 },

            { name: "Sarah", age: 18 },

            { name: "Alvin", age: 22 },

            { name: "Cherie", age: 14 },

            { name: "Malcolm", age: 15 }

      ];


      const canWatchRatedR = moviePatrons.filter((patron) => patron.age > 17);
    • The other functional loop is .map() which returns a brand new array by applying actions to all the elements of a previous array. It's syntax is const newArrayName = arrayName.map((input element) => expression) It differs from foreach as it creates a new array with the new values, rather than affecting the original array. e.g.:
      const array1 = [1, 4, 9, 16];

      const array2 = array1.map((number) => number*2);


      console.log(array1);

      // This would return Array [1, 4, 9, 16]
      console.log(array2);

      // This would return Array [2, 8, 18, 32]

    Node.js Basics

    • Node.js is a downloadable program for JavaScript code to be run (outside of a browser) in a terminal, to allow for asset compilation, scripting, monitoring and as the basis for web servers.
    • Pathing is integral to using node.js, using pwd inside the VSCode terminal will show the current path.
    • Once Node.js is installed, type node filename.js in the terminal to run a JavaScript file with node.
    • In the browser, setTimeout is a property of the window object. In node, it belongs to a special timeout object
    • Running console.log(process.argv); returns an array of command line arguments. Those items can be accessed via index number, e.g. console.log(process.argv[2]). This just allows you to get inputs from what is entered into the command line, if you wanted to use that in the code (for example to verify user input).

    Node Package Manager

    • Using require(), node can pull in other packages similar to how a CDN link works.
    • Node comes with some inbuilt packages, but the third party packages can be obtained from npmjs.com.
      1. The first step to use npmjs is setting up the package.json by running npm init -y in the VSCode terminal. (Side note: In the package.json you can create custom scripts to essentially create shortcuts for large lines you want to put in the terminal.)
      2. After the package.json is created, the third party packages can be installed with npm i packagename@^version. Packages can be found on npmjs.com, e.g. Inquirer which would be installed with npm i inquirer@^8.0.0.
      3. This will create a node_modules folder.
      4. The JavaScript file then needs to include the newly-downloaded package as a dependency with const packageName = require("packageName");, e.g. const inquirer = require("inquirer");

    Node File System

    • fs is a Node standard library package for reading and writing files. Putting const fs = require("fs"); in the JS file adds it.
    • To read the contents of a file and manipulate the data you can use the following lines of code. The .readFile("filename", "encoding", (error, data) => ...) allows you to use an if-else statement or a ternary operator to print either the error or the data, in the following example:
      fs.readFile('data.csv', 'utf8', (error, data) =>

        error ? console.error(error) : console.log(data)

      );
    • "utf8" encodes the raw buffer data in human-readable format.
    • Likewise, using .writeFile("filename", "input", (error) => ...) allows you to write data to a file. The full syntax is:
      fs.writeFile('log.txt', process.argv[2], (error) =>

        error ? console.error(err) : console.log('Success!')

      );
    • N.B. In the above examples the error ? ... line is using the ternary operator.

    Modularisation

    • Modularisation is the idea that code can be split into segments based on its functionality to make it more readable and easier to troubleshoot.
    • module.exports is an object we use to store variables or methods that other JS files can access. There can only be one of thse per JS file. Its syntax is:
      module.exports = {

         keyName: variable/method from the file,

         keyName: variable/method from the file,

      // Repeat as necessary...
      };
    • Likewise, to get the exports from one file, you need to require("filepath") in the other to receive them. This sets the variables/methods you exported into a custom variable in this new JS file, e.g.:
      const objectName = require("filepath");
    • These "imports" can then be called in the regular way of accessing an object's properties/methods.

    "Rest", "Spread" & "Reduce"

    • The rest operator allows you to not specify the number of input parameters to a function. Its syntax is function functionName(...name), where name refers to the name of the array of input parameters. e.g.:
      function add(...nums){

         let sum = 0;

         for (const num of nums) {

            sum += num

         }

         return sum

      }
      This example would add all of the input parameters that the add function is run with. The inputs are listed as an array, so running add(1,2,3,4,5) would have nums return as [1,2,3,4,5], and the function add would return 1+2+3+4+5 or 15.
    • The spread operator is a way to combine two different arrays, instead of nesting one inside another. You would use it as const arrayOne = [...arrayTwo, "example value"], e.g.:
      const fruits = ["apple", "banana", "orange"];

      const foods = [...fruits, "potato", "carrot", "lettuce"];
      The result of this would display all the values of fruits inside the foods array too.
    • The spread operator can also be used to add values to a new version of an object (or merge two objects). For example if you had an object with set values already, and wanted to add a few already-defined variables to it without using objectName.newKey = value, e.g.:
      const room = {

            height: 200,

            depth: 400,

            chairs: 3,

         };


      const desks = 3;

      const lights = 2;

      const painted = "yes";


      const roomData = {desks, lights, painted, ...room}

      const roomData = {

            height: 200,

            depth: 400,

            chairs: 3,

            desks: 3,

            lights: 2,

            painted: "yes",

         };
    • Finally, the reduce method iterates through all values of an array and "reduces" them into a single value. It works in a similar way to the forEach() method, but collects all the iterated results into one value. Its syntax is .reduce(function, initialValue), where the initial value is an initial value to start from, if left blank defaults to the first item in the array (index 0). The function must refer to an accumulator value and a currentValue value as its input paramters. e.g.:
      const nums = [1, 4, 7];


      const numsTotal = nums.reduce((accumulator, currentValue) => {

         accumulator + currentValue;

      },0)
      This would return a value of 1+4+7 or 12. The function is written directly into the reduce arguments in this example.

    Destructuring

    • Destructuring is a way to separate an objects properties into individual values, a quicker way of writing out const variableName = objectName.keyName however many times for each property within the object.
    • Its syntax is const {keyName: optionalVariableName, key2Name: optionalVariable2Name} = objectName. It then defines a new const as keyName with the value objectName.keyName and likewise for key2Name.
    • const jaime = {

         name: 'Jaime Lannister',

      };


      const { name: jaimeName } = jaime;
      // A new variable "jaimename" is defined here with the value "jaime.name"
      console.log(jaimeName); // prints "Jaime Lannister"
    • Object destructuring will look for the keyname that matches the thing you type - hence the need to specify a second variable name if you want to name it something other than the keyname. Array destructuring won't look for a matching value to what you type, it only goes by the index number.
    • This can also be done to arrays e.g. const [a, b, c] = arrayName.
    • Another way to use this is in function definitions. The input parameter would be destructured from an object to the format above.
      const betterLogCharacter = ({ name, parents }) =>

         console.log(`${name}'s parents are: ${parents[0]} and ${parents[1]}.`);
      Instead of writing (object)(object) as the function's input parameter, it has been destructured to {name, parents} i.e. the function is now searching for input parameters matching object.name and object.parents.

    Object-Oriented Programming

    Constructors

    • Constructor functions are used to create multiple different objects from a blueprint.
    • Typically, constructors are named with a capital letter at the start - this is just a naming convention it isn't required.
    • The syntax for a constructor function is as follows:
      function ConstructorName(parameters) {

         this.keyName = value;

      // If you want an object method it would be written as follows:
         this.functionName = function(){}

      }
    • The constructor function can be invoked by using const objectName = new ConstructorName(parameters).
    • Once the object is created, it can be referenced and manipulated as usual.

    Prototypes

    • Objects, arrays and primitive data types all have a .prototype.
    • The .prototype has methods and properties attached to it, but the methods declared on the prototype are declared once and memory is allocated for them once, despite all objects (that are made from it) having access to them.
    • Instance methods only exist on a particular instance of an object, prototype methods are on all instances.
    • Examples of this include .forEach(), .toLowercase() or .push() - which are available to all arrays, strings and arrays universally through the .prototype.
    • This adds an alternative to using constructor functions, as new methods can instead be added onto the .prototype to be accessed wherever, e.g.:
      Movie.prototype.logInfo = function() {

         console.log(`${this.title} was released in ${this.releaseYear}`);

      };

      const theShining = new Movie("The Shining ", 1980)

      theShining.logInfo();

    Test-Driven Development

    • Jest is a npm package that allows automated testing. You can create a test to describe what something should do, give an input and expected result, then run the process, and check that the result from running it matches the expected result.
    • After installing jest with npm install jest create the JavaScript file, and then create a JSfileName.test.js to accompany it. Then add this code to the package.json:
      {

         "scripts": {

            "test": "jest"

         }

      }
    • The structure of a test should be as below:
      • Arrange: declare what a starting value (to use in the test) is. Also if needed, declare an expected result.
      • Act: Enact steps to test.
      • Assert: Confirm whether or not actual result matches expected result.
    • Here is an example for testing an constructor's object method:
    • describe("testName", () => {

         it("describe what the test should do", () => {

            // ARRANGE

            const initialValue = initialValue;

            const expectedResult = expectedResult;

            // ACT

            const actualResult = new Constructor().method(initialValue);

            // ASSERT

            expect(actualResult).toEqual(expectedResult);

         });

      });
    • The test can then be run using npm run test.

    Classes

    • Classes were introduced with the ES6 update.
    • They work the same as constructor functions and are formatted like:
      class ClassName {

         constructor(parameters) {

            this.keyName = value;

      // If you want an object method it would be written as follows:
            functionName() {}

         }

      }
    • Here is a working example:
      class Shape {

         constructor(area, perimeter) {

            this.area = area;

            this.perimeter = perimeter;

         }


         printInfo() {

            console.log(`Area: ${this.area}`);

            console.log(`Perimeter: ${this.perimeter}`);

         }

      }


      const shape = new Shape(25, 25);

      shape.printInfo();
      This would print Area: 25, Perimeter: 25.
    • It is invoked the same way as a constructor function, with const objectName = new ClassName(parameters).

    Subclasses

    • Subclasses are basically just new classes that can inherit all the things you've set up in another class. They are written out as below:
      class Rectangle extends Shape {

         constructor(sideA, sideB) {

            const area = sideA * sideB;

            const perimeter = sideA * 2 + sideB * 2;

            super(area, perimeter);


            this.sideA = sideA;

            this.sideB = sideB;

         }

      }
      Here the important bits are extends in reference to the previous class this one inherits from. And the super that defines the input parameters that the original class wanted using the parameters this new Subclass accepts.
    • super() is how parameters are passed along from the subclass to the parent class. extends Class is how you set up a subclass.

    React.js

    React Basics

    • To create a new react package use npm create vite@latest fileName -- --template react.
    • Next, run npm install followed by npm run dev.
    • npm run dev starts the live server for a react project and ctrl + c will kill the server.
    • Functions can be created to return DOM objects, e.g.:
      function HelloReact() {

         return <p>Hello World!</p>;

      }
    • They can then be called with return <HelloReact />; which is equivalent to vanilla JS's HelloReact() - i.e. just calling a previously defined function.
    • When writing the contents of the HTML, they must be wrapped in a set of <> tags.
    • HTML classes must be referenced with className rather than class.
    • Variables or expressions can be referenced inside components with {}. It works similarly to the template literal format except there is no dollar sign.

    CSS and React

    • CSS styling can either be done inline within the .jsx file for each component, or in a corresponding stylesheet imported into each component's page via import "../styles/Header.css";.
    • When using stylesheets, the styles do apply to the whole DOM, it is just that they are loaded when the corresponding component loads. With inline styling, the styles apply only to the component.
    • When styled inline the CSS is treated as an object, e.g.:
      const styles = {

         card: {

            margin: 20,

            background: "#e8eaf6"

         },

         heading: {

            background: "#3f51b5",

            minHeight: 50,

            lineHeight: 3.5,

            fontSize: "1.2rem",

            color: "white",

            padding: "0 20px"

         },

         content: {

            padding: 20

         }

      };
      The objects can also be separated, if you're only created styles for the card, for example, you could just do const cardStyles = {} instead of nesting the card object within the styles object.
    • In inline styling, the names (e.g. font-size) are switched to camel case: fontSize.
    • Bootstrap can be added to a react project with npm i --save bootstrap @popperjs/core in the terminal, and import "bootstrap/dist/css/bootstrap.min.css"; and import * as bootstrap from "bootstrap" in the main.jsx file.

    Props

    • Fundamentally, a React component is a JavaScript function that returns data. Each component describes and returns some part of our application's UI. Since a component is a function, it can receive arguments. This allows us to write components that behave different based on the arguments they receive. The arguments that are passed into components are called props.
      function Alert(props) {

         return (

            <div className={`alert alert-${props.type || "success"}`} role="alert">

      // If props.type returns as false the default value will be success which will make alert-success show.
               {props.children}

            </div>

         );

      }
    • The props are passed into the component in App.jsx, where different values can be passed into it depending on context, e.g.:
      function App() {

         return <Alert type="danger">Invalid user id or password;

      }
      This is returning an object:
      {

         "type": "danger",

         "children": "Invalid user id or password"

      }
      children is always what is between the opening and closing tags of the component.

    States

    • States differ to props in that props are meant to be set in the code and never updated. States are bits of data that change upon UI interactions and change elements in turn.
    • The loop in which state's operate is: the state drives UI rendering - it decides how an element is rendered. Then events (i.e. human interactions) modify the state and the cycle repeats.
    • The useState hook offers a way for components to manage state directly, eliminating the need for data retrieval through prop drilling.
    • const [state, setState] = React.useState(initialValue)
    • Alternatively you can use import React, { useState } from 'react'; which imports it deconstructed in that line so you can later use useState in place of React.useState.
    • const [count, setCount] = useState(0);


      const handleIncrement = () => {

         setCount(count + 1);

      };
    • The above example takes a state of "count", a setState of "setCount" and an initial value of "0". The function defined below makes it so that every iteration the new value is the former value plus 1.
    • When updating the state of an object, make sure to use the spread (...) operator to not entirely redefine the object upon updating the state, e.g.:
      const [ houseState, setHouseState ] = React.useState({

         doors: 4,

         windows: 23,

         occupants: 5,

         name: "House",

      })


      // ...
      setHouseState({ ...houseState, doors: 7 })

      // This retains all the other key-value pairs in the object, and updates only the doors.

    Hooks & State Management

    • Hooks are functions that allow us to extend the capabilities of our components.
    • Only call Hooks from top-level components. Never from within loops, conditonals or nested functions. Only call them from React components, never from JS functions.
    • "State" is a specialized property that is used for component specific storage data. Whenever state is updated, the .jsx component is re-rendered.
    • useState Hook
    • See states.
    • useEffect Hook
    • useEffect can be imported the same way as useState or used with React.useEffect.
    • Effects, a.k.a. side effects, are bits of code that are responsible for managing external interactions (e.g. Data fetching / API calls, subscribing to events, changes to the DOM).
    • useEffect takes two arguments, first a callback function and second an array of dependencies. When the component re-renders, useEffect re-runs its callback function if any dependency values change.
    • By specifying dependencies, we can control when the hook runs, avoiding unecessary executions of the callback function.
    • If props.name is specified, useEffect runs once on initial page render, and subsequently whenever props.name changes:
      useEffect(() => {

         callback function

      }, [props.name]);
      If an empty array is provided, useEffect will only run the first time the component renders.
      useEffect(() => {

         callback function

      }, []);
    • Custom Hooks
    • Any custom hook has to start with use like useState or useEffect.
    • They're best suited to extract logic that may be repeated, and therefore a good practice to keep React functions cleaner.
    • The general pattern for a custom hook can be:
      function usehookName() {

         const [ state, setState ] = useState();

      // Set a state for the hook that updates based on the code inside the hook.
         // Code that performs the purpose of the hook.


         return setState

      // return a value that can be read by the elements that use the hook.
      }
      Here is an example:
      function useCounter() {

         const [ count, setCount ] = useState(0)

      // State to keep track of, that updates based on code inside the code.
         const increment = () => setCount(count + 1)

      // A function that changes state.
         return [ count, increment]

      // Returns the "count" value and the "increment" function.

      -------
      function CounterComponent() {

         const [ count, increment ] = useCounter()

         return (

            <>

               <p>Count: {count}</p>

               <button onClick={increment}>Add 1</button>

            </>

         )

      }

    Forms

    • The first thing with React forms is to track the state: the form is a stateful component. However, forms differ in that there are multiple things to track. The useState() therefore has an object as the default value, e.g.:
      const [formData, setFormData] = useState({

         firstName: "",

         lastName: "",

      });
      All this does is creates 2 things, formData.firstName and formData.lastName, that can be updated based on user input (once a function is created). The values for the two keys are blank because we don't need an actual default value.
    • The input fields need to have a value, name, and onChange function, as well as a type and optionally placeholder.
      <input

         value={formData.firstName}

         name="firstName"

         onChange={handleInputChange}

         type="text"

         placeholder="First Name"

      />
    • Therefore the next step is to create that function:
      const handleInputChange = (e) => {

      // [1]
         const { name, value } = event.target

      // [2]
         setFormData({

      // [3]
            ...formData,

      // [4]
            [name]: value,

         })

      }
      1. We need to track the data being entered in, so we need an input parameter of e or event. Console logging e.target would return the element that the function is attached to, i.e. the input field. We want to access the name and value of that element specifically though, so we need event.target.name and event.target.value. When destructured, that displays as below.
      2. The next step is to update the state based on data the user enters. We are accessing the user data with the destructured event.target.name and event.target.value consts above. So now insert them into the setFormData().
      3. The spread operator is used so that the formData previously stored isn't overwritten, just added to.
      4. Because this function is being used in all input fields, it has to be written dynamically so that one field's data doesn't update all the states. We want to alter specifically the key value pair that's stored inside of [name].
    • This code gets the name of the input field being altered, finds the matching object key of the formData state, then updates it (while keeping the previous data - thanks to the spread operator), with the value of the same input field.
    • To create a form submission event, create an element that runs a function that handles form submission, and then write a function that does whatever you want with the formData.
    • It's important to keep using event.preventDefault() even in React to prevent the form from refreshing the page.

    Axios

      Axios is a NPM package used to make API calls. It simplifies the fetch function a little by removing the need to parse the .json data received. It needs to be installed with npm i axios and imported with import axios from "axios"; as normal with a NPM package.
    • A common way of using axios is to create an API.js file inside a utils folder in the react project. Inside that the function can be exported as an object method as shown below:
    • const baseURL = API Base URL;

      const apiKey = API Key;


      export default {

         search: function(query) {

            return axios.get(baseURL + query + apiKey)

         },

      }
    • The next step is to call this method in whichever component you want to run the API search on. You can use .then to handle the received data asynchronously.
      API(or whichever file the method was exported from).search(query)

         .then((res) => whatever you want to do with the data)

         .catch((err) => console.log(err));
    • This can be combined with forms, props and states to create a dynamic API searching component.

    React Router

    • Conditional rendering is the selective rendering of components based on conditions. There are 2 main ways to do this, a) using the ternary operator to render one thing if truthy and another if falsy; or b) using an if-else statement.
    • React Router simulates page navigation by conditionally rendering different components.
    • Therefore the faster way to do this in React is with React Router. React Router is another NPM package, install with npm i react-router-dom and then add import { BrowserRouter as Router, Route, Routes } from "react-router-dom"; to App.jsx.
    • function App() {

         return(

            <Router>

               <elementName>
      // [1]
               <Routes>
      // [2]
                  <Route path="path" element={elementName} />
      // [3]
                  <Route path="path/*" element={elementName} />
      // [4]
      1. This element renders all the time, as it is outside of the Routes component.
      2. The Route elements are wrapped inside a Routes component.
      3. THe path is the thing that displays in the browser URL. The element is what conditionally renders and is written as an element inside curly brackets, e.g.: element={<Home />}.
      4. To define a route that will have descendant routes, use /* inside the path.
    • React router also contains 2 other common components: NavLink and Link, which can be imported via import { NavLink, Link } from "react-router-dom"; inside a JSX file. These replace anchor tags for navigating within the same website. If you wanted to navigate to another site, then a regular anchor tab could be used.
    • The key difference between NavLink and Link is that NavLink tracks the active page, which is useful for styling, whereas Link doesn't. The easiest way to do this is to use a ternary statement in the className of a NavLink element, e.g.:
      <NavLink

         to="path"

         className={({isActive}) =>

            isActive ? "nav-link active" : "nav-link"

         }
      All this does is put className as (in this example) nav-link active if isActive returns truthy, or just nav-link if falsy.
    • The basic pattern with using React Router is to set up Routes on the homepage, and to use NavLink or Link anywhere else within the project.

    Netlify Deployment

    Title

    Title

    Title

    Title

    Title

    Title

    Title

    Title

    Title

    Title