๐ŸงฑComponents

Basic building blocks of a React app.

Components

Components are the core building block of React apps. Actually, React really is just a library for creating components in its core.

A typical React app therefore could be depicted as a component tree - having one root component ("App") and then an potentially infinite amount of nested child components.

Each component needs to return/ render some JSX code - it defines which HTML code React should render to the real DOM in the end.

JSX is NOT HTML but it looks a lot like it. Differences can be seen when looking closely though (for example className in JSX vs class in "normal HTML"). JSX is just syntactic sugar for JavaScript, allowing you to write HTML-ish code instead of nested React.createElement(...) calls.

When creating components, you have the choice between two different ways:

  1. Functional Components

const Cmp = () => {
  return <div> Some JSX </div>;
}
  1. Class-based Components

class Cmp extends Component {
  render() {
    return <div> Some JSX </div>
  }
}

It's best practice to use functional components as much as possible.

The create-react-app template structure

  • node_modules: contains all the project dependencies

  • public: contains all the compiled and bundled project files

  • [.gitignore, package.json, README.md, yarn.lock]: listing out dependencies, project build, dev/env. Lock file for version control management. Ignore file is to exclude things from remote repo.

  • src: contains all the styles and components

App.js
import React, {Component} from "react"
import "./styles.css"

const App = () => {
  return (
    <div className="App">
      <h1>Hello React</h1>
      <h2>This is a react web page!</h2>
    </div>
  );
}

export default App;
Index.js
import ReactDOM from "react-dom"
import App from "./app"

const rootElement = document.getElementById("root")
ReactDOM.render(<App />, rootElement);

In the index.js file, the root element is defined in the index.html, which is present in the public folder. The above ReactDOM.render() function call will bind the <App /> component (defined in the app.js file) to the rootElement.

NOTE: The render() function call can have only 1 main <div> element, and we have to nest everything inside of this one main <div> element. This is somewhat loosened in React v.16^

But it is best to practice to have only 1 root element or component.

Understanding JSX

Consider the example in app.js file:

App.js
import React, {Component} from 'react'
import './style.css'

const App = () => {
  return (
    <div className="App">
      <h1>Hello React</h1>
      <h2>This is a React App</h2>
    </div>
  );
}

export default App;

If you notice the <div> component, the className is not the usual HTML syntax, it's JSX. So, at the back, this is not exactly how it is compiled.

The <div className="App"> JSX code is converted to:

return React.createElement(div, null, 'h1', 'Hello React');

// h1 Hello React

In the React.createElement() function, the parameter list is:

  • div: This is the first parameter, which translates to which element (or a React component) should be rendered in the DOM.

  • null: This is the second parameter, which is the config for the JS object.

  • h1, 'Hello React': The parameters after the first two are referred to as children. These children components or elements are the ones to be nested inside the div in the first parameter (which could also be a React component). There can be infinite number of children.

But in the above example, the h1 tag is not rendered as an HTML h1 tag, it's rendered as plain text. So, as mentioned above, we have to nest this element inside the main div element, like so:

return React.createElement(div, {className: 'App'}), React.createElement(h1, null, 'Hello React'));

// <h1>Hello React</h1>

So here, instead of null in the second parameter, we pass the JSX element property of className , and then nest the h1 element within the div.

This is what JSX get compiled to by the build tools included in the create-react-app. Hence, this is why we need to import React, {Component} from 'react' even though we never use it in our JSX code.

React Components

Functional Components

import React, {Component} from 'react'
const Comp = () => {
  return(
    <div>Some JSX</div>
  );
}

Class Components

import React, {Component} from 'react'
class Comp extends Component {
  render() {
    return(
      <div>Some JSX</div>
    )
  }
}

Dynamic content in components

props

Attribute values that we pass to a component to render dynamic data.

Example:

<Person name='Sid' age='25' />
<Person name='Max' age='26' />

// In the component
const Person = ({name, age}) => {
  return (
    <p> I am {name} and {age} years old</p>
  );
}

In the above example, I have used object de-structuring to make the code more "readable". If you're not familiar with object de-structuring, you can use the other syntax as well:

const Person = (props) => {
  return(
    <p>I am {props.name} and {props.age} years old</p>
  );
}

This, obviously is tedious to write since we have to use props.<propertyName> every time we need to access the property of that component. So, it's always better to use object de-structuring whenever possible.

The children prop

If there is any data between the opening and closing tags of a React component, we call those data types as children props.

Example:

<Person name="Sid" age="25">Hobbies: Art</Person>

In our React component:

const Person = ({name, age, children}) => {
  return (
    <div>
      <p>I am {name} and {age} years old</p>
      <p>{children}</p>
    </div>
  );
}
export default Person

'children' is a reserved keyword in React, so if there are no child components within the parent component, i.e, between the opening and closing tags of the outer component, then children will be null.

state

The state of a component is managed from within the component.

NOTE: These are available only in class based components, which extend the Component class from react.

State is a reserved keyword and is of type Object.

Example:

state = {
  persons: [
    {name: 'Sid', age:'26'}
    {name: 'Max', age:'27'}
    {name: 'Dennis', age:'28'}
  ]
}

To access the state properties on the components:

<Person name={this.state.persons[0].name} age={this.state.persons[0].age} />

Handling events with methods

Refer to Event Handling page on this notebook for more events that we can listen to.

Example:

switchNameHandler = () => {
  console.log('clicked')
}

<button onClick={this.switchNameHandler}>Switch Name</button>

Manipulating the state

Class based components

