How to create nested array of objects using useState react hook - javascript

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
}))
})

Related

How to map JSON Object to array with keys in javascript

There's about million questions (and answers) out there on this topic, but none of them are doing what I need to do. I have a JSON object where the value for each key is an object. I want to convert this to an array and maintain the top level keys.
{
"someKey1": {
active: true,
name: "foo"
},
"someKey2": {
active: false,
name: "bar"
}
}
If I use Object.keys() I get the top level keys, but not their values. If I use Object.values() I get an array with all the values, but not their keys. I'm trying to use keys and map, but am only getting the values returned:
const data = {
"someKey1": {
active: true,
name: "foo"
},
"someKey2": {
active: false,
name: "bar"
}
}
const items = Object.keys(data).map(function(key) {
return data[key];
});
// returns [{active: true, name: foo},{active: false, name: bar}]
Is there a way to get both? I want to get an array I can iterate over that looks something like this:
[{
key: someKey1,
active: true,
name: "foo"
},
{
key: someKey2,
active: true,
name: "foo"
}]
OR
[
"someKey1": {
active: true,
name: "foo"
},
"someKey2": {
active: false,
name: "bar"
}
]
I think you are going in the right direction, if you want to add the "key" property you have to map the properties manually and for the second option since you don't need the "key" property it can be done a little bit more elegantly:
For the first option:
Object.keys(data).map(v => ({
key: v,
...data[v]
}));
For the second option even simpler:
Object.keys(data).map(v => ({[v]: {...data[v]}}))
You can easily map your data to a new object:
Object.keys(data).map(key => ({ ...data[key], "key": key }));

How to remove element(s) from a state array variable?

I have a state variable tasks, which is an array of objects:
const [tasks, setTasks] = useState([
{
user: "",
text: "Finish this page",
deadline: new Date("05/01/2022"),
complete: true,
priority: "high",
project: "Gravity",
// ToDo: Set priority based on date while creating task
},
{
text: "Finish Pierian UI/UX",
deadline: new Date("05/10/2022"),
complete: false,
priority: "med",
},
{
text: "Finish Internship",
deadline: new Date("05/05/2022"),
complete: false,
priority: "low",
},
{
text: "Anaadyanta",
deadline: new Date("05/12/2022"),
complete: false,
priority: "med",
},
{
text: "Random task",
deadline: new Date("05/19/2022"),
complete: false,
priority: "high",
},
]);
I want to remove elements whose complete is true. I am unable to think of the correct syntax while using setTasks. Please help.
You should filter tasks and set new state:
const updatedTasks = tasks.filter((el)=> el.complete !== 'true');
setTasks(updatedTasks);
you should pass callback inside setTasks:
setTask((prevTasks) => prevTasks.filter(({ complete }) => !complete));
The prevTasks always going to be the latest state, React insures it. You should always use callback when next state relies on previous.
Also the name of prev variable passed in callback could be whatever you choose, here I named it prevTasks.

How do I preserve the order of this javascript array sort?

const modules = [
{ name: 'Wood', checked: false },
{ name: 'Metal', checked: false },
{ name: 'Earth', checked: true },
{ name: 'Water', checked: false },
{ name: 'Air', checked: true },
{ name: 'Fire', checked: false },
]
I am trying to sort the array so that the True values come first , then False values.
const orderedModules = modules.sort((a, b) => (a.checked ? -1 : 1))
However, I'd like to preserve the order of the True values. The code above sometimes puts Air first, then Earth (if ran twice). How can I preserve the order all the time?
The callback used as the compare function can return 3 options: negative number, positive number or zero. The negative number indicates that the first parameter should be before the second parameter in the array order. The positive number indicates that the first parameter should be after the second parameter in the array order. And zero means that the order should be kept as is.
Sort array method on MDN
If you want to order just the true values first in the same order in the array and then the false values, probably adding more logic to return zero from the compare function if both are true will solve your issue.
Here is an example:
const modules = [
{ name: 'Wood', checked: false },
{ name: 'Metal', checked: false },
{ name: 'Earth', checked: true },
{ name: 'Water', checked: false },
{ name: 'Air', checked: true },
{ name: 'Fire', checked: false },
];
modules.sort((a,b) => a.checked && b.checked ? 0 : a.checked ? -1 : 1);
console.log(modules);
If you don't mind creating a new array, just iterate over the array twice. The first time, push to the new array the objects with the true values as the iteration encounters them. The second time, push the objects with the false values. (JavaScript passes objects by reference so the new array won't cause them to get duplicated.)
Probably this might help you. not sure for optimize way but this function iterate over an array one time only.
I am using reduce function to separate out true and false values and then return them in the order you want.
const shortItems = (array) => {
const orderedModulesObject = array.reduce((orderedModulesObject, currentModule) => {
if(currentModule.checked){
orderedModulesObject.trueValues = orderedModulesObject.trueValues.concat(currentModule);
} else {
orderedModulesObject.falseValues = orderedModulesObject.falseValues.concat(currentModule);
}
return orderedModulesObject;
}, { trueValues: [], falseValues: []});
return orderedModulesObject.trueValues.concat(orderedModulesObject.falseValues);
}
const modules = [
{ name: 'Wood', checked: false },
{ name: 'Metal', checked: false },
{ name: 'Earth', checked: true },
{ name: 'Water', checked: false },
{ name: 'Air', checked: true },
{ name: 'Fire', checked: false },
]
console.log(shortItems(modules));
the reason is that sort actually changes the original array. Although modules is defined with const, the values inside can change, as long as you don't assign the variable to something else.
according to this answer, you can sort without mutating the original using spread syntax. this code should work:
const orderedModules = [...modules].sort((a, b) => (a.checked ? -1 : 1))
to make an array or object not able to be modified, you can use Object.freeze()
const modules = Object.freeze([
{ name: 'Wood', checked: false },
{ name: 'Metal', checked: false },
{ name: 'Earth', checked: true },
{ name: 'Water', checked: false },
{ name: 'Air', checked: true },
{ name: 'Fire', checked: false },
])
Edit: I just realized the order isn't correct, but it at least is the same every time. but that's because the sorting isn't exactly right. here's the correct code:
const orderedModules = [...modules].sort((a, b) => (a.checked != b.checked ? (a.checked ? -1 : 1 ) : 0))

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()

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