import inline from '../../assets/middleware.inline.jpg';

# Middleware

Middleware are plain objects that modify the positioning
coordinates in some fashion, or provide useful data for
rendering, as calculated by the positioning cycle.

<CircleImage name="middleware" inline={inline} />

This allows you to customize the behavior of the positioning and
be as granular as you want, adding your own custom logic.

`computePosition(){:js}` starts with initial positioning via
`placement{:.objectKey}` — then middleware are executed as an
in-between "middle" step of the initial placement computation and
eventual return of data for rendering once the promise has
resolved.

Each middleware is executed in order, like a stack:

```js
computePosition(referenceEl, floatingEl, {
  placement: 'right',
  middleware: [],
});
```

## Example

```js
const shiftByOnePixel = {
  name: 'shiftByOnePixel',
  fn({x, y}) {
    return {
      x: x + 1,
      y: y + 1,
    };
  },
};
```

This (not particularly useful) middleware adds `1{:js}` pixel to
the coordinates. To use this middleware, add it to your
`middleware{:.objectKey}` array:

```js
computePosition(referenceEl, floatingEl, {
  placement: 'right',
  middleware: [shiftByOnePixel],
});
```

Here, `computePosition(){:js}` will compute coordinates that will
place the floating element to the right center of the reference
element, lying flush with it. Middleware are then executed,
resulting in these coordinates getting shifted by one pixel. Then
that data is returned to the caller.

### Shape

A middleware is an object which has a `name{:.objectKey}`
property and a `fn{:.function}` property. The `fn{:.function}`
property provides the logic of the middleware, which returns new
positioning coordinates or useful data.

### Data

Any data can be passed via an optional `data{:.objectKey}`
property of the object that is returned from `fn{:.function}`.
This will be accessible to the consumer via the
`middlewareData{:.param}` property:

```js {7-9}
const shiftByOnePixel = {
  name: 'shiftByOnePixel',
  fn({x, y}) {
    return {
      x: x + 1,
      y: y + 1,
      data: {
        amount: 1,
      },
    };
  },
};
```

```js
computePosition(referenceEl, floatingEl, {
  middleware: [shiftByOnePixel],
}).then(({middlewareData}) => {
  console.log(middlewareData.shiftByOnePixel);
});
```

### Function

You may notice that Floating UI's core middleware are actually
functions. This is so you can pass options in:

```js
const shiftByAmount = (amount = 0) => ({
  name: 'shiftByAmount',
  options: amount,
  fn: ({x, y}) => ({
    x: x + amount,
    y: y + amount,
  }),
});
```

It returns an object and uses a closure to pass the configured
behavior:

```js
const middleware = [shiftByAmount(10)];
```

The `options{:.objectKey}` key on a middleware object allows
libraries like React to compare and update middleware on
component re-renders.

### Always return an object

Inside `fn{:.function}` make sure to return an object. It doesn't
need to contain properties, but to remind you that it should be
pure, you must return an object. Never mutate any values that get
passed in from `fn{:.function}`.

## MiddlewareState

An object is passed to `fn{:.function}` containing useful data
about the middleware lifecycle being executed.

In the previous examples, we destructured `x{:.param}` and
`y{:.param}` out of the `fn{:.function}` parameter object. These
are only two properties that get passed into middleware, but
there are many more.

The properties passed are below:

```ts
type MiddlewareState = {
  x: number;
  y: number;
  initialPlacement: Placement;
  placement: Placement;
  strategy: Strategy;
  middlewareData: MiddlewareData;
  elements: Elements;
  rects: ElementRects;
  platform: Platform;
};
```

### x

This is the x-axis coordinate to position the floating element
to.

### y

This is the y-axis coordinate to position the floating element
to.

### elements

This is an object containing the reference and floating elements.

### rects

This is an object containing the `Rect{:.class}`s of the
reference and floating elements, an object of shape
`{width, height, x, y}`.

### middlewareData

This is an object containing all the data of any middleware at
the current step in the lifecycle. The lifecycle loops over the
`middleware{:.objectKey}` array, so later middleware have access
to data from any middleware run prior.

### strategy

The positioning strategy.

### initialPlacement

The initial (or preferred) placement passed in to
`computePosition(){:js}`.

### placement

The stateful resultant placement. Middleware like
`flip{:.function}` change `initialPlacement{:.objectKey}` to a
new one.

### platform

An object containing methods to make Floating UI work on the
current platform, e.g. DOM or React Native.

## Ordering

The order in which middleware are placed in the array matters, as
middleware **use** the coordinates that were returned from
previous ones. This means they perform their work based on the
current positioning state.

Three `shiftByOnePixel{:.const}` in the middleware array means
the coordinates get shifted by 3 pixels in total:

```js
const shiftByOnePixel = {
  name: 'shiftByOnePixel',
  fn: ({x, y}) => ({x: x + 1, y: y + 1}),
};
const middleware = [
  shiftByOnePixel,
  shiftByOnePixel,
  shiftByOnePixel,
];
```

If the later `shiftByOnePixel` implementations had a condition
based on the current value of `x{:.param}` and `y{:.param}`, the
condition can change based on their placement in the array.

Understanding this can help in knowing which order to place
middleware in, as placing a middleware before or after another
can produce a different result.

In general, `offset(){:js}` should always go at the beginning of
the middleware array, while `arrow(){:js}` and `hide(){:js}` at
the end. The other core middleware can be shifted around
depending on the desired behavior.

```js
const middleware = [
  offset(),
  // ...
  arrow({element: arrowElement}),
  hide(),
];
```

## Resetting the lifecycle

There are use cases for needing to reset the middleware lifecycle
so that other middleware perform fresh logic.

- When `flip(){:js}` and `autoPlacement(){:js}` change the
  placement, they reset the lifecycle so that other middleware
  that modify the coordinates based on the current
  `placement{:.objectKey}` do not perform stale logic.
- `size(){:js}` resets the lifecycle with the newly applied
  dimensions, as many middleware read the dimensions to perform
  their logic.
- `inline(){:js}` resets the lifecycle when it changes the
  reference rect to a custom implementation, similar to a
  [Virtual Element](/docs/virtual-elements).

In order to do this, add a `reset{:.objectKey}` property to the
returned object from `fn{:.function}`.

```ts
type Reset =
  | true
  | {
      placement?: Placement;
      // `true` will compute the new `rects` if the
      // dimensions were mutated. Otherwise, you can
      // return your own new rects.
      rects?: true | ElementRects;
    };
```

```js
const middleware = {
  name: 'middleware',
  fn() {
    if (someCondition) {
      return {
        reset: {
          placement: nextPlacement,
        },
      };
    }

    return {};
  },
};
```

Data supplied to `middlewareData{:.param}` is preserved by doing
this, so you can read it at any point after you've reset the
lifecycle.
