React component isn't rendering after updating state by useState - javascript

I want to update an state object userInfo which contains an array named work which is an array of objects
My Database object userInfo looks like this
{
"_id":"61a6a1d64707c03465eae052",
"fullName": "Md Nurul Islam",
"works":
[
{
"isEditing": false,
"_id": "61a6a1d64707c03465eae053",
"company": "Amazon Logistics",
"position": "Sortation Associate",
"isCurrent": true
},
{
"isEditing": false,
"_id":"61a6a1d64707c03465eae054",
"company": "The Rani Indian Takeway",
"position": "Customer Service Assistant",
"isCurrent": false,
}
]
}
But I don't want update the database, i just want to update the state userInfo in frontend to go editing mood, for this when the user clicks edit next to work section, the properties isEditing will be toggled.
Below is my code
const [userInfo, setUserInfo] = useState(user);
const workToggleHandler = (work) =>
{
setUserInfo((prev) => {
prev.works.map((p) => {
if (p._id === work._id) {
p.isEditing = !p.isEditing;
}
return p;
});
return prev;
});
With this code, my state object is updated But my components are not re-rendered

you can put into the useEffect when particular state is updated then that effect will call
useEffect(()=>{
your code will go here
}[userInfo]),

I just changed my code on workToggleHandler function and its working completely as i want.
Below my code
const [userInfo, setUserInfo] = useState(user);
const workToggleHandler = (work) => {
setUserInfo({
...userInfo,
works: userInfo.works.map((p) => {
if (p._id === work._id) {
p.isEditing = !p.isEditing;
}
return p;
}),
});
};

React does not look at the data of the object but rather on the pointer of the object. Since this is not changed (only "prev" is edited, no new object with a new object address), it doesn't not rerender.
A solution would be to copy the object.
let newUserInfo = Object.assign({}, prev);
newUserInfo.works.map((p) => {
if (p._id === work._id) {
p.isEditing = !p.isEditing;
}
return p;
});
return newUserInfo;
Disclaimer: Maybe a deep copy is necessary....

Related

ReactJS and creating ‘object of objects’ using state hooks?

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>

How to map this data to reach data property?

I'm trying to clean up the data received from firebase to view them in a FlatList. How can I clean my data to a simple array where I can iterate in FlatList?
EDIT! There are many other coins in my database that I want to pull into the FlatList. So the solution that I'm looking for is to view all these coins in my FlatList and then show their data such as price, market_cap etc.
My data is currently stored in a state and looks like this.
favoriteList data is:
Object {
"bitcoin": Object {
"-MahI1hCDr0CJ_1T_umy": Object {
"data": Object {
"ath": 54205,
"ath_change_percentage": -40.72194,
"ath_date": "2021-04-14T11:54:46.763Z",
"atl": 51.3,
"atl_change_percentage": 62536.71794,
"atl_date": "2013-07-05T00:00:00.000Z",
"circulating_supply": 18719656,
"current_price": 32164,
"fully_diluted_valuation": 674764316483,
"high_24h": 33004,
"id": "bitcoin",
"image": "https://assets.coingecko.com/coins/images/1/large/bitcoin.png?1547033579",
"last_updated": "2021-05-27T10:07:02.525Z",
"low_24h": 30652,
"market_cap": 601493137412,
"market_cap_change_24h": -15118857257.119507,
"market_cap_change_percentage_24h": -2.45192,
"market_cap_rank": 1,
"max_supply": 21000000,
"name": "Bitcoin",
"price_change_24h": -641.85835686,
"price_change_percentage_1h_in_currency": 0.25769270475453127,
"price_change_percentage_24h": -1.95655,
"price_change_percentage_24h_in_currency": -1.9565521832416402,
"price_change_percentage_7d_in_currency": 4.978932125496787,
"symbol": "btc",
"total_supply": 21000000,
"total_volume": 36947814578,
},
},
},
}
The firebase structure is like this where the data above is fetched from:
Object.keys(favourite.bitcoin)[idx] This line gives you the name of key at index 0 into object favourite.bitcoin.
So the variable key will be your firebase key.
let favourite = {
bitcoin: {
"-MahI1hCDr0CJ_1T_umy": {
data: {
ath: 54205,
ath_change_percentage: -40.72194,
ath_date: "2021-04-14T11:54:46.763Z",
atl: 51.3,
atl_change_percentage: 62536.71794,
atl_date: "2013-07-05T00:00:00.000Z",
circulating_supply: 18719656,
current_price: 32164,
fully_diluted_valuation: 674764316483,
high_24h: 33004,
id: "bitcoin",
image:
"https://assets.coingecko.com/coins/images/1/large/bitcoin.png?1547033579",
last_updated: "2021-05-27T10:07:02.525Z",
low_24h: 30652,
market_cap: 601493137412,
market_cap_change_24h: -15118857257.119507,
market_cap_change_percentage_24h: -2.45192,
market_cap_rank: 1,
max_supply: 21000000,
name: "Bitcoin",
price_change_24h: -641.85835686,
price_change_percentage_1h_in_currency: 0.25769270475453127,
price_change_percentage_24h: -1.95655,
price_change_percentage_24h_in_currency: -1.9565521832416402,
price_change_percentage_7d_in_currency: 4.978932125496787,
symbol: "btc",
total_supply: 21000000,
total_volume: 36947814578,
},
},
},
};
let idx = 0; //key at index 0
let key = Object.keys(favourite.bitcoin)[idx];
console.log(key)
let data = favourite.bitcoin[key].data;
console.log(data)
Please let me know if it's works or not !
To get the data from your database, you need to query its parent reference. This will allow you to do things like "find all entries under /favorites/bitcoin that have a current price of over 30000".
Because you want to simply query for all the data under /favorites/bitcoin in your question, you would do the following:
Get a reference for /favorites/bitcoin
Get the data under /favorites/bitcoin
Iterate over the data, and assemble an array
Use this new array for your FlatList
These steps can be made into the following function:
function getDataForFlatlistUnder(databasePath) {
return firebase.database()
.ref(databasePath)
// consider using .limitToFirst(10) or similar queries
.once("value")
.then((listSnapshot) => {
// listSnapshot contains all the data under `${databasePath}`
const arrayOfDataObjects = [];
// For each entry under `listSnapshot`, pull its data into the array
// Note: this is a DataSnapshot#forEach() NOT Array#forEach()
listSnapshot.forEach((entrySnapshot) => {
// entrySnapshot contains all the data under `${databasePath}/${entrySnapshot.key}`
const data = entrySnapshot.child("data").val();
// data is your data object
// i.e. { ath, ath_change_percentage, ath_date, atl, ... }
// add the key into the data for use with the FlatList
data._key = entrySnapshot.key;
arrayOfDataObjects.push(data);
});
return arrayOfDataObjects;
});
}
Which you can use in your component like so:
function renderItem((dataObject) => {
// TODO: render data in dataObject
});
function MyComponent() {
const [listData, setListData] = useState();
const [listDataError, setListDataError] = useState(null);
const [listDataLoading, setListDataLoading] = useState(true);
useEffect(() => {
const disposed = false;
getDataForFlatlistUnder("favorites/bitcoin")
.then((arrayOfDataObjects) => {
if (disposed) return; // component was removed already, do nothing
setListData(arrayOfDataObjects);
setListDataLoading(false);
})
.catch((err) => {
if (disposed) return; // component was removed already, do nothing
// optionally empty data: setListData([]);
setListDataError(err);
setListDataLoading(false);
});
// return a cleanup function to prevent the callbacks above
// trying to update the state of a dead component
return () => disposed = true;
}, []); // <-- run this code once when component is mounted and dispose when unmounted
if (listDataLoading)
return null; // or show loading spinner/throbber/etc
if (listDataError !== null) {
return (
<Text>
{"Error: " + listDataError.message}
</Text>
);
}
return (
<FlatList
data={listData}
renderItem={renderItem}
keyExtractor={item => item._key} // use the key we inserted earlier
/>
);
}
Note: This code is for a one-off grab of the data, if you want realtime updates, you would modify it to use .on("value", callback) instead. Make sure to use .off("value", callback) in the unsubscribe function of the useEffect call to clean it up properly.
It is interesting to see how programmers interpret questions. Or perhaps how beginners fail to articulate clearly what they want to achieve. Here is the answer:
const formatData = (data) => {
let arr = [];
let test = Object.values(data).forEach((o) => {
Object.values(o).forEach((a) =>
Object.values(a).forEach((b) => arr.push(b))
);
setFormattedData(arr);
});

Update ngrx state in nested object Angular

I would be able to update an object nested in another object in my application but i got some problems.
Let's assume that the entity that I want to update is something like this:
{
{
"id": 0,
"name": "Yellow Car",
"details": {
"engine": {},
"ownerInfo": {
"name": "Luke",
"lastName": "Cage",
"email": "l.cage#hisemail.blabla"
},
},
"created": "2018-01-17",
"lastUpdate": "2020-09-03",
}
I can easily update some part of this entity in this way:
let car: Car = {
...car,
...this.form.value
};
let carUpdate: Update<Car> = {
id: car.id,
changes: car
};
this.store.dispatch(carUpdated({carUpdate}));
But in this way I can only update name, created, lastUpdate and I can't update the nested object details. What happens if I try to edit the detail object now? Nothing i wanna happens.
This is the selector:
export const carUpdated = createAction(
"[Edit Car] Car Updated",
props<{carUpdate: Update<Car>}>()
);
The effect:
saveCar$ = createEffect(
() => this.actions$
.pipe(
ofType(CarActions.carUpdated),
concatMap(action => this.carService.editCar(
action.carUpdate.changes,
action.carUpdate.id
))
),
{dispatch: false}
)
The reducer:
on(CarActions.carUpdated, (state, action) =>
adapter.updateOne(action.carUpdate, state)),
The service sends to the backend the right data and it's working good without the state management.
What I am doing now is retrieve the single carObject in this way in the component in the ngOnInit
car$ = this.store.pipe(
select(selectCar(id))
)
and the selector is:
export const selectCar = (id) => createSelector(
selectAllCars,
(cars: any) => cars.filter((car) => {
let filteredCar = car.id == id;
if(filteredCar) {
return car;
}
}).map(car => car)
);
and then after my edit I can use the dispatch to confirm my edit
this.store.dispatch(carUpdated({carUpdate}));
but as I said if I try to update the details object i have this
let car: Car = {
...car, // the entire car object
...this.form.value // only the details
};
let carUpdate: Update<Car> = {
id: car.id,
changes: car //the details are mixed with the car object and not inside the object itself
};
something like this:
{
"id": 0,
"name": "Yellow Car",
"engine": {},
"details": {
},
"ownerInfo": {
"name": "Luke",
"lastName": "Cage",
"email": "l.cage#hisemail.blabla"
},
"created": "2018-01-17",
"lastUpdate": "2020-09-03",
}
Is there an easy way to fix this?
I'll try to provide a general answer which might show techniques on how to update one object with another object.
If in the form, you only have the details as update (update is Partial<Car["details"]>, you can do this:
const update: Partial<Car["details"]> = this.form.value;
const newCar: Car = {
...oldCar,
details: {
...oldCar.details,
...update
}
Then there's the possibility that the update is (partial) Car with partial details:
const update: Partial<Car> & { details: Partial<Car["details"]> } = this.form.value;
const newCar: Car = {
...oldCar,
...update,
details: {
...oldCar.details,
...update.details
}
}
An improbable option is that you have mixed detail and car properties in the update (for whatever reason - you might be doing something wrong). Then you can pick them by hand. Note that this will delete old values if new values are undefined.
const update: Pick<Car, 'name' | 'lastUpdate'> & Pick<Car["details"], 'ownerInfo'> = this.form.value;
const newCar: Car = {
...oldCar,
name: update.name,
lastUpdate: update.lastUpdate,
name: update.name
details: {
...oldCar.details,
ownerInfo: details.ownerInfo
}
}

Change Nested State Value With A Single Function? (javascript/React)

I have some react user privilege state data I need to manage. I would like the ability to change the object privileges based on their property through a dynamic function. I'm not sure how to target the specific nested privilege property to change the value. Is this possible?
Question: How can I change the value of a nested privilege property to the functions type and value parameter?
Heres an Example:
const [userPrivilages, setUserPrivilages] = useState([{
_id: "123"
privilages: {
edit: true, //before!
share: true,
del: false
}
},
{
...more users
}
])
//my attempt
const changePrivilage = (type, value) => {
const newPrivilages = userPrivilages.map(user => {
return {
...user,
privilages: {
...privilages,
//change the privilage of "type" from the functions parameter to the value parameter
}
}) setUserPrivilages(newPrivilages)
}
changePrivilage("edit", false)
Desired output:
const [userPrivilages, setUserPrivilages] = useState([{
_id: "123"
privilages: {
edit: false, //After!
share: true,
del: false
}
},
{
...more users
}
])
Thanks!
You can use [] to refer to variable as a key like below:
const changePrivilage = (type, value) => {
const newPrivilages = userPrivilages.map(user => {
return {
...user,
privilages: {
...user.privilages,
[type]: value // here it is !!!
}
}) setUserPrivilages(newPrivilages)
}
Try this :
(see comments for understanding code)
const changePrivilage = (type,value) => {
const newUserPrivilages = userPrivilages.map(user => {
let newPrivilages = user.privilages; // get old privilages of user
newPrivilages[type] = value; // update type with new value
return {
...user,
privilages: newPrivilages, // set privilages as newPrivilages
};
});
setUserPrivilages(newUserPrivilages);
};
Note : this will change properties for all users. If you want to update only for specific user, pass _id as well to changePrivilage and execute newPrivilages[type] = value; // update type with new value inside if condition comparing user _id.

Get Firebase Object data based on ID in another ref

I have this broken CodePen that I am attempting to get the info of an object in another Firebase Ref based on the objects .key or id...
I have to Firebase Refs: The content
[
{
"name": "objecta",
".key": "objecta"
},
{
"name": "objectb",
".key": "objectb"
},
{
"name": "objectc",
".key": "objectc"
}
]
and the Related that lists the object by key and then the key of the ones that are related to that item.
[
{
"objectc": true,
".key": "objectb"
}
]
I am trying to use the following code to go through the relatedRef by key:
var theKey = "objectb"
function getDiscoverBarContent(key, cb) {
relatedRef.child(key).on("child_added", snap => {
let relateRef = relatedRef.child(snap.key);
relateRef.once("value", cb);
console.log('relate:' + relateRef)
});
}
then get the data of the object that it's related to and log it to the console:
getDiscoverBarContent(theKey, snap => {
var snapVal = snap.val();
console.log(snapVal)
});
Currently it is returning null when I need it to return the object info in contentRef referenced in the relatedRef...any ideas?
I was referencing the wrong ref in the JS function. Everything was right except this:
var theKey = "objectb"
function getDiscoverBarContent(key, cb) {
relatedRef.child(key).on("child_added", snap => {
//This should have referenced the contentRef:
let relateRef = relatedRef.child(snap.key);
//like so
let relateRef = contentRef.child(snap.key);
relateRef.once("value", cb);
console.log('relate:' + relateRef)
});
}

Categories

Resources