object of array not update in state - javascript

I have form where user can add as much as he wants documents. Each document have several inputs.
And I'm trying to get each document inputs values and put it to state as array of objects.
State should look like:
[
{
id: 'UGN2WP68P1',
name: 'passport',
placeIssue: 'paris'
},
{
id: 'TD0KUWWIM6',
name: 'shengen visa',
placeIssue: 'paris'
}
...
]
So I write a function which is called on inputs change. He check are there object with same id, if there is no object with same id he creates new and add to array, if object exist with same id then he update object:
const addNewDocumentObj = (id, type, val) => {
// Get object with same id
let docObj = addedDocsArr.filter( el => el.id === id)
// If there is no object with same id, creates new one
if (docObj.length === 0) {
if (type === 'name'){
let obj = {
id: id,
docId: val.id
}
setAddedDocsArr(addedDocsArr.concat(obj))
} else if (type === 'placeIssue') {
let obj = {
id: id,
placeIssue: val
}
setAddedDocsArr(addedDocsArr.concat(obj))
}
// If object exist with same id then updates with new value and adds to array
} else {
if (type === 'name'){
let newObject = Object.assign(docObj, {name: val.id})
let newArray = addedDocsArr.filter(el => el.id !== id)
setAddedDocsArr(newArray.concat(newObject))
} else if (type === 'placeIssue') {
let newObject = Object.assign(docObj, {placeIssue: val})
let newArray = addedDocsArr.filter(el => el.id !== id)
setAddedDocsArr(newArray.concat(newObject))
}
}
}
But it doesn't work, and I can't understand why, maybe my logic is bad and there is better practise?
UPDATE:
In React debugger I noticed how state changes. If I add select document name, in state object looks like that:
{name: 'passport', id: 'UGN2WP68P1'}
If I enter document place of issue value. Then object changes and show data like that:
{placeIssue: 'paris', id: 'UGN2WP68P1'}
But result should be:
{name: 'passport', placeIssue: 'paris', id: 'UGN2WP68P1'}
So it looks like that object not updated but created new one

Maybe you need something like:
const addNewDocumentObj = (id, type, val) => {
// Get object with same id
let docObj = addedDocsArr.find(el => el.id === id)
// If there is no object with same id, creates new one
if (!docObj) {
docObj = { id, placeIssue: val }
// and pushes it to addedDocsArray
addedDocsArr.push(docObj)
}
if (type === 'name') {
docObj.name = val.id
} else if (type === 'placeIssue') {
docObj.placeIssue = val
}
setAddedDocsArr(addedDocsArr)
}
First of all, why are you using filter if you are actually try to find something in array? Just use find.
Second, if object with given id is already exists, there is no need to filter your array and then put that object back... Just find that object in array and update it! It is already in your array! Remember that Array contains references to your objects, so when you grab your object from the Array and edit it, your edit the same object that Array have.
Last one, Idk what logic your setAddedDocsArr function have. In my example I assume that the only thing it does is set its argument (newArray) to the variable named addedDocsArr. So instead of that, in situation where object with given id is not present, I just push it in old array.

