> ## Documentation Index
> Fetch the complete documentation index at: https://algolia.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Access state outside the lifecycle

> InstantSearch.js provides `renderState`, which you can use to render custom widgets or refine the search outside the InstantSearch.js lifecycle.

export const Facet = () => <Tooltip tip="An attribute in your records that lets users filter or group results (for example, by color, brand, or price)." cta="Faceting" href="/doc/guides/managing-results/refine-results/faceting">
    facet
  </Tooltip>;

Each InstantSearch widget owns its part of the search state. While this fits the majority of use cases, **sometimes you might want to read the state or refine the search outside of the InstantSearch lifecycle**. For example, you may want to let users refine on a specific category from a product page, and not just from a refinement list.

InstantSearch.js lets you access the render state of each widget, which you can use to create custom widgets or refine the search outside the InstantSearch.js lifecycle.

<Note>
  The `renderState` property is available starting in InstantSearch.js v4.9.
</Note>

## Refining a search from the outside

In InstantSearch, the [`refinementList`](/doc/api-reference/widgets/refinement-list/js) widget controls refinements for a given attribute.
You can access its state from the `renderState` property.

<Note>
  All examples in this guide assume you've included InstantSearch.js in your web page from a CDN. If, instead, you're using it with a package manager, adjust how you [import InstantSearch.js and its widgets](/doc/guides/building-search-ui/installation/js) for more information.
</Note>

```js JavaScript theme={"system"}
const search = instantsearch({
  indexName,
  searchClient,
});

search.addWidgets([
  // ...
  instantsearch.widgets.refinementList({
    container: '#brand',
    attribute: 'brand',
    sortBy: ['isRefined'],
  }),
]);

search.renderState[indexName].refinementList.brand; // returns an object containing the render state
```

The exposed state matches the render options exposed to the [`connectRefinementList`](/doc/api-reference/widgets/refinement-list/js#customize-the-ui-with-connectrefinementlist) render options.
You can access the state of `brand` and use the [`refine`](/doc/api-reference/widgets/refinement-list/js#param-refine) method to programmatically set a refinement.

```js JavaScript theme={"system"}
search.renderState[indexName].refinementList.brand.refine('Apple');
```

This lets you refine on a brand from anywhere in your app, even outside of the InstantSearch lifecycle. For example, you might want to let users search in the related brand whenever they visit a product page.

```html HTML theme={"system"}
<button id="apple-search-button">
  Search "Apple" products
</button>
```

```js JavaScript theme={"system"}
document
  .querySelector('#apple-search-button')
  .addEventListener('click', () => {
    search.renderState[indexName].refinementList.brand.refine('Apple');
  });
```

## Accessing the state of other widgets

When refining on a brand, you might end up with no results at all. By default, the [`hits`](/doc/api-reference/widgets/hits/js) widget displays a "No results" message.
You can customize it using the [`connectHits`](/doc/api-reference/widgets/hits/js#customize-the-ui-with-connecthits) connector along with the [`refinementList`](/doc/api-reference/widgets/refinement-list/js) render state.

First, remove the default template for `empty` hits from the `hits` widget so it doesn't display anything when there are no hits.

```js JavaScript theme={"system"}
instantsearch.widgets.hits({
  container: '#hits',
  templates: {
    // ...
    empty: ''
  }
});
```

Second, add a button to reproduce the no results situation.

```html HTML theme={"system"}
<button id="pear-search-button">
  Search "Pear" products
</button>
```

```js JavaScript theme={"system"}
document
  .querySelector('#pear-search-button')
  .addEventListener('click', () => {
    search.renderState[indexName].refinementList.brand.refine('Pear');
  });
```

Then, you can add a dedicated custom widget to handle the no results situation.

```js JavaScript theme={"system"}
const renderer = ({ items, widgetParams }) => {
  const container = document.querySelector(widgetParams.container);
  if (items.length > 0) {
    container.innerHTML = '';
    return;
  }

  const brandState = search.renderState[indexName].refinementList.brand;
  const isPearRefined =
    brandState.items.filter((item) => item.label === 'Pear' && item.isRefined)
      .length > 0;

  if (!isPearRefined) {
    container.innerHTML = 'No results';
    return;
  }

  container.innerHTML = `
    <p>No results for Pear</p>
    <button>
      Remove "Pear" filter
    </button>
  `;
  container.querySelector('button').addEventListener('click', () => {
    search.renderState[indexName].refinementList.brand.refine('Pear');
  });
};

const emptyHits = instantsearch.connectors.connectHits(renderer);

search.addWidgets([
  emptyHits({
    container: '#empty-hits',
  })
]);
```

Even though you're using the `connectHits` connector,
you can access the render state of the `brand` [`refinementList`](/doc/api-reference/widgets/refinement-list/js).
This lets you check whether "Pear" is a selected <Facet /> value and display a button to remove it.

The `refine` method is a toggle function. When you call it with a value that's already refined, it removes it from the search state, and vice versa.

<Note>
  If you're not familiar with customizing widgets with connectors, please check the guide on [customizing the complete UI of widgets](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#customize-the-complete-ui-of-the-widgets).
</Note>

## Using the panel widget

The [`panel`](/doc/api-reference/widgets/panel/js) widget gives you easier access to the render state.

This is the basic implementation of a [`panel`](/doc/api-reference/widgets/panel/js).

```js JavaScript theme={"system"}
const categories = panel({
  templates: {
    header: 'Categories',
  },
})(instantsearch.widgets.hierarchicalMenu);

search.addWidgets([
  categories({
    container: '#categories',
    attributes: [
      'hierarchicalCategories.lvl0',
      'hierarchicalCategories.lvl1',
      'hierarchicalCategories.lvl2',
      'hierarchicalCategories.lvl3',
    ],
  }),
])
```

The `renderState` of [`hierarchicalMenu`](/doc/api-reference/widgets/hierarchical-menu/js) for each attribute are available in the `collapsed` and `hidden` methods of widget.

```js JavaScript theme={"system"}
const categories = panel({
  templates: {
    header: 'Categories',
  },
  collapsed(options) {
    // ...
  },
  hidden(options) {
    // ...
  }
})(instantsearch.widgets.hierarchicalMenu);
```

The provided `options` include the same state as you'd find under `search.renderState[indexName].hierarchicalMenu['hierarchicalCategories.lvl0']`.

For example, when there are no facets to display, you can hide or collapse the [`hierarchicalMenu`](/doc/api-reference/widgets/hierarchical-menu/js):

```js JavaScript theme={"system"}
const categories = panel({
  // ...
  hidden({ items }) {
    return items.length === 0;
  },
})(instantsearch.widgets.hierarchicalMenu);
```

You can see a [live demo on CodeSandbox](https://codesandbox.io/p/sandbox/github/algolia/doc-code-samples/tree/master/instantsearch.js/render-state).
