Setstate of specific object in array - javascript

I have a cats array that I pull from an api
i map over these and render them on a page
each one gets rendered with a like button, when I hit like I want it to like it and when i hit like again, it should unlike it
my initialState is :
state = {
cats: []
};
then once i call the api state looks like this:
cats: [
{url: Array(1), id: Array(1), source_url: Array(1), liked: false}
{url: Array(1), id: Array(1), source_url: Array(1), liked: false}
]
I have a like cat method whereby I find the cat that I liked like this:
var cat = this.state.cats.find(c => c.id[0] === cat.id[0])
considering I have all this information, how do I call setState for that specific cat to change the liked from false to true?
I was thinking something like this:
this.setState(prevState => ({ cats: {
cat: {
...prevState.cat,
liked: !prevState.cat.liked
}
}}))
but it does not know what liked is of undefined
any ideas?

One problem with your approach is that there's no prevState.cat.
Assuming the (un)liked cat is stored in cat:
this.setState(prevState => ({
cats: prevState.cats.map(c => c.id[0] === cat.id[0] ? Object.assign(c, { liked: !c.liked }) : c)
}));
Demo:
var state;
function setState(a) {
state = Object.assign(state, a(state));
}
state = {
cats: [
{url: [0], id: [1], source_url: [0], liked: false},
{url: [0], id: [2], source_url: [0], liked: false}
]
};
var cat = state.cats[1];
setState(prevState => ({
cats: prevState.cats.map(c => c.id[0] === cat.id[0] ? Object.assign(c, { liked: !c.liked }) : c)
}));
console.log(state.cats[1].liked);

Related

How to create nested array of objects using useState react hook

I have a problem finding a solution on how to fill empty nested array by using the useState hook. I'm a react beginner and I have might miss something. In steps below I shall try to summarize the problem.
1.) I receive this data format as props - {question}:
{
category: "General Knowledge"
correct_answer: "Arby's"
difficulty: "easy"
incorrect_answers: (3) ['McDonald's', 'Burger King', 'Wendy's']
question: "In which fast food chain can you order a Jamocha Shake?"
type: "multiple"
}
What my goal output is ty create an object with this structure:
value: "Opel",
isClicked: false,
isCorrect: true,
incorrect_answers: [
{isClicked: false, value: "Bugatti"},
{isClicked: false, value: "Bentley},
{etc...}
]
With this approach I achieve the result, but I would like to find a more correct react way.
useEffect(() => {
const obj = {
value: question.correct_answer,
isClicked: false,
isCorrect: true,
incorrect_answers: []
}
question.incorrect_answers.map((item) =>
obj.incorrect_answers.push({
value: item,
isClicked: false,
})
)
setAnswers(obj)
}, [])
My goal is to have mentioned data structure formed in useState with the right approach on how to access nested arr and fill it with objects.
a) I use the useState for setting up state and its data structure for answers obj.
const [answers, setAnswers] = useState({
value: question.correct_answer,
isClicked: false,
isCorrect: true,
incorrect_answers: [
//I want multiple objects {value: '...', isClicked: ...},
// Would be nice if I was able to insert objects in this a) step.
]
})
b) Perhaps on the useEffect or on some other funcion I want to fill incorect_answers array with objects.
useEffect(() => {
// c) Im accesing the answers state and filling incorect_answers with obj
setAnswers(prevState => {
return {
...prevState,
incorrect_answers: [{
value: question.incorrect_answers.map((item) => item),
isClicked: false,
isCorrect: false
}]
}
})
// d) my output:
/* {
value: "Opel",
isClicked: false,
isCorrect: true,
incorrect_answers: [
{isClicked: false, value: [bugatti, bentley, bmw, citroen]},
]
} */
}, [])
If you're using map you shouldnt ignore the response from it, and you don't need to push to the array
useEffect(() => {
const obj = {
value: question.correct_answer,
isClicked: false,
isCorrect: true,
incorrect_answers: question.incorrect_answers.map(item => ({
value: item,
isClicked: false
}))
}
setAnswers(obj)
}, [])
The same method can be used when first filling your state
const [answers, setAnswers] = useState({
value: question.correct_answer,
isClicked: false,
isCorrect: true,
incorrect_answers: question.incorrect_answers.map(item => ({
value: item,
isClicked: false
}))
})

Counting true occurences in array and binding them to a variable in angular

