Array length is zero but has accessible elements - javascript

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"

Related

I am trying to only return items in an array that contain a matching key with a true value where the key comes from a separate array

Brief example:
piece of state: ['youth', 'college'];
event Object:
{ name: theEvent,
ageDivisions: {
youth: true,
middleSchool: true,
highSchool: true,
college: true
open: false
},
}
What I want to accomplish
I want to filter through multiple event objects and only return when the event at least contains all the matching strings in the state array. this array is created based on what the user selects from the form (image Below)
My approach so far:
So far my approach has been to turn the events age divisions parameter into an array of arrays that contain the key and the value. Positions 1 and 2 respectfully
Here is the code:
codePenLink
if (filterParamaters.ageDivisions !== undefined && filterParamaters.ageDivisions.length != false) {
let parsedEvents = events.data.map(({ attributes: event }) => {
return {
...event,
...event.attributes,
ageDivisions: JSON.parse(event.ageDivisions),
eventStyles: JSON.parse(event.eventStyles),
}
})
console.log({ parsedEvents });
filteredEventss = parsedEvents.filter((event) => {
// make event .attributes.ageDivisions object an array of key value pairs
console.log({ eventThatWeAreFiltering: event });
if ((event.ageDivisions !== undefined && event.ageDivisions !== null)) {
let eventAgeDivisions = Object.entries(event.ageDivisions);
console.log({ theAgifiedObject: eventAgeDivisions });
// This maps through each object in the Object key value pair Array
// It might be easier to use map and just chang the array to only have the matching values
let onlyTrueEventAgedivisions = eventAgeDivisions.map((ageDivision, index) => {
console.log({ theAgeDivision: ageDivision[2] });
if (ageDivision[2] === true) {
return [ageDivision[0], ageDivision[2]];
} else {
return false;
}
})
console.log({ theTrueEventAgedivisions: onlyTrueEventAgedivisions });
}
})
console.log({ theFinishedFilteredevents: filteredEventss });
}
What I did was check if the ageDivisions existed in each object and if it does, run this filterParameters.ageDivisions list, checking if every property that you want to be true is set to true .
const result = parsedEvents.filter((e) => e.ageDivisions && filterParameters.ageDivisions.every(prop => e.ageDivisions[prop]))
console.log(result);

Nested Object mutates in React

