State management in React Dynamic from JSON - javascript

I have a dynamic form templated this way:
class betClient extends Component(
state = {
.............
}
................
const ServerConfigDetails = this.state.ServerConfigDetail.map(field => {
let SecretFields = "access_token" || "api_token" || "private_token" || "password" || "security_token" || "client_secret" || "refresh_token" || "oauth_client_id" || "oauth_client_secret" || "developer_token"
if (field.name === SecretFields) {
return <SecretInput inputlabel = {
field.name == SecretFields ? field.name : null
}
placeholder = {
field.placeholder
}
/>
}
if (field.name === "start_date") {
return <SelectDateInput inputlabel = "Start Date" / >
}
if (field.name === "end_date") {
return <SelectDateInput inputlabel = "End Date" / >
}
// Here we template all other fields that aren't date or secret field
if (field.name !== SecretFields) {
return <TextInputs inputlabel = {
field.label == null ? field.name : field.label
}
placeholder = {
field.placeholder
}
/>
}
})
render() {
return (
<div>
<Modal>
<form>
{ServerConfigDetails}
</form>
</Modal>
</div>
)
}
)
Everything works fine, the form is rendered dynamically but I somehow don't know how to get the form inputs preferably as an object into a state without having to declare each field individually in the initial state. Any help with this?
Not sure if this is clear enough but I will gladly provide any extra information.

I would first suggest, we should decide how our state might look like -
{
selects: {
// With individual keys with default values
},
dynamic: {} // no key-value pairs at first
}
Then make all your components - controlled - you control what to set by passing a value prop (and finally passing to your native inputs)
const { selects, dynamic } = this.state;
// pass a value prop as selects.<key-name> or empty string
And finally add onChange handlers for both your select and dynamic input field components. (and finally passing to your native inputs)
onChangeSelectField (event, fieldName) {}
//Same for InputField
These handlers will handle state such that keys are added if not present with value from the event target; add replace (overwrite) current key-value if a new value is entered.
{
const currentDynamicData = this.state.dynamic;
const newData = { ... currentDynamicData, {... updatedData}}; // updatedData is newer key-value pair
this.setState({ dynamic: newData });
}
This one is way to go about it.

Related

How to type unique letters only in input text box in html or react?