I have a set of input fields with checkboxes where the user can choose an option. Then it's saved in local storage as true if checked and false if not. The localStorage under key "passengers" looks like this:
0: {name: "", child: false, luggage: true}
1: {name: "", child: true, luggage: false}
2: {name: "", child: false, luggage: true}
3: {name: "", child: true, luggage: false}
I want to count occurrences of true and store as a number in a variable
public luggageCounter: number;
I was trying to use
this.luggageCounter = countTrue([this.passengers[i].luggage]], true)
console.log(this.luggageCounter)
and const countTrue = (arr, val) => arr.reduce((a, v) => (v === val ? a +1 : a), 0)
but both of these solutions don't work. I was also thinking about making it easier and toggling the value from HTML by adding code below with [ngClass] and binding it with method in component.ts but also with no success.
<div>Extra luggage: {{ passengers[i].luggage ? 'Yes' : 'No' }}<div>
Any ideas how to make this simple task work? I'm out of ideas :)
Please try the following solution
const data = [
{ name: "", child: false, luggage: true },
{ name: "", child: true, luggage: false },
{ name: "", child: false, luggage: true },
{ name: "", child: true, luggage: false },
];
const total = data.reduce((previousValue, currentValue) => {
if (currentValue.luggage) {
previousValue += 1;
}
return previousValue;
}, 0);
console.log(total);
See
Array.prototype.reduce()

Angular Array not updating after maping function

