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>
Related
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});
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 calling data from an api into my react app using axios, like so:
const adapter = axios.create({
baseURL: "http://localhost:4000",
});
const getData = async () => {
const response = await adapter.get("/test-api");
return response.data;
};
This runs in a context, and I have a basic reducer function that I pass to the context:
const initialState = {
loading: true,
error: false,
data: [],
errorMessage: "",
};
const reducer = (state, action) => {
switch (action.type) {
case ACTIONS.FETCH_SUCCESS:
return {
...state,
loading: false,
data: action.payload,
};
case ACTIONS.FETCH_ERROR:
return {
...state,
error: true,
errorMessage: "Error loading data",
};
default:
return state;
}
};
The data I'm returning from my api is shaped like this:
{
"data": [
{
"id": 1,
"name": "Name 1",
"items": [
{
"id": "klqo1gnh",
"name": "Item 1",
"date": "2019-05-12"
}
]
},
{
"id": 2,
"name": "Name 2",
"items": [
{
"id": "klqo2fho",
"name": "Item 1",
"date": "2021-05-05"
},
{
"id": "klro8wip",
"name": "Item 2",
"date": "2012-05-05"
}
]
}
]
}
And I've written a simple function that finds the item whose nested array, items here, has the earliest date, using moment:
const sortDataByDate = (items) => {
return items.sort((first, second) => {
if (moment(first.items.date).isSame(second.items.date)) {
return -1;
} else if (moment(first.items.date).isBefore(second.items.date)) {
return -1;
} else {
return 1;
}
});
};
I then fetch everything in this function:
const fetchData = useCallback(async () => {
try {
await getData().then((response) => {
dispatch({
type: ACTIONS.FETCH_SUCCESS,
payload: response,
});
});
} catch (error) {
dispatch({ type: ACTIONS.FETCH_ERROR });
}
}, []);
I then run fetchData() inside a useEffect within my context:
useEffect(() => {
fetchData();
}, [fetchData]);
All this to say, here's the problem. My sortDataByDate function works sporadically; sometimes the data is ordered correctly, other times it's not. What I'd like to do is fetch my data, sort it with sortDataByDate, and then set the payload with that sorted data, so it's sorted globally rather than on a component level. Inside my App it seems to work consistently, so I think that I have missed something on a context level. Any suggestions?
You need to sort inner items first and get the earliest date:
const sortDataByDate = (items) => {
return items.sort((first, second) => {
if (moment(first.items[0].date).isSame(second.items[0].date)) {
return -1;
} else if (moment(first.items[0].date).isBefore(second.items[0].date)) {
return -1;
} else {
return 1;
}
});
};
I'm trying to convert this code
// this.setState({
// description:'', // resets title after upload
// images: [
// {
// id: newImage[0].id,
// user:{
// username: newImage[0].user.username
// },
// // comments:{
// // comment_body: newImage[0].comments.comment_body
// // },
// image_title: newImage[0].image_title,
// img_url: newImage[0].img_url,
// created_at: new Date().toLocaleString().replace(',', ''),
// updated_at: new Date().toLocaleString().replace(',', '')
// },
// ...this.state.images
// ]
// })
to a redux reducer, here was my attempt
Snippet Reducer
case UPLOAD_IMAGE:
const newState = {...state}
const myImages = newState.images // old images
const newImage = action.newImage
console.log(newImage); // gets the new uploaded image.
return {
images:[
...myImages,
{
id: newImage[0].id,
user:{
username: newImage[0].user.username
},
// comments:{
// comment_body: newImage[0].comments.comment_body
// },
image_title: newImage[0].image_title,
img_url: newImage[0].img_url,
created_at: new Date().toLocaleString().replace(',', ''),
updated_at: new Date().toLocaleString().replace(',', '')
}
]
}
As of right now, images[] are being mapped in the dashboard component, and it fetches images from redux reducer case GET_IMAGES:. The issue comes when uploading an image. I get the error
TypeError: Cannot read property 'length' of undefined
and on refresh, the new uploaded images shows within the map iteration.
My guess is that the old images are contained in an array, and the new images is in an array within an object i guess i don't know.
old images data structure (edited for privacy)
[
{
"id": 217,
"image_title": "ddddddf",
"img_url": "http:/************579/uploads/lshndpj***g",
"created_at": "2019-06-16T17:03:00.605Z",
"updated_at": "2019-06-16T17:03:00.605Z",
"user_id": 1,
"user": {
"id": 1,
"googleId": null,
"username": "E****",
"password": *****$12$16UTTfIH6gXhnRMnBHFGHuHiI***EAGF3GCjO62",
"email": "e****",
"created_at": "2019-06-05T04:50:20.133Z",
"updated_at": "2019-06-05T04:50:20.133Z"
},
"comments": []
},
New uploaded Image data structure(edited for privacy)
{
"0": {
"id": 218,
"image_title": "owl",
"img_url": "http:/*********0705077/uploads******",
"created_at": "2019-06-16T17:11:19.066Z",
"updated_at": "2019-06-16T17:11:19.066Z",
"user_id": 1,
"user": {
"id": 1,
"googleId": null,
"username": "BOB",
"password": "$2b$12******"
"created_at": "2019-06-05T04:50:20.133Z",
"updated_at": "2019-06-05T04:50:20.133Z"
},
"comments": []
},
Maybe there is a discrepancy with the data structure which is why it gives me the type error when looping through the array.
full code
actions
export const uploadImage = data => {
return (dispatch) => {
Axios.post('/images/upload', data).then((response) => {
const newImage = {...response.data}
console.log(newImage);
dispatch({type:UPLOAD_IMAGE, newImage})
}
}
// get images
export const getImages = () => {
return (dispatch) => {
return Axios.get('/images/uploads').then( (response) => {
const data = response.data;
dispatch({
type: GET_IMAGES,
data
})
});
}
}
reducer
import { GET_IMAGES, POST_COMMENT, DELETE_IMAGE, UPLOAD_IMAGE } from '../actions/types';
const initialState = {
images:[],
}
export default (state = initialState, action) => {
switch (action.type) {
case GET_IMAGES:
console.log(action.data);
return{
...state,
images:action.data
}
case UPLOAD_IMAGE:
const newState = {...state}
const myImages = newState.images // old images
const newImage = action.newImage
console.log(newImage); // gets the new uploaded image.
return {
images:[
...myImages,
{
id: newImage[0].id,
user:{
username: newImage[0].user.username
},
// comments:{
// comment_body: newImage[0].comments.comment_body
// },
image_title: newImage[0].image_title,
img_url: newImage[0].img_url,
created_at: new Date().toLocaleString().replace(',', ''),
updated_at: new Date().toLocaleString().replace(',', '')
}
]
}
default:
return state;
}
}
Dashboard.js
const { image} = this.props
{image.images.length > 0 ? (
image.images.map( (img, i) => (
<div key={i}>
<ImageContainer img={img} deleteImg={() => this.deleteImg(img.id)}/>
</div>
))
) : (
<div>
<Grid item md={8}>
<Typography>No Images yet</Typography>
</Grid>
</div>
)}
const mapStateToProps = (state) => ({
image: state.image
})
const mapDispatchToProps = (dispatch) => ({
getImages: () => dispatch(getImages()),
deleteImage : (id) => dispatch(deleteImage (id)),
uploadImage: (data) => dispatch(uploadImage(data))
})
export default connect(mapStateToProps, mapDispatchToProps)(Dashboard)
Whatever you return from a reducer replaces the previous redux state. So in your case, your initial state was something similar to:
{
images: [],
...
}
But in UPLOAD_IMAGE action, your are returning an object with only likes property:
{
likes: ...
}
So the previous state gets replaced by this and your state.image.images returns undefined. To solve this use the spread pattern, assign previous state into the new state first, then assign likes data:
{
...previousState,
likes: ...
}
As I've mentioned in comment that there is issue in your reducer, you can fix it by
case UPDATE_IMAGE:
return {
images: [ ...state.images, action.newImage[0]]
}
with the help from #mezba and #Abhay Sehgal
I found a working solution, thanks folks
case UPLOAD_IMAGE:
const newState = {...state}
const myImages = newState.images // old images
const newImage = action.newImage
console.log(newImage[0]); // gets the new uploaded image.
return {
images:[
{
id: newImage[0].id,
user:{
username: newImage[0].user.username
},
// comments:{
// comment_body: newImage[0].comments.comment_body
// },
image_title: newImage[0].image_title,
img_url: newImage[0].img_url,
created_at: new Date().toLocaleString().replace(',', ''),
updated_at: new Date().toLocaleString().replace(',', '')
},
myImages[0]
]
}
I have array like this in react native
const data = [
{ key: 1, label: 'Service1'},
{ key: 2, label: 'Service2' },
{ key: 3, label: 'Service3' },
{ key: 4, label: 'Service4' },
{ key: 5, label: 'Service4' },
];
and json data:
"services": [
{
"id": 1,
"name": "Hotels",
},
{
"id": 2,
"name": "Embassies",
},
]
How to map id to key and name to label???
You want to fill your const data with values from JSON, correct?
Try this:
var jsonData = {
"services": [
{ "id": 1, "name": "Hotels" },
{ "id": 2, "name": "Embassies" }
]
};
var data = jsonData.services.map(function(item) {
return {
key: item.id,
label: item.name
};
});
console.log(data);
if your data like below (removed services key)
var jsonData = [
{ "id": 1, "name": "Hotels" },
{ "id": 2, "name": "Embassies" }
];
var data = jsonData.map(function(item) {
return {
key: item.id,
label: item.name
};
});
console.log(data);
i know it to much late,but i hope its helpfull for others,How to fetch the response of JSON array in react native?How to map json data with array in react native
export default class ExpenseNew extends Component {
constructor(){
super();
this.state={
PickerSelectedVal : '',
accountnameMain:[],
}
}
componentDidMount(){
var account_nam=[]
fetch('your Url', {
method: 'GET',
headers: { 'Authorization': 'Bearer ' + your token }
})
.then((response) => response.json())
.then((customerselect) => {
// alert(JSON.stringify(customerselect))
global.customerdata = JSON.stringify(customerselect)
var customername = JSON.parse(customerdata);
//alert(JSON.stringify(customername));
for (i = 0; i < customername.cus_data.length; i++) {
var dataa = customername.cus_data[i]["account_name"];
account_nam.push(dataa)
}
this.setState({accountnameMain:account_nam});
})
.done();
}
render() {
return (
<Picker
selectedValue={this.state.PickerSelectedVal}
placeholder="Select your customer"
mode="dropdown"
iosIcon={<Icon name="arrow-down" />}
onValueChange={(itemValue, itemIndex) => this.setState({PickerSelectedVal: itemValue})} >
{this.state.accountnameMain.map((item, key)=>(
<Picker.Item label={item} value={item} key={key}/>)
)}
</Picker>
)
}
}
the above example is fetch array of data from json,and map data in to dropdown/picker,i hope its helpfull for others,if you have any query, asked from me