unable to edit pre populated textboxes using react - reactjs

I have a form that has textboxes that are prepopulated from an WebAPI. When I try to delete the text in the textbox to make a change it doesn't delete the prepopulate text. If I try to type over the text, I can see only the first letter of the word I'm typing in the console, but nothing changes on the UI: It' like the textbox is in readonly mode WHICH IT IS NOT
const Details = () => {
const [ server, setServer] = useState([]);
useEffect(() = > {
getServerNames();
}
const getServerName = async() => {
//gets the list of server and their details from the API
}
const serverNameChange = (e) => {
setServer(e.target.value);
}
return (
<div>
{ details.map((data) => {
<input type="text" name="server" onChange={serverNameChange} value={data.serverName} />
))}
</div>
)
};
What am I missing to allow the users to edit the textbox? The textbox is prepopulated with data, however, it can be changed. This is only happening on textboxes that are prepopulated. I don't want to click an Edit button, I want to give the user the ability to make a change in the textbox and then save it.

That might be due to the fact, that data.serverName never changes. It’s basically a static value. If you set the value of an Input, you have to handle the changes (when typing) in the onchange event.
From what I assume, according to your code is that you have multiple input boxes with preloaded values in them and you want to change your serverName if one of them get changed by the value that is in the textinput.
If so, map your details into a state variable:
const [serverNames, setServerNames] = useState(details.map( data => data.serverName));
Map the inputs from your state variable like so:
{serverNames.map((name,index) => {
< input type="text" name="server" onChange={(e) => {updateServerState(e, index)}} value={serverNames[index]} />
}
}
And your updateServerState method looks like that:
updateServerState(e, index) {
let myStateData = [...serverNames];
myStateData[index] = e.target.value;
setServerNames(myStateData);
setServer(e.target.value);
}
Caution: I haven‘t tested the code, just wrote it down. But that should give you an idea of how to solve your issue.
TL;DR; Never use non-state variables for a dynamic value.

Related

React event listener callback functions don't use updated states

When accessed from reDraw the resizableList is an empty array, when accessed from addImageClick it shows the actual array. the TextInput element contains a text input which calls eventBus.dispatch('addtext') on change.
So, after I add an image, I have two TextInput elements, and two Resizable elements. I change the text in one of the TextInput elements, empty state array. I trigger the add image button which logs the array before resetting it, array has two elements in it before reset.
import React,{useState,useRef,useEffect} from "react";
import Resizable from "./Resizable";
import TextInput from "./TextInput";
import eventBus from "../eventbus/EventBus";
export default function Meme(){
const [image, setImage] = useState(new Image());
const [resizableList, setResizableList] = useState([]);
const hiddenFileInput = useRef(null);
const cvs = useRef(null);
function reDraw(){
const width = 600*(image.width/image.height);
const ctx = cvs.current.getContext("2d");
ctx.clearRect(0, 0, 1200, 600);
ctx.drawImage(image,(1200-(width))/2,0,width,600);
console.log(resizableList);
for (let i=0;i<resizableList.length;i++){
let el = document.getElementById(`${i}--textbox`);
console.log('test');
}
console.log('ran');
console.log(image);
}
function addResizable(width){
let resizable = {
cvs:cvs,
imgwidth:width,
image:image
}
setResizableList((prevResizableList)=>[...prevResizableList, resizable]);
}
function draw() {
const width = 600*(image.width/image.height);
const ctx = cvs.current.getContext("2d");
console.log(image.width);
ctx.clearRect(0, 0, 1200, 600);
ctx.drawImage(image,(1200-(width))/2,0,width,600);
addResizable(width);
addResizable(width);
};
function addImageClick(e){
e.preventDefault();
console.log(resizableList);
setResizableList([]);
hiddenFileInput.current.click();
};
useEffect(()=>{
eventBus.on('addtext',reDraw);
image.onload = draw;
console.log('useeffect');
},[])
return (
<main>
<form className="form">
<div className='form--textboxes'>
{resizableList.map((item,index)=>{
return (<TextInput cvs={item.cvs} image={item.image} imgwidth={item.imgwidth} key={index} id={index}/>)
})}
<button className='form--button--textboxes'>Add Text</button>
</div>
<button className="form--button" onClick={addImageClick}>Add Image</button>
<div className="form--image">
<canvas id='meme' ref={cvs} className="form--meme" height="600" width="1200"></canvas>
{resizableList.map((item,index)=>{
return (<Resizable cvs={item.cvs} imgwidth={item.imgwidth} key={index} id={index}/>)
})}
</div>
</form>
<input
type="file"
name="myImage"
style={{display:'none'}}
ref={hiddenFileInput}
onChange={(event) => {
image.src = URL.createObjectURL(event.target.files[0]);
}}
/>
</main>
)
};
I apologize if the explanation above is too long. So far I've been able to get by just by reviewing questions, it's the first time I actually had to write one.
Update: What I basically want to do is loop over the resizableList array, and update the text on the canvas based on the position and size of each resizable. I know how to do that but I can't, because when I access the array from the reDraw function, the array shows as empty. I tried looking for alternatives but I couldn't find any. I create an id for each textbox/resizable of format: arrayindex--textbox and arrayindex--box. That's how the textboxes and resizables relate to eachother.
2nd Update: Sooooooo...
function addTextClick(e){
e.preventDefault();
console.log(resizableList);
}
function reDraw(){
const width = 600*(image.width/image.height);
const ctx = cvs.current.getContext("2d");
ctx.clearRect(0, 0, 1200, 600);
ctx.drawImage(image,(1200-(width))/2,0,width,600);
console.log(resizableList);
button.current.click();
}
As I said previously, reDraw is triggered by an event fired by the text input's onChange. I also made this button which has the addTextClick as it's onClick. And yes, I trigger input's onchange, I get one empty array followed by one length 2 array(which is the resizableList array) in my console. ?????
I guess this kinda fixes my problem as I can just create a hidden button but this is more of a workaround and I would still like to understand why this is happening.
Updated with solution:
So, I ran into this same issue while working on some other functionality of my webapp.
The problem is that when you add an event listener the callback function is set with the state values present at the time. So it doesn't matter how many times your states are updated after setting the event listener, it's still going to use the states it had when it was set up.
Funny enough, after understanding what was causing the problem I found some posts from others running into this problem and using exactly the same workaround I used above, which is to create a reference to a hidden button and have the event listener's callback function trigger the onClick.
Another solution is to use the useEffect hook and recreate the event listener every time a state is updated, but in my case that doesn't really work that well since I have a lot of states that are updated constantly.

Handling form with single state in React for web app

I have an idea to create simple web app for my own needs. I would like user to be able to use picklist to define number of form fields needed (ex. 1 - 10). Picklist would create chosen number of components with text inputs. User can type string into text input and all data would be stored as single state. I could do it just simply saying string1, string2 and having 10 states but I wonder if this would be doable as single state because I would like all text inputs to create single string in form of token.
Example:
User choose 3 text inputs:
hi
hello
goodbye
This would form token of {hi/hello/goodbye}.
Please let me know how to handle that with React.
Thanks.
Probably you can solve this using an array of objects inside your state and latter assessing to the values. I'm not sure what do you want to do, but if you want to create an app that generates dynamically inputs and after set the values in the corresponding state, you can do something like this.
export default function DinamicLinks() {
const [quantity, setQuantity] = useState(0)
const [messages, setMessages] = useState([])
const createInputs = (quantity) => {
const params = []
for (let i = 1; i <= quantity; i++){
let param = {name:i};
params.push(param)
}
return params
}
const handleMessages = (passedMessage) => {
const filteredMessages = messages.filter((message) => message.name !== passedMessage.name)
setMessages([...filteredMessages, passedMessage])
}
return (
<div className="App">
<button onClick={() => setQuantity(quantity + 1)}>More inputs</button>
<button onClick={() => setQuantity(quantity - 1)}>Less inputs</button>
{createInputs(quantity).map(({name}) => <input key={name} onChange={(e) => handleMessages({name, message:e.currentTarget.value})}></input>)}
<button onClick={() => console.log(messages)}>See messages</button>
</div>
);
}
This code will go to generate inputs dynamically and will go to store his values inside an state, that store objects inside an array, later you can implement the logic to handle the text in the way you whatever you want. By the way, this code isn't the best in performance, but I want to give you and easy solution quickly. Undoubtedly there are better ways to do this, and also is valid to mention, don't abuse the arrow functions like I did in this example, are an easy ways to have trouble of performance in React.

How to trigger onchange only when the content changes in draft js editor?

The onchange method in draft js is executed by the Editor when edits and selection changes occur. But I want to call trigger onchange only when the user is writing or the content changes and not on focus or selection. Is there any way I can compare the previous and current editor state?
You cannot do exactly that because draft-js keep selection state of your mouse inside the editor state, onChange must be triggered, and you must update the editor state, but you check for the old and new editor state plain text and know for sure if some characters are added or deleted
const [editorState,setEditorState]= useState(EditorState.createEmpty());
function onChange(newEditorState: EditorState) {
const currentPlainText = editorState.getCurrentContent().getPlainText();
const newPlainText = newEditorState.getCurrentContent().getPlainText();
setEditorState(newEditorState);
if (currentPlainText.trim() !== newPlainText.trim()) {
/**
* do what you want knowing that you have different content in the editor
*/
}
}
I use undoStack.size property to detect the content change. It works great.
https://draftjs.org/docs/api-reference-editor-state/#undostack
onEditorStateChange = (editorState) => {
const changed = !!editorState.getUndoStack().size
...
}

Control Focus of Multiple FormControl Fields

I have a mapped list of input fields:
<FormControl
name={row_index}
value={barcode.barcode}
placeholder="Barcode - then Enter"
onChange={this.onChange}
onKeyPress={this._handleKeyPress}
disabled={barcode.submitted}
/>
I am currently using onKeyPress to handle submit:
_handleKeyPress = (e) => {
if (e.key === 'Enter') {
const name = e.target.name;
const barcodes = this.state.barcodes;
const this_barcode = barcodes[name];
let apiFormatted = {"barcode": this_barcode.barcode, "uid": this.props.currentSession}
this.postBarcodeAPI(apiFormatted, name)
}
}
I am attempting to focus on the next input field after the current one is successfully submitted. React documentation has an example for manually setting focus on a single input field using ref={(input) => { this.textInput = input; }} />. I have tried using this[‘textInput’+‘1’].focus() (using computed property names, but am getting an error that function is invalid.
EDIT
Per Chase's answer, I am linking to the autofocus documentation, although it doesn't work in this case.
https://developer.mozilla.org/en-US/docs/Web/API/HTMLSelectElement/autofocus
My working solution:
const focusing = index === lastSubmittedIndex + 1 ? true : false;
const refText = focusing || index === 0 ? input => input && input.focus() : null;
<FormControl
name={row_index}
value={barcode.barcode}
placeholder="Barcode - then Enter"
onChange={this.onChange}
onKeyPress={this._handleKeyPress}
disabled={barcode.submitted || barcode.apiCalling}
inputRef={refText}
/>
What I corrected in my code:
1) I am supposed to use inputRef instead of ref for Bootstrap's FormControl component, see here.
2) I am using ilya-semenov's very neat code.
Update
I have other buttons on the page, when user presses them and is at bottom of page, page jumps up to top. Not sure why.
Unless you've set a ref with the key textInput1 then this won't work. Another suggestion would be to move all of the inputs into a separate component and then you can use this.props.children to traverse all your inputs and grab the input at whatever position you want.
EDIT:
You can also use the autoFocus prop to determine if an input should be focused or not.

How to get the selected text from text area in react?

I am trying to make a text editor in react.Does anyone knows how to get the selected text from the textarea so that styles can be applied on the selected text.I know we can use window.getSelection in javascript but I am curious to know If any other methods are available for this functionality?
Yes there is a method to do this, specially in React. The way you should go to achieve this is as follow.
step 1:- use ref in your textarea ui element. like
`<textarea
className='html-editor'
ref='myTextarea'
value = {this.state.textareaVal}
onChange={(event)=>{
this.setState({
textareaVal:event.target.value;
});
}}
>
</textarea>`
step 2:- now you can access the DOM element,using react refs.
let textVal = this.refs.myTextarea;
step 3:- use selectionStart and selectionEnd :- using selectionStart and
selectionEnd you can get to know your start and end pointer
of selected text.which can be done as below;
let cursorStart = textVal.selectionStart;
let cursorEnd = textVal.selectionEnd;
now you have start and end index of your selected text.
step 4 :- use javascript substring function to get the selected text.
this.state.textareaVal.substring(cursorStart,cursorEnd)
The best way to make a Text Editor in React is to use DraftJS.
If you are using React, DraftJS is the way to go about it. It abstracts away many of the challenges you would face while trying to create your own text editor from scratch. This includes managing the state of your editor (similarly to how you would manage a component's state), managing text selection, applying different attributes and so on.
You can get started by checking out the docs, and I would suggest watching the introduction video on that page, which goes through the difficulties DraftJS aims to solve.
I hope that helps.
How to do it in functional component? Expanding on the answer given by Sanjeev.
function MyEditor() {
const [state,setValue] = useState({value: ""});
//1
const myRef = React.createRef()
const inputsHandler = (e) =>{
var taxt = e.target.innerHTML
let textArray = taxt.split(/\n/gm)
console.log(textArray)
setValue( {value: e.target.value} )
}
const onDone = () => {
console.log("on done", stateNew)
dispatch(updateEditorVisibility())
// dispatch(props.reducer(state.value))
dispatch(stateNew.editorReducerAction(state.value))
}
return (
<div>
<textarea
type="text"
ref={myRef}
name="first_name"
onChange={inputsHandler}
value={state.value}/>
<button onClick={() => {
let textVal = myRef.current;
let cursorStart = textVal.selectionStart;
let cursorEnd = textVal.selectionEnd;
let selectedText = state.value.substring(cursorStart,cursorEnd)
console.log(selectedText)
}}>Log</button>
</div>
)}
Create a ref using create ref
const myRef = React.createRef();
set the ref in your textarea
ref={myRef}
To access use
let cursorStart = textVal.selectionStart;
let cursorEnd = textVal.selectionEnd;
let selectedText = state.value.substring(cursorStart,cursorEnd)
console.log(selectedText)````

Resources