store and update associative array in state react js - javascript

How to update my associative array while the key already exists else I want to create a new key and store that array in the state. I had tried to store a value in the associative array.
When I entering data in the input field it can call handleChange after that I want to do that above I mentioned operations
import React, { Component } from 'react';
class App extends Component {
constructor(props) {
super(props);
this.onChangeEvent = this.onChangeEvent.bind(this);
this.onChangeCount = this.onChangeCount.bind(this);
this.onSubmit = this.onSubmit.bind(this);
this.state = {
name: [],
inputCount:5
}
}
// associtive array
handleChange = (e) => {
// I want to check the key is already exist or not if exist means create update the value else create a new key and also I want to validate all the data.
const check = e.target.name
const value = e.target.value
const name = []
name[check] = value
console.log(name)
this.setState({
name,
})
}
render() {
let options = []
for (let i = 0; i < this.state.inputCount; i += 1) { options.push(i); }
return (
{this.state.inputCount && (
options1.map((i) => {
return (<div key={i}>
<input onChange={this.handleChange} type="text" className="name" name={`name${i}`} placeholder="Name" /><br />
</div>)
})
)}
);
}
}
export default App;

First, there are no "associative arrays" in JavaScript, though there are Objects, which is basically the same. So right off the bat, you should change the initialization of name to be {} instead of [].
To check if a key exists in an Object, simply do check in obj or even name.hasOwnProperty(check), where check is the key you want to check.
Setting and updating a value is done the same way in JavaScript, regardless if the key exists or not: name[check] = value.

Related

ReactJS - As can I generate several form fields with value properties sharing the same names but not the same values?

