Skip to content

Commit

Permalink
Answers -> Search 1: Customer facing renaming (#141)
Browse files Browse the repository at this point in the history
* Update README.md

* README and package updates

* prelim renaming

* Deprecation tag and exports updates

* public deprecation message

* Deprecation fix

* Update index.ts

* Delete deprecation.test.tsx

* deprecated file

* fix lint issues

* more updates

* test fixes

* readme fix

* fix types exports
  • Loading branch information
ElemelonWind authored Jul 14, 2022
1 parent 0a34725 commit e0b5013
Show file tree
Hide file tree
Showing 18 changed files with 203 additions and 151 deletions.
88 changes: 44 additions & 44 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,114 +1,114 @@
# Answers Headless React
# Search Headless React

Answers Headless React is the official React UI Bindings layer for [Answers Headless](https://www.npmjs.com/package/@yext/answers-headless).
Search Headless React is the official React UI Bindings layer for [Search Headless](https://www.npmjs.com/package/@yext/search-headless).

Written in 100% TypeScript.

<div>
<a href="https://npmjs.org/package/@yext/answers-headless-react">
<img src="https://img.shields.io/npm/v/@yext/answers-headless-react" alt="NPM version"/>
<a href="https://npmjs.org/package/@yext/search-headless-react">
<img src="https://img.shields.io/npm/v/@yext/search-headless-react" alt="NPM version"/>
</a>
<a href="./LICENSE">
<img src="https://img.shields.io/badge/License-BSD%203--Clause-blue.svg" alt="License"/>
</a>
<a href='https://coveralls.io/github/yext/answers-headless-react?branch=main'>
<img src='https://coveralls.io/repos/github/yext/answers-headless-react/badge.svg?branch=main' alt='Coverage Status' />
<a href='https://coveralls.io/github/yext/search-headless-react?branch=main'>
<img src='https://coveralls.io/repos/github/yext/search-headless-react/badge.svg?branch=main' alt='Coverage Status' />
</a>
</div>
<br>

## Installation

```shell
npm install @yext/answers-headless-react
npm install @yext/search-headless-react
```

## Getting Started - `AnswersHeadlessProvider`
## Getting Started - `SearchHeadlessProvider`

Answers Headless React includes an `<AnswersHeadlessProvider />` component, which instantiates an AnswersHeadless instance and makes it available to the rest of your app.
Search Headless React includes an `<SearchHeadlessProvider />` component, which instantiates an SearchHeadless instance and makes it available to the rest of your app.

```tsx
import { AnswersHeadlessProvider } from '@yext/answers-headless-react';
import { SearchHeadlessProvider } from '@yext/search-headless-react';
import SearchBar from './SearchBar';
import MostRecentSearch from './MostRecentSearch';
import UniversalResults from './UniversalResults';

function MyApp() {
return (
<AnswersHeadlessProvider
<SearchHeadlessProvider
apiKey='your api key'
experienceKey='your experience key'
locale='en'
>
{/* Add components that use Answers as children */}
{/* Add components that use Search as children */}
<SearchBar/>
<MostRecentSearch/>
<UniversalResults/>
</AnswersHeadlessProvider>
</SearchHeadlessProvider>
);
}
```

## Respond to State Updates with `useAnswersState`
## Respond to State Updates with `useSearchState`

`useAnswersState` reads a value from the `AnswersHeadless` state and subscribes to updates.
`useSearchState` reads a value from the `SearchHeadless` state and subscribes to updates.

```tsx
import { useAnswersState } from '@yext/answers-headless-react';
import { useSearchState } from '@yext/search-headless-react';

export default function MostRecentSearch() {
const mostRecentSearch = useAnswersState(state => state.query.mostRecentSearch);
default function MostRecentSearch() {
const mostRecentSearch = useSearchState(state => state.query.mostRecentSearch);
return <div>Showing results for {mostRecentSearch}</div>;
}
```

## Dispatch Actions with `useAnswersActions`
## Dispatch Actions with `useSearchActions`

`useAnswersActions` allows you to dispatch actions using the `AnswersHeadless` instance.
`useSearchActions` allows you to dispatch actions using the `SearchHeadless` instance.

These include performing searches, getting autocomplete suggestions, and adding filters.

For a full list of capabilities see [the answers-headless docs](https://www.npmjs.com/package/@yext/answers-headless).
For a full list of capabilities see [the search-headless docs](https://www.npmjs.com/package/@yext/search-headless).

```tsx
import { useAnswersActions } from '@yext/answers-headless-react';
import { useSearchActions } from '@yext/search-headless-react';
import { ChangeEvent, KeyboardEvent, useCallback } from 'react';

function SearchBar() {
const answers = useAnswersActions();
const search = useSearchActions();
const handleTyping = useCallback((e: ChangeEvent<HTMLInputElement>) => {
answers.setQuery(e.target.value);
}, [answers]);
search.setQuery(e.target.value);
}, [search]);

const handleKeyDown = useCallback((evt: KeyboardEvent<HTMLInputElement>) => {
if (evt.key === 'Enter' ) {
answers.executeUniversalQuery();
search.executeUniversalQuery();
}
}, [answers]);
}, [search]);

return <input onChange={handleTyping} onKeyDown={handleKeyDown}/>;
}
```

## `AnswersHeadlessContext`
### Class Components
## `SearchHeadlessContext`
## Class Components

For users that want to use class components instead of functional components, you can use the `AnswersHeadlessContext` directly to dispatch actions and receive updates from state.
For users that want to use class components instead of functional components, you can use the `SearchHeadlessContext` directly to dispatch actions, and the `subscribeToStateUpdates` HOC to receive updates from state.

As an example, here is our simple SearchBar again, rewritten as a class using `AnswersHeadlessContext`.
As an example, here is our simple SearchBar again, rewritten as a class using `SearchHeadlessContext`.

```tsx
import { AnswersHeadlessContext, AnswersHeadless, State } from '@yext/answers-headless-react';
import { SearchHeadlessContext, SearchHeadless, State } from '@yext/search-headless-react';
import { Component } from 'react';

export default class Searcher extends Component {
static contextType = AnswersHeadlessContext;
static contextType = SearchHeadlessContext;
unsubscribeQueryListener: any;
state = { query: "" };

componentDidMount() {
const answers: AnswersHeadless = this.context;
this.unsubscribeQueryListener = answers.addListener({
const search: SearchHeadless = this.context;
this.unsubscribeQueryListener = search.addListener({
valueAccessor: (state: State) => state.query.mostRecentSearch,
callback: newPropsFromState => {
this.setState({ query: newPropsFromState })
Expand All @@ -121,15 +121,15 @@ export default class Searcher extends Component {
}

render() {
const answers: AnswersHeadless = this.context;
const search: SearchHeadless = this.context;
return (
<div>
<p>Query: {this.state.query}</p>
<input
onChange={evt => answers.setQuery(evt.target.value)}
onChange={evt => search.setQuery(evt.target.value)}
onKeyDown={evt => {
if (evt.key === 'Enter') {
answers.executeUniversalQuery();
search.executeUniversalQuery();
}
}}
/>
Expand All @@ -139,14 +139,14 @@ export default class Searcher extends Component {
}
```

## `useAnswersUtilities`
## `useSearchUtilities`

We offer a `useAnswersUtilities` convenience hook for accessing `AnswersHeadless.utilities`, which offers a number of stateless utility methods.
The `answersUtilities` and `answersUtilitiesFromActions` variables below are equivalent.
We offer a `useSearchUtilities` convenience hook for accessing `SearchHeadless.utilities`, which offers a number of stateless utility methods.
The `searchUtilities` and `searchUtilitiesFromActions` variables below are equivalent.

For class components, you can access `AnswersUtilities` through `AnswersHeadlessContext`.
For class components, you can access `SearchUtilities` through `SearchHeadlessContext`.

```ts
const answersUtilities = useAnswersUtilities();
const answersUtilitiesFromActions = useAnswersActions().utilities;
const searchUtilities = useSearchUtilities();
const searchUtilitiesFromActions = useSearchActions().utilities;
```
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@yext/answers-headless-react",
"name": "@yext/search-headless-react",
"version": "1.2.0",
"description": "The official React UI Bindings layer for Answers Headless",
"description": "The official React UI Bindings layer for Search Headless",
"main": "./lib/esm/src/index.js",
"license": "BSD-3-Clause",
"types": "./lib/esm/src/index.d.ts",
Expand Down Expand Up @@ -106,6 +106,6 @@
},
"repository": {
"type": "git",
"url": "https://github.com/yext/answers-headless-react.git"
"url": "https://github.com/yext/search-headless-react.git"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ import { AnswersHeadless } from '@yext/answers-headless';
import { createContext } from 'react';

// The default is empty because we don't know the user's config yet
export const AnswersHeadlessContext = createContext<AnswersHeadless>({} as AnswersHeadless);
export const SearchHeadlessContext = createContext<AnswersHeadless>({} as AnswersHeadless);
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { PropsWithChildren } from 'react';
import { provideAnswersHeadless, AnswersHeadless, HeadlessConfig } from '@yext/answers-headless';
import { AnswersHeadlessContext } from './AnswersHeadlessContext';
import { SearchHeadlessContext } from './SearchHeadlessContext';
import acquireSessionId from './utils/acquireSessionId';
import packageJson from '../package.json';

Expand All @@ -11,7 +11,7 @@ type Props = HeadlessConfig & {
sessionTrackingEnabled?: boolean
};

export function AnswersHeadlessProvider(props: PropsWithChildren<Props>): JSX.Element {
export function SearchHeadlessProvider(props: PropsWithChildren<Props>): JSX.Element {
const { children, verticalKey, sessionTrackingEnabled=true, ...answersConfig } = props;
const additionalHttpHeaders = {
'Client-SDK': {
Expand All @@ -27,8 +27,8 @@ export function AnswersHeadlessProvider(props: PropsWithChildren<Props>): JSX.El
sessionId && answers.setSessionId(sessionId);
}
return (
<AnswersHeadlessContext.Provider value={answers}>
<SearchHeadlessContext.Provider value={answers}>
{children}
</AnswersHeadlessContext.Provider>
</SearchHeadlessContext.Provider>
);
}
}
51 changes: 51 additions & 0 deletions src/deprecated.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { useSearchActions, SearchActions } from './useSearchActions';
import { StateSelector, useSearchState } from './useSearchState';
import { useSearchUtilities, SearchUtilities } from './useSearchUtilities';
import { SearchHeadlessProvider } from './SearchHeadlessProvider';
import { SearchHeadlessContext } from './SearchHeadlessContext';
import { PropsWithChildren } from 'react';
import { HeadlessConfig } from '@yext/answers-headless';

type Props = HeadlessConfig & {
verticalKey?: string,
sessionTrackingEnabled?: boolean
};

/**
* @deprecated AnswersHeadlessContext has been deprecated and replaced by SearchHeadlessContext
*/
export const AnswersHeadlessContext = SearchHeadlessContext;

/**
* @deprecated AnswersActions has been deprecated and replaced by SearchActions
*/
export type AnswersActions = SearchActions;

/**
* @deprecated AnswersUtilities has been deprecated and replaced by SearchUtilities
*/
export type AnswersUtilities = SearchUtilities;

/**
* @deprecated useAnswersActions has been deprecated and replaced by useSearchActions
*/
export function useAnswersActions(): SearchActions { return useSearchActions(); }

/**
* @deprecated useAnswersState has been deprecated and replaced by useSearchState
*/
export function useAnswersState<T>(stateSelector: StateSelector<T>): T {
return useSearchState(stateSelector);
}

/**
* @deprecated useAnswersUtilities has been deprecated and repalced by useSearchUtilities
*/
export function useAnswersUtilities(): SearchUtilities { return useSearchUtilities(); }

/**
* @deprecated AnswersHeadlessProvider has been deprecated and replaced by SearfchHeadlessProvider
*/
export function AnswersHeadlessProvider(props: PropsWithChildren<Props>): JSX.Element {
return SearchHeadlessProvider(props);
}
27 changes: 14 additions & 13 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
import { useAnswersActions, AnswersActions } from './useAnswersActions';
import { useAnswersState, StateSelector } from './useAnswersState';
import { useAnswersUtilities, AnswersUtilities } from './useAnswersUtilities';
import { useSearchActions, SearchActions } from './useSearchActions';
import { useSearchState, StateSelector } from './useSearchState';
import { useSearchUtilities, SearchUtilities } from './useSearchUtilities';
import { subscribeToStateUpdates } from './subscribeToStateUpdates';
import { AnswersHeadlessProvider } from './AnswersHeadlessProvider';
import { AnswersHeadlessContext } from './AnswersHeadlessContext';
import { SearchHeadlessProvider } from './SearchHeadlessProvider';
import { SearchHeadlessContext } from './SearchHeadlessContext';

export * from '@yext/answers-headless';
export * from './deprecated';
export {
AnswersHeadlessContext,
SearchHeadlessContext,
subscribeToStateUpdates,
useAnswersActions,
useAnswersState,
useAnswersUtilities,
AnswersHeadlessProvider,
AnswersActions,
AnswersUtilities,
useSearchActions,
useSearchState,
useSearchUtilities,
SearchHeadlessProvider,
SearchActions,
SearchUtilities,
StateSelector
};
};
14 changes: 7 additions & 7 deletions src/subscribeToStateUpdates.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,32 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { ComponentType, useReducer, useEffect, useContext } from 'react';
import { State } from '@yext/answers-headless';
import { AnswersHeadlessContext } from './AnswersHeadlessContext';
import { SearchHeadlessContext } from './SearchHeadlessContext';
import isShallowEqual from './utils/isShallowEqual';

type SubscriberGenerator = (WrappedComponent: ComponentType<any>) => (props: any) => JSX.Element;

/**
* Generates a HOC that updates a given Component's props based on the current
* answers-headless state and a given mapping function.
* search-headless state and a given mapping function.
*
* @deprecated
* For class component, use `AnswersHeadlessContext` directly to dispatch actions and receive state updates.
* For functional component, use `useAnswersActions` and `useAnswersState` instead.
* For class component, use `SearchHeadlessContext` directly to dispatch actions and receive state updates.
* For functional component, use `useSearchActions` and `useSearchState` instead.
*/
export function subscribeToStateUpdates(
mapStateToProps: (s: State) => Record<string, unknown>
): SubscriberGenerator {
const generateSubscriberHOC: SubscriberGenerator = WrappedComponent => {
/**
* Keep manual track of the props mapped from state instead of storing
* it in the AnswersHeadlessSubscriber's state. This avoids react's batching
* it in the SearchHeadlessSubscriber's state. This avoids react's batching
* of state updates, which can result in mappedState not updating immediately.
* This can, in turn, result in extra answers-headless listener invocations.
* This can, in turn, result in extra search-headless listener invocations.
*/
let previousPropsFromState = {};
return function AnswersHeadlessSubscriber(props: Record<string, unknown>) {
const answers = useContext(AnswersHeadlessContext);
const answers = useContext(SearchHeadlessContext);
const [mergedProps, dispatch] = useReducer(() => {
return {
...props,
Expand Down
Loading

0 comments on commit e0b5013

Please sign in to comment.