Finished App:
Implementation of Handle submit:
const handleSubmit = (event) => {
event.preventDefault();
if (!uid) {
alert("Please enter the ID");
return;
}
let existingRecords = docs.filter((doc) => doc.id === uid);
if (!existingRecords.length) {
let newRecord = {
id: uid,
name: name,
issuePlace: place
};
setDocs([...docs, newRecord]);
setId("");
setName("");
setPlace("");
} else {
let unmodifiedRecords = docs.filter((doc) => doc.id !== uid);
if (name) {
existingRecords[0].name = name;
}
if (place) {
existingRecords[0].issuePlace = place;
}
unmodifiedRecords.push(existingRecords[0]);
setDocs(unmodifiedRecords);
setId("");
setName("");
setPlace("");
}
};
And Here is the full finished example:
import React, { useState } from "react";
import "./styles.css";
export default function App() {
const [docs, setDocs] = useState([
{ id: "1", name: "passport", issuePlace: "delhi" }
]);
const [uid, setId] = useState("");
const [name, setName] = useState("");
const [place, setPlace] = useState("");
const handleSubmit = (event) => {
event.preventDefault();
if (!uid) {
alert("Please enter the ID");
return;
}
let existingRecords = docs.filter((doc) => doc.id === uid);
if (!existingRecords.length) {
let newRecord = {
id: uid,
name: name,
issuePlace: place
};
setDocs([...docs, newRecord]);
setId("");
setName("");
setPlace("");
} else {
let unmodifiedRecords = docs.filter((doc) => doc.id !== uid);
if (name) {
existingRecords[0].name = name;
}
if (place) {
existingRecords[0].issuePlace = place;
}
unmodifiedRecords.push(existingRecords[0]);
setDocs(unmodifiedRecords);
setId("");
setName("");
setPlace("");
}
};
return (
<div className="App">
<form onSubmit={handleSubmit}>
<table>
<tr>
<td>
<label>ID: </label>
</td>
<td>
<input
value={uid}
onChange={(e) => {
setId(e.target.value);
}}
/>
</td>
</tr>
<tr>
<td>
<label>Name: </label>
</td>
<td>
<input
value={name}
onChange={(e) => {
setName(e.target.value);
}}
/>
</td>
</tr>
<tr>
<td>
<label>Isuue Place: </label>
</td>
<td>
<input
value={place}
onChange={(e) => {
setPlace(e.target.value);
}}
/>
</td>
</tr>
<tr>
<td>
<button type="submit">Submit</button>
</td>
</tr>
</table>
</form>
{docs.map((doc) => (
<div className="records">
<span>{"ID:" + doc.id + " "}</span>
<span>{"Name:" + doc.name + " "}</span>
<span>{"Issue Place:" + doc.issuePlace + " "}</span>
</div>
))}
</div>
);
}
Check out finished example with source code at: Codesandbox Link

I find a pretty easy way how to solve this problem. I read documentations of react forms and find multiple inputs idea React Forms
So I changed my code to:
// Update or add information of added documents inputs
const addNewDocumentObj = (id, e, name) => {
const newInput = addedDocsArr.map(el => {
if(id === el.id) {
if(name === 'name'){
el[name] = e.target.value
} else if (name === 'placeIssue'){
el[name] = e.target.value
}
}
return el
})
setAddedDocsArr(newInput);
}
// Add new document inputs
const addNewDocument = () => {
let blockId = randomNumber(10, true, false)
setAddedDocsArr([...addedDocsArr, {id: blockId, name: '', placeIssue: ''}])
}
And it works perfectly!

Related

Vue reactivity is always one step behind