I want to create an input box that allows to type only a distinct alphabet letter in the input box
( No duplicate alphabet value, ONLY ONE)
I looked up all the attributes for input box but I couldn't find one and there were no example.
Do I have to handle it within JavaScript functions?
(I am using React)
<input
className="App-Contain-Input"
name="containedLetter"
type={"text"}
onChange={containedChange}
/>
Here is how this can be done, note that we have to use onkeydown which fires when a key is being pressed, but not before it is being released (this way we can intercept and prevent the key stroke from being taken into account):
function MyInput() {
const containedChange = (event) => {
if (event.key.length === 1 && event.code.startsWith('Key') && event.code.length === 4 && event.target.value.indexOf(event.key) !== -1) {
event.preventDefault()
return false;
}
return true
}
return (
<input
id="input"
className="App-Contain-Input"
name="containedLetter"
type="text"
onkeydown={containedChange}
/>
}
A way to do it with a cheeky two-liner is to get rid of any non-letters with regex, then let Set do the de-duping by converting to Array => Set => Array => String:
As a side note, many of the other solutions posted here make one or both of two assumptions:
The user is only ever going to edit the last character... but what if they edit from the beginning or middle? Any last character solution will then fail.
The user will never copy/paste a complete value... again, if they do, most of these solutions will fail.
In general, it's best to be completely agnostic as to how the value is going to arrive to the input, and simply deal with the value after it has arrived.
import { useState } from 'react';
export default function Home() {
const [value, setValue] = useState('');
return (
<div>
<input
type='text'
value={value}
onChange={(e) => {
const onlyLetters = e.target.value.replace(/[^a-zA-Z]/g, '');
setValue(Array.from(new Set(onlyLetters.split(''))).join(''));
}}
/>
</div>
);
}
Your containedChange function can be like this:
const containedChange = (e) => {
const value = e.target.value;
const lastChar = value[value.length - 1];
if (value.slice(value.length - 1, value.length).indexOf(lastChar) != -1) {
// show error message
}
The function checks whether the last entered character already exists in the previous value or not.
An isogram is a word with no repeating letters. You can check this using regex
function isIsogram (str) {
return !/(.).*\1/.test(str);
}
Or using array.some
function isIsogram(str) {
var strArray = str.split("");
return !strArray.some(function(el,idx,arr){
return arr.lastIndexOf(el)!=idx;
});
}
In react - you need to use state to reject inputs which contain repeated letters (not an Isogram). Complete example on codesandbox.
import { useState } from 'react';
export default function App() {
const [ text, setText ] = useState('');
function isIsogram(str) {
var strArray = str.split("");
return !strArray.some(function(el,idx,arr){
return arr.lastIndexOf(el)!==idx;
});
}
function handleChange(e) {
const { value } = e.target;
console.log(isIsogram(value));
if (value.length === 1 || isIsogram(value)) {
setText(value);
}
}
return (
<input value={text} onChange={handleChange} />
);
}
Use a state to maintain the current value of the input. Then when the value changes get the last letter of the input, check that it's a letter, and if it is a letter and it isn't already in the state, update the state with the new input value.
const { useState } = React;
function Example() {
const [ input, setInput ] = useState('');
function handleChange(e) {
const { value } = e.target;
const lastChar = value.slice(-1);
const isLetter = /[a-zA-Z]/.test(lastChar);
if (isLetter && !input.includes(lastChar)) {
setInput(value);
}
}
function handleKeyDown(e) {
if (e.code === 'Backspace') {
setInput(input.slice(0, input.length - 1));
}
}
return (
<input
value={input}
onChange={handleChange}
onKeyDown={handleKeyDown}
/>
);
}
ReactDOM.render(
<Example />,
document.getElementById('react')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>
One pure js solution would be to handle the input value in onkeyup event where we have access to the latest key as well as updated input target value.
If the latest key pressed is duplicate then we will found it on 2 location in the target value, first on some where middle of the target value and second at the end of the target value. So we can remove the value at the end (which is the duplicate one).
The below code snippet shows the implementation of above in React
const Input = () => {
const handleKeyUp = (e) => {
const { key, target: { value }} = e;
const len = value.length;
if (value.indexOf(key) < len - 2) {
e.target.value = value.slice(0, len - 1);
}
}
return (
<div>
<label>Unique Keywords</label>
<input type="text" placeholder="my-input" onKeyUp={handleKeyUp} />
</div>
);
}

Array length is zero but has accessible elements

I would like to be able to store some form of dynamic structure in the state.
Each input element calls the same function "handleFormInput".
in "name" the input name is stored.
In "value" the actual value.
After each change these values are stored in the state.
If the user now clicks on the form button, the function "handleForm" is called. This function checks the state and reacts accordingly.
My problem: I check the length of the array, which is always 0.
If I output everything via Console.log, however, I see the elements in the array, also correctly. What have I not considered?
Called on every input change made
handleFormInput(event){
const target = event.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;
this.state.saveFormData[name] = value;
this.setState({
saveFormData : this.state.saveFormData
}, () => {console.log(this.state.saveFormData)});
}
Called when "Submitting"
handleForm(){
var l = 0;
this.state.saveFormData.forEach(element => {
l++;
});
console.log(l); // output: 0
if(this.state.saveFormData.length == 0){
this.setState({ openNew: false })
console.log(this.state.saveFormData);
}else{
console.log(this.state.saveFormData);
alert('we found Data' + JSON.stringify(this.state.saveFormData));
}
}
Ouput of console.log
Array []
​
length: 0
​
ort: "asd"
​
<prototype>: Array []
Working Example in a Nutshell
if you start with the input, the array is written to the state after each input. Stack Snippet already shows an empty array here. The console in the browser shows an array with one element but with a length of 0.
In the similar answers I find, it always has to do with the fact that the console does not map the exact moment of the array, but only shows a reference, and retrieves the data just in time when it is unfolded.
in my case it seems to behave differently. Since also in the console the array length of 0 is shown
function App() {
return ( < DataEditor / > );
}
class DataEditor extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [],
def: [],
defRaw: [],
loaded: false,
isLoading: false,
openNew: false,
editFormLoaded: false,
editFormData: [],
saveFormData: []
}
}
handleFormInput(event) {
const target = event.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;
this.state.saveFormData[name] = value;
this.setState({
saveFormData: this.state.saveFormData
});
}
handleForm() {
console.log("The Array Length is propably zero but we can access the element")
console.log(this.state.saveFormData.ort);
console.log("Log the testet Array")
console.log(this.state.saveFormData);
if (this.state.saveFormData.length == 0) {
this.setState({
openNew: false
})
console.log(this.state.saveFormData);
} else {
console.log(this.state.saveFormData);
alert('we found Data' + JSON.stringify(this.state.saveFormData));
}
}
render() {
return ( <
div >
<
input type = "text"
name = "ort"
onChange = {
(e) => this.handleFormInput(e)
}
/> <
button type = "button"
onClick = {
() => this.handleForm()
} > Test State Access and Array < /button> < /
div >
);
}
}
ReactDOM.render( < App / > , document.getElementById("root"));
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="root"></div>
This has nothing to do with react, it's simply that on the inside array is still an instance of an object and can have it's properties modified the same way, without actually adding them to the iterable options.
Example here:
const array = [];
array.ort = "test";
console.log(array); // []
console.log(array.length); // 0
console.log(array.ort); // "test"
I suggest using an object instead and iterating over it's values with Object.values or Object.entries.
const object = {};
object.ort = "test";
console.log(object); // {ort: "test"}
console.log(Object.values(object).length); // 1
console.log(object.ort); // "test"