I am in almost desperate need of help. I am a mechanical engineer and I'm doing a type of calculator for my line of work. I have had an issue I've spent weeks on. I can't seem to solve it.
To not bore you with long code I will try to generalise it as much as possible.
I will first present an example code.
Then I will explain the expected behaviour and what is actually happening for me.
Finally I will explain what I have tried so far to solve this issue.
I will add more content at the bottom based on comments to help clarify my question.
CODE EXAMPLE
THE PARENT OBJECT
import {childObject} from "./childObject"
// in my code "childObject" are actually different from each other
const object1 = Object.assign({}, childObject);
const object2 = Object.assign({}, childObject);
const object3 = Object.assign({}, childObject);
const object4 = Object.assign({}, childObject);
const object5 = Object.assign({}, childObject);
const object6 = Object.assign({}, childObject);
const exampleObject = {
name: "foo",
otherInfo: "bar",
nestedObject:{
standardType: [object1, object2, object3],
specialType: [object4, object5, object6]
},
sumfunc(){}
}
THE CHILD OBJECT
export const childObject = {
name: "I'm losing my mind",
value: "" //<-- this will change
otherInfo: "please help me",
sumfunc(){}
}
EXPLAINING
What I am doing is the following:
Searchbar with all types of parentObjects.
Allowing user to select one or multiple of same or different parentObjects.
Storing the copied selection in a redux store.
Displaying the selection, each parentObject as a form. [see picture]
When typing in form the value of the nested object will change
Now... The issue is when I open the searchbar and select the same parentObject, thus copying it, all its values are mutated. As seen in picture above.
WHAT I HAVE TRIED
I have tried to use lodash clone and deepClone on the selected parentObject.
I have tried to use loads clone and deepClone on the selected childObjects.
I have tried, since the object have the same structure, to go through all key value pairs and shallow copy them.
I have tried to not send the parentObject via the searchbar component to the reducer, instead I just send a string and the reducer itself will add the parentObject to the store.
All methods that I've tried have not stopped the mutation. The deepClone method stopped the mutations, but in return the functions in the objects stopped working (maybe I need to bind it somehow?)
MORE CONTENT
The code that updates the value of the nestedObject
const inputsHandler = (e: React.ChangeEvent<HTMLInputElement>) => {
const formCopy = Object.assign({}, formEQ);
const inputFieldName = e.target.name;
// if anything other than a empty, number or decimal inputted, then return
const isNum = e.target.value.match(/^(?:\d{1,8}(?:\.\d{0,8})?)?$/);
if (!isNum) return;
// Update priority list to calculate the last updated input
formCopy.priorityList = formCopy.priorityList.sort((a, b) => {
if (a === inputFieldName) return 1;
if (b === inputFieldName) return -1;
else return 0;
});
// Update selected input field
formCopy.inputs[calcmode] = formCopy.inputs[calcmode].map((input) => {
if (input.name === inputFieldName) {
input.value = e.target.value;
}
return input;
});
// If more than two inputs empty do not calculate
const emptyInputs = formCopy.inputs[calcmode].reduce(
(acc, nV) => (nV.value === "" ? (acc += 1) : acc),
0
);
// Calculate the last edited input field
formCopy.inputs[calcmode] = formCopy.inputs[calcmode].map((input) => {
if (input.name === formCopy.priorityList[0] && emptyInputs <= 1) {
const calculatedValue = formCopy.calculate(
formCopy.priorityList[0],
calcmode
);
input.value = Number(calculatedValue).toFixed(2);
}
return input;
});
// Final set hook, now with calculated value
setformEQ({ ...formCopy });
};
Please good people of StackOverFlow... Help me!
Your code has few problems :
you are filtering based on name property of child object and all of them has the same name. Always provide unique id to the objects so that they can be differentiated in easy manner.
Your filter logic is so wrong :
formCopy.inputs[calcmode] = formCopy.inputs[calcmode].map((input) => {
if (input.name === inputFieldName) {
input.value = e.target.value; // < -- Culprit
}
return input;
});
Never mutate inline, always create a new copy.
This is how your code change function should be (I have removed dynamic key selection for clarity) :
const change = (e, id) => {
const inputFieldName = e.target.name;
// local copy of array
const nestedArr = [...qform.nestedObject.standardType];
// finding item to be updated
const index = nestedArr.findIndex((i) => i.id === id);
console.log({ index, inputFieldName, e, id });
if (index !== -1) {
const item = nestedArr[index];
item.value = e.target.value;
nestedArr[index] = item;
// deep copy till k depth where k is the key to be updated
const newObject = {
...qform,
nestedObject: {
...qform.nestedObject,
standardType: [...nestedArr],
},
};
setQform(newObject);
}}
Check this Example : Demo

Remove object from an array?

remove an object from an array is not working properly ,but it add object perfectly
const addItem =(selected)=>{
let data = selectedItems ? [...selectedItems] : [];
if (data.length) {
let index = data.indexOf(selected);
console.log(index);
if (index !== -1) {
data.splice(index, 1);
setSelectedItems(data);
} else {
data.push(selected);
}
} else {
data.push(selected);
}
console.log("selected", selectedItems);
setSelectedItems(data);
}
render button function add or remove on click it
<div className="file-list">
<MappedElement
data={[{ _id: 1 }, { _id: 2 }]}
renderElement={(value, index, arr) => {
let check=selectedItems.some((obj) => obj._id === value._id);
console.log("check", check);
return (
<DocumentCard key={index} className={file-list-item ${check ?
"active" : ""}}
onClick={() => addItem(value, arr, index)} /> ); }} />
</div>
For a selectedItems array that looks like:
const selectedItems = [
{ _id: 1, /* other fields */ },
{ _id: 2, /* other fields */ },
{ _id: 3, /* other fields */ },
/* other objects */
];
And a selected object that looks like:
const selected = { _id: 1 };
In order to perform the desired behavior, which is: if the element exists, remove it, else, add it, you can write the following:
// copy selected items with a fail-safe empty array
const data = selectedItems ? [...selectedItems] : [];
// find index of selected element
const removalIndex = data.findIndex(({ _id }) => (_id === selected._id));
// if selected element exists in data array, remove it
if (removalIndex !== -1) {
data.splice(removalIndex, 1);
}
// if selected element doesn't exist in data array, add it
else {
data.push(selected);
}
// update selected elements
setSelectedItems(data);
NOTE: if your array of selected items contains duplicates, meaning multiple objects that contain the same value for _id, then this approach will be removing only the first instance of those. If you want to remove all of them, you'll have to use a loop or recursivity.
Your problem is probably in indexOf method you're using.
You can not use this to find objects in your array.
There are several options you can use. You can use find or findIndex and pass a callback to find an object by the specified (preferably unique property of the object).
Example
let found = data.findIndex(item => item.id === selected.id);
Use
const addItem =(selected)=>{
let data = selectedItems ? [...selectedItems] : [];
if (data.length) {
let index = data.findIndex(value => value._id === selected._id)ΠΆ
console.log(index);
if (index !== -1) {
data.splice(index, 1);
} else {
data.push(selected);
}
} else {
data.push(selected);
}
console.log("selected", selectedItems);
setSelectedItems(data);
}

