Lambda School: Full Stack – Unit 2, Sprint 5: Components I

Components are reusable pieces of code, usually fulfilling a single, specific function. With regard to web applications, JavaScript doesn’t have true standalone components like other languages, but instead relies on an amalgamation of independent JavaScript, HTML and CSS to create reusable code. Components serve to help keep code DRY (Don’t Repeat Yourself) and in other languages (or libraries and frameworks) are generally self-contained files with all necessary elements for their functionality included.

In general, if there’s a process that’s being done repeatedly in a web application, or a group of elements that are repeatedly being rendered onscreen in different places, those are good candidates for conversion to a component, so they can be called as needed and not have their code duplicated throughout an application or website.

The most complex JavaScript component that I’ve written for myself so far is the one that renders project details onto the page in my portfolio. Previously, all of the projects were added individually using HTML and were styled with CSS/LESS. When I refactored my portfolio to use JavaScript, I reduced the amount of repeated code significantly by creating a component (really, a function) using JavaScript that rendered an HTML block onscreen (.createElement) using a line of data from an array which had details for the given project. The component rendered the HTML, and each element included an appropriate class, where necessary, which allowed it to be styled using CSS.

So, basically, each project in my portfolio, which previously needed about 8 lines of HTML code (multiplied by my current 15 projects, with an additional 5 that I need to finish and then add to the portfolio) was reduced to a single line of data in an array, which was consumed by a function that gets it rendered onscreen. Updating them is now a centralized process, with regard to code. The only time I might have to individually update projects is if I need to modify the actual data in the array for a given project – which I did when I added a project type under each project’s name.

My next iteration will redo it using React, to make it a true self-contained component, instead of one which relies on external CSS for positioning and styling – and to turn it into a single-page application (SPA).

components-I-1

HTML & CSS

In the Lambda School Training Kit, creating buttons is used as an example of when to write a component. We’re given some sample code like this HTML:

<div class="custom-buttons">
  <button>Button 1</button>
  <button>Button 2</button>
  <button>Button 3</button>
  <button>Button 4</button>
</div>

What we have here is essentially the same code (and any associated CSS) being repeated to create 4 buttons. The only difference is possibly the button’s name. It’s a good candidate for conversion into a component. To accomplish this, the first thing that’s needed, in this case, is adding custom classes to the buttons and the <div>, so that they can be easily styled later on.

<div class="custom-buttons">
  <button class="custom-btn">Button 1</button>
  <button class="custom-btn">Button 2</button>
  <button class="custom-btn">Button 3</button>
  <button class="custom-btn">Button 4</button>
</div>

We’re advised to use custom classes that are specific to the elements in the component. The rationale is that if the CSS is very specific, its easy to move the component around (even to other programs) without accidentally impacting other page elements that might be the same type(s) or have similar class names.

Components are also saved in their own files, instead of occupying space in a file with other code. We’re advised that if we’re using a preprocessor (like LESS) a common practice is to name the file after the component. It can then be imported into the main component’s file for easy usage, like so:

@import custom-btn.less

components-I-2

JavaScript

In a component, JavaScript consumes data and outputs content to the DOM, generally via a function. Component functions make use of methods such as .createElement, .textContent, .classList, .appendChild and .addEventListener to attach HTML elements to nodes on the DOM and make them interactive. For example, the HTML buttons above could be created via a JS component like this:

let button = document.createElement('button');
button.textContent = 'Button 1';
button.classList.add('button');
button.addEventListener('click', (e) => {
    console.log('clicked!');
});
parent.appendChild(button);
  • .createElement creates a new HTML element and stores it in memory. It can later be placed on the DOM.
  • .textContent places (or displays) specific text in an element.
  • .classList is used to add or remove a class from an element. This can dynamically alter an element’s appearance, or even control whether or not it appears onscreen at all.
  • .appendChild attaches a new HTML element to the end of an existing element onscreen.
  • .prepend is a companion method that places a new HTML element before the specified element.
  • .addEventListener, of course, lets elements respond to user interactions, allowing input from devices, such as mouse clicks, keypresses, hover effects and more.

So, in the above example, a new button element is created. Its given the name “Button 1”. The “button” class is added to it, and an event listener is added that will console.log the word “clicked!” when the button is clicked. Finally, the button is added to the page, appended below an existing button, already onscreen.

2019.09.04 01

Functions

Now that we see how JavaScript can be used to create an HTML button, and place it onscreen, the above function can be modified more to make it a true function, which is reusable, DRY and can even accept input to name each button something unique:

//button creator component
function buttonCreator(buttonText){
    const button = document.createElement('button');
    button.textContent = buttonText;
    button.classList.add('button');
    button.addEventListener('click', (e) => {
        console.log('clicked!');
    });
    return button;
}

//create two buttons and place them onscreen
let firstButton = buttonCreator('Button 1');
let secondButton = buttonCreator('Button 2');
parent.appendChild(firstButton);
parent.appendChild(secondButton);

2019.09.04 02

Creating components from data

At times, we will need to create components from data in an array or other object or data source (like an API). As mentioned above, my upgraded portfolio uses data from an array to render projects onscreen. An example with two lines of data from it (the cancer charity website and a Star Wars project for Lambda) looks like this:

const featuredProjects = [
    {img: "img/breast-cancer-comfort.jpg", name: "Breast Cancer Comfort", type: "Client Website", visit: "https://breast-cancer-comfort.netlify.com/", view: "https://github.com/vishalicious213/bcc", desc: "Breast cancer charity in Long Island, NY. Formerly WordPress site. Built webpage with HTML/CSS/LESS. Navigation bar written with JavaScript.", stack: ["js", "html", "css", "less"]},
    {img: "img/react-wars.jpg", name: "React Wars", type: "Sprint Challenge V: React", visit: "https://vish213-reactwars.netlify.com", view: "https://github.com/vishalicious213/6.5-Sprint-Challenge-React-Wars", desc: "Used axios to access the Star Wars API, then used React components, state and side effects to render information about Star Wars characters. Imported and rendered images separately.", stack: ["js", "react", "axios"]},
]

Previously, all of the data (above) for each project was its own block of HTML and all of them reused the same CSS. Now, its essentially a single line of data (an object) inside of an array of objects.

We can feed data from an array into the buttonCreator function from above to programmatically create as many buttons as we need for a web page. For example, given the array:

const data = [
    "Button One",
    "Button Two",
    "Button Three",
    "Button Four"
]

We can use .forEach or .map to loop through each item in our array and pass the data to a callback function. .forEach will create each element and immediately add it to the DOM:

data.forEach((arrayItem) => {
  let newButton = buttonCreator(arrayItem);
  parent.appendChild(newButton);
});

.map returns a new array, based on the output from our callback function. We can then use the array as needed:

let newComponents = data.map((arrayItem) => {
  let newButton = buttonCreator(arrayItem);
  // Remember, we always need to return something when we use .map
  return newButton;
});

Once we have the array, It can be manipulated further, or added to the DOM with .forEach, like this:

newComponents.forEach(component => {
  parent.appendChild(component);
});

2019.09.04 04

One thought on “Lambda School: Full Stack – Unit 2, Sprint 5: Components I

Leave a comment