const initState = {
questions:[
{
id: uuidv4(),
answers:[
{answerid: uuidv4()},
{answerid: uuidv4()}
]
},
],
}
this is the state i wanna function to add object in the answers array so the state will be after add like that:
const initState = {
questions:[
{
id: uuidv4(),
answers:[
{answerid: uuidv4()},
{answerid: uuidv4()},
{answerid: uuidv4()}
]
},
],
}
you can use a library like Immer for deeply nested objects update,
My try as below
const initState = {
questions: [
{
id: 1,
answers: [
{
answerid: 34,
},
{
answerid: 12,
},
],
},
],
};
const newState = {
...initState,
questions: initState.questions.map((q) => {
const newq = {
...q,
answers: [
...q.answers,
{
a: "b",
},
],
};
return newq;
}),
};
newState.questions[0].id = 2; // to test it doesn't mutate original object
console.log({initState, newState});
Related
I am developing a website on the stack: React, redux, typescript.
I can't output a nested array with data from a data object in JSON
My code:
app.tsx
const App: React.FC = () => {
const {tasks, loading, error} = useTypedSelector(state => state.task)
const dispatch: Dispatch<any> = useDispatch()
useEffect(() => {
dispatch(fetchTasks())
}, [])
if (loading) {
return <h1>Идет загрузка...</h1>
}
if (error) {
return <h1>{error}</h1>
}
return (
<div className="Gant_Container">
<div>
<p className="Project_Period">{Object.values(tasks)[0]} / {Object.values(tasks)[1]}</p>
</div>
<div>
{Object.values(tasks).map((task, id) => {
return (<div key={id}>
{task.id}
{task.title}
{chart.start}
{chart.end}
</div>)
})}
</div>
</div>
);
};
export default Gantt_Container;
store/index.ts
export const store = createStore(rootReducer, applyMiddleware(thunk))
reducers/index.ts
export const rootReducer = combineReducers({
task: taskReducer,
})
export type RootState = ReturnType<typeof rootReducer>
reducers/taskReducer.tsx
const initialState: TaskState = {
tasks: [],
loading: false,
error: null
}
export const taskReducer = (state = initialState, action: TaskAction): TaskState => {
switch (action.type) {
case TaskActionTypes.FETCH_TASKS:
return {loading: true, error: null, tasks: []}
case TaskActionTypes.FETCH_TASKS_SUCCESS:
return {loading: false, error: null, tasks: action.payload}
case TaskActionTypes.FETCH_TASKS_ERROR:
return {loading: false, error: action.payload, tasks: []}
default:
return state
}
}
action-creators/task.ts
export const fetchTasks = () => {
return async (dispatch: Dispatch<TaskAction>) => {
try {
dispatch({type: TaskActionTypes.FETCH_TASKS})
const response = await axios.get("") // The data is coming from the backend, I have hidden the data
dispatch({type: TaskActionTypes.FETCH_TASKS_SUCCESS, payload: response.data})
} catch (e) {
dispatch({
type: TaskActionTypes.FETCH_TASKS_ERROR,
payload: 'Произошла ошибка при загрузке данных'
})
}
}
}
types/task.ts
export interface TaskState {
tasks: any[];
loading: boolean;
error: null | string;
}
export enum TaskActionTypes {
FETCH_TASKS = 'FETCH_TASKS',
FETCH_TASKS_SUCCESS = 'FETCH_TASKS_SUCCESS',
FETCH_TASKS_ERROR = 'FETCH_TASKS_ERROR'
}
interface FetchTasksAction {
type: TaskActionTypes.FETCH_TASKS;
}
interface FetchTasksSuccessAction {
type: TaskActionTypes.FETCH_TASKS_SUCCESS;
payload: any[]
}
interface FetchTasksErrorAction {
type: TaskActionTypes.FETCH_TASKS_ERROR;
payload: string;
}
export type TaskAction = FetchTasksAction | FetchTasksSuccessAction | FetchTasksErrorAction
useTypedSelector.ts
export const useTypedSelector: TypedUseSelectorHook<RootState> = useSelector
.json
{
"name": "Project",
"data": "2022",
"task": {
"id": 1,
"title": "Apple",
"start": "2021",
"end": "2022",
"sub": [
{
"id": 2,
"title": "tomato",
"start": "2021",
"end": "2022",
"sub": [
{
"id": 3,
"title": "Orange",
"start": "2019",
"end": "2020",
"sub": [
{
"id": 4,
"title": "Banana",
"start": "2022",
"end": "2022",
"sub": [
{
"id": 5,
"title": "Strawberry",
"start": "2015",
"end": "2018"
},
{
"id": 6,
"title": "cherry",
"period_start": "2001,
"period_end": "2003"
}
]
}
]
}
]
}
]
}
}
Unfortunately I am not able to edit this json file.
I can output all the data before sub, and after I can't output them. I need to output absolutely all the data from json.
I have tried many ways from the internet, but I have not succeeded
This is solution for how you convert nested object into single array, Please use in your code like this:
It will work like recursion function.
const obj = {
name: 'Project',
data: '2022',
task: {
id: 1,
title: 'Apple',
start: '2021',
end: '2022',
sub: [{
id: 2,
title: 'tomato',
start: '2021',
end: '2022',
sub: [{
id: 3,
title: 'Orange',
start: '2019',
end: '2020',
sub: [{
id: 4,
title: 'Banana',
start: '2022',
end: '2022',
sub: [{
id: 5,
title: 'Strawberry',
start: '2015',
end: '2018',
},
{
id: 6,
title: 'cherry',
start: '2001',
end: '2003',
},
],
}, ],
}, ],
}, ],
},
};
const arr = [];
const foo = (task) => {
if (!task.id) return;
arr.push({
id: task.id,
title: task.title,
start: task.start,
end: task.end,
});
if (task.sub && task.sub.length > 0) task.sub.forEach(item => foo(item));
};
foo(obj.task);
console.log('>>>>> arr : ', arr);
I think what you are missing is the user of object.keys:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys
It allow you to return a array of keys of your object that you can map on.
What I suggest is doing something like this :
const GanttContainer: React.FC = () => {
const {tasks, loading, error} = useTypedSelector(state => state.task)
const dispatch: Dispatch<any> = useDispatch()
useEffect(() => {
dispatch(fetchTasks())
}, [])
if (loading) {
return <h1>Идет загрузка...</h1>
}
if (error) {
return <h1>{error}</h1>
}
return (
....
{Object.keys(tasks).map((taskKeys, id) => {
return (
<div key={id}>
{tasks[taskKeys]}
</div>)
....
);
};
export default GanttContainer;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
I have an array of object like this:
const tempobj = [
{
id: "1",
fanimate: [
{
id: "111",
animate: "xyz",
},
],
},];
Now I want to add more animations inside this array, such that each object gets added in the fanimate such that:
const tempobj = [
{
id: "1",
fanimate: [
{
id: "111",
animate: "xyz",
},
{
id: "222",
animate: "def",
},
],
},];
I tried using the hook useState, but I am getting undefined results
const tempobj = [
{
id: "1",
fanimate: [
{
id: "111",
animate: "xyz",
},
],
}];
const modified = tempobj.map(temp => {
const newtemp = {
id: temp.id,
fanimate: [...temp.fanimate, {id:"222", animate:"def"}]
}
return newtemp;
})
console.log(modified);
You could just spread all the places
const tempObj = {
id:'1',
fan:[
{
id:'2',
animate:'xyz'
}
]
}
console.log(tempObj)
const newtest={...testObj,fan:[...testObj.fan, {id:'3', animate:'tuz'}]}
console.log(newtest)
Try this:
const tempobj = [
{
id: "1",
fanimate: [
{
id: "111",
animate: "xyz"
}
]
}
];
const [state, setState] = useState(tempobj);
function updateArray(newItem) {
setState(
state.map((item) => ({ ...item, fanimate: [...item.fanimate, newItem] }))
);
}
You can push the newItem to the original fanimate array using spread operator, everytime a new item is added, the original array data is copied by ...item.fanimate:
{ ...item, fanimate: [...item.fanimate, newItem] }
BTW the naming of tempobj really should be tempArr or tempArray.
A working sandbox
I'm trying to convert an array of objects like this:
[{grandParentField:'grandParent1', parentField:'parent1', childField: 'child1'},
{grandParentField:'grandParent1', parentField:'parent1', childField: 'child2'},
{grandParentField:'grandParent2', parentField:'parent1', childField: 'child3'},
{grandParentField:'grandParent2', parentField:'parent2', childField: 'child4'}]
into this form:
[
{
text: 'grandparent1',
items: [
{
text: 'parent1',
items: [{ text: 'child1' }, { text: 'child2' }]
}
]
},
{
text: 'grandparent2',
items: [
{
text: 'parent1',
items: [{ text: 'child3' }]
},
{
text: 'parent2',
items: [{ text: 'child4' }]
}
]
}
]
This Thread is similar to what I want, but not quite.
children will always be unique, but parents can have multiple grandparents.
Honestly I've tried so many things I'm not even sure which one to include as an example of what has gotten me closest.
Something like this but able to take in an array of Objects, and pump out the {text: string, items:[{text: string, items:[{text:string]]} structure:
var groupBy = function(xs, key) {
return xs.reduce(function(rv, x) {
(rv[x[key]] = rv[x[key]] || []).push(x);
return rv;
}, {});
};
console.log(groupBy(['one', 'two', 'three'], 'length'));
// => {3: ["one", "two"], 5: ["three"]}
Recursive approach, should work for every n-nested input that you will provide:
const input =[{grandParentField:"grandParent1",parentField:"parent1",childField:"child1"},{grandParentField:"grandParent1",parentField:"parent1",childField:"child2"},{grandParentField:"grandParent2",parentField:"parent1",childField:"child3"},{grandParentField:"grandParent2",parentField:"parent2",childField:"child4"}];
const nestedGroupBy = (nodes, order, orderIdx = 0) => {
const key = order[orderIdx]
let grouped = nodes.reduce((acc, e, i) => {
let node = acc.find(x => x.text == e[key])
if (!node) {
node = { text: e[key], items: [] }
acc.push(node)
}
node.items ? node.items.push(e) : node.items = [e]
return acc
}, [])
if (order[orderIdx + 1])
grouped = grouped.map(e => ({
text: e.text,
items: nestedGroupBy(e.items, order, orderIdx + 1)
}))
else
grouped = grouped.map(e => ({ text: e.text }) )
return grouped
}
const res = nestedGroupBy(input, Object.keys(input[0]))
console.log(res)
.as-console-wrapper { max-height: 100% !important; top: 0; }
Without getting too crazy with types, I'd say that you want your output to be of this shape:
interface Tree {
text: string,
items?: Tree[]
}
So let's make a function called group() which takes your array and a list of keys that you want to process in the order they should be processed. So for your example it would be used like this:
const data = [
{ grandParentField: 'grandParent1', parentField: 'parent1', childField: 'child1' },
{ grandParentField: 'grandParent1', parentField: 'parent1', childField: 'child2' },
{ grandParentField: 'grandParent2', parentField: 'parent1', childField: 'child3' },
{ grandParentField: 'grandParent2', parentField: 'parent2', childField: 'child4' }
];
const groupedData = group(data, "grandParentField", "parentField", "childField");
Here's the implementation of group():
function group(data: Array<Record<string, string>>, key: string, ...otherKeys: string[]): Tree[] {
const objMap: Record<string, any[]> = {}
for (const d of data) {
if (!(d[key] in objMap)) {
objMap[d[key]] = []
}
objMap[d[key]].push(d);
}
return Object.keys(objMap).map(k => otherKeys.length ?
{
text: k,
items: group(objMap[k], otherKeys[0], ...otherKeys.slice(1))
} : {
text: k
}
);
}
First we group the elements from data into a dictionary of arrays called objMap, where each element d goes into the key of objMap at d[key] (so the first element goes into the key named "grandParent1" if key is "grandParentField").
Once this grouping is done, we return a new array by walking through objMap's keys. If we have no otherKeys, we just return an array of {text: string} elements using the keys of objMap as the text field. If we do have other keys, then we need to recursively call group() on the elements stored in objMap at the proper key.
You can verify that this works for your example:
console.log(JSON.stringify(groupedData, undefined, 2));
/* [
{
"text": "grandParent1",
"items": [
{
"text": "parent1",
"items": [
{
"text": "child1"
},
{
"text": "child2"
}
]
}
]
},
{
"text": "grandParent2",
"items": [
{
"text": "parent1",
"items": [
{
"text": "child3"
}
]
},
{
"text": "parent2",
"items": [
{
"text": "child4"
}
]
}
]
}
] */
Playground link to code
I've been stuck for whole day and please help me to fix it.
I have a json data which like this :
[
{
"menu": "menu_1",
"icon": "icon_1",
"detail": {
"name": "name_1",
"phone": "phone_1"
}
},
{
"menu": "menu_2",
"icon": "icon_2",
"detail": {
"name": "name_2",
"phone": "phone_2"
}
},
{
"menu": "menu_3",
"icon": "icon_3",
"detail": {
"name": "name_3",
"phone": "phone_3"
}
}
]
I put them into the "data" state and My goal is I wanna change the "detail" state with certain index ( ex: state "data" with index 1 change the "detail" data )
Currently my code is :
this.setState({
data: {
...this.state.data,
detail:{
this.state.data[1].detail:{
"name": "billy",
"phone": "893823839"
}
}
}
})
That setState is clearly wanna change the state with certain index but fail..
How do I supposed to do?
I guess this is what you're looking for, we could replace an element inside an array using splice :
const index = 1;
this.setState({
data: [...this.state.data].splice(index, 1, {
...this.state.data[index],
details: { name: "billy", phone: "893823839" },
}),
});
Update: we could use slice also to make an immutable update with index :
this.setState({
data: [
...this.state.data.slice(0, index),
{
...this.state.data[index],
details: { name: "billy", phone: "893823839" },
},
...this.state.data.slice(index + 1, this.state.data.length),
],
});
could you try it ?
this is an example that i tested using splice:
const items = [{ id: 1 }, { id: 2 }, { id: 3 }];
const indexToBeModified = 1; // { id: 2 } ==> { foo: "foo", id: 2 }
items.splice(indexToBeModified, 1, { ...items[indexToBeModified], foo: "foo" });
console.log("items", items);
Here is a little modified example. It uses prevState to prevent any unwanted changes that may happen when directly interacting with this.state.
import React, { Component } from "react";
export default class App extends Component {
constructor() {
super();
this.state = {
data: [
{
menu: "menu_1",
icon: "icon_1",
detail: {
name: "name_1",
phone: "phone_1"
}
},
{
menu: "menu_2",
icon: "icon_2",
detail: {
name: "name_2",
phone: "phone_2"
}
},
{
menu: "menu_3",
icon: "icon_3",
detail: {
name: "name_3",
phone: "phone_3"
}
}
]
};
this.modifyData = this.modifyData.bind(this);
}
modifyData(index) {
this.setState((prevState) => {
prevState.data[index].detail={
name: "billy",
phone: "893823839"
};
return {
data: [prevState.data]
};
},()=>{console.log(this.state.data)});
}
render() {
return (
<button onClick={() => this.modifyData(0)}>Click to modify data</button>
);
}
}
Here is a code sandbox reference.
I have a state like:
this.state = {
questionListData: [
{
id: 1,
question: "Who was the first man to step on Moon?",
options: [
{
opt: "Abdul Kalam"
},
{
opt: "Albert Einstein"
},
{
opt: "Sheldon Cooper"
},
{
opt: "Salman Khan"
}
]
},
{
id: 2,
question: "Who was the Second man to step on Moon?",
options: [
{
opt: "Abdul Kalam2"
},
{
opt: "Albert Einstein2"
},
{
opt: "Sheldon Cooper2"
},
{
opt: "Salman Khan2"
}
]
},
{
id: 1,
question: "Who was the Third man to step on Moon?",
options: [
{
opt: "Abdul Kalam3"
},
{
opt: "Albert Einstein3"
},
{
opt: "Sheldon Cooper3"
},
{
opt: "Salman Khan3"
}
]
},
{
question: "Who was the Fourth man to step on Moon?",
options: [
{
opt: "Abdul Kalam"
},
{
opt: "Albert Einstein"
},
{
opt: "Sheldon Cooper"
},
{
opt: "Salman Khan"
}
]
}
]
};
I am dynamically adding to this list of questions under the questionListData array. While adding, id is not generated as it is user defined. So i want to automatically add an id if it is not there. for that i had done this:
this.state.questionListData.map((r,u)=>{
console.log('new r',r)
if(!this.state.questionListData[u].id){alert('!r.id')
this.setState({...this.state.questionListData[u], id:u+1 })
}
})
But if I do this, id is not added in that perticular index of the questionListData array, but outside it, like so:
questionListData:[
"id":4
{"id":1,
"question":"Who was the first man to step on Moon?",
"options":[
{"opt":"Abdul Kalam"},
{"opt":"Albert Einstein"},
{"opt":"Sheldon Cooper"},
{"opt":"Salman Khan"}
]
}
]
Please help me with the correct syntax of spread operator to add the id to that pwrticular index inside this.setState. Thanks.
Yes because you're adding id in your state and not in your list object in your state.
const newList = this.state.questionListData.map((obj, idx) => {
if(!obj.id){
obj.id = idx + 1;
}
return obj;
});
this.setState({ questionListData: newList });
You can do it like this:
const newList = this.state.questionListData.map((item, index) => {
if (!item.id) {
return {
...item,
id: index + 1
}
}
return item;
})
this.setState({questionListData: newList});