Where to call reset method for RTKQ useMutation()? - redux-toolkit

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?

Related

res.data won't save to state correctly

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.

My useQuery hook is refetching everytime its called. I thought it is suppose to hand back the cache?

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.

How to determine whether user is ready and nonce can be submitted to server?

This is one of many (imho rather incomplete) examples in the docs:
var button = document.querySelector('#submit-button');
braintree.dropin.create({
authorization: 'CLIENT_AUTHORIZATION',
container: '#dropin-container'
}, function (createErr, instance) {
button.addEventListener('click', function () {
instance.requestPaymentMethod(function (requestPaymentMethodErr, payload) {
// Submit payload.nonce to your server
});
});
});
It's all nice and easy but I don't see how I can change the state of button according to the state of "is the user done with adding a payment method?".
Is this even possible? It seems that the click on the button actually performs the fetching of the nonce (which comes as payload.nonce). However, how can I disable button until the user has finished his conversation with Braintree/PayPal?
And the answer is look in the docs (no not those docs) - those docs.
I still don't know how I found that link.
The dropin instance has a on() function where you can register a callback for certain events (just do it - look at the docs already):
instance.on('paymentMethodRequestable', function(event) {
thiz._logger.info('Payment method is now requestable');
setTimeout(() => thiz.paymentMethodAvailable = true, 400);
});

How do I know when component's DOM is updated and finished transitions in Ractive?

I'm trying to wrap IScroll library into Ractive component. Is there a way to be notified when component's DOM changed and finished any transitions so that I can update the scroller? I see the following ways to achieve this:
Declare dependencies (of the component's template) explicitly, like
<Scroll context="{{...}}" > </Scroll>
I can observe context variable, but still can't wait for transitions to finish.
Patch Ractive.set() so that it emits custom event when used:
// Broadcast promise returned by `set`.
var oldset = Ractive.prototype.set;
Ractive.prototype.set = function () {
var ret = oldset.apply(this, arguments);
this.fire('set', ret);
return ret;
};
Then, in the component's initialization code, I can subscribe to the set event:
this._parent.on('set', function (ret){
ret.then(update);
});
This will not work for Ractive.push() and other methods. Also this will notify my component about all changes made by set-ting something, not only about those affecting component's DOM. Finally, I must explicitly refer to component's parent using this._parent, which means my components can not be nested.
So, is there are better way to achieve this in Ractive?
The initialization options allow oncomplete (or ractive.on('complete'... if you prefer) for initial render. See http://docs.ractivejs.org/latest/lifecycle-events
All of the data modification methods, set, animate, push, slice, etc. return a promise that will not be called until DOM is updated and transitions have completed.
Here's a simple example (http://jsfiddle.net/ctfyes7t/):
{{#if show}}
<li intro='fade:{ duration: 2000 }'>ta da!</li>
{{/if}}
r.set('show', true).then(function(){
// called when fade intro complete
});
The problem can be approached from another direction: changes to the specific parts of DOM can be observed using MutationObserver.
So, I end up with the following approach to wrapping external widgets:
basically, we get component's top node (and initialize 3rd party code) using intro transition, and then in oncomplete callback register observer with something like this:
this.observer = new MutationObserver(update);
this.observer.observe(this.node, {
childList: true,
subtree: true,
characterData: true,
attributes: true, // transitions may change attributes
});
where function update updates 3rd-party widget.
Here is the source code of the component.

Google Dart: change event for IListElement

I'm wondering if you can listen for when the elements of a UListElement has changed (i.e. LIElement added or removed)?
UListElement toDoList = query('#to-do-list');
LIElement newToDo = new LIElement();
newToDo.text = "New Task";
toDoList.elements.add(newToDo);
// how do I execute some code once the newToDo has been added to the toDoList?
I'm assuming elements.add() is asynchronous as I come from an ActionScript background. Is this a valid assumption?
Higher level frameworks give you events for this, but at the HTML5 API level, you have to use a MutationObserver.
Here's an example where a mutation observer can be set on any DOM Element object. The handler can then process the mutation events as needed.
void setMutationObserver(Element element){
new MutationObserver(mutationHandler)
.observe(element, subtree: true, childList: true, attributes: false);
}
void mutationHandler(List<MutationRecord> mutations,
MutationObserver observer){
for (final MutationRecord r in mutations){
r.addedNodes.forEach((node){
// do stuff here
});
r.removedNodes.forEach((node){
// or here
});
}
}
element.elements.add(otherElement) is synchronous and is the equivalent of element.appendChild(otherElement) in Javascript (ie. DOM Level 2 Core : appendChild).
You may also be interested in our work with MDV:
http://blog.sethladd.com/2012/11/your-first-input-field-binding-with-web.html
http://blog.sethladd.com/2012/11/your-first-model-driven-view-with-dart.html