A state being updated even if I don't call setstate React

(React 16.8.6)
I have the problem that when a variable change, it automatically changes also the state even if I don't call any setState. In the handleInputChange when I assign the value, it automatically updates the states clientsData and editedClientsData . I would like to update only editedClientsData.
Here is the code:
1 - set the state (fired on a button click):
getClients(calls) {
axios.all(calls)
.then(responseArr => {
let cleanedDate = []
responseArr.map(el => {
cleanedDate.push(el.data.data)
})
this.setState({
clientsData: cleanedDate,
editedClientsData: cleanedDate,
loading: false
})
this.loadDataChart()
});
}
2 - load the inputs fields
render(){
return (...
this.state.editedClientsData.map(this.renderInput)
...)
}
renderInput = (client, i) => {
const { activeYear } = this.state
return (<tr key={client.id}>
<td>{client.name}</td>
<td><Input
type="number"
name={client.id}
id="exampleNumber"
placeholder="number placeholder"
value={client.chartData[activeYear].r}
onChange={this.handleInputChange}
/></td>
<td>{client.chartData[activeYear].x}</td>
<td>{client.chartData[activeYear].y}</td>
</tr>
)
}
handleInputChange(event) {
let inputs = this.state.editedClientsData.slice();
const { activeYear } = this.state
for (let i in inputs) {
if (inputs[i].id == event.target.name) {
inputs[i].chartData[activeYear]['r'] = parseFloat(event.target.value)
console.log(inputs)
// this.setState({ editedClientsData: inputs })
break;
}
}
}
I tried to assign a fixed number
I tried to save as const {clientsData} = this.state , updating editedClientsData and the this.setState({clientsData})
All of these tests failed. Hope in your helps, thank you!
In the getClients function, you are assigning the same array cleanedDate to both the props (clientsData and editedClientsData) of the state.
Both the properties are pointing to the same array. So edit will be reflected to both the properties. Now, assuming that the array contains objects, so copying the array using slice will also not work, because both the different arrays are containing the references to the same data. Consider the example below:
var a = [{prop: 1}];
var b = a.slice();
var c = a.slice();
console.log(c == b); // false
b[0].prop = 2;
console.log(b[0].prop); // 2
console.log(c[0].prop); // also 2, because slice does shallow copy.
In this case you need to deeply copy the data, so no reference is duplicated. You can use Lodash utility for this or create your own utility for the same.

Javascript - array.map match index

