0

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'));
halfer
  • 19,824
  • 17
  • 99
  • 186
TechnoCorner
  • 4,879
  • 10
  • 43
  • 81

3 Answers3

1

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>
  );
}
Marko Borković
  • 1,884
  • 1
  • 7
  • 22
  • Thank you. Just one question right now we are adding `$` when we press enter. Is it possible to add `$` before we press enter? secondly, if i am entering numbers is it possible to add `commas` to the number? (like 30,000) etc. – TechnoCorner Jul 08 '21 at 19:21
  • Yeah it is possible, you can do it by removing this line: `if (iter === splitTextValue.length - 1) return line;` and to edit the numbers you would need to check if the line is a valid number and then add commas every 3 positions in the string excluding the last position. – Marko Borković Jul 08 '21 at 19:22
  • awesome. one last thing, when I enter something press return/enter, go back to edit it, it doesn't seem to like it. How can we can fix this? This is awesome @Marko Borković – TechnoCorner Jul 08 '21 at 19:29
  • I think useEffect runs everytime there is a change and lets say we edit something that's not last line, the cursor keeps jumping towards the last line :( .. Creation experience is great, but editing experience isnt. How can we get around this? – TechnoCorner Jul 08 '21 at 19:32
  • Well this is one of the problems of using textArea and just modifying its content, I would recommend creating a custom component that basically adds `$` as a separate `div` next to every line of input. I would also recommend keeping each line separate in order to make data modification easier. At this point it gets really complex to fix all these small bugs that are much easier to solve by simply using a better "design" of the component. – Marko Borković Jul 08 '21 at 19:38
1

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.

Quentin Grisel
  • 4,794
  • 1
  • 10
  • 15
  • Thanks so much for your response. I did try your stackblitz but it looks like the backspace editing problem still exists which makes it not very usable for me :( appreciate your responses tho. – TechnoCorner Jul 09 '21 at 16:52
  • @TechnoCorner What do you mean ? It seems to works correctly for me : I press backspace (to erase a character), and it works well. Could you detail me what happen please so I can try to fix it maybe ? – Quentin Grisel Jul 09 '21 at 16:56
  • Appreciate your response. if you enter 100, press enter and enter 200 and try to go back and edit 100. The cursor jumps back to last line and I can't even edit 100. Thanks for your help @Quentin Grisel – TechnoCorner Jul 10 '21 at 20:34
  • @TechnoCorner Ok well there's no problem with backspace, I suppose you were talking about arrowLeft... It's actually pretty annoying to deal with since after fixing the catch of these arrow keys, you need to deals with the fact that backspace (erase key) set your cursor to the last char of the input because of the refresh of the component. That's exactly the kind of problem you want to avoid by creating your own component (as you already saw with Marko's second answer). Even if it's probably not the cleanest, it seems to works well ;) – Quentin Grisel Jul 11 '21 at 21:09
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.

Marko Borković
  • 1,884
  • 1
  • 7
  • 22
  • This is awesome!! wow!! Only one thing that i actually see is that, whenever we enter something go back to previous line, edit it (works fine). However, to get back to last line, I can't use my keyboard. I would have to click the last line manually (which is kinda annoying). Thank you so so much for your help @Marko Borković this is why I love SO. – TechnoCorner Jul 09 '21 at 17:12
  • 1
    @TechnoCorner In order to implement the changing of lines using the up/down keys you can setup a keyboard event and then simply focus on the one above or below depending on the key. Also don't forget to mark the thread as answered :) – Marko Borković Jul 09 '21 at 17:22
  • Yes definitely will do!. you're awesome!! one last question. Since you are using `Input` to keep track of each line it's easier, however if i want to copy paste something (Like a comma seperated string for dollars), how can i do that? It was easier with `TextArea` but it's not possible because of `input` .. please help @Marko Borković – TechnoCorner Jul 10 '21 at 02:30
  • 1
    @TechnoCorner I am pretty sure react has an `onPaste` method, you could process the pasted text and format it accordingly. After that you can remove the pasted text from the current input and add the formatted values to `recordList.data` – Marko Borković Jul 10 '21 at 11:32
  • I'd really appreciate if you can write some comments on the code since it's super imperative and I'm having hard time understanding some of the logical pieces. @Marko Borković (especially `filteredValue`) – TechnoCorner Jul 10 '21 at 19:19
  • I just got the copy paste working. one last question, how do i get "select all" working? Since these are seperate inputs, is it possible to do a selectAll? Appreciate some inputs @Marko Borković upvoted all your answers – TechnoCorner Jul 10 '21 at 20:45
  • Just following up to see if you had any thoughts? on the copy paste section. I've tried using https://stackoverflow.com/a/61217786/1625557 however that didn't work either :( @Marko Borković – TechnoCorner Jul 13 '21 at 16:09