After 2 days of fierce struggles, I decided to post for the first time on Stack Overflow :).
My concern is the following:
As part of a project, I want to generate a number of input fields equal to the size of an array.
Example :
function App() {
const [UseState, setUseState] = useState("");
const array = [0, 1, 2];
const HandleAdding = (e) => {
e.preventDefault;
const returnJson = { UseState };
//rest of the code
};
return (
<div>
{array.map(() => (
<form onSubmit={HandleAdding}>
<input value={UseState} onChange={(e) => setUseState(e.target.value)} />
</form>
))}
</div>
);
}
We can already know the problem, when inserting a value on a field, this same value will be inserted on all the other fields which share the same UseState as value.
For even more concrete, here is an illustration of the problem on CodeSandBox:
https://codesandbox.io/s/adoring-rgb-n0bdk?file=/src/App.js
So try to fill in a field and you will notice the problem. I tried a lot of things to solve my problem like this:
https://codesandbox.io/s/blissful-rain-mcol2?file=/src/App.js (I'm trying to adapt the code in order to solve my problem).
I do not put anything more to prevent it from becoming too long.
So, would you have a solution to solve the problem?
For my part I am short of ideas, and still a beginner on React.
Thanks in advance! ^^
function App() {
const [UseState, setUseState] = useState({});
const array = [0, 1, 2];
const size = array.length;
const HandleAdding = (e) => {
e.preventDefault;
const returnJson = { UseState };
//rest of the code
};
return (
<div>
{array.map((x) => (
<form onSubmit={HandleAdding}>
<input value={UseState[x]} onChange={(e) => setUseState({...UseState, [x]: e.target.value})} />
</form>
))}
</div>
);
}
You can easily solve the problem by changing the structure of UseState to become an object with the pattern below
UseState = {
0: 'Isaac',
1: 'James',
2: 'John'
}
You need to store multiple value, so when you retrieve for value, use UseState[x], and when you update, you need to spread the object and update the value of a particular properties
Generally, for forms, you hold the values of the inputs in one object in state, and then update that state with the new values when the input has been changed.
At the moment you're creating more forms than you need. You don't even need the form element because the input elements doesn't actually require it.
Initialise your state with an object.
map over the array instead of the length of the array.
Name your inputs so when the values change you can easily update the state.
const { useState } = React;
function Example() {
// Initialise state with an object
const [ data, setData ] = useState({});
// Example input names
const arr = ['name', 'address', 'number'];
// When the input changes, get the name
// and the value, and update the state using
// that information
function handleChange(e) {
const { name, value } = e.target;
setData({ ...data, [name]: value });
}
function handleSubmit() {
console.log(data);
}
function createInputs(arr) {
// `map` over the array and return some JSX
return arr.map(el => {
return (
<label>{el.toUpperCase()}:
<input type="text" name={el} />
</label>
);
});
}
return (
<div onChange={handleChange}>
{createInputs(arr)}
<button onClick={handleSubmit}>Submit</button>
</div>
);
};
// Render it
ReactDOM.render(
<Example />,
document.getElementById("react")
);
label { display: block; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>

Add value from inputs to array if not exists

I am rendering in my React application an Array and for each Array I have an input element.
This elements gets as name the ID from the Array entryand a value can be entered.
I have a handleChange() functions, which gets the ID and the input value.
This data should be added to an Array, which I want to save to my State.
Later on, I have for each Array entry a Submit button, where I have to send the input value to an API endpoint.
constructor(props: Props) {
super(props);
this.state = {
sendTransferObj: []
};
this.handleChange = this.handleChange.bind(this);
}
...
handleChange(transId: string, event: React.ChangeEvent<HTMLInputElement>) {
console.log(transId, event.target.value); // returns => '123', '1'
let inputObject = [{ id: transId, value: event.target.value }]
let arrayForState = [];
const index = inputObject.findIndex(inputObject => inputObject.id != transId)
if (index === -1) {
arrayForState.push(inputObject);
} else {
console.log("object already exists")
}
console.log(arrayForState)
this.setState({
...this.state,
sendTransferObj: arrayForState
}); // error!
}
...
<Form.Control as="input" name={trans.id} onChange={this.handleChange.bind(this, trans.id)} />
My problem is currently in my handleChange(), where the input value gets saved to my arrayForObject for one input, but if I enter in a second input element a value, it gets overwritten.
Also I get various errors, when I later want to do a setState() for save this Array.
This is my whole component which I am using for reference (getting the Array that I render from my state with Redux): https://codesandbox.io/s/practical-ptolemy-69zbu?fontsize=14&theme=dark
You need to append the inputObject to the sendTransferObj array in the current state instead of appending it to a new empty array (which will cause the overwriting). You can do this using the spread syntax
handleChange(transId: string, event: React.ChangeEvent<HTMLInputElement>) {
let inputObject = [{ id: transId, value: event.target.value }]
const index = inputObject.findIndex(inputObject => inputObject.id != transId)
this.setState({
...this.state,
sendTransferObj: index === -1 ? [
...this.state.sendTransferObj,
inputObject
] : this.state.sendTransferObj
});
}
This won't update the field when the inputObject is found, though. I would recommend storing the values in an object with the ID as keys instead (and formatting it into the desired format when requesting the API). This way, you don't have to iterate through the array to find the matching object.
constructor(props: Props) {
super(props);
this.state = {
sendTransferObj: {}
};
this.handleChange = this.handleChange.bind(this);
}
...
handleChange(transId: string, event: React.ChangeEvent<HTMLInputElement>) {
this.setState({
...this.state,
sendTransferObj: {
...sendTransferObj,
[transId]: event.target.value
}
});
}

Regarding LifeCycles in ReactJS / Problem in the code snippet

I'm trying to learn ReactJS and found out about these lifecycles. However I've a doubt regarding how componentDidUpdate() functions and I want to know the reason behind it. Please take a look at the code below. It is a simple code that calculates the area of a triangle. Just to understand how we can send data from parent to child and vice versa I'm initialising the variables in parent component, then passing them to child to calculate the area and then passing back the results to change the state to final one where the results are rendered.
App.js
class App extends React.Component{
constructor(props){
super(props);
console.log("In the parent's constructor");
let initializeState=[{'base' : 0,'height' : 0,'area' : 0}];
this.state={ values : initializeState }
console.log(this.state.values);
}
componentDidMount(){
console.log("Mounting values");
let b = [{}];
b['base']=10;
b['height']=20;
b['area']=0;
this.setState({values: b});
}
update = (base,height,area) => {
console.log("Updating state with calculated area");
let updatedValue = [{}];
updatedValue['base'] = base;
updatedValue['height'] = height;
updatedValue['area'] = area;
this.setState({values: updatedValue});
}
render(){
console.log('Inside Parent render');
return(<React.Fragment>
<Triangle val = {this.state.values} update={this.update} />
</React.Fragment>
)
}
}
class Triangle extends React.Component{
shouldComponentUpdate(newProps, newState){
console.log('validating data');
if(newProps.val.base >0 && newProps.val.height >0){
return true;
}else{
return false;
}
}
componentDidUpdate(prevProps, prevState, snapshot){
console.log('In the child componentDidUpdate');
console.log('If you\'ve reached this, state has been re-rendered')
console.log(prevProps.val.base);
console.log(prevProps.val.height);
console.log(prevProps.val.area);
}
calcArea = () => {
console.log('Calculating area now');
let area = 1/2* this.props.val.base * this.props.val.height;
this.props.update(this.props.val.base,this.props.val.height,area);
}
render(){
console.log("In the child's render method")
return(
<React.Fragment>
<h2>Base : {this.props.val.base}</h2>
<h2>Height : {this.props.val.height}</h2>
<h2>Area : {this.props.val.area} </h2>
<button onClick={this.calcArea}>Click to calculate AREA</button>
</React.Fragment>
)
}
}
export default App;
So Everything is working fine, i.e., This is the series of output:
In the parent's constructor
App.js:11 [{…}]
App.js:33 Inside Parent render
App.js:66 In the child's render method
App.js:15 Mounting values
App.js:33 Inside Parent render
App.js:43 validating data
App.js:66 In the child's render method
App.js:52 In the child componentDidUpdate
App.js:53 If you've reached this, state has been re-rendered
Now till this point, the component has re-rendered according to the new values mentioned inside componentDidMount function. However the next output is :
App.js:54 undefined
App.js:55 undefined
App.js:56 undefined
It should be the values that have been destroyed. ie., 0, 0, 0 (mentioned inside the parent's constructor using this.state ). Though when I hit the calculate Area and the component is re-rendered, it shows the correct values which have been destroyed. ie.,
Calculating area now
App.js:24 Updating state with calculated area
App.js:33 Inside Parent render
App.js:43 validating data
App.js:66 In the child's render method
App.js:52 In the child componentDidUpdate
App.js:53 If you've reached this, state has been re-rendered
App.js:54 10
App.js:55 20
App.js:56 0
Can someone tell me why the results are "undefined" when the state is being changed for the first time and not the values that we have instantiated ??
There are some issues in the JavaScript which are probably the reason why you are getting undefined values.
let initializeState=[{'base' : 0,'height' : 0,'area' : 0}];
this.state={ values : initializeState }
Here you are setting the state as an object with a key values which then holds an array of objects [{}].
This leads to issues in other places where an array of objects is defined but then it is accessed like an object:
let b = [{}];
b['base']=10;
This code should be:
let b = {};
b.base = 10;
There is no need to use bracket notation when you are using a "normal" string as the key. This notation is used when using variables for the key or string keys that start with a number, have hyphens, etc:
const a_string = 'base';
const a_string_with_hyphen = 'some-key-name';
an_object[a_string] = 123;
an_object[a_string_with_hyphen] = 456;
Use let when defining a variable which value will change, otherwise use a const:
let value_that_will_change = 123;
const value_that_wont_change = 456;
value_that_will_change = 789;
Regarding react specifically, I changed the code a little to show different approaches, so that you can see how the state is changing. I used inputs to modify the values, that I think can be handy in this case:
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = { base: 0, height: 0, area: 0 };
console.log("App. constructor.", "state:", this.state);
}
componentDidMount() {
const values = {
base: 10,
height: 20,
area: 200
};
this.setState(values);
console.log(
"App. componentDidMount.",
"state:",
this.state,
"Updating state with new values:",
values
);
}
componentDidUpdate(prevProps, prevState) {
console.log(
"App. componentDidUpdate.",
"prev state:",
prevState,
"new state:",
this.state
);
}
updateBase = base_new => {
this.setState({
base: base_new,
area: (1 / 2) * base_new * this.state.height
});
console.log("App. updateBase.", "new base:", base_new);
};
updateHeight = event => {
const height_new = parseInt(event.target.value);
this.setState({
height: height_new,
area: (1 / 2) * height_new * this.state.base
});
console.log("App. updateHeight.", "new height:", height_new);
};
doubleBase = () => {
const { base, height } = this.state;
const base_new = 2 * base;
const area_new = (1 / 2) * base_new * height;
this.setState({ base: base_new, area: area_new });
console.log("App. doubleBase.", "new area:", area_new);
};
render() {
const { state, updateBase, updateHeight, doubleBase } = this;
console.log("App. render.", "state:", this.state);
return (
<Triangle
{...state}
updateBase={updateBase}
updateHeight={updateHeight}
doubleBase={doubleBase}
/>
);
}
}
class Triangle extends React.Component {
componentDidMount() {
const {
updateBase,
updateHeight,
doubleBase,
...parent_state_props
} = this.props;
console.log("Triangle. componentDidMount.", "props:", parent_state_props);
}
componentDidUpdate(prevProps) {
const {
updateBase,
updateHeight,
doubleBase,
...parent_state_props
} = this.props;
const {
updateBase: updateBase_prev,
updateHeight: updateHeight_prev,
doubleBase: doubleBase_prev,
...parent_state_props_prev
} = prevProps;
console.log(
"Triangle. componentDidUpdate.",
"prev props:",
parent_state_props_prev,
"new props:",
parent_state_props
);
}
render() {
const {
updateBase,
updateHeight,
doubleBase,
...parent_state_props
} = this.props;
const { base, height, area } = parent_state_props;
console.log("Triangle. render.", "props:", parent_state_props);
return (
<React.Fragment>
<label for="base" style={{ display: "block" }}>
Base
</label>
<input
id="base"
type="number"
value={base}
// Here we are defining the function directly and sending the value.
onChange={event => updateBase(parseInt(event.target.value))}
/>
<label for="height" style={{ display: "block" }}>
Height
</label>
<input
id="height"
type="number"
value={height}
// Here we are sending the event to the function.
onChange={updateHeight}
/>
<h2>{`Area: ${area}`}</h2>
<button onClick={doubleBase}>Double base</button>
</React.Fragment>
);
}
}
In the above code I left shouldComponentUpdate out. This method is used to prevent the Component from rendering. The reason for this is that every time a parent Component renders, it will make all its children render. This is ok if the props that the children receives changed, but not necessary when those received props didn't change. Basically nothing changed in the children but it is still rendering. If this supposes a performance issue you can use the PureComponent instead of Component, or use your own logic in shouldComponentUpdate.
One last thing, if you accept the advice, I encourage you to learn React Hooks which were introduced this year and is the new way to build with React.
Let me know if something is not clear or I missed something.
On componentDidMount in App component, we are setting the new values ,
previously they were undefined.
Now, inside componentDidUpdate in Triangle component, you are logging the prevProps which were never there, as a result they are undefined.
componentDidUpdate(prevProps, prevState, snapshot){
console.log('In the child componentDidUpdate');
console.log('If you\'ve reached this, state has been re-rendered')
console.log(prevProps.val.base);
console.log(prevProps.val.height);
console.log(prevProps.val.area);
console.log(this.props.val.base);
console.log(this.props.val.height);
console.log(this.props.val.area);
}
Change as above, you will get to know that the new props are set.