How to add object to array and also remove it when user clicks?

when user presses a button am just adding the data to an state, and now when user represses it i need to remove the data / and do not let the data to be added to the array ? Am using this on react native !
My state;
state = { selected = [] };
Now the function to all data to state;
Handler(data){
cont {selected } = this.state;
if(!selected.includes(data))
{
this.setState({ selected : [...selected, data] })
}else{
const index = selected.indexOf(data);
if (index > -1) {
const selection = selected ;
selection.splice(index, 1);
this.setState({ selected : selection });
}
}
}
But, above keeps on adding the data, as am sending the data as object
Data sent to above function, {available: 'true'}
So, how to do this ? check if the object already presented and remove it ? It works when i pass array like "[true]", but its not working when i pass object !

Targeting nested object properties inside an parent object

I'm trying to change the state of opening/closing times (and the post to an endpoint) on multiple days which are return to me in an object like so:
I have rendered these in a React component:
{this.state.time.map(each => (
<Fragment key={each.code}>
<OpeningHours
code={each.code}
day={each.description}
name={each.code}
open={each.open !== null ? each.open : '00:00'}
close={each.close !== null ? each.close : '00:00'}
onChange={this.onTimeChange}
/>
</Fragment>
))}
How would I iterate through the array of objects until I find the index of either the opening or closing time of this day? So far I've tried this, which works, but only if there was, say, just an opening time or one field in general. The issue arises since I have 2 fields to edit:
onTimeChange(e) {
let inputs = this.state.inputs.slice();
for(let i in inputs){
if(inputs[i].name == event.target.name){
inputs[i].value = event.target.value;
this.setState ({inputs});
break;
}
}
}
const newInputs = inputs.map(p =>
p.name === <unique_name/code>
? { ...p, [e.target.name]: e.target.value }
: p
);
this.setState({inputs:newInputs,});
Change the onTimeChange() method to get the unique_name:onTimeChange(e, code)
A simple example: https://codesandbox.io/s/v1y14rl2oy
Since index is unique, you can do
inputs[index] = { ...inputs[index], [e.target.name]: e.target.value }
too without using map() as in the sandbox.

React only allow users number keydown input

I am trying to have a input field that does not allows user to type numeric value. (A non-numeric or a special character value will not be even allow to be typed).
One of the common approach on SO is the <input type="number" />. However, it suffers from two things:
1: The arrow: which I am able to get rid of following another stack post, which is not a problem.
2: The input field still allows negative sigh "-" as input.
I also tried: <input type="text" pattern="[0-9]*" onChange={this.handleInputToken}/>
handleInputToken(e){
console.log(e.currentTarget.value)
}
But this still allows non-numeric input
Is there an existing npm module or library that allows this simple implementation? Since it feels like a very common stuff with form data.
You can build a typed input component and have whatever logic.
This is very, very basic and not tested but will refuse to accept anything that does not coerce to a number and keep the old value, effectively silently dropping anything bad. it will still accept negative numbers, but you can extend the handleChange to fix it
class NumericInput extends React.Component {
state = {
value: ''
}
handleChange = e => {
const { onChange } = this.props;
const value = e.target.value.trim();
const num = Number(value);
let newValue = this.state.value;
if (!isNaN(num) && value !== '') {
newValue = num;
}
else if (value === ''){
newValue = value;
}
this.setState({
value: newValue
}, () => onChange && onChange(this.state.value));
}
render() {
const { onChange, ...rest } = this.props;
return <input {...rest} value={this.state.value} onChange={this.handleChange} />;
}
}
https://codesandbox.io/s/zq5ooml10l
obviously, you can use static getDerivedStateFromProps() to keep a value={} in sync from upstream and have it as a true 'controlled' component
Try something like this:
<input
type="number"
min="0"
value={this.state.number}
onChange={this.handleNumberChange}
/>
handleNumberChange(e) {
const asciiVal = e.target.value.charCodeAt(0);
if (
!(
(asciiVal > 95 && asciiVal < 106) ||
(asciiVal > 47 && asciiVal < 58) ||
asciiVal == 8
)
) {
return false;
}
this.setState({ number: e.target.value });
}
As soon as you try entering negative number, it will return false and the value won't be set in the input field

Categories