Skip to main content

Overview

The .off() method unsubscribes from realtime updates by removing event listeners. This is essential for preventing memory leaks and stopping unwanted callbacks when you no longer need to listen to data changes.

Syntax

gun.get(key).on(callback).off()

Usage

Basic unsubscribe

const ref = gun.get('messages').on(function(data){
  console.log('New message:', data)
})

// Later, stop listening
ref.off()

Event object pattern

gun.get('status').on(function(data, key, msg, event){
  console.log('Status:', data)
  
  // Unsubscribe from within callback
  if(data.complete){
    event.off()
  }
})

Clean up on component unmount

// React example
function MessageList() {
  useEffect(() => {
    const ref = gun.get('messages').map().on((msg) => {
      setMessages(prev => [...prev, msg])
    })
    
    // Cleanup function
    return () => ref.off()
  }, [])
}

Implementation details

From ~/workspace/source/src/on.js:51:
Gun.chain.off = function(){
  var gun = this, at = gun._, tmp;
  var cat = at.back;
  if(!cat){ return }
  at.ack = 0; // so can resubscribe.
  if(tmp = cat.next){
    if(tmp[at.get]){
      delete tmp[at.get];
    }
  }
  if (tmp = cat.any) {
    delete cat.any;
    cat.any = {};
  }
  if(tmp = cat.ask){
    delete tmp[at.get];
  }
}

When to use .off()

Component unmount

Always call .off() when components unmount to prevent memory leaks

Conditional listening

Stop listening when certain conditions are met (e.g., data is complete)

Route changes

Unsubscribe when navigating away from a page or view

Resource cleanup

Clean up subscriptions before creating new ones on the same reference

Comparison: .on() vs .once()

Feature.on() + .off().once()
SubscriptionContinuousOne-time
CleanupManual with .off()Automatic
Use caseRealtime updatesInitial data load
MemoryMust manageSelf-cleaning

Best practices

Always clean up subscriptions - Call .off() in cleanup functions to prevent memory leaks and unexpected behavior.

Store references

// Good - store reference for cleanup
const subscription = gun.get('data').on(callback)
later: subscription.off()

// Bad - can't unsubscribe
gun.get('data').on(callback)
// No way to call .off() later!

Framework integration

// Vue
export default {
  data() {
    return { subscription: null }
  },
  mounted() {
    this.subscription = gun.get('data').on(this.handleData)
  },
  beforeUnmount() {
    if(this.subscription) {
      this.subscription.off()
    }
  }
}

// Angular
export class MyComponent implements OnDestroy {
  subscription: any;
  
  ngOnInit() {
    this.subscription = gun.get('data').on(data => {
      this.data = data;
    });
  }
  
  ngOnDestroy() {
    if(this.subscription) {
      this.subscription.off();
    }
  }
}

Common patterns

Conditional unsubscribe

gun.get('task').on(function(task, key, msg, event){
  updateUI(task)
  
  // Stop listening when task is complete
  if(task.status === 'completed'){
    event.off()
    console.log('Task completed, stopped listening')
  }
})

Multiple subscriptions

const subs = []

// Subscribe to multiple keys
['user', 'settings', 'preferences'].forEach(key => {
  subs.push(
    gun.get(key).on(data => console.log(key, data))
  )
})

// Unsubscribe from all
function cleanup() {
  subs.forEach(sub => sub.off())
  subs.length = 0
}

Resubscribe pattern

let currentSub = null

function subscribeToUser(userId) {
  // Clean up previous subscription
  if(currentSub) {
    currentSub.off()
  }
  
  // Subscribe to new user
  currentSub = gun.get('users').get(userId).on(data => {
    displayUser(data)
  })
}

Troubleshooting

Memory leaks

Forgetting to call .off() causes memory leaks. Symptoms include:
  • Increasing memory usage over time
  • Multiple callbacks firing for the same event
  • Slow performance after navigation
// Problem: Memory leak
function loadMessages() {
  gun.get('messages').map().on(msg => {
    addToList(msg)
  })
  // No cleanup!
}

// Solution: Store and clean up
let messagesSub = null

function loadMessages() {
  if(messagesSub) messagesSub.off()
  messagesSub = gun.get('messages').map().on(msg => {
    addToList(msg)
  })
}

function cleanup() {
  if(messagesSub) messagesSub.off()
}

Double subscription

// Problem: Subscribing multiple times
button.onclick = () => {
  gun.get('counter').on(updateDisplay) // Creates new subscription each click!
}

// Solution: Track subscription state
let isSubscribed = false

button.onclick = () => {
  if(!isSubscribed) {
    gun.get('counter').on(updateDisplay)
    isSubscribed = true
  }
}

See also

.on()

Subscribe to realtime updates

.once()

Read data one time without subscribing