React - Dynamically set state without hardcoding key field

In ES6, ComputedPropertyName allows us to do things like use a variable as a key, which in turn means we can set state dynamically. However, if you look around at examples of setting state dynamically, they tend to all have one thing in common -- the state key's name is hardcoded. As an example:
class Input extends React.Component {
state = { state1: "" };
handleChange = event => {
const {
target: { name, value }
} = event;
this.setState({
[name]: value
});
};
render() {
return (
<div>
<label>
<input
type="text"
name="state1"
value="new value"
onChange={this.handleChange}
/>
</label>
</div>
);
}
}
This works because we have a state key called "state1", as seen in the line state = { state1: "" };, and we are hardcoding name in the input field to be that state key, as seen in the line name="state1".
I do not like this solution, because it means I now have to keep track of state.state1" in more than one location. If I were to refactorstate.state1to instead bestate.state2, I would have to go findname="state1"1 and update that to read name="state2". Instead of worry about that, I am wondering if there is a way to set state dynamically without hardcoding this state key. That is, I'm looking to change
<input
type="text"
name="state1"
value="new value"
onChange={this.handleChange}
/>
Into something like:
<input
type="text"
name={this.state.state1.keyname}
value="new value"
onChange={this.handleChange}
/>
Obviously the above doesn't work because keyname is undefined, but the intention here is that name can take on the value of "state1" without me having to hardcode it. How can this be achieved?
You can have an array with objects with keys type and name which you can use to set the initial state and render the inputs dynamically. This way you'll only have to change the value once in the array. You can do something like this.
Here is a codesandbox
import React from "react";
import ReactDOM from "react-dom";
class App extends React.Component {
constructor() {
super();
this.arr = [
{ type: "text", name: "state1" },
{ type: "password", name: "state2" }
];
// set the state keys dynamically from this.arr
this.state = this.arr.reduce((agg, item) => {
agg[item.name] = "";
return agg;
}, {});
}
handleChange = event => {
const {
target: { name, value }
} = event;
this.setState(
{
[name]: value
}
);
};
renderInputs = () => {
return this.arr.map((item, i) => (
<div key={i}>
<label>
<input
type={item.type}
name={item.name}
value={this.state[item.name]}
onChange={this.handleChange}
/>
</label>
</div>
));
};
render() {
const inputs = this.renderInputs();
return <div>{inputs}</div>;
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Hope this helps !
There is the new useReducer() that comes with hooks and context. Check this out i think that is the best pattern to solve your issue. https://reactjs.org/docs/hooks-reference.html.

React replacing array of objects with a new array of objects of the same type

So I have an array is a class's state. Let's call it A. A is populated with objects of type B through a function f in the constructor. Later, I generate using f and new data, a new array of objects of type B called C. I then use setState({A: C}). However, this results in the data from the first array staying on the display. I'm not sure how to fix this at all.
Edit: code snippets
class ClassBox extends Component {
constructor(props) {
super(props);
// note data here uses the keys from the config file
this.state = {
data: this.props.data,
filteredData: [],
functionData: []
};
this.generateFunctionData = this.generateFunctionData.bind(this);
this.state.functionData = this.generateFunctionData();
this.state.filteredData = this.state.functionData;
this.handleSearch = this.handleSearch.bind(this);
}
generateFunctionData(useData = false, data = null){
return useData ? ProcessJSON.extractFunctions(data.functions).map((_function, index) =>
{return createMethodBox(_function.Func_name, _function.Description, _function.Access_Mod, index)}) : ProcessJSON.extractFunctions(this.props.data.functions).map((_function, index) =>
{return createMethodBox(_function.Func_name, _function.Description, _function.Access_Mod, index)});
}
handleSearch(input) {
// convert to lower case to avoid capitalization issues
const inputLowerCase = input.toString().toLowerCase();
// filter the list of files based on the input
const matchingList = this.state.functionData.filter((method) => {
return method.props.name.toLowerCase().includes(inputLowerCase)
}
);
this.setState({
filteredData: matchingList
});
}
render() {
console.log(this.state.filteredData)
return (
<Container>
<NameContainer>
<h1>{this.state.data.className}</h1>
</NameContainer>
<ContentContainer>
<DescriptionContainer>
{this.state.data.description}
</DescriptionContainer>
<StyledDivider/>
<VarContainer>
<h1>Variables</h1>
<VarTableContainer>
<BootstrapTable
keyField="id"
data={[]}
columns={testColumns}
bordered={false}
noDataIndication="Table is Empty"
classes="var-table"
/>
</VarTableContainer>
{/*{this.state.data.variables}*/}
</VarContainer>
<StyledDivider/>
<MethodContainer>
<MethodHeader>
<h1>Methods</h1>
<StyledSearchBar onSearch={this.handleSearch}
isDynamic={true} allowEmptySearch={false} minChars={0}
className='searchBar'/>
</MethodHeader>
<Methods>
{this.state.filteredData}
</Methods>
</MethodContainer>
</ContentContainer>
</Container>
);
}
class Classes extends Component {
constructor(props) {
super(props);
this.state = {
data: this.props.data,
displayIndex: this.props.displayIndex
};
this.treeData = createTreeData(this.state.data);
this.classBox = null
}
componentDidUpdate(prevProps, prevState, snapshot) {
if(prevState.displayIndex !== this.props.displayIndex){
const funcData = this.classBox.generateFunctionData(true, this.state.data[0][this.props.displayIndex]);
console.log(funcData);
this.classBox.setState({data: this.state.data[0][this.props.displayIndex], functionData: funcData, filteredData: funcData });
this.classBox.forceUpdate();
this.setState({displayIndex: this.props.displayIndex});
}
}
render() {
this.treeData = createTreeData(this.state.data);
return (
<Container>
<FileTreeContainer>
<StyledTreeMenu data={treeData}/>
</FileTreeContainer>
<ClassInfoContainer>
<ClassBox ref = {el => this.classBox = el} data = {this.state.data[0][this.state.displayIndex]}/>
</ClassInfoContainer>
</Container>
)
}
Classes contains an instance of ClassBox. After executing componentDidUpdate, the page still shows the old method boxes, even though functionData has changed.
EDIT 2: it's also worth noting that when I replace the class view with the landing view and go back to the class view it shows the page correctly.
The way your are setting the state should be correct, as you are setting it to a newly created array from .filter.
I think the issue is with you storing the method components in the filteredData state. Components should not be stored in state.
I believe your component is just re-rendering, but not removing the old generated components. Maybe try mapping the search input to the state and generate the method components that way.

Categories