'How to prepending before icon for text area for React component
I'm trying to implement React version of TextArea which appends "$" every-time I press Return/Enter.
I'm having hard time prepending a sign # or % or dollar every time someone presses enter. How can I go about this?
This is my basic attempt but I'm kind of lost:
const MyComponent = () => {
const [name, setName] = React.useState('');
return (
<div>
<textarea value={name} onChange={(e) => { setName(e.target.value) }} />
</div>
);
}
ReactDOM.render(<MyComponent />, document.getElementById('root'));
Solution 1:[1]
Ok so I had a bit of time on my hands and thought this could be a good learning experience. So I present to you: MoneyInputList
import './css/MoneyInputList.css'
import { useState, useEffect } from 'react';
let latestAdded = 'moneyInputList-input-0';
let lastSize = 0;
const MoneyInputList = () => {
const [recordList, setRecordList] = useState({data: ['']});
const handleFormSubmit = (e) => {
e.preventDefault();
if(recordList.data[recordList.data.length-1] !== ''){
setRecordList({data: [...recordList.data, '']})
}
};
useEffect(() => {
if(lastSize !== recordList.data.length)
document.getElementById(latestAdded).focus();
lastSize = recordList.data.length;
}, [recordList]);
return (
<form autoComplete='off' onSubmit={handleFormSubmit}>
<div className="main-container">
{recordList.data.length > 0 &&
recordList.data.map((record, iter) => {
latestAdded = "moneyInputList-input-"+iter;
return (
<div key={"moneyInputList-field-"+iter} className="record-field">
<div className="record-sign">$</div>
<input className="record-input" id={"moneyInputList-input-"+iter} value={recordList.data[iter]} onChange={(e) => {
if(e.target.value === '' && iter !== recordList.data.length-1){
let modifiedData = [];
recordList.data.forEach((e,i) => {
if(i !== iter)
modifiedData.push(e);
});
setRecordList({data: modifiedData});
return;
}
const filteredValue = e.target.value.split('').filter(e=>(e.charCodeAt() >= '0'.charCodeAt() && e.charCodeAt() <= '9'.charCodeAt()));
let formattedValue = [];
filteredValue.forEach((elem, i) => {
if((filteredValue.length - i) % 3 === 0 && i !== 0)
formattedValue.push(',');
formattedValue.push(elem);
});
formattedValue = formattedValue.join('');
e.target.value = formattedValue;
let myData = recordList.data;
myData[iter] = e.target.value;
setRecordList({data: myData});
}} type="text"/>
</div>
)})
}
</div>
<input style={{flex: 0, visibility: 'collapse', height: 0, width: 0, padding: 0, margin: 0}} type="submit"/>
</form>
)
}
export default MoneyInputList;
This component should do what you need it to do. It is not the best code but it works. You can see it working here. Of course you might still need to change some stuff in order for it to fit in your codebase and maybe implement redux, but the general idea is here. You use it by typing whatever number you want pressing enter will create a new line and deleting the content of a line will remove it.
Solution 2:[2]
I hope I understood correctly what you are trying to do, here is a super scuffed version of it. You might need to change the code a bit to fit your use case.
import { useState, useEffect } from "react";
export default function App() {
const [textValue, setTextValue] = useState("");
const [displayedTextValue, setDisplayedTextValue] = useState("");
useEffect(() => {
let splitTextValue = textValue.split("\n");
splitTextValue = splitTextValue.map((line, iter) => {
if (iter === splitTextValue.length - 1) return line;
if (line[0] !== "$") return "$ " + line;
return line;
});
setDisplayedTextValue(splitTextValue.join("\n"));
}, [textValue]);
return (
<div>
<textarea
value={displayedTextValue}
onChange={(e) => {
setTextValue(e.target.value);
}}
/>
</div>
);
}
Solution 3:[3]
Here is a version working with key event that I think is cleaner when handling thing depending on keys.
Here is the repro on Stackblitz and here is the code :
import React from 'react';
import { render } from 'react-dom';
const App = () => {
const enterKeyCode = 'Enter';
const backspaceKeyCode = 'Backspace';
const [val, setVal] = React.useState('$ ');
const [keyCodeEvent, setKeyCodeEvent] = React.useState();
React.useEffect(() => {
if (keyCodeEvent) {
// Handle numpad 'enter' key
if (keyCodeEvent[0].includes(enterKeyCode)) {
setVal(`${val}\n$ `);
} else if (keyCodeEvent[0] === backspaceKeyCode) {
setVal(`${val.slice(0, -1)}`);
} else {
setVal(`${val}${keyCodeEvent[1]}`);
}
}
}, [keyCodeEvent]);
return (
<div>
{/* Empty onChange to prevent warning in console */}
<textarea onKeyDown={e => setKeyCodeEvent([e.code, e.key])} value={val} onChange={() => {}} />
</div>
);
};
render(<App />, document.getElementById('root'));
I read your comments on Marko Borkovi? 's answer so I handled the backspace but he is totally right when saying you should build a special component for this. It will be way easier to improve and cleaner. You are not safe from some others bugs if you want to add features to your component.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | |
| Solution 2 | Marko Borkovi? |
| Solution 3 | Quentin Grisel |