There's an array looking as follows:
[[3,0], [6,0], [2,0], [9,0]....]
I'm trying to create a React/Redux reducer that changes the value of one of the 0s to 1. I click on an element and an action is dispatched. Idx is the index of an element in an array (eg. 0, 1, 2, 3)
export const toggleTile = (idx) => {
return {
type: TOGGLE_TILE,
idx
};
};
The reducer below does not work as I'd like it to be. I just created the skeleton of the conditional statements. If I click on a tile with index 3 (so the fourth tile), it changes the [n,0] to [n,1] for all elements. First of all it should only do if I click any of the tiles, and it should change [n,0] to [n,1] only for the clicked tile so I'm trying to change the 3 in the code below to the index of an 'i' element being mapped.
export default (state = [], action = {}) => {
switch (action.type) {
case LOAD_GRID:
return action.payload || [];
case TOGGLE_TILE:
return state.map((i) => {
if (action.idx === 3) {
return (i[1] === 0
? [i[0], parseInt(i[1], 10) + 1]
: [i[0], parseInt(i[1], 10) - 1]
);
}
return [i[0], i[1]];
});
default:
return state;
}
};
A grid component:
export default class Grid extends Component {
render() {
const mygrid = [];
this.props.inGrid.forEach((r, i) => {
mygrid.push(
<Square
key={i}
idx={i}
sqValue={r}
toggleTile={this.props.toggleTile}
/>
);
});
const { grid } = styles;
return (
<View style={grid}>
{mygrid}
</View>
);
}
}
export default class Square extends Component {
myaction() {
this.props.toggleTile(this.props.idx);
console.log(this.props.idx);
}
render() {
const { square, textStyle, squareActive } = styles;
const { sqValue } = this.props;
return (
<TouchableHighlight
style={[square, sqValue[1] && squareActive]}
onPress={this.myaction.bind(this)}
>
<View>
<Text style={textStyle}>{sqValue[0]},{sqValue[1]}</Text>
</View>
</TouchableHighlight>
);
}
}
Please advise.
There are a number of ways you can do this, with varying degrees of verbosity (due to Redux's insistence on immutability), but here's a pretty straightforward one:
case TOGGLE_TILE:
const nextValue = state[action.idx].slice(); // Make a copy of the tuple to be toggled
nextValue[1] = nextValue[1] === 0 ? 1 : 0; // Toggle it
const nextState = state.slice(); // Make a copy of the state
nextState[action.idx] = nextValue; // Replace the old tuple with the toggled copy
return nextState;
Or:
case TOGGLE_TILE:
const prevValue = state[action.idx];
const nextState = state.slice();
nextState[action.idx] = [ prevValue[0], prevValue[1] === 0 ? 1 : 0 ];
return nextState;
Ok, I'm gonna try and see what we can do with just the following portion of code you shared.
I would like to note that the code presented is not succinct. It would be a great benefit to yourself, your team, as well as anyone here on this site if your code was refactored the more you understand what you need to build.
// So state is just an array of arrays...
var state = [3,0], [6,0], [2,0], [9,0]];
return state.map((i) => { // i => [3,0] or [9,0] !! i is not index !!
// Map is going to iterate over the entire array of arrays.
if (action.idx === 3) {
// action.idx is what comes in from the click.
// Here is where your doing your work.
// If the first element of "i" is zero, then
// return the same array but add 1 to the second element of array.
// so [3,0] or [4,0] should become [3,1] or [4,1] but only for #3 as
// action.idx === 3 says to only change when... Nope, this is not how it will work. You need your exception in the MAP.
return (i[1] === 0 ? [i[0], parseInt(i[1], 10) + 1] : [i[0], parseInt(i[1], 10) - 1]);
}
// ?? Why don't you just return i, as i is each array of numbers.
return [i[0], i[1]];
});
// It seams to me that something like this should work, just plug and play.
// I am assuming a few things here that I will spell out. If they are incorrect, let me know and I'll change it.
// state will be an array of arrays that only contain two numbers each.
// They may or may not be in original order.
// The second element of each array will be either 0 or 1.
var state = [3,0], [6,0], [2,0], [9,0]];
state.map(function(cell){ // call it what you want, you called it "i".
if(cell[0] === action.idx){ // If clicked action index is === to cell[0]
// You could just hard code 3 as you did above, but this makes it dynamic.
// and only changes the cell that was clicked.
cell[1] = cell[1] ? 1 : 0; // if cell[1] is 0, then it is falsey, no need for complex logic. No need to parseInt if they are numbers to begin with. But if you do, then use "+" to change a string to number.
}
return cell;
});
Without notes
var state = [3,0], [6,0], [2,0], [9,0]];
state.map(function(cell){
if(cell[0] === action.idx){
cell[1] = cell[1] ? 1 : 0;
}
return cell;
});

Categories