I have an array of Categories that I filter when a user click on a button that selects a specific categories to view. however after maping the categories array, the categories display the desired result in the console however it seems to get lost somehow and the categories dont update in the DOM?
ngOnInit() {
this.initCategories();
this.shopService.filterCategories.subscribe(
(fCategory: string) => {
const filteredCategories = this.categories.filter(category => {
return category.name !== fCategory;
});
for (const obj of filteredCategories) {
obj.checked = false;
}
const newCategories = [];
this.categories.map(obj => {
filteredCategories.filter(fCat => obj);
newCategories.push(obj);
});
this.categories = newCategories;
console.log(this.categories)
}
);
}
initCategories(){
this.categories = [
{name: 'dress', checked: true, displayName: 'Dresses'},
{name: 'top', checked: true, displayName: 'Shirts'},
{name: 'skirt', checked: true, displayName: 'Skirts/Pants'},
{name: 'purse', checked: true, displayName: 'Purse'},
{name: 'bag', checked: true, displayName: 'Bags'},
];
}
result
[{…}, {…}, {…}, {…}, {…}]
0: {name: "dress", checked: true, displayName: "Dresses"}
1: {name: "top", checked: false, displayName: "Shirts"}
2: {name: "skirt", checked: false, displayName: "Skirts/Pants"}
3: {name: "purse", checked: false, displayName: "Purse"}
4: {name: "bag", checked: false, displayName: "Bags"}
however when I log the categories array in ngAfterViewInit
I get this.
[{…}, {…}, {…}, {…}, {…}]
0: {name: "dress", checked: true, displayName: "Dresses"}
1: {name: "top", checked: true, displayName: "Shirts"}
2: {name: "skirt", checked: true, displayName: "Skirts/Pants"}
3: {name: "purse", checked: true, displayName: "Purse"}
4: {name: "bag", checked: true, displayName: "Bags"}
what I tried
this.shopService.filterCategories.subscribe(
(fCategory: string) => {
const filteredCategories = this.categories.filter(category => {
return category.name !== fCategory;
});
for (const obj of filteredCategories) {
obj.checked = false;
}
let newCategories;
newCategories = [...this.categories.map(obj => {
filteredCategories.filter(fCat => obj);
})];
this.categories = newCategories;
console.log(this.categories)
}
);
}
I think you need to play with
this.categories.map(obj => {
filteredCategories.filter(fCat => obj);
both of them return a new array, they don't touch current one.
Therefore I would assume that filteredCategories.filter at least should be assigned somewhere.
// an empty array
const newCategories = [];
// starting a loop, forEach would fit here better because it doesn't return anything.
this.categories.map(obj => {
// the result of this filter won't be assigned anywhere.
filteredCategories.filter(fCat => obj);
// pushing obj to newCategories for every iteration.
// may be you need to wrap it with `if` based on filter result.
newCategories.push(obj);
});
// newCategories is an array with the same items as this.categories.
// because we simply iterate without any conditions.
console.log(newCategories);
In the update part of your question filter still doesn't do anything.
Its result should be assigned or used in a condition.
newCategories = [...this.categories.map(obj => {
filteredCategories.filter(fCat => obj); // <- should be assigned
})];
if you want to add only filtered only active category.
ngOnInit() {
this.initCategories();
this.shopService.filterCategories.subscribe(
(fCategory: string) => {
const filteredCategories: FilterBarComponent['categories'] = [];
for (const category of this.categories) {
filteredCategories.push({
...category,
checked: category.name === fCategory,
});
}
this.categories = filteredCategories;
this.updateCategories();
}
);
}

Add object to nested array

I have initial state as
sites = [
{id, name, vehicles[], drivers[]},
{id, name, vehicles[], drivers[]},
{id, name, vehicles[], drivers[]},
{id, name, vehicles[], drivers[]},
];
I'm trying to add a vehicle to a given site when selected from a list which is in a component SiteVehcleSelection and the method that handles the selection is:
handleVehicleSelection = (event) => {
const vehicle = this.props.vehicles.find((v) => v.id === parseInt(event.target.dataset.id, 10));
this.props.handleVehicleSelection(event, this.state.site.id, {...vehicle});
};
which passes it up to parent SiteList method:
handleVehicleSelection = (event, siteId, vehicle) => {
this.props.dispatch(siteActions.handleVehicleSelect(siteId, vehicle), event.target.checked);
}
called from the SiteList class:
export function handleVehicleSelect(siteId, vehicle, cmd){
return (dispatch) => {
debugger;
return fetch(`${BASE_URL}/accounts/site-vehicle-action/${siteId}/${vehicle.id}/${cmd}`, {
method: 'PUT',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: ''
}).then((res) => {
return res.json();
}).then((json) => {
if (json.msg === true) {
dispatch(vehicleSelect(siteId, vehicle));
}
});
}
}
which dispatches to this:
export function vehicleSelect(siteId, vehicle){
return {type: actionTypes.ADD_VEHICLE_TO_SITE, siteId, vehicle};
}
and my reducer is:
case actionTypes.ADD_VEHICLE_TO_SITE:
debugger;
const siteIndex = state.findIndex((site) => site.id === action.siteId);
console.log(state);
const newState = [...state.slice(0, siteIndex), {...state[siteIndex], vehicles: [...state[siteIndex].vehicles, action.vehicle],}, ...state.slice(siteIndex +1)];
console.log(newState);
return newState;
when I log before and after the changes have taken place, the vehicle has been added to the correct site but it does not show/refresh in the view here is the logging of the state before and after.
Before change :
0: {drivers: Array(0), id: 1, name: "Site One", vehicles: Array(0)}
1: {drivers: Array(0), id: 2, name: "Site Two", vehicles: Array(0)}
2: {drivers: Array(0), id: 3, name: "Site Three", vehicles: Array(0)}
length: 3
__proto__: Array(0)
After change:
0: {drivers: Array(0), id: 1, name: "Site One", vehicles: Array(1)}
1: {drivers: Array(0), id: 2, name: "Site Two", vehicles: Array(0)}
2: {drivers: Array(0), id: 3, name: "Site Three", vehicles: Array(0)}
length: 3
__proto__: Array(0)
can see the first one had the vehicle added correctly and this is the new state but nothing happens on return as if sitesList does not refresh.
Hope this edit helps explain more.
I think below code will shed some light. I assume you have the corresponding indexes.
let state = [
{ id: "id1", name: "name1", items: [{},{}] },
{ id: "id2", name: "name2", items: [{},{}] },
]
function reducer() {
switch("Action") {
case ADD_SITE: { // add new element to state
return [
...state,
payload.site,
]
}
case ADD_SITE_AT_INDEX: { // add new element to state at index: idx
return [
...state.slice(0, idx),
payload.newSite,
...state.slice(idx)
]
}
case ADD_ITEM: { // add new item to site with index: idx
return [
...state.slice(0, idx),
{
...state[idx],
items: [
...state[idx].items,
payload.newItem
],
},
...state.slice(idx+1)
]
}
case ADD_ITEM_AT_INDEX: { // add new item to site with index: idx, at item index: item_idx
return [
...state.slice(0, idx),
{
...state[idx],
items: [
...state[idx].items.slice(0, item_idx),
payload.newItem,
...state[idx].items.slice(item_idx),
],
},
...state.slice(idx+1)
]
}
}
}
let say this is your structure of state with keys (i am giving random name to the keys so i can explain)
{
data:[{id:"some_id",name:"some_name",items1:[{},{}]}]
}
//suppose this is your reducer code.
case ADD_ITEM:
return{
...state,
data: state.data.map(val=>{
if(val.Id===action.payload.Id){
return{
...val,
items1:[...val.items1,action.payload.data]
}
}
return{...val}
})
}
here you'll be sending Id and data from the action like:
{type:"ADD_ITEM",payload:{Id,data}}
where id will be the id of first level object which's array needs to be updated,
and data will be the data you want to add into the array..
If you just want to add an object to an array with a given structure, you can do this:
Structure
[
{ id, name, items1[{object},{object}]
]
copy an existing state and then add a new object to the end of the array.
return {
...state,
items1: state.items1.concat(action.newObject),
};
or with ES6 spread
return {
...state,
items1: [...state.items1, action.newObject],
};
// state: { id, name, items1: [{object},{object},{newObject}] }

How to change property of item in ImmutableList({}) inside Immutable.Map({})

How do I change property of item in ImmutableList({}) that is inside Immutable.Map({})?
I have:
const initialState = Immutable.Map({
width: window.board.width,
height: window.board.height,
lines: Immutable.List([
Immutable.Map({id: 1, active: false, name: 'Some name'}),
Immutable.Map({id: 2, active: false, name: 'Sad name'}),
Immutable.Map({id: 3, active: true, name: 'Cool name'})
])
});
Lets say I want to set item with id 1 in the List. Then change its property active to true. I also want to set property active to false for all the other items in the List.
How do I do that? Thanks a lot in advance.
Edit (final solution):
export function activateLine(lineId) {
return function (dispatch, getState) {
const updatedLines = getState().board.update('lines', Immutable.List(),
(oldList) => oldList.map((listItem) => {
return listItem.set("active", (listItem.get("id") === lineId));
})).get('lines');
return dispatch({
type: types.ACTIVATE_LINE,
payload: updatedLines
});
};
}
You can traverse immutable like so (quick note though i would highly recommend you make the entire object immutable). You can keep down the lines of code and keep it a bit more elegant by just using immutable itself -
const changeImmutableObject = initialState.update('lines', Immutable.List(),
(oldList) => oldList.map((listItem) => {
if(listItem.get("id") === 1) {
return listItem.set("active", true);
} else {
return listItem.set("active", false);
}
})
)
See working fiddle - https://jsfiddle.net/o04btr3j/214/
This updates the list key inside your object (if it is not there for whatever reason it defaults to an immutable list), then maps over the properties. If it has an id of 1, it will set active to true, otherwise it will set active to false.
You can also just initialState.toJS() your immutable object and do your logic in plain js, or do and immutableobject.get('property') and modify and then set that back in your object, however I would recommend you just use the built in methods in immutable as it is much less code.
Also, If I'm not mistaken you can just do something like this :
const initialState = Immutable.fromJS({
width: window.board.width,
height: window.board.height,
lines:[
{id: 1, active: false, name: 'Some name'},
{id: 2, active: false, name: 'Sad name'},
{id: 3, active: true, name: 'Cool name'}
]
});
You can immutable.fromJS() an object to put it into an immutable object. And all immutable objects should have a .toJS method on them to turn them back into regular javascript objects/lists/whatever.
I have created lithe example on link to solution on jsbin.com
const initialState = Immutable.Map({
lines: Immutable.List([
Immutable.Map({id: 1, active: false, name: 'Some name'}),
Immutable.Map({id: 2, active: false, name: 'Sad name'}),
Immutable.Map({id: 3, active: true, name: 'Cool name'})
])
});
console.log('initialState', initialState.toJS())
// update lines, map over items and set active to false
const stateLinesAllNotActive = initialState.updateIn(
['lines'],
items => items.map((item) => item.set('active', false))
)
console.log('stateLinesAllNotActive', stateLinesAllNotActive.toJS())
// lines and key for item with id === 1, then set active to this item
const stateFirstActive = stateLinesAllNotActive.updateIn(
[
'lines',
stateLinesAllNotActive.get('lines').findIndex((item) => (item.get("id") === 1))
],
item => item.set("active", true)
)
console.log('stateFirstActive', stateFirstActive.toJS())
What follows should work, but, as someone said... I think could be better a full update...
const initialState = Immutable.Map({
lines: Immutable.List([
Immutable.Map({id: 1, active: false, name: 'Some name'}),
Immutable.Map({id: 2, active: false, name: 'Sad name'}),
Immutable.Map({id: 3, active: true, name: 'Cool name'})
])
});
let lines = initialState.get('lines');
let id = 1; // what you want update...
let updater = item => {
let updated = item.set('name', "Giuseppe");
return updated;
};
let index = lines.findIndex((i) => i.get("id") === id);
let result = lines.update(index, updater);
initialState.set("lines", result);
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/immutable/3.8.1/immutable.js"></script>

Categories