I have the following setup:
A type StringMap which I want to work like Map<> but since I'm using vue2, Map<> reactivity is not possible and I've tried to do it myself via an Array:
import Vue from "vue";
export default class StringMap {
entries: Array<[string, number]>;
constructor(data?: Record<string, number>) {
Vue.set(this, "entries", data ? Object.keys(data).map((key) => [key, data[key]]) : []);
}
get(key: string): number {
return this.entries.find((e) => e[0] === key)?.[1];
}
set(key: string, value: number): this {
const entry = this.entries.find((e) => e[0] === key);
if (entry) {
Vue.set(entry, 1, value);
}
else {
this.entries.push([key, value]);
}
return this;
}
has(key: string): boolean {
return this.entries.some((e, idx, arr) => e[0] === key);
}
delete(key: string): boolean {
const idx = this.entries.findIndex((e, idx, obj) => e[0] === key);
if (idx != -1) {
this.entries.splice(idx, 1);
}
return idx != -1;
}
clear(): void {
Vue.set(this, "entries", []);
}
}
In my Vue-Template, I have an input-field that listens to the #change method and calls calculateTotals()
<input :id="'viewModel.phasenChangeRequests[0].ppmProjektPhaseResource[' + index +'].personalInternPlanFach_PT'"
v-model="viewModel.phasenChangeRequests[0].ppmProjektPhaseResource[index].personalInternPlanFach_PT"
class="form-control"
asp-horizontal="true"
#change="calculateTotals('totalPersonalInternPlanFach_PT')"/>
And the calculateTotals is just looping over the data and summing the fields:
calculateTotals(key: string = null) {
if (this.type === "PhasenCrs") {
let totalPersonalInternPlanFachPt = 0;
for (let year = this.startyear; year < this.startyear + 5; year++) {
const phasen = this.viewModel.phasenChangeRequests;
phasen.forEach((phase) => {
var ressourcenOfYear = phase.ppmProjektPhaseResource.filter(x => x.resourceYear === year)[0];
totalPersonalInternPlanFachPt += parseFloat(ressourcenOfYear.personalInternPlanFach_PT);
});
}
if (key === null) {
console.log(`Key 'totalPersonalInternPlanFach_PT' doesn't exist yet, set it to: ${totalPersonalInternPlanFachPt}`);
this.totals2.set("totalPersonalInternPlanFach_PT", totalPersonalInternPlanFachPt);
} else {
switch (key) {
case 'totalPersonalInternPlanFach_PT':
{
console.log(`set totalPersonalInternPlanFach_PT: ${totalPersonalInternPlanFachPt}`);
this.totals2.set("totalPersonalInternPlanFach_PT", totalPersonalInternPlanFachPt);
}
}
}
}
}
totals2 is a StringMap that is initialized in the created() method:
totals2: StringMap;
async created() {
this.startyear = parseInt(moment(this.startdate, "DD.MM.YYYY").format("YYYY"));
this.projekt = JSON.parse(this.projektjson);
this.totals2 = new StringMap();
await Axios.get(this.url)
.then(res => {
this.viewModel = res.data;
})
.then(() => {
this.calculateTotals();
});
}
When I run it, it initially looks like this:
So as you can see, all fields for each year are summed correctly.
When I now change the value of 2021, the following happens:
But in the console, I can see that the total was calculated correctly:
When I now change the value back to the initial value, it shows this:
So that the previous change is now reflected in the total field but the console is showing the correct result again.
It seems that the value in the view is always one tick behind the real value...
Why is this happening?
Thanks in advance

Validate Duplicate Data Entry in Array - JavaScript

My problem is that I want to insert values that are not repeated when doing a push
This is my code :
addAddress: function() {
this.insertAddresses.Adress = this.address_address
this.insertAddresses.State = this.selectedStateAddress
this.insertAddresses.City = this.selectedCityAddress
if(this.insertAddresses.Adress !== "" && this.insertAddresses.State !== null && this.insertAddresses.City !== null) {
let copia = Object.assign({}, this.insertAddresses);
this.addresses.push(copia)
}
else
{
this.$message.error('Not enough data to add');
return
}
},
When adding a new element to my object, it returns the following.
When I press the add button again, it adds the same values again, I want to perform a validation so that the data is not the same. How could I perform this validation in the correct way?
Verify that the item doesn't already exist in the array before inserting.
You can search the array using Array.prototype.find:
export default {
methods: {
addAddress() {
const newItem = {
Address: this.address_address,
State: this.selectedStateAddress,
City: this.selectedCityAddress
}
this.insertItem(newItem)
},
insertItem(item) {
const existingItem = this.addresses.find(a => {
return
a.State === item.State
&& a.City === item.City
&& a.Address === item.Address
})
if (!existingItem) {
this.addresses.push(item)
}
}
}
}
On the other hand, if your app requires better performance (e.g., there are many addresses), you could save a separate dictonary to track whether the address already exists:
export default {
data() {
return {
seenAddresses: {}
}
},
methods: {
insertItem(item) {
const { Address, State, City } = item
const key = JSON.stringify({ Address, State, City })
const seen = this.seenAddresses[key]
if (!seen) {
this.seenAddresses[key] = item
this.addresses.push(item)
}
}
}
}
demo
check it:
let filter= this.addresses.find(x=> this.insertAddresses.State==x.State)
if (filter==null) {
this.$message.error('your message');
}
OR FILTER ALL
let filter= this.addresses.find(x=> this.insertAddresses.Adress==x.Adress && this.insertAddresses.State==x.State && this.insertAddresses.City==x.City)
if (filter==null) {
this.$message.error('your message');
}
``