state = {
  persons: [
    {name: 'Sid', age:'26'}
    {name: 'Max', age:'27'}
    {name: 'Dennis', age:'28'}
  ],
  otherState: 'some other state value'
}

switchNameHandler = () => {
  this.setState({
    persons: [
      {name: 'Sid', age:'27'}
      {name: 'Max', age:'27'}
      {name: 'Dennis', age:'29'}
    ]
  });
}

The setState() function is provided by React Component, which we import initially.

In the setState(), we only change a part of the original state, then the updates to the original state are merged from the eventHandler, w

Functional Components

React Hooks are the way in which we manipulate state in functional components in React.

There are many hooks, but we will use the useState hook to manipulate state in the functional components here.

NOTE: We can have as many useState hooks in our React functional component.

The useState hook returns an array with exactly 2 elements:

  • 1st element is the current state

  • 2nd element is a function that allows us to update the state

import React, {useState} from 'react'

NOTE: We can use array de-structuring here to capture both the elements that the useState hook provides us with.

const [personState, setPersonState] = useState({
  persons: [
    {name: 'Sid', age:'26'}
    {name: 'Max', age:'27'}
    {name: 'Dennis', age:'28'}
  ],
  otherState: 'some other state value'
})

Let's implement the eventHandler

const switchNameHandler = () => {
  setPersonState({
    persons: [
      {name: 'Sid', age:'27'}
      {name: 'Max', age:'27'}
      {name: 'Dennis', age:'29'}
    ]
  });
}

In hooks, we do not use the this keyword to call the event handler

<button onClick={switchNameHandler}>Switch Name</button>

//add props to Component

<Person name={personState.persons[0].name} age={personState.persons[0].age}/>

NOTE: When we are updating the state using the useState() hook, we need to manually update or add the other data as well, because this hook replaces the whole state, and does not merge the state like back in this.setState()

console.log(personState) //before clicking 'Switch Name'
/*
    persons: [
    {name: 'Sid', age:'26'}
    {name: 'Max', age:'27'}
    {name: 'Dennis', age:'28'}
    ],
    otherState: 'some other state value'
*/
console.log(personState) //after clicking 'Switch Name'
/*
    persons: [
    {name: 'Sid', age:'27'} --> age updated
    {name: 'Max', age:'27'}
    {name: 'Dennis', age:'29'} --> age updated
    ],
    ~~otherState: 'some other state value'~~ --> state property is lost
*/

To retain otherState within the state when using useState hook, we need to manually add the state property when manipulating the state:

const switchNameHandler = () => {
  setPersonState({
    persons: [
      {name: 'Sid', age:'27'}
      {name: 'Max', age:'27'}
      {name: 'Dennis', age:'29'}
    ],
    otherState: 'some other state value'
  });
}

Passing method reference b/w components

We can also pass methods ( as a reference) as props to components!

const switchNameHandler = () => {
  //perform state manipulation
}

<Person
  name={this.state.persons[0].name}
  age={this.state.persons[0].age}
  click={this.switchNameHandler}
/>

In the Person component:

import React, {Component} from 'react'
const Person = ({name, age, click, children}) => {
  return (
    <p onClick={click}>
      Hi! I am {name} and {age} years old.
      {children}
    </p>
  );
}

In the above example, we passed a function or method reference to a Person component, which does not have direct access to the state, but still manipulates the state in the app.

Two way binding in components

Listen for text input and change the name of the person accordingly. Also show the initially set name in the text field.

App.js
nameChangeHandler = (event) => {
    this.setState({
    persons: [
        {name: 'rahul', age:'28'},
        {name: event.target.value, age: 28},
        {name: 'dennis', age: 29}
    ]
    })
}

render() {
  return(
    <Person 
      name={this.state.persons[1].name}
      age={this.state.persons[1].age}
      change={this.nameChangeHandler}
    />
  );
}
Person.js
const Person = ({name, age, change, children}) => {
  return (
    <div>
      <p>
        I am {name} and {age} years old
        {children}
      </p>
      <input type="text" onChange={change} value={name}/>
    </div>
  );
}

Styling React Components

  • Using stylesheets: This is same as using the basic HTML, CSS and JS web page, where we have all our stylesheets in a separate file, and then we inject these files into the component.

    NOTE: The styles via external files are global.

We import the stylesheet like so:

import './style.css'

Webpack is a tool that helps it recognize and compile it behind-the-scene.

  • Inline styles: For inline styles, we create a style object inside the component, inside the render() method call:

render() {
  const style={
    backgroundColor: 'white',
    font: 'inherit',
    border: '1px solid black',
    padding: '8px',
    cursor: 'pointer'
  }
  return (
    <button style={style}> Click Me</button>
  );
}

NOTE: All the styles in inline styles are camelCase. Styles that are inline have a scope limited to that component itself!

Stateless v/s Stateful Components

It does not mean if a component is a class-based component, it is a stateful component, and if a component is a functional component, it is a stateless component. But, this was the scenario before the introduction of React hooks in v16.

So, a stateful component is the one, which implements the useState() or state , and the one that doesn't, is a stateless component.

Class based v/s Functional Components

Class Based

class XY extends Component {...}
  • Has access to state

  • Uses Lifecycle Hooks

Access to state and props via this keyword.

this.state.XY
this.state.props

NOTE: Unless you want to manage state or access to lifecycle hooks, do not use class based components!

Functional Components

const XY = () => {...}
  • Access to state using useState()

  • No lifecycle hooks

Access to props via props

props.XY

NOTE: This should be the preferred way to create components and state management, without the use of lifecycle hooks!

Last updated