# Join

A join is similar to an `on` transition in that it takes an Event and transitions the FSM to a new State.&#x20;

The difference is that a join transitions the FSM from being in concurrent region into a single state and may requires multiple events to occur before the transition occurs.&#x20;

You will always pair a join with a [fork](/states/concurrent-states/fork.md). The fork causes the FSM to enter a concurrent region and a join causes it to exit the concurrent region.

When a join causes the FSM to exit a concurrent region it exits ALL states of the concurrent region (even states that lack an onJoin clause.

To add a join you use the 'onJoin' builder within a state that has a 'coregion' as an ancestor.

All onJoin within a single coregion MUST target the same state and the state must not be a child of the current 'coregion'.

A join is essentially an AND gate in that all events noted in onJoin statements (with in the coregion) must be triggered before the join will transition the FSM out of the coregion.

You might look at a join as shopping list. Each time an event fires the join ticks it off an event in its list. When the last event on the list is ticked off, the transition occurs.

OnJoin takes a State which is the new state the FSM will be in once the transition completes.&#x20;

All onJoin State targets MUST target the same state (we my relax this rule in a later implementation).

```
return StateMachine.create((g) => g
    ..initialState<CheckingAir>()
    ..state<CheckingAir>((b) => b
      ..onFork<OnCheckAir>((b) => b
        ..target<HandleFan>()
        ..target<HandleLamp>(), condition: (s, e) => e.quality < 10)
      ..state<CleaningAir>((b) => b
        ..coregion<HandleEquipment>((b) => b
          ..state<HandleFan>((b) =>
            ..onJoin<OnRunning, CheckingAir>())
         ..state<HandleLamp>((b) =>
            ..onJoin<OnLampOn, CheckingAir>())
        ..state<WaitForGoodAir>((b) {}))));
```

```
StateMachine.create((g) => g
    ..initialState<MaintainAir>()
    ..state<MaintainAir>((b) => b
      ..state<MonitorAir>((b) => b
        ..onFork<OnBadAir>(
            (b) => b
              ..target<HandleFan>()
              ..target<WaitForGoodAir>(),
            condition: (s, e) => e.quality < 10))
      ..coregion<CleanAir>((b) => b
        ..state<HandleFan>((b) => b
          ..onJoin<OnFanRunning, MonitorAir>())
        ..state<WaitForGoodAir>((b) => b
          ..onJoin<OnGoodAir, MonitorAir>())))
    );
```

## Rules

* A join must only be defined within a coregion
* Any number of nested states may exist between the join and its owning coregion.
* If coregions are nested then the join is 'owned' by the nearest ancestor coregion.
* All joins in a coregion MUST have the same target state.
* For a join to cause a state transition, all events for the joins in the owning coregion must have been received.
* Each time the FSM enters a coregion the set of received events will be reset and all join events must be received again before the join will trigger.
* When a join receives an event but does not trigger, the FSM will continue to evaluate additional transitions until it finds one that triggers.
* A coregion's child state does may not have an 'onJoin'. The state will be exited when the coregion exits.&#x20;


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://fsm2.onepub.dev/states/concurrent-states/join.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
