Handling form with single state in React for web app - reactjs

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.

Related

unable to edit pre populated textboxes using react

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.

How can I use a parameter variable's literal value inside a Javascript function in React JS?

I am creating a form using React JS and I want some of the sections in the form to be dynamically added/removed using +/- button(s). I have created a custom Hook "inputs" which is a collection (object) of the data input from all the sections in the form. inputs contains an array for a section that I want to be dynamically added/removed. e.g., workInfos is one such section:
const [inputs, setInputs] = useState({workInfos: [],
educationInfos : [],
basicInfo : {},
skillInfo : {}});
I have created a button to add the workInfo section dynamically and assigned a function "handleDynamicAddition" to its onClick property:
<Button className = "button" variant="contained" color="secondary" style = {buttonStyle} onClick = {props.handleDynamicAddition()} >+</Button>
handleDynamicAddition function looks like:
const handleDynamicAddition = () => {
setInputs((inputs) => ({...inputs, workInfos : [...inputs.workInfos, {}]}));
}
This works fine as long as I use it for just one section.
I want to use the same function for all the sections that I want to add/remove dynamically, say, educationInfos.
For different buttons, I can pass the specific section that I want to add dynamically using that button, as a parameter to handleDynamicAddition but how do I use this parameter's actual value inside setInputs?
Just off the top of my head, you try something like this
Button:
<Button
className="button"
variant="contained"
color="secondary"
style={buttonStyle}
name={/*one of the objects keys as a string, like: "workInfos", "educationInfos", etc.*/}
onClick={handleDynamicAddition}
>
+
</Button>
Handle:
const handleDynamicAddition = (evt) => {
const {name} = evt.currentTarget;
setInputs((inputs) => ({
...inputs,
[name]: [...inputs[name], {}]
}));
};
This pulls the "name" attribute from the clicked button and uses that to help assign "added sections" to the inputs object.
Kinda want to add this. If the form is pretty complicated, you're probably going to want to split things up. It's nice to have everything be "all in one place" but this sorta code should only really be used with primitive values like: [name]: value where value is a string or number. If you try to force this kind of thing to work with "objects inside of objects inside of arrays" it's going to become really unreadable compared to if you just broke things down and split them up.
Also, you might want to use a useReducer here, or even maybe look into Redux, instead of a useState. Depending on how complicated the application is going to get.

React - toggle css class but do not rerender everything

I have a Gatsby website and I want to use it to show flashcards. I have found multiple existing solutions online, but I would like to try it a different way. However, I am completely stuck.
Desired result:
Show one random word at a time. When the users clicks on a button 'show', the meaning of the word should be shown.
Issue:
I can generate a random combination of the words. But when the user clicks on 'show', a new combination is loaded instead of the answer. I understand why this happens. However, I do not understand how to fix this.
I have tried this (stripped to the bare essentials):
export const Flashcard = () => {
const [flip, setFlip] = useState(false)
const onButtonClick = () => {
setFlip(!flip)
}
let word = words[GenerateRandomNumber(0, words.length - 1)] // words is an array containing all the possible combinations
return (
<div className={`card ${flip ? "flip" : ""}`}>
<div className="front">
{word["language1"]}
</div>
<div className="back">
{word["language2"]}
</div>
<button onClick={onButtonClick}>Show answer</button>
</div>
)
}
export default Flashcard
I feel like I am on a totally wrong path, but don't see how to fix it. Should I structure my logic different? Should I use some other hooks? Or anything else?
In addition, I would also like the user having the possibility to say he knew the meaning (clicking on a button 'correct') or not (button 'wrong'). If correct, a new word is shown, if wrong, the word is stored to be shown later again. How should I trigger this logic?
I think you should add a state to store the picked word
const [word, setWord] = useState("");
and When you do a click you need to get a random word and store in word
useEffect(() => {
const randomNumber = GenerateRandomNumber(0, words.length - 1);
const newWord = words[randomNumber];
setWord(newWord);
}, [flip]); //This useEffect will call everytime when flip change.
in return you can refer the word
{word["language1"]}
here is a demo

Summing information from API/divs

I got access to the API and I stored them using useState. I have a question - how can I easily sum values of two divs together in React? I don't want to use jQuery, I want it to be nice and clean. Any ideas? Here you can see a part of the relevant code. Maybe useRef?
const [details, setDetails] = useState({})
const search = evt => {
if (evt.key === "Enter") {
fetch(`${api.base}forecast?q=${query}&units=metric&APPID=${api.key}`)
.then(res => res.json())
.then(result => {
setDetails(result);
setQuery('');
console.log(result);
return result;
})
----
<div className="detail--icon">Morning: {Math.round(details.daily[1].temp.morn)}°c</div>
<div className="detail--icon">Day: {Math.round(details.daily[1].temp.day)}°c</div>
<div className="detail--icon">Night: {Math.round(details.daily[1].temp.night)}°c</div>
<div className="detail--icon">Humidity: {details.daily[1].humidity}%</div>
Not sure what do you mean by sum values of divs, but given that the content you're showing in the repeating divs differs quite a bit (Math.round is not used everywhere, suffixes are different, data is not part of a single object, e.g. first three are part of details.daily[1].temp, but last one is part of details.daily[1]), I'd suggest two options:
A helper function which accepts the content between the tags (content param will be JSX):
const getDetail = (content) => {
return (
<div className="detail--icon">{content}</div>
)
}
And use it like:
{getDetail(<>Morning: {Math.round(details.daily[1].temp.morn)}°c</>)}
Note that you need to put <> and </> in-between to make it a valid JSX (this is shorthand for React.Fragment).
Or a small component (probably slightly less amount of code):
const Detail = (props) => (
<div className="detail--icon">{props.children}</div>
)
...
<Detail>Morning: {Math.round(details.daily[1].temp.morn)}°c</Detail>
You can go further by storing all of the content of the rows in an array of JSX elements and do a Array.map on it, but I'm not sure how readable that would be.
Another thing apart from the markup refactoring I'd suggest is to store details.daily[1] or details.daily[1].temp in a variable before rendering and just write daily or daily.temp where necessary.

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