Variable inside socket.on('connect') not updating - socket.io

This React with Socket.io.
[canPass, setCanPass] = useState(true)
[example, setExample] = useState('')
useEffect(()=>{
if(socket !== ''){
setCanPass(false)
console.log(example)
if(canPass){
socket.on('connect', ()=>{
console.log(example)
})
}
}
},[socket, example])
This code is inside of a Provider, so the variable "example" will change often.
Initial value of "socket" is an empty string. After the socket is created, the effect will set the listener "connect".
The flag "canPass" avoids setting the listener over and over any time the effect gets triggered.
NOW THE PROBLEM IS: Variable "example" updates normally inside of the Effect, but when the listener function is triggered (let's say server drops and reestablish, or user turn off and on wifi) variable "example" INSIDE of the listener function remains unchanged.
Any ideas how to fix this? Thanks!!
EDIT -> FIXED!
I don't really understand why this question is not that frecuent as I expected. Ok, so, the thing is an event can link more than one listener, and each listener is an instance of the function, it means, it creates a separated scope and the variables inside wont update.
To fix it, I just added another effect, observing the changes of variable "example", ereasing the listener with the method removeAllListeners() and set it again with the new values of "example". And that's it.

Related

Cypress: How to capture text from a selector on one page to use as text on another page

New cypress user here, I am aware that cypress does not handle variables like how testcafe and others do due to the asyn nature of it. Using the example given and what I could find I have this as an example:
cy.get('selector').invoke('text').as('text_needed')
cy.get('#text_needed')
const txtneeded = this.text_needed
cy.log(txtneeded)
This looks at a given selector, takes what it finds and uses it as text and set it as a variable usable at any time in the test and outputs it to the log. The plan is to use that text in a search filter in another page to find the item it references.
The problem is that it fails with Cannot read properties of undefined (reading 'text_needed')
Is this because the content of the selector is not assigned to text properly, the outer html is <a data-v-78d50a00="" data-v-3d3629a7="" href="#">PO90944</a> The PO90944 is what I want to capture.
Your help would be appreciated!
You cannot save an alias and access it via this.* in the same execution context (callback) because it's a synchronous operation and your alias is not yet resolved at this time.
This is a correct way to go:
cy.get('selector').invoke('text').as('text_needed')
cy.get('#text_needed').then(txtneeded => {
cy.log(txtneeded)
})
First, make sure to define it as traditional function, not as an arrow function as this context doesn't work as you'd expect there, more info here.
Next, typically in a single test you should use .then() callback to perform additional actions on elements, and use aliases when you need to share context between hooks or different tests, so please consider the following:
// using aliases together with this within the single test won't work
cy.get(<selector>).invoke('text').as('text_needed')
cy.get('#text_needed').should('contain', 'PO90944') // works fine
cy.log(this.text_needed) // undefined
// this will work as expected
cy.get(<selector>).invoke('text').then(($el) => {
cy.wrap($el).should('contain', 'PO90944'); // works fine
cy.log($el) // works fine
});
Setting alias in beforeEach hook for example, would let you access this.text_needed in your tests without problems.
Everything nicely explained here.
Edit based on comments:
it('Some test', function() {
cy.visit('www.example.com');
cy.get('h1').invoke('text').as('someVar');
});
it('Some other test', function() {
cy.visit('www.example.com');
cy.log('I expect "Example Domain" here: ' + this.someVar);
});
And here's the output from cypress runner:

How and in what way do content scripts share content scoped variables across different web pages?

There are some key parts of the MDN content script documentation I am having trouble understanding regarding variable scope:
There is only one global scope per frame, per extension. This means that variables from one content script can directly be accessed by another content script, regardless of how the content script was loaded.
This paragraph may also be relevant (my italics) given the questions below:
... you can ask the browser to load a content script whenever the browser loads a page whose URL matches a given pattern.
I assume (testing all this has proved difficult, please bear with me) that this means:
The content script code will be run every time a new page is loaded that matches the URLs provided in manifest.json (in my case "matches": [<"all_urls">]
The content script code will run every time a page is refreshed/reloaded.
The content script code will not be run when a user switches between already open tabs (requires listening to window focus or tabs.onActivated events).
The variables initialized on one page via the content script share a global scope with those variables initialized by the same content script on another page.
Given the following code,
background-script.js:
let contentPort = null
async function connectToActiveTab () {
let tab = await browser.tabs.query({ active: true, currentWindow: true })
tab = tab[0]
const tabName = `tab-${tab.id}`
if (contentPort) {
contentPort.disconnect()
}
contentPort = await browser.tabs.connect(tab.id, { name: tabName })
}
// listening to onCreated probably not needed so long as content script reliably sends 'connecToActiveTab' message
browser.tabs.onCreated.addListener(connectToActiveTab)
browser.runtime.onMessage.addListener(connectToActiveTab)
content-script.js
let contentPort = null
function connectionHandler (port, info) {
if (contentPort && contentPort.name !== port.name) {
// if content port doesn't match port we have changed tabs/windows
contentPort.disconnect()
}
if (!contentPort) {
contentPort = port
contentPort.onMessage.addListener(messageHandler)
}
}
async function main () {
// should always be true when a new page opens since content script code is run on each new page, testing has shown inconsistent results
if (!contentPort) {
await browser.runtime.sendMessage({ type: 'connectToActiveTab' })
}
}
browser.runtime.onConnect.addListener(connectionHandler)
main()
And from assumptions 1 and 4, I also assume:
The existing contentPort (if defined) will fire a disconnect event (which I could handle in the background script) and be replaced by a new connection to the currently active tab each time a new tab is opened.
The behaviour I have seen in Firefox while testing has so far been a bit erratic and I think I may be doing some things wrong. So now, here are the specific questions:
Are all of my 5 assumptions true? If not, which ones are not?
Is firing the disconnect() event unnecessary, since I should rely on Firefox to properly clean up and close existing connections without manually firing a disconnect event once the original contentPort variable is overwritten? (the code here would suggest otherwise)
Are the connect() methods synchronous, thus negating the need for await and asynchronous functions given the example code?
The tabs.connect() examples don't use await but neither the MDN runtime or connect docs explicitly say whether the methods are synchronous or not.
Thanks for taking the time to go through these deep dive questions regarding content script behaviour, my hope is that clear and concise answers to these could perhaps be added to the SO extension FAQ pages/knowledge base.

How does a Meteor database mutator know if it's being called from a Meteor.method vs. normal code?

If one does something on the client like
Foo.insert({blah : 1})
where Foo is a Meteor collection, this actually triggers a Meteor.method call as documented in the last paragraph of http://docs.meteor.com/#meteor_methods that executes code on both the client and the server.
However, let's say I define a method (on client and server) that does the same thing:
Meteor.methods({
bar: function() {
Foo.insert({blah : 1})
}
});
Now, I trigger this on the client with
Meteor.call("bar");
Foo.insert will now be called both on the client and the server as a result of the method. However, how does the client-side invocation of insert know not to call the server again, since it is itself a method?
Moreover, is there a way to call insert on the client side without automatically triggering the canonical server-side latency-compensation call and resulting synchronization? (For why I'd want to do this, see Loading a Meteor client app with fake fire-and-forget data)
The client side one becomes a 'stub'. It has this isSimulation property set that makes it appear as if the data is inserted for latency compensation.
I think if a .method is run on the client the latency compensation is always enabled and it shouldn't enter in the database. So anything running on a client side method would be a 'fake' type simulation
If you try setting this.isSimulation to false you'll get some weird error that shows you that the client side insert starts to throw errors with the insert.
I'm not too sure how to run it in a false simulation. I think you'd have to run it off in some other method var dothis = function() {...} type method.
To do this 'fake fire' & forget and have client side only data, which is what I presume (please correct if I'm wrong so I change the answer) modify the _collection property in your client side method.
e.g if you have
mydata = new Meteor.Collection("something");
function something() {
mydata.insert({..});
}
Modify the method to do this instead
mydata._collection.insert({..});
This will make sure that the data isn't synchronized to the server, yet will have the local collection have this 'fake data'.
Hope this helps!

Meteor 0.5.9: replacement for using Session in a server method?

So, I was attempting to do something like the following:
if(Meteor.isServer){
Meteor.methods({connect_to_api: function(vars){
// get data from remote API
return data;
}});
}
if(Meteor.isClient){
Template.myTpl.content = function(){
Meteor.call('connect_to_api', vars, function(err,data){
Session.set('placeholder', data);
});
return Session.get('placeholder');
};
}
This seemed to be working fine, but, of course, now breaks in 0.5.9 as the Session object has been removed from the server. How in the world do you now create a reactive Template that uses a server-only (stuff we don't want loading on the client) method call and get data back from that Method call. You can't put any Session references in the callback function because it doesn't exist on the server, and I don't know of any other reactive data sources available for this scenario.
I'm pretty new to Meteor, so I'm really trying to pin down best-practices stuff that has the best chance of being future-proof. Apparently the above implementation was not it.
EDIT: To clarify, this is not a problem of when I'm returning from the Template function. This is a problem of Session existing on the server. The above code will generate the following error message on the server:
Exception while invoking method 'connect_to_api' ReferenceError: Session is not defined
at Meteor.methods.connect_to_api (path/to/file.js:#:#)
at _.extend.protocol_handlers.method.exception ... etc etc
Setting the session in the callback seems to work fine, see this project I created on github: https://github.com/jtblin/meteor_session_test. In this example, I return data in a server method, and set it in the session in the callback.
There are 2 issues with your code:
1) Missing closing brace placement in Meteor.methods. The code should be:
Meteor.methods({
connect_to_api: function(vars) {
// get data from remote API
return data;
}
});
2) As explained above, you return the value in the session, before the callback is completed, i.e. before the callback method had the time to set the session variable. I guess this is why you don't see any data in the session variable yet.
I feel like an idiot (not the first time, not the last). Thanks to jtblin for showing me that Session.set does indeed work in the callback, I went back and scoured my Meteor.method function. Turns out there was one spot buried in the code where I was using Session.get which was what was throwing the error. Once I passed that value in from the client rather than trying to get it in the method itself, all was right with the world.
Oh, and you can indeed order things as above without issue.

Magento. OnBlockToHtml Before-After

In magento there are 2 events:
core_block_abstract_to_html_before
core_block_abstract_to_html_after
They are called every time when toHtml method is called. What I want is to echo to output some valuable data, BUT I get following error:
HEADERS ALREADY SENT
So is there any way to append to output?
If you look at the events being fired, the first event (core_block_abstract_to_html_before) receives the block instance as a parameter. The second event (core_block_abstract_to_html_after) receives both the block and the transport object from which you can extract the rendered content (this is the major feature of that transport object, as the rendered string is a local variable to the method otherwise). You can see the transport object receiving the rendered string in the line immediately preceding the dispatching of the second event:
self::$_transportObject->setHtml($html);
How you add to the block output depends on what you are trying to do. If you need to wrap your output and your preamble needs to use the ..._before event, you should set a param on the block in that event's observer in your module, e.g.
public function coreBlockAbstractToHtmlBefore($observer)
{
$arg = 'Whatever you are doing';
$observer->getBlock()->setYourCustomParam($arg); //e.g. using Magento setter method
}
Then, in your ...after event observer, you can evaluate your custom param and prepend it to the output like so:
public function coreBlockAbstractToHtmlAfter($observer)
{
$argBefore = $observer->getBlock()->getYourCustomParam();
$argAfter = 'Whatever you are doing afterwards';
//get output from _toHtml()
$normalOutput = $observer->getTransport()->getHtml();
//change the output; assume that both args are strings for this ex....
$observer->getTransport()->setHtml( $argBefore . $normalOutput . $argAfter )
}
That said, don't forget that you have a number of options at your disposal, including $block->setFrameTag($open,$close) which can be (somewhat hackishly) used to wrap whatever you want around block output.
A caveat regarding these approaches: the output that is being added will not be cached in the block html cache.
One thing I'd like to add as well is that if you need to target your rendering to specific scopes you can always use the full-action-name-automatic event from Mage_Core_Controller_Varien_Action::renderLayout() method (Mage::dispatchEvent('controller_action_layout_render_before_'.$this->getFullActionName());). You just use the same class to observe this event, invoke it as a singleton for all events, and set a flag.
And finally, a note on your "Headers already sent" error: typical Magento rendering uses a response object, and output should be added to that response object via $response->appendBody('string');.
You can edit the template that it is rendering. Or add another template file and call it from the template file that is rendering (after adding the new one to the layout file).

Resources