This question already has answers here:
The useState set method is not reflecting a change immediately
(15 answers)
Closed 2 years ago.
I believe its quite simple but I am not able to figureout how to replace entire object using useState and setState. Here I have the empty data:
const emptyData = {
"foos": {},
"bars": {}
};
const [chartData, setChartData] = useState(emptyData);
I successfully get JSON response (response_json) from my API call which looks like below:
{
"foos": {
"foo1": {
"id": "foo1",
"foo_position": {
"pos_x": 300,
"pos_y": 100
},
},
"foo2": {
"id": "foo2",
"foo_position": {
"pos_x": 300,
"pos_y": 300
},
},
"foo3": {
"id": "foo3",
"foo_position": {
"pos_x": 300,
"pos_y": 500
},
}
},
"bars": {
"bar1": {
"id": "bar1"
},
"bar2": {
"id": "bar2"
}
}
}
Now I want to replace entire chart data or foos and bars which doesnt seem not to happen:
useEffect(() => {
async function handlePageLoad() {
const apiURL = getDataUrl(id);
const response = await getData(apiURL);
if (response.status === 200) {
const response_json = await response.json();
setChartData(response_json);
alert(JSON.stringify(chartData));
}
}
handlePageLoad();
}, []);
I also tried but no success:
setChartData({ ...chartData, foos: response_json.foos});
Edit: I put an alert to see if the data has been changed or not but I see following in the alert message which is similar to the initial data:
{"foos":{},"bars":{}}
Any help is much appreciated
I think you just need to wait for the mutation to be merged. try the following to log the state after it has been updated
useEffect(() => {
async function handlePageLoad() {
const apiURL = getDataUrl(id);
const response = await getData(apiURL);
if (response.status === 200) {
const response_json = await response.json();
setChartData(response_json);
// alert(JSON.stringify(chartData));
}
}
handlePageLoad().then(() => alert(JSON.stringify(chartData)));
}, [])
Related
first of all I am using the Mockjs to simulate the backend data:
{
url: "/mockApi/system",
method: "get",
timeout: 500,
statusCode: 200,
response: { //
status: 200,
message: 'ok',
data: {
'onlineStatus|3': [{
'statusId': '#integer(1,3)',
'onlineStatusText': '#ctitle(3)',
'onlineStatusIcon': Random.image('20*20'),
'createTime': '#datetime'
}],
'websiteInfo': [{
'id|+1': 1,
}]
}
}
}
the data structure would be: https://imgur.com/a/7FqvVTK
and I retrieve this mock data in Pinia store:
import axios from "axios"
import { defineStore } from "pinia"
export const useSystem = defineStore('System', {
state: () => {
return {
systemConfig: {
onlineStatus: [],
},
}
},
actions: {
getSystemConfig() {
const axiosInstance = axios.interceptors.request.use(function (config) {
// Do something before request is sent
config.baseURL = '/mockApi'
return config
}, function (error) {
// Do something with request error
return Promise.reject(error);
})
axios.get('/system/').then(res => {
this.systemConfig.onlineStatus = res.data.data.onlineStatus
})
// console.log(res.data.data.onlineStatus)
axios.interceptors.request.eject(axiosInstance)
}
}
})
I use this store in the parent component Profile.vue:
export default {
setup() {
const systemConfigStore = useSystem()
systemConfigStore.getSystemConfig()
const { systemConfig } = storeToRefs(systemConfigStore)
return {
systemConfig,
}
},
computed: {
getUserOnlineStatusIndex() {
return this.userData.onlineStatus//this would be 1-3 int.
},
getUserOnlineStatus() {
return this.systemConfig.onlineStatus
},
showUserOnlineStatusText() {
return this.getUserOnlineStatus[this.getUserOnlineStatusIndex - 1]
},
},
components: {UserOnlineStatus }
}
template in Profile.vue I import the child component userOnlineStatus.vue
<UserOnlineStatus :userCurrentOnlineStatus="userData.onlineStatus">
{{ showUserOnlineStatusText }}
</UserOnlineStatus>
here is what I have got https://imgur.com/fq33uL8
but I only want to get the onlineStatusText property of the returned object, so I change the computed code in the parent component Profile.vue:
export default {
setup() {
const systemConfigStore = useSystem()
systemConfigStore.getSystemConfig()
const { systemConfig } = storeToRefs(systemConfigStore)
return {
systemConfig,
}
},
computed: {
getUserOnlineStatusIndex() {
return this.userData.onlineStatus//this would be 1-3 int.
},
getUserOnlineStatus() {
return this.systemConfig.onlineStatus
},
showUserOnlineStatusText() {
return this.getUserOnlineStatus[this.getUserOnlineStatusIndex - 1]['onlineStatusText']//👀I chage it here!
},
},
components: {UserOnlineStatus }
}
but I will get the error in the console and it doesn't work:
https://imgur.com/Gb68Slk
what should I do if I just want to display the specific propery of the retrived data?
I am out of my wits...
I have tried move the store function to the child components, but get the same result.
and I google this issue for two days, nothing found.
Maybe it's because of I was trying to read the value that the Profile.vue hasn't retrieved yet?
in this case, how could I make sure that I have got all the value ready before the page rendered in vue3? Or can I watch this specific property changed, then go on rendering the page?
every UX that has data is coming from remote source (async data) should has spinner or skeleton.
you can use the optional chaining for safe access (if no time to await):
return this.getUserOnlineStatus?.[this.getUserOnlineStatusIndex - 1]?.['onlineStatusText']
I'm trying to create an 'object of objects' in ReactJS using state hooks, but I'm unsure how to dynamically create it based on the data coming in.
The data arrives on a websocket, which I have placed in a Context and is being used by the component in question. The JSON data hits the onmessage, it invokes my useEffect state hook to then call a function to update the useState variable accordingly.
The inbound websocket data messages come in one at a time and look something like this (important keys listed, but there lots more props inside them) :
{
"name": "PipelineA",
"state": "succeeded",
"group": "Group1"
}
{
"name": "PipelineE",
"state": "succeeded",
"group": "Group1"
}
{
"name": "PipelineZ",
"state": "succeeded",
"group": "Group4"
}
...where the name and group are the values I want to use to create an 'object of objects'. So the group will be used to create a group of pipelines that are all part of that same group, which within that object, each pipeline will have its name as the 'key' for its entire data. So, the end state of the ‘object of objects’ would look something like this:
{
"Group1": {
"PipelineA": {
"name": "PipelineA",
"state": "running",
"group": "Group1"
},
"PipelineB": {
"name": "PipelineB",
"state": "running",
"group": "Group1"
}
},
"Group2": {
"PipelineC": {
"name": "PipelineC",
"state": "running",
"group": "Group2"
},
"PipelineD": {
"name": "PipelineD",
"state": "running",
"group": "Group2"
}
},
...etc...
}
So the idea being, pipelines of Group1 will be added to the Group1 object, if PipelineA already exists, it just overwrites it, if it does not, it adds it. And so on and so on.
I'm (somewhat) fine with doing this outside of React in plain JS, but I cannot for the life of me figure out how to do it in ReactJS.
const [groupedPipelineObjects, setGroupedPipelineObjects] = useState({});
const [socketState, ready, message, send] = useContext(WebsocketContext);
useEffect(() => {
if (message) {
updatePipelineTypeObjects(message)
}
}, [message]);
const updatePipelineGroupObjects = (data) => {
const pipelineName = data.name
const pipelineGroup = data.group
// let groupObj = {pipelineGroup: {}} // do I need to create it first?
setGroupedPipelineObjects(prevState => ({
...prevState,
[pipelineGroup]: {[pipelineName]: data} // <-- doesnt do what I need
}))
}
And help or suggestions would be appreciated. FYI the pipeline names are unique so no duplicates, hence using them as keys.
Also, why am I doing it this way? I already have it working with just an object of all the pipelines where the pipeline name is the key and its data is the value, which then renders a huge page or expandable table rows. But I need to condense it and have the Groups as the main rows for which I then expand them to reveal the pipelines within. I thought doing this would make it easier to render the components.
It's just that you haven't gone quite far enough. What you have will replace the group entirely, rather than just adding or replacing the relevant pipeline within it. Instead, copy and update the existing group if there is one:
const updatePipelineGroupObjects = (data) => {
const pipelineName = data.name;
const pipelineGroup = data.group;
// let groupObj = {pipelineGroup: {}} // do I need to create it first?
setGroupedPipelineObjects((prevState) => {
const groups = { ...prevState };
if (groups[pipelineGroup]) {
// Update the existing group with this pipeline,
// adding or updating it
groups[pipelineGroup] = {
...groups[pipelineGroup],
[pipelineName]: data,
};
} else {
// Add new group with this pipeline
groups[pipelineGroup] = {
[pipelineName]: data,
};
}
return groups;
});
};
Also, you're trying to use iterable destructuring ([]) here:
const [ socketState, ready, message, send ] = useContext(WebsocketContext);
but as I understand it, your context object is a plain object, not an iterable, so you'd want object destructuring ({}):
const { socketState, ready, message, send } = useContext(WebsocketContext);
Live Example:
const { useState, useEffect, useContext } = React;
const WebsocketContext = React.createContext({ message: null });
const Example = () => {
const [groupedPipelineObjects, setGroupedPipelineObjects] = useState({});
const { socketState, ready, message, send } = useContext(WebsocketContext);
useEffect(() => {
if (message) {
updatePipelineGroupObjects(message);
}
}, [message]);
const updatePipelineGroupObjects = (data) => {
const pipelineName = data.name;
const pipelineGroup = data.group;
// let groupObj = {pipelineGroup: {}} // do I need to create it first?
setGroupedPipelineObjects((prevState) => {
const groups = { ...prevState };
if (groups[pipelineGroup]) {
// Update the existing group with this pipeline,
// adding or updating it
groups[pipelineGroup] = {
...groups[pipelineGroup],
[pipelineName]: data,
};
} else {
// Add new group with this pipeline
groups[pipelineGroup] = {
[pipelineName]: data,
};
}
return groups;
});
};
return <pre>{JSON.stringify(groupedPipelineObjects, null, 4)}</pre>;
};
// Mocked messages from web socket
const messages = [
{
name: "PipelineA",
state: "succeeded",
group: "Group1",
},
{
name: "PipelineB",
state: "running",
group: "Group1",
},
{
name: "PipelineC",
state: "running",
group: "Group2",
},
{
name: "PipelineD",
state: "running",
group: "Group2",
},
{
name: "PipelineE",
state: "succeeded",
group: "Group1",
},
{
name: "PipelineZ",
state: "succeeded",
group: "Group4",
},
];
const App = () => {
const [fakeSocketContext, setFakeSocketContext] = useState({ message: null });
useEffect(() => {
let timer = 0;
let index = 0;
tick();
function tick() {
const message = messages[index];
if (message) {
setFakeSocketContext({ message });
++index;
timer = setTimeout(tick, 800);
}
}
return () => {
clearTimeout(timer);
};
}, []);
return (
<WebsocketContext.Provider value={fakeSocketContext}>
<Example />
</WebsocketContext.Provider>
);
};
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
I have retrieved data from an API, and now trying to transform the data to send a POST request. I want to group two User ID's that match, and POST their common cities in a array instead of separate objects.
For example, data I retrieve looks like this:
{
"events": [
{
"city": "city-1",
"user": "d1177368-2310-11e8-9e2a-9b860a0d9039"
},
{
"city": "city-2",
"user": "d1177368-2310-11e8-9e2a-9b860a0d9039"
}
]}
I want my POST request data to look similar to this:
{
"user": {
"d1177368-2310-11e8-9e2a-9b860a0d9039": [
{
"city": [
"city-1",
"city-2"
]
}]}}
So far this is my React component for the request:
import React, { useEffect, useState } from "react";
import axios from "../data/axios";
export default function Events({ fetchEvents }) {
const [events, setEvents] = useState([]);
useEffect(() => {
async function fetchData() {
const requests = await axios.get(fetchEvents);
setEvents(requests.data.events);
return requests;
}
fetchData();
}, [fetchEvents]);
//here is my issue:
function createSessions(user, city) {
if (user === user) {
}
}
Thank you
Iterate over the events array, reducing it into an object with a user object property. The user object has the user values from the events array elements as key and the cities are pushed into a city array property.
events.reduce(
(result, el) => {
if (!result.user[el.user]) {
result.user[el.user] = [{ city: [] }];
}
result.user[el.user][0].city.push(el.city);
return result;
},
{ user: {} }
);
const data = {
events: [
{
city: "city-1",
user: "d1177368-2310-11e8-9e2a-9b860a0d9039"
},
{
city: "city-2",
user: "d1177368-2310-11e8-9e2a-9b860a0d9039"
}
]
};
const data2 = data.events.reduce(
(result, el) => {
if (!result.user[el.user]) {
result.user[el.user] = [{ city: [] }];
}
result.user[el.user][0].city.push(el.city);
return result;
},
{ user: {} }
);
console.log(data2);
async fetch() {
try {
console.log(await this.$api.events.all(-1, false)); // <-- First log statement
const res = await this.$api.events.all(-1, false); // <-- Assignment
console.log(res); // <-- Second log statement
if (!this.events) {
this.events = []
}
res.data.forEach((event, index) => {
const id = event.hashid;
const existingIndex = this.events.findIndex((other) => {
return other.hashid = id;
});
if (existingIndex == -1) {
this.events.push(events);
} else {
this.events[existingIndex] = event;
}
});
for (var i = this.events.length - 1; i >= 0; --i) {
const id = this.events[i].hashid
const wasRemoved =
res.data.findIndex((event) => {
return event.hashid == id
}) == -1
if (wasRemoved) {
this.events.splice(i, 1)
}
}
this.$store.commit('cache/updateEventData', {
updated_at: new Date(Date.now()),
data: this.events
});
} catch (err) {
console.log(err)
}
}
// The other functions, maybe this somehow helps
async function refreshTokenFirstThen(adminApi, func) {
await adminApi.refreshAsync();
return func();
}
all(count = -1, description = true) {
const func = () => {
return $axios.get(`${baseURL}/admin/event`, {
'params': {
'count': count,
'description': description ? 1 : 0
},
'headers': {
'Authorization': `Bearer ${store.state.admin.token}`
}
});
}
if (store.getters["admin/isTokenExpired"]) {
return refreshTokenFirstThen(adminApi, func);
}
return func();
},
Both log statements are giving slightly different results even though the same result is expected. But this only happens when is use the function in this specific component. When using the same function in other components, everything works as expected.
First data output:
[
{
"name": "First Name",
"hashid": "VQW9xg7j",
// some more correct attributes
},
{
"name": "Second name",
"hashid": "zlWvEgxQ",
// some more correct attributes
}
]
While the second console.log gives the following output:
[
{
"name": "First Name",
"hashid": "zlWvEgxQ",
// some more correct attributes, but this time with reactiveGetter and reactiveSetter
<get hashid()>: reactiveGetter()
​​ length: 0
​​​​ name: "reactiveGetter"
​​​​ prototype: Object { … }
​​​​<prototype>: function ()
​​​<set hashid()>: reactiveSetter(newVal)
​​​​length: 1
​​​​name: "reactiveSetter"
​​​​prototype: Object { … }
​​​​<prototype>: function ()
},
{
"name": "Second name",
"hashid": "zlWvEgxQ",
// some more correct attributes and still without reactiveGetter and reactiveSetter
}
]
As it can be seen, somehow the value of my hashid attribute changes, when assigning the response of the function call.
The next weird behavior happening here, is that the first object where the hashid field changes also gets reactiveGetter and reactiveSetter (but the second object in the array does not get these).
So it looks to me like something is happening with the assignment that I don't know about. Another guess would be that this has something to do with the Vuex store, because I do not change the Vuex tore in the other place where I use the same function.
It is verified that the backend always sends the correct data, as this is dummy data, consisting of an array with two objects with some attributes. So no other data except this two objects is expected.
Can someone explain to me why this behavior occurs?
There are few problems...
Do not use console.log with objects. Browsers tend to show "live view" of object - reference
this.events.findIndex((other) => { return other.hashid = id; }); is wrong, you are using assignment operator (=) instead of identity operator (===). That's why the hashid of the first element changes...
What is the best way to refactoring code and best performance Here Is what I do In getData function I query get Data from databae using async/await then I got some ugly data I try to map and delete element that duplicate data
async getData({req,res}) {
let data = await this.getData()
data = [
{
"id": 3,
"employee_id": 2290,
"getId": {
"id": 9070
},
"getName": {
"name": "test"
},
},
{
"id": 4,
"employee_id": 2291,
"getId": {
"id": 9070
},
"getName": {
"name": "test"
},
}
] //example I await call database get data look like this
//before I remove them I want to keep duplicate data I set new variable for keep them
//which part is the most ugly is their anyway to do something about this ?
const getId = data[0].getId
const getName = data[0].getName
// in this part I map and delete element that duplicate which is data.getName and data.getId
// I create seperate function for code clean
data = await this.removeElement(data)
//after that I return response data
return response.status(200).json({
status: 200,
success: true,
getId: getId,
getName: getName,
data: data
});
}
async removeElement(data) {
// im not sure is their any beeter way to do something like this ?
return Promise.all(
data.map(async item => {
await delete item.getId;
await delete item.getName;
return item;
})
);
}
so my output response will look like this :
getId : {
id : 9070
}
getName : {
name : 'test'
}
data : [
{
"id": 3,
"employee_id": 2290,
},
{
"id": 4,
"employee_id": 2291,
}
]
I really appreciate for your help thanks
Removing properties from an object is not an asynchronous process. There's no need to await it or have a Promise.all around such a loop.
To extract the two common properties from the first item of the array concisely, you can destructure:
const { getId, getName } = data[0];
Shorthand property names will help too. In full:
const data = await this.getData();
const { getId, getName } = data[0];
const trimmedData = data.map(({ id, employee_id }) => ({ id, employee_id }));
return response.status(200).json({
status: 200,
success: true,
getId,
getName,
data: trimmedData
});
The data.map(({ id, employee_id }) => ({ id, employee_id })) takes the array and constructs a new array of objects which contain only the id and employee_id properties in the original objects.
If you need to blacklist properties rather than extract the desired properties, then you can do something similar to the above with rest syntax:
const trimmedData = data.map(({ getId, getName, ...rest }) => rest);