React | NextJS

11.1 Events, Passing Functions & Props.children

Module Still Under Development

# Synthetic Events

When building a standard webpage and working with the DOM and JavaScript events, the best practice is to use the addEventListener method to connect events to DOM elements.

With React, the best practice is to avoid targeting DOM elements. We want the virtual DOM and the React rendering engine to control what elements exist on the page.

So, if we are NOT working with references to HTML elements, how do we attach event listeners?

React has Synthetic Events. These are created by adding "attributes" into our JSX elements.

function hello(ev) {
  //ev is the Synthetic Event object
  console.log('hello!');
}

export default function MyComponent() {
  function goodbye(ev) {
    //ev is the Synthetic Event object
    console.log('hasta la vista.');
  }

  return (
    <>
      <div onClick={hello}>Click to log a message.</div>
      <div onClick={goodbye}>Click to log another message.</div>
    </>
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

Inside the same script file, either inside or outside the component function, you can have functions. These functions can be connected to the JSX elements with the Synthetic event attribute, like onClick. As long as the function you are calling is within an accessible scope you can call it from your Synthetic event.

JSX vs HTML

In HTML our goal is to avoid adding inline script attributes and inline event listeners. We want to use addEventListener in our main script that is shared with every website page.

In JSX, we are using synthetic event listeners like onClick. These are NOT event listeners that get added to the HTML. The JSX is transpiled into JavaScript that monitors the events in the most efficient way.

Notice that there are no double quotes and no parentheses, after the function name, inside the curly braces. When the JSX is parsed, React converts onClick into an actual event listener and assigns the name of the provided function to that listener.

Here is a list of some common Synthetic events:

onKeyPress, onKeyDown, onKeyUp,
onFocus, onBlur,
onInput, onSubmit, onChange,
onClick, onContextMenu, onMouseOver, onMouseOut,
onSelect, onScroll, onTouchStart, onTouchEnd,
1
2
3
4
5

The Synthetic events will contain many of the same properties and methods as a standard JS event - preventDefault(), target, stopPropagation(), timestamp, type, etc.

Here is the reference list of the different React Event Objects (opens new window)

If you want to pass another parameter to your function, you need to wrap the function with a bind.

<button onClick={hello.bind(this, someNum, props.name)}>Click me</button>
1

Alternatively, you can add the values that you want to pass from inside the first function.

export default function MyComponent(props) {
  let someNum = 123;
  const [name, setName] = useState(props.name);

  function f1(ev) {
    //call another function and pass in the event, a variable, and a state value
    f2(ev, someNum, name);
  }
  function f2(ev, someNum, someName) {
    //this function has all three arguments
  }
  return <></>;
}
1
2
3
4
5
6
7
8
9
10
11
12
13

This means that you can change the button click from the first example to this:

<button onClick={(ev) => hello(someNum, props.name)}>Click me</button>
1

The official guide to interactivity and events in React (opens new window)

# Passing Functions through Props

As we have already seen, we can pass objects and values from a parent component to a child component through props. In JavaScript, functions are first class objects. They can be assigned to variables and passed around like any other object.

So, this means that we can declare a function in a parent component and then pass it down into child components. You can pass it as many layers deep as you need and then attach it to a Synthetic event in the child component.

Here is an example with parent and child components. The function is declared in the parent component and passed through props to the child. The child component has the synthetic click event and will trigger the function from the parent.

//Parent.js
import Child from 'Child.js';

export default function Parent() {
  const [num, setNum] = useState(0);

  function count(ev) {
    //increment the num variable
    setNum(num++);
  }

  //pass a reference to the count function through an attribute to the Child component
  //Parent will show the current value of num
  return (
    <div>
      <p>{num}</p>
      <Child func={count} />
    </div>
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

Above is the example Parent component and below is the Child component. Parent has a state variable called num and displays its current value. Parent also has the function count which is responsible for adding one to the current value of num. The count function gets passed through props to the Child component.

# Props children

Guide to passing JSX as children in props (opens new window)

When you are writing JSX elements and you add a child component that has been imported, you can nest other JSX inside the imported component, just like you would nest any HTML.

import SomeComponent from './SomeComponent.js';

export default function ParentComponent(props) {
  //just like all components, this one is passed a props object
  //in the return object, the first SomeComponent has no children
  //the second SomeComponent does.
  return (
    <>
      <SomeComponent />
      <SomeComponent>
        <h2>A Heading</h2>
        <p>first paragraph</p>
        <p>second paragraph</p>
        <p>third paragraph</p>
      </SomeComponent>
    </>
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

In the example above there are two instances of the imported component <SomeComponent>. The first one has no children. The second one does have three nested paragraphs.

The props object that gets passed into each component has a property called children. The children property contains everything that is nested inside. You can use destructuring to extract the children directly.

export default function SomeComponent({ children }) {
  //destructure the props object
  return <>{children}</>;
}
1
2
3
4

The React.Children object is a utility object for working with props.children. It has a .count property (number of child elements), a .forEach() method, a .map() method, and an .only property which is true if there is only one child of the component.

In the following example we will output a custom message if there were no nested children OR a numbered paragraph for each nested child.

//SomeComponent.js
import React from 'react';

export default function SomeComponent(props){
    //props.children holds the nested contents
    //React.Children is the same thing
    return (
        {Children.count === 0 ?
            <p>Nothing inside SomeComponent</p> :
            Children.map( (child, index) => {
                //output something for each nested child of <SomeComponent>
                return <p>{index}</p>;
            })
        }
    );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

Looking Ahead

This semester you are learning React for building SPA and then Next.js for multi-page web applications.

There is also a library called React Router which allows you to layer multi-page routing onto a React Application.

In level 3 you will be learning how to use React to build Android and iOS applications. We will be using a framework called React Native which adds a layer of routing and navigation on top of React.

In React, when building web sites, we are using react-dom to convert the JSX objects and virtual DOM into actual HTML in the browser.

In React Native there are different React JSX components that get converted from the JSX into actual mobile device interface components by the react-native framework.

# PropTypes

When passing prop values from a parent component to a child component, it would make sense to do validation on the data being passed.

React has a module called PropTypes that let's us check for the existence of and datatype of anything being passed through props.

To use PropTypes we will need to import it. (The capitalization is important)

import { PropTypes } from 'prop-types';

export default function MyComponent(props) {
  return <div></div>;
}
1
2
3
4
5

Once you have it in your page then it can look at all the props as they are passed into your component.

Outside your component function you can define an object that lists the possible props properties and defines their data-types.

//again, this is case sensitive
MyComponent.propTypes = {
  titles: PropTypes.array.isRequired,
  random: PropTypes.bool.isRequired,
  getNewList: PropTypes.func, //yes, we can pass functions as props
};
1
2
3
4
5
6

Again, NOTE the capitalization of the two different PropTypes. The one that is a property of the component starts with a lowercase letter. All the prop values inside the schema object use the uppercase version that we imported.

Reference for PropTypes (opens new window)

There are lots of different data-types and methods in the imported PropTypes object. You can also add .isRequired after the data-type to say that the specific property is required.

PropTypes.string;
PropTypes.number;
PropTypes.bool;
PropTypes.array;
PropTypes.object;
PropTypes.element; //a React component
PropTypes.symbol;
PropTypes.node; //a DOM element
PropTypes.any.isRequired; //any datatype but required
PropTypes.arrayOf(PropTypes.number); //all array items are numbers
PropTypes.oneOf(['a', 'b', 'c']); //an enumeration
PropTypes.oneOfType([PropTypes.number, PropTypes.string]);
PropTypes.shape({
  id: PropTypes.number,
  name: PropTypes.string,
}); //define a schema for an object
PropTypes.exact({
  name: PropTypes.string,
  quantity: PropTypes.number,
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

And with PropTypes we can also provide default values for the properties. This way, if a property is missing from props the default value will be set before the validation is done.

// Specifies the default values for props:
MyComponent.defaultProps = {
  name: 'Ricky Bobby',
  id: 0,
};
1
2
3
4
5

Missing Props

If a prop, which isRequired, is actually missing, the Rendering of your app will be stopped and you will see error messages on the screen in the browser as well as in the browser extension - React Dev Tools.

# Alternative to PropTypes

Another thing that you will come across when working with React Projects is TypeScript. TypeScript is not really another language. It is a superset of JavaScript, just like SASS is a superset of CSS.

Imagine having a new version of JavaScript that included types for functions and variables. When you declare a variable you have to explicitly say what type of data it will hold. When you declare a function you have to explicitly say what type of data its parameters will be and what type of data it will return. That is what TypeScript is - a layer on top of JavaScript that adds types to variables and functions.

So, if you are using TypeScript, then you don't need to use PropTypes as the validation of props and params will already be handled.

When you use TypeScript as the primary language for the project, then your files become .ts instead of .js or .tsx instead of .jsx.

Typescript also needs to be converted back from TypeScript into JavaScript before it can run in the browser.

It means when you run the npm run dev command Vite will be doing that conversion back into JS.

TypeScript is meant for the developer, not the end user or the browser.

# What to do this week

TODO Things to do before next week.

Last Updated: 5/16/2024, 3:31:21 PM