React Events
Basic of Events
Event Types:
- MouseEvent:
- User interactions with a pointing device (such as a mouse).
- KeyboardEvent:
- User interactions with the keyboard.
- TouchEvent:
- User interactions with touch-based devices.
- WheelEvent:
- User interactions with a mouse wheel or similar input device.
- DragEvent:
- User interactions with drag-and-drop operations.
Element Types:
- HTMLElement: Represents a generic HTML element. Specific HTML elements extend this base type.
- HTMLInputElement: Represents an
<input>
element. - HTMLSelectElement: Represents a
<select>
element. - HTMLOptionElement: Represents an
<option>
element. - HTMLButtonElement: Represents a
<button>
element. - HTMLTextAreaElement: Represents a
<textarea>
element. - HTMLAnchorElement: Represents an
<a>
element. - HTMLImageElement: Represents an
<img>
element. - HTMLCanvasElement: Represents a
<canvas>
element. - HTMLDivElement: Represents a
<div>
element. - HTMLSpanElement: Represents a
<span>
element.
Syntax
React.ChangeEvent<HTMLInputElement>
React.MouseEvent<HTMLButtonElement>
React.FormEvent<HTMLFormElement>
React.MouseEvent<T>
React.MouseEvent<T = HTMLButtonElement>
React.MouseEvent<T extends HTMLElement = HTMLButtonElement>
React.SyntheticEvent<HTMLSelectElement>
React.SyntheticEvent<T>
Related
function useCallback<T extends Function>(callback: T, deps: DependencyList): T;
children: React.ReactNode;
Examples
Input box input
interface MyComponentProps {
onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
}
const MyComponent = ({ onChange }: MyComponentProps) => {
const onChange = (event: React.ChangeEvent<HTMLInputElement>) => {
console.log(event.target.value);
}
return <input onChange={onChange} />;
};
onClick
interface MyComponentProps {
onClick: (event: React.MouseEvent<HTMLButtonElement>) => void;
}
const MyComponent = ({ onClick }: MyComponentProps) => {
const onClick = (event: React.MouseEvent<HTMLButtonElement>) => {
console.log(event.target.value);
}
return <button onClick={onClick}>Click Me</button>;
};
Submit form
interface MyComponentProps {
onSubmit: (event: React.FormEvent<HTMLFormElement>) => void;
}
const MyComponent = ({ onSubmit }: MyComponentProps) => {
const onSubmit = (event: React.FormEvent<HTMLFormElement>) => {
preventDefault();
console.log(event.target.value);
}
return <form onSubmit={onSubmit}>Click Me</form>;
};
onSelect
interface MyComponentProps {
onSelect: (event: React.SyntheticEvent<HTMLSelectElement>) => void;
}
const MyComponent = ({ onSelect }: MyComponentProps) => {
const onSelect = (event: React.SyntheticEvent<HTMLSelectElement>) => {
console.log(event.target.value);
}
return <select onSelect={onSelect}>Click Me</select>;
};
Typing event handlers with generics
import React from "react";
interface MyComponentProps<T> {
onClick: (event: React.MouseEvent<T>) => void;
}
const MyComponent = <T extends HTMLElement>({ onClick }: MyComponentProps<T>) => {
const handleClick = (event: React.MouseEvent<T>) => {
console.log(event.currentTarget.value);
onClick(event);
};
return <button onClick={handleClick}>Click Me</button>;
};
currentTarget refers to the element that the event listener is attached to, while target can be a descendant of the element. Since we're attaching the listener to the button element, using currentTarget ensures getting the correct value.
Typing event handlers with generics and default type
interface MyComponentProps<T = HTMLButtonElement> {
onClick: (event: React.MouseEvent<T>) => void;
}
const MyComponent = <T extends HTMLElement>({ onClick }: MyComponentProps<T>) => {
return <button onClick={onClick}>Click Me</button>;
};
Typing event handlers with generics and default type and default props
interface MyComponentProps<T = HTMLButtonElement> {
onClick: (event: React.MouseEvent<T>) => void;
}
const MyComponent = <T extends HTMLElement = HTMLButtonElement>({ onClick }: MyComponentProps<T>) => {
return <button onClick={onClick}>Click Me</button>;
};
Handling clicked buttons
Button Component
Make sure you have a button component in your React component's render method. It can be created using the <button>
HTML element or a UI library component like Button from Material-UI or Button from Ant Design.
Event Handler
Define an event handler function that will be called when the button is clicked. This function will handle the logic you want to execute when the button is clicked.
Function Binding
When defining the event handler function, ensure that you bind the correct context to it. In JavaScript classes, this can be done using the bind method in the constructor, or by using arrow functions to automatically bind the correct context.
Event Prop
Pass the event handler function as a prop to the button component. For example, if your event handler function is called handleClick, you can pass it as a prop like this: <button onClick={handleClick}>Click Me</button>
.
Function Signature
Define the function signature of the event handler. In TypeScript, you can specify the event type by using the React.MouseEvent generic. For example, handleClick(event: React.MouseEvent<HTMLButtonElement>)
.
Event Object
Access event properties within the event handler function. For example, you can access the target element using event.target.
Prevent Default
If you want to prevent the default behavior of the button (e.g., form submission), you can call event.preventDefault() within the event handler.
State Update
Inside the event handler function, you can update the component's state if necessary. Use the setState function or state hooks like useState or useReducer to update the state.
Pass Arguments
You can pass additional arguments to the event handler by using arrow functions or by using bind to create a new function. For example, <button onClick={() => handleClick(arg1, arg2)}>Click Me</button>
.
useCallback
To optimize performance, consider using the useCallback hook to memoize the event handler function, especially if it's used in child components or passed as a dependency to other hooks.
Calculator button click example
// ParentComponent.tsx
import React from 'react';
import Calculator from './Calculator';
const ParentComponent = () => {
const digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
const handleDigitClick = (digit: number) => {
// Handle the digit click in the parent component
console.log(`Clicked digit: ${digit}`);
};
return (
<Calculator digits={digits} onDigitClick={handleDigitClick} />
);
};
export default ParentComponent;
// Calculator.tsx
import React from 'react';
interface CalculatorProps {
digits: number[];
onDigitClick: (digit: number) => void;
}
const Calculator = ({ digits, onDigitClick }:Calculator) => {
return (
<div>
{digits.map(digit => (
<button key={digit} onClick={() => onDigitClick(digit)}>
{digit}
</button>
))}
</div>
);
};
export default Calculator;
Calculator example with reducer
import React, { useReducer } from 'react';
interface CalculatorState {
currentValue: string;
result: number;
}
type CalculatorAction =
| { type: 'APPEND_DIGIT'; digit: string }
| { type: 'ADD' }
| { type: 'SUBTRACT' }
| { type: 'EQUAL' };
const calculatorReducer = (state: CalculatorState, action: CalculatorAction): CalculatorState => {
switch (action.type) {
case 'APPEND_DIGIT':
return { ...state, currentValue: state.currentValue + action.digit };
case 'ADD':
return {
currentValue: '',
result: state.result + parseInt(state.currentValue),
};
case 'SUBTRACT':
return {
currentValue: '',
result: state.result - parseInt(state.currentValue),
};
case 'EQUAL':
return {
currentValue: '',
result: state.result,
};
default:
return state;
}
};
const Calculator: React.FC = () => {
const [state, dispatch] = useReducer(calculatorReducer, { currentValue: '', result: 0 });
const handleDigitClick = (digit: string) => {
// Append the clicked digit to the current value
dispatch({ type: 'APPEND_DIGIT', digit });
};
const handleAdditionClick = () => {
// Perform addition
dispatch({ type: 'ADD' });
};
const handleSubtractionClick = () => {
// Perform subtraction
dispatch({ type: 'SUBTRACT' });
};
const handleEqualClick = () => {
// Perform calculation and display the result
dispatch({ type: 'EQUAL' });
};
return (
<div>
<div>Result: {state.result}</div>
<div>Current Value: {state.currentValue}</div>
{[0, 1, 2, 3, 4, 5, 6, 7, 8, 9].map((digit) => (
<button key={digit} onClick={() => handleDigitClick(String(digit))}>
{digit}
</button>
))}
<button onClick={handleAdditionClick}>+</button>
<button onClick={handleSubtractionClick}>-</button>
<button onClick={handleEqualClick}>=</button>
</div>
);
};
export default Calculator;
Calculator example with Redux RTK
// store.ts
import { configureStore } from '@reduxjs/toolkit';
import calculatorReducer from './calculatorSlice';
const store = configureStore({
reducer: {
calculator: calculatorReducer,
},
});
export default store;
// calculatorSlice.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
interface CalculatorState {
currentValue: string;
result: number;
}
const initialState: CalculatorState = {
currentValue: '',
result: 0,
};
const calculatorSlice = createSlice({
name: 'calculator',
initialState,
reducers: {
appendDigit: (state, action: PayloadAction<string>) => {
state.currentValue += action.payload;
},
add: (state) => {
state.result += parseInt(state.currentValue);
state.currentValue = '';
},
subtract: (state) => {
state.result -= parseInt(state.currentValue);
state.currentValue = '';
},
equal: (state) => {
state.currentValue = '';
},
},
});
export const { appendDigit, add, subtract, equal } = calculatorSlice.actions;
export default calculatorSlice.reducer;
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { appendDigit, add, subtract, equal } from './calculatorSlice';
const Calculator: React.FC = () => {
const currentValue = useSelector((state: RootState) => state.calculator.currentValue);
const result = useSelector((state: RootState) => state.calculator.result);
const dispatch = useDispatch();
const handleDigitClick = (digit: string) => {
dispatch(appendDigit(digit));
};
const handleAdditionClick = () => {
dispatch(add());
};
const handleSubtractionClick = () => {
dispatch(subtract());
};
const handleEqualClick = () => {
dispatch(equal());
};
return (
<div>
<div>Result: {result}</div>
<div>Current Value: {currentValue}</div>
{[0, 1, 2, 3, 4, 5, 6, 7, 8, 9].map((digit) => (
<button key={digit} onClick={() => handleDigitClick(String(digit))}>
{digit}
</button>
))}
<button onClick={handleAdditionClick}>+</button>
<button onClick={handleSubtractionClick}>-</button>
<button onClick={handleEqualClick}>=</button>
</div>
);
};
export default Calculator;
Calculator example with Tests
- install relevant libraries
npm install --save-dev jest react-testing-library @reduxjs/toolkit @testing-library/react @testing-library/jest-dom
Create a file named
Calculator.test.tsx
in the same directory as the Calculator.tsx component.Import dependencies /
import React from 'react';
import { render, fireEvent } from '@testing-library/react';
import { Provider } from 'react-redux';
import store from './store';
import Calculator from './Calculator';
describe('Calculator', () => {
it('should render the calculator with initial values', () => {
const { getByText } = render(
<Provider store={store}>
<Calculator />
</Provider>
);
// Check if initial result is rendered
expect(getByText('Result: 0')).toBeInTheDocument();
// Check if initial current value is rendered
expect(getByText('Current Value:')).toBeInTheDocument();
});
it('should update current value when digit buttons are clicked', () => {
const { getByText } = render(
<Provider store={store}>
<Calculator />
</Provider>
);
// Click digit buttons
fireEvent.click(getByText('1'));
fireEvent.click(getByText('2'));
fireEvent.click(getByText('3'));
// Check if current value is updated
expect(getByText('Current Value: 123')).toBeInTheDocument();
});
it('should perform addition when plus button is clicked', () => {
const { getByText } = render(
<Provider store={store}>
<Calculator />
</Provider>
);
// Click digit buttons
fireEvent.click(getByText('1'));
fireEvent.click(getByText('2'));
fireEvent.click(getByText('+'));
fireEvent.click(getByText('3'));
fireEvent.click(getByText('='));
// Check if result is updated after addition
expect(getByText('Result: 15')).toBeInTheDocument();
});
it('should perform subtraction when minus button is clicked', () => {
const { getByText } = render(
<Provider store={store}>
<Calculator />
</Provider>
);
// Click digit buttons
fireEvent.click(getByText('7'));
fireEvent.click(getByText('5'));
fireEvent.click(getByText('-'));
fireEvent.click(getByText('2'));
fireEvent.click(getByText('='));
// Check if result is updated after subtraction
expect(getByText('Result: 73')).toBeInTheDocument();
});
});
npm test