How do I avoid infinite loop when using Redux state in useEffect dependency array?

I'm trying to figure out why my useEffect function ends up in an infinite loop.
I have two variables that are hooked into my Redux store:
const vehicles: AllVehiclesCollection = useSelector((state: ReduxState) => state.claims?.vehicles ?? {});
const properties: AllPropertiesCollection = useSelector((state: ReduxState) => state.claims?.properties ?? {});
and I have an action that is dispatched to the store that updates these only after a user clicks a button.
I have a useEffect that will trigger based on either of these variables changing.
useEffect(() => {
let fullVehicleList: DropdownData[] = getFormattedVehicleListForDisplay();
let fullPropertyList: DropdownData[] = getFormattedPropertyListForDisplay();
let fullList = fullVehicleList.concat(fullPropertyList);
if (fullList.length > 0) {
setVehiclesAndPropertiesList(fullList);
} else {
setVehiclesAndPropertiesList(null);
}
}, [vehicles, properties]);
Nowhere in this code are the vehicles or properties variables changed or any actions dispatched that would change the Redux state.
getFormattedVehicleListForDisplay function:
const getFormattedVehicleListForDisplay = () => {
let list: DropdownData[] = [];
if (Object.keys(vehicles).length > 0) {
let thisPolicysVehicles = [];
if (vehicles !== null) {
const key = `${selectedPolicy.symbol}${selectedPolicy.number}`;
thisPolicysVehicles = vehicles[key];
}
if (thisPolicysVehicles && thisPolicysVehicles.length > 0) {
thisPolicysVehicles.forEach((vehicle: VehicleInformation) => {
if (vehicle.vehicleMake !== OTHER_VEHICLE) {
list.push({
label: formatVehicleForDisplay(vehicle),
value: { ...vehicle, type: 'V' },
});
} else {
list.push({ label: vehicle.vehicleMake, value: {} });
}
});
}
}
return list;
};
getFormattedPropertyListForDisplay function:
const getFormattedPropertyListForDisplay = () => {
let list: DropdownDataOMIG[] = [];
if (Object.keys(properties).length > 0) {
let thisPolicysProperties = [];
if (properties !== null) {
const key = `${selectedPolicy.symbol}${selectedPolicy.number}`;
thisPolicysProperties = properties[key];
}
if (thisPolicysProperties && thisPolicysProperties.length > 0) {
thisPolicysProperties.forEach((property: LocationInformation) => {
if (property.locStreet1 !== OTHER_PROP) {
list.push({
label: formatPropertyForDisplay(property),
value: { ...property, type: 'P' },
});
} else {
list.push({ label: property.locStreet1, value: {} });
}
});
}
}
return list;
};
For reference, the data in vehicles and properties is a set of key-value pairs where the key is a unique identifier of a given account number and the value is an array of vehicle/property objects for that account.
Any idea why this goes into an infinite loop when using Redux state in the dependency array? Is there a different way to use Redux state in a dependency array? Thanks!
When using
const vehicles = useSelector((state: ReduxState) => state.claims?.vehicles ?? {});
Each time this is triggered, and you don't have vehicles in your store, you return a new object {}. and {} === {} // false
So ain your useEffect dependency array, it's each time a new Object, so useEffect is triggered.
So either remove your || {} in your selector (because null === null & undefined === undefined) or consider moving to useShallowSelector as explained in react-redux documentation

how to add specific links to search keywords

