Listening for changes is the core purpose of observables, so Legend-State provides many options. Critically, you can listen to changes at any level in an object's hierarchy and it will be notified by changes in any children.

## Observing changes

### onChange

`onChange` listens to an observable for any changes anywhere within it. Use this as specifically as possible because it will fire notifications for every change recursively up the tree.

```js
const state$ = observable({ text: 'hi' })

state$.text.onChange(({ value }) => console.log('text changed to', value))
state$.onChange(({ value }) => console.log('state changed to', value))

state$.text.set('hello')

// Log: text changed to "hello"
// Log: state changed to { text: "hello" }
```

`onChange` has some extra options for more advanced use:

1. `getPrevious`: Function to compare with the previous value. It is a function to let you opt into getting the previous value if needed, because it has some performance cost in cloning the object to compute the previous value.
2. `changes`: Array of all of the changes to this observable in the latest batch. This is intended mainly for internal usage by the persistence plugins to know what to sync/update and the history plugin to track all changes, but it may be good for other uses too.
3. `trackingType`: Whether to track only shallow changes
4. `initial`: Whether to run the callback immediately with the current value
5. `immediate`: Whether to run the callback immediately instead of within a batch. This is used internally by `computed` to make sure its value is always correct, but it may be useful for other specific uses.

```js
// Full example
state$.onChange(({ value, getPrevious, changes }) => {
    const prev = getPrevious();
    changes.forEach(({ path, valueAtPath, prevAtPath }) => {
        console.log(valueAtPath, 'changed at', path, 'from', prevAtPath)
    })
}, { initial: true, trackingType: true })
```

#### Dispose of listeners

Listening to an observable returns a dispose function to stop listening. Just call it when you want to stop listening.

```js
const state$ = observable({ text: 'hello' })

const onChange = () => { ... }

const dispose = state$.text.onChange(onChange)

// Cancel listening manually
dispose()
```

### observe

`observe` can run arbitrary code when observables change, and automatically tracks the observables accessed while running, so it will update whenever any accessed observable changes.

This can be useful to use multiple observables at once, for the benefit of cleanup effects, or if you just like it more than [onChange](#onchange) 😎.

The callback parameter has some useful properties:
- `num`: How many times it's run. Use this to do something only the first time or not the first time.
- `previous`: The previous value, which will be undefined on the first run and set to the return value
- `cancel`: Set to `true` to stop tracking the observables when you are done observing
- `onCleanup`: A function to call before running the selector again

`observe` has an optional second `reaction` parameter which will run after the selector, and does not track changes. This can be useful for observing an `event` or a single `observable`.

```js
import { observe, observable } from "@legendapp/state"
const state = observable({ isOnline: false, toasts: [] })

const dispose = observe((e) => {
    // This observe will automatically track state.isOnline for changes
    if (!state.isOnline.get()) {
        // Show an "Offline" toast when offline
        const toast = { id: 'offline', text: 'Offline', color: 'red' }
        state.toasts.push(toast)

        // Remove the toast when the observe is re-run, which will be when isOnline becomes true
        e.onCleanup = () => state.toasts.splice(state.toasts.indexOf(toast), 1)
    }
})

// Cancel the observe
dispose()
```

Or use the second parameter to run a reaction when a selector changes. It has an additional `value` parameter, which contains the value of the selector.

```js
// Observe the return value of a selector and observe all accessed observables
observe(state.isOnline, (e) => {
    console.log('Online status', e.value)
})
// Observe the return value of a selector and observe all accessed observables
observe(() => state.isOnline.get() && state.user.get(), (e) => {
    console.log('Signed in status', e.value)
})
```

### when

`when` runs the given function **only once** when the predicate returns a truthy value, and automatically tracks the observables accessed while running the predicate so it will update whenever one of them changes. When the value becomes truthy it will call the function and dispose the listeners. If not given a callback function it will return a promise that resolves when the predicate returns a truthy value.

The predicate can either be an observable or a function.

```js
import { when } from "@legendapp/state"

const state$ = observable({ ok: false })

// Option 1: Promise
await when(state$.ok)

// Option 2: callback
const dispose = when(() => state$.ok.get(), () => console.log("Don't worry, it's ok"))

// Cancel listening manually
dispose()
```

### whenReady

`whenReady` is the same as `when` except it waits for objects and arrays to not be empty.

```js
import { whenReady } from "@legendapp/state"

const state$ = observable({ arr: [] })

whenReady(state$.arr, () => console.log("Array has some values"))
// Not ready yet

state$.arr.push('hello')

// "Array has some values"
```

## Observing contexts

The core power of Legend-State is the "observing contexts" in which any call to `get()` or array looping will subscribe the observer to track changes in that node, and the observer will re-run whenever it changes. This includes `observe`, `when`, `computed`, `useSelector`, `observer`, `<Memo>`, `<Computed>`, and reactive props.

These operations track:
1. Call `get()` on an observable: `settings.get()`
2. Array looping functions (shallow listener): `arr.map(settings.accounts, () => ...)`
3. Accessing array length (shallow listener): `if (arr.length > 0) ...`
4. Object.keys (shallow listener): `Object.keys(settings)`

These operation do not track:
1. Accessing through an observable: `state.settings`
2. Call `peek()` on an observable: `settings.peek()`

Some examples of the automatic behavior:

```js
const state = observable({
    settings: {
        theme: 'dark'
    },
    chats: {
        messages: [
            { id: 0, text: 'hi' }
        ]
    }
})

observe(() => {
    // Example 1:
    const theme = state.settings.theme.get()
    // ✅ Tracking [state.settings.theme] because of get()

    // Example 2:
    const settings = state.settings
    // ❌ Not tracking because it's an object

    const theme = settings.theme.get()
    // ✅ Tracking [state.settings.theme] because of get()

    // Example 3:
    const theme$ = state.settings.theme
    // ❌ Not tracking with no get()

    // Example 4:
    state.chats.messages.map(m => <Message key={m.get().id} message={m} />)
    // ✅ Tracking [state.chats.messages (shallow)] because of map()

    // Example 5:
    Object.keys(state.settings)
    // ✅ Tracking [state.settings (shallow)]
})
```

The automatic behavior can be modified with two observable functions:

<div style={{ maxWidth: 300 }}>
| Function     | Value      | Tracked |
| -----------  | ---------- | ------- |
| `get()`      | raw        | yes     |
| `get(true)`  | raw        | shallow |
| `peek()`     | raw        | no      |
</div>

### get()

`get` returns the raw data of an observable and tracks it, so you can work with it without doing any further tracking. You may want to use `get()` to:

- Get the value of an observable wrapper of a primitive
- Track this object and not its individual fields. Minimizing the number of listeners is better for performance.

```js
    const theme = state.settings.theme.get()
    // ✅ Tracking [state.settings.theme]
```

### shallow modifier

`get()` observes recursively by default, so any child changing will cause an update. You can modify it to be a shallow listener by just adding a `true` parameter. This can be useful when a component only needs to re-render if an object's keys or an array's items change.

```jsx
const state = observable({ messages: [] })

observe(() => {
    // Only need this to update when messages added/removed
    const messages = state.messages.get(true)

    console.log('Latest message', messages[0])
})
```
