I have a MERN app which pulls data from a collection in MongoDB to render a timer component in the DOM. Currently in my collection, I have three timers titled first timer, another timer and even another. When I make a get request and run console.log(res.data), I see all the timers and their relevant data logged to the console. However, when I try to set state of timers using the useState hook, only the last timer is saved to state. Here is the code of my component:
function Wrapper() {
const [timers, setTimers] = useState([]);
const [title, setTitle] = useState('');
useEffect(() => {
axios
.get('http://localhost:3001/')
.then((res) => {
console.log(res.data);
res.data.map((timer) => {
let newTimer = (
<Timer title={timer.title} id={timer._id} time={timer.time} />
);
let allTimers = timers.slice();
allTimers.push(newTimer);
setTimers(allTimers);
console.log(allTimers);
});
})
.catch((err) => {
console.log(err);
});
}, []);
Here I am making my get request and mapping through the res.data to create a new timer component for each iteration. Then, I make a copy of timers (since state is immutable), push my new timer to allTimers variable and finally run setTimers(allTimers). Here is what React renders:
I expect allTimers to contain one, two and then all three of the timers in my database when logged to the consol on line 17. However, only the most recent timer shows up so it seems like I am setting state incorrectly but I'm not sure how. Anyone have any suggestions?
Because you set useEffect to Only runs on initial render, it always refers to the initial state of timers, which is an empty array, and even as you try to update it with useState but on the next loop it still refers to the empty array. The only update to have real effect is the last one, pushing the third timer into the empty array.
You can move your entire map loop to the return statement of the functional component to render an element for each timer.
Related
Since RTK 1.7.0, mutation result object has a reset() method to unsubscribe from the state update of a mutation from another component:
const [mutate, { isLoading, reset }] = useMutation({ fixedCacheKey });
I use it to get isLoading state from another component's mutation. But I don't know where to call the cleanup reset():
In the click handler:
await mutate()
reset()
Or in the cleanup
useEffect(() => () => reset(), [reset])
If it's this case, then do I need to do this for both (original and subscriber) components? And this is weird: the identity of reset function changes after state update, so the isLoading is cleared right after it changes.
I think the docs about this function is not very clear. Hope someone can improve it.
I'm using React 17.0.2, RTK 1.7.1.
Mutations never share state between usage in different components in the fist place - unless you force that by using fixedCacheKey. Are you maybe trying to solve a non-problem here?
I have a selector and I subscribe to them on ngOnInit; but the code inside the subscribe is executed every time when the page is initialized (refreshed).
#Select(SurveysSelectors.deleteSurveys) deleteSurveys$: Observable<IDeleteSurveys>;
.
.
.
ngOnInit(): void {
this.deleteSurveys$.pipe(takeUntil(this.destroy$), debounceTime(600)).subscribe((result: IDeleteSurveys) => {
if (!result.surveyDeleteResult.esriUpdate) {
return;
}
this.esriUpdate(result.surveyIds, result.surveyDeleteResult.iotFunc);
});
}
Is this normal? I expected that the code inside subscribe to run only when a change is made on the slice of state that selector returns.
this is expected since your ngOnInit will run every time your component is initialized. In NGXS the selectors are hot and always will emit on subscription the last value, in this case the initial value. A possible workaround would be to use a skip(1) to be sure you only react to changes that happen after you subscribed.
Example below:
this.deleteSurveys$.pipe(takeUntil(this.destroy$), skip(1), debounceTime(600)).subscribe((result: IDeleteSurveys) => {
if (!result.surveyDeleteResult.esriUpdate) {
return;
}
this.esriUpdate(result.surveyIds, result.surveyDeleteResult.iotFunc);
});
I hope this can help you.
I'm a little confused here. I thought react-query, when using useQuery will hand back 'cache' n subsequent calls to the same "useQuery". But everytime I call it it, it refetches and makes the network call.
Is this the "proper way" to do this? I figured it would just auto hand me the "cache" versions. I tried extending staleTime and cacheTime, neither worked. Always made a network call. I also tried initialData with the cache there.. didn't work.
SO, I am doing the following, but seems dirty.
Here is the what I have for the hook:
export default function useProducts ({
queryKey="someDefaultKey", id
}){
const queryClient = useQueryClient();
return useQuery(
[queryKey, id],
async () => {
const cachedData = await queryClient.getQueryData([queryKey, id]);
if (cachedData) return cachedData;
return await products.getOne({ id })
}, {
enabled: !!id
}
);
}
This is initiated like so:
const { refetch, data } = useProducts(
{
id
}
}
);
I call "refetch" with an onclick in two diff locations.. I'd assume after I retrieve the data.. then subsequent clicks will hand back cache?
I’m afraid there are multiple misconceptions here:
react query operates on stale-while-revalidate, so it will give you data from the cache and then refetch in the background. You can customize this behavior by setting staleTime, which will tell the library how long the data can be considered fresh. No background updates will happen.
when you call refetch, it will refetch. It’s an imperative action. If you don’t want it, don’t call refetch.
you don’t need to manually read from the cache in the queryFn - the library will do that for you.
I am utilizing cellChanged.node.setDataValue(fieldChanged, oldValue) inside of the (cellValueChanged) event emitter, and I'm having trouble figuring out how to call a function once the setDataValue function has finished executing. I need to do this to do a check to see if a user has the permission to update a cell.
Here is the full code that checks:
if(this.showPaywallNotification) {
// Okay, so the budget is above what we allow HOWEVER...
if(budget > BUDGET_AMOUNT) {
this.showPaywallNotification = false;
cellChanged.node.setDataValue(fieldChanged, oldValue)
// Note: This timeout is in place to prevent the infinite updating bug
// This is problematic because if the user changes the cells fast enough, they can get around the paywall. If I change the timeout to be smaller, the resulting change triggers the update, which ends up creating an infinite loop of updates.
setTimeout(() => {
this.showPaywallNotification = true;
}, 230)
}
}
Is there a way I can replace my setTimeout() function with something better that can always ensure the user can't get around my paywall by just updating the cell faster than the timeout can execute?
You don't have to do polling. setDataValue is a not an async function.
Also, onCellValueChanged won't get called again if you call node.setDataValue.
Have a look at this plunk: Cell Editing - Revert to old value. Try updating any Age value to negative.
onCellValueChanged($event) {
if ($event.colDef.field === 'age' && $event.newValue < 0) {
// debugger
$event.node.setDataValue('age', $event.oldValue);
console.log('value reverted');
}
}
Let me know if something is not clear, or this is not sufficient.
There is a lot of information on this topic out there, but I can't seem to get it working for myself. I am using toastr to display notifications of events to the user in the top right hand corner of my app. I need to add an observer for the 'added' event to a collection, and create the toastr notification when an item is added. The problem is that the observer fires when the collection is initialized. I've tried about a half dozen different ways trying to check of the collection is ready() before I allow the observer code to continue through and show notifications, but I can't get it working consistently, especially when changing pages. Here is some sample code:
MainController = RouteController.extend({
before: [
function() {
deviceEventsInitializing = true;
var alerts = this.subscribe("alerts", Meteor.user()._id);
if (alerts.ready()) {
deviceEventsInitializing = false;
}
Alerts.find().observeChanges({
added: function(id, doc) {
if (deviceEventsInitializing || deviceEventsInitializing == undefined) {
return;
}
doToastrStuff();
}
});
this.next();
}
],
});
This is just my latest attempt. The flow goes like this:
1.) Subscription happens, all the items in the collection hit the observer but deviceEventsInitializing is true so it does nothing.
2.) alerts.ready() fires and deviceEventsInitilizing is set to false.
3.) The added trigger fires again for all the events in the collection, causing toastr to be called for every item.
All I'm interested in is the following:
1.) Some trigger or event where I can set a variable that says the subscription is reloading the collection.
2.) Some trigger or event that tells me that this reloading of the collection is complete so I can set a variable indicating that.
I think you're on the right track, but trying to manage reactivity like this using solely IronRouter can be a nightmare. I've tried and failed before.
Instead, leverage Mongo to limit your reactivity to only alerts you care about. Let's imagine your alerts database looks something like:
{
_id: 1,
hasNotified: false,
...
}
Now, Mongo is deciding what is new vs. not new instead of trying to determine state based on IronRouter timing. In fact, because in Meteor any alerts cursor is natively reactive, you don't even need to observeChanges:
MainController = RouteController.extend({
waitOn: function() {
return this.subscribe("alerts", Meteor.userId());
},
data: function() {
var newAlerts = Alerts.find({hasNotified: false}).forEach(function(doc) {
doToastrStuff();
Alerts.update({_id: doc._id}, {$set: {hasNotified: true}});
});
}
})
With this kind architecture, navigating to other routes, reloading the page, etc. will not re-fire any of your alerts because Mongo stores your alert state.