This is a search code i am working on but I want to give specific links to the members of the array 'people' so that 'rock' would have its own link to another page, 'brock', would have its own link and so on, the search is working perfectly but a i want the result to link to other pages
here is the code(without html)
const people = [
{name: 'david'},
{name: 'patel'},
{name: 'kevin'},
{name: 'coco'},
{name: 'brock'},
{name: 'rock'}
];
const list = document.getElementById('list');
//function to set the list
function setList(group){
clearlist();
for(const person of group)
{
const item = document.createElement('a');
item.classList.add('list-group-item');
const text =document.createTextNode(person.name);
item.appendChild(text);
list.appendChild(item);
}
if(group.lenght==0){
setnoresult();
}
}
function clearlist(){
while(list.firstChild)
{
list.removeChild(list.firstChild);
}
}
function setnoresult(){
const item = document.createElement('li');
item.classList.add('list-group-item');
const text =document.createTextNode('no result found');
item.appendChild(text);
list.appendChild(item);
}
function getrelevancy(value, searchterm){
if(value === searchterm)
{
return 2;
}
else if(value.startsWith(searchterm))
{
return 1;
}
else if(value.includes(searchterm))
{
return 0;
}
else{
return -1;
}
}
const searchinput = document.getElementById('search');
searchinput.addEventListener('input',(event) => {
let value = event.target.value;
if(value && value.trim().length >0)
{
value = value.trim().toLowerCase();
setList(people.filter(person => {
return person.name.includes(value);
}).sort((personA, personB) =>{
return getrelevancy(personB.name, value- getrelevancy(personA.name ,value))
}));
}
else{
clearlist();
}
// console.log(event.target.value);
})
You could something like a link: 'page.com to your object then add the link the <a> by doing item.href = people[index].link. Hard to explain, let me just show an example:
const people = [ {name: "Jeff", link: "page.com/jeff"}, {name: "Jo", link: "page.com/Jo"} ];
And then call setList like this:
setList(people.filter(person => { return people.indexOf(person.name) }));
or something like that. Then set the href and name by:
const text = document.createTextNode(people[group].name);
item.href = people[group].link;
I really hope you understand what I just said.

Javascript array filter two searchbox

How to filter array values dynamically with two string (searchboxes)? Here's what I've tried so far:
if (!!this.searchCompany.toLowerCase() || !!this.searchName.toLowerCase()) {
this.filter = true
this.newval = this.list.filter(post => {
if (!!this.searchCompany.toLowerCase()) {
return post.profile.company ? post.profile.company.toLowerCase().includes(this.searchCompany.toLowerCase()) : null
} else if (!!this.searchName.toLowerCase()) {
return post.profile.full_name.toLowerCase().includes(this.searchName.toLowerCase())
} else {
return post
}
})
this.searchCompany and this.searchName are the searchboxes in my form.
The array content of this.list
UPD:
Here is your condition checking
if(!!this.searchCompany.toLowerCase())
It does not check whether the value is presented and in lower case. Firstly it will be transformed to lower case by String method and then cast to boolean type. As we know, empty string casts to boolean FALSE and any non empty string to TRUE. Moreover, you can get TypeError in case that searchCompany property is not presented in this.
Here small example snippet how you can search in array of objects
const posts = [{
company: 'Coca-Cola',
name: 'Rocket'
},
{
company: 'Pepsi-Cola',
name: 'Groot'
}
]
let form = document.forms['search-form']
let resultBox = document.querySelector('#result-box')
let searchButton = document.querySelector('#search')
searchButton.addEventListener('click', doSearch, false)
function searchPost(parameter, searchString) {
return posts.find(post => post[parameter].toLowerCase().includes(searchString.toLowerCase()))
}
function isFormEmpty() {
return [...form.children].filter(e => e.type === 'text').reduce((r, c) => r && !c.value.length, true)
}
function doSearch() {
if (isFormEmpty(form)) {
e.preventDefault()
return false
}
let searchNameEl = form.querySelector('[name="searchName"]')
let searchCompanyEl = form.querySelector('[name="searchCompany"]')
let searchParam = searchCompanyEl.value.length ? 'company' : 'name'
let searchText = searchCompanyEl.value.length ? searchCompanyEl.value : searchNameEl.value
let post = searchPost(searchParam, searchText)
resultBox.textContent = JSON.stringify(post)
}
form {
display: block;
}
.result {
margin-top: 10px;
}
<form id="search-form">
<label>Search company</label>
<input type="text" name="searchCompany" />
<label>Search name</label>
<input type="text" name="searchName" />
<button type="button" id="search">Search</button>
</form>
<div class="result">
<pre id="result-box"></pre>
</div>

Categories