Table of contents
Hello World!
The "Tabulator key" commonly known as the "Tab" key has multiple purposes, depending on where it is being used. For example, if we hit the tab key in a spreadsheet, it would move the cursor one cell rightwards. If we hit it in a text editor or word processor, it would indent the cursor horizontally rightwards. If we hit tab in a form, it would move to the next form element. If we hit it on a random website, it would move to the next accessible element on the page (like a button or a text field). Likewise, the functionality of the tab key varies from place to place. In this article, let's see how we can override the tab functionality in a react form's text area.
Scenario
Let's consider we have a form in a react app, which contains fields to get input from users. We have a scenario, where we need to write a python code inside a text area component. We know that indentation is very important in python and a wrong indentation would break the functioning of code (If you don't know why indentation is important, check this article). But if we hit the tab button in a text area, instead of adding horizontal indentation, it would move to the next accessible element on the page. To overcome this, we will have to override the functionality of the tab key.
Implementation
Actual behaviour
Let us create a normal text area component in a react app and try to hit the tab key. Below would be the implementation:
import React, { useState } from "react";
const NormalTextArea = () => {
// State to store value typed in text area
const [val, setVal] = useState("");
return (
<textarea value={val} onChange={(e) => setVal(e.target.value)}></textarea>
);
};
export default NormalTextArea;
When we run the code and hit on the tab button, instead of adding indentation, it would move to the next accessible element on the page like this.
Override the tab functionality
To insert horizontal indentation on the tab press, we will need to override the default behaviour of the tab key. We will write a handleTab
function and call it when onKeyDown
event is triggered. We also need to modify the value
state to cursor position and target element. The value state would be changed like this:
const [value, setValue] = useState({ value: "", position: -1, target: null });
The handleTab
function would be like this:
const handleTab = (e) => {
// Storing the content
let content = e.target.value;
// Storing current cursor position
let position = e.target.selectionStart;
// Checking if key pressed is tab
if (e.key === "Tab") {
// Preventing default behaviour of tab key.
e.preventDefault();
// Inserting horizontal indentation (4 spaces in this case) in respective cursor position
let newText =
content.substring(0, position) +
" ".repeat(TAB_SPACE) +
content.substring(position);
// Setting value, position and target after indentation.
setValue({
value: newText,
position: position + TAB_SPACE,
target: e.target
});
} else {
// if key pressed is not tab, just updating position and target.
setValue((pr) => ({
...pr,
position,
target: e.target
}));
}
}
Since the structure of value
is changed, we will have to write a function to store value when the text area value is changed. The function would be like this:
const handleKeyChange = (e) => {
setValue({
value: e.target.value,
position: e.target.selectionStart,
target: e.target
});
};
Now, whenever the val
is changed, we will have to change the position of the cursor accordingly. For that, we can create a ref for the text area and set the selectionStart
with value.position
. We can do this in an useEffect
like this:
useEffect(() => {
textRef.current.selectionStart = value.position;
}, [value]);
The text area component would be like this:
<textarea
ref={textRef}
onKeyDown={handleTab}
value={value.value}
onChange={handleKeyChange}
></textarea>
Now we will be able to add indentation on the tab click.
The final code would be:
import React, { useRef, useState, useEffect } from "react";
const TabTextArea = () => {
const TAB_SPACE = 4;
// Storing value, position and element in state
const [value, setValue] = useState({ value: "", position: -1, target: null });
// Track the value change and updating the cursor position
useEffect(() => {
textRef.current.selectionStart = value.position;
}, [value]);
// Function triggered when text area values changed
const handleKeyChange = (e) => {
setValue({
value: e.target.value,
position: e.target.selectionStart,
target: e.target
});
};
// Function to handle tab key press
const handleTab = (e) => {
let content = e.target.value;
let position = e.target.selectionStart;
if (e.key === "Tab") {
e.preventDefault();
let newText =
content.substring(0, position) +
" ".repeat(TAB_SPACE) +
content.substring(position);
setValue({
value: newText,
position: position + TAB_SPACE,
target: e.target
});
} else {
setValue((pr) => ({
...pr,
position,
target: e.target
}));
}
};
// Ref created for text area
const textRef = useRef();
return (
<textarea
ref={textRef}
onKeyDown={handleTab}
value={value.value}
onChange={handleKeyChange}
></textarea>
);
};
export default TabTextArea;
Here is the implementation of the code:
Hope this is useful to you. Looking forward to your feedback and suggestions. Thank you!