Skip to content

Commit

Permalink
Rename StatefulCore to AnswersHeadless (#45)
Browse files Browse the repository at this point in the history
Update the naming to match the change from yext/search-headless#37 which was released in (v0.1.0-beta.0) of answers-headless

J=SLAP-1649
TEST=manual

Build the sample app and confirm it still works
  • Loading branch information
cea2aj committed Oct 25, 2021
1 parent 0e09edc commit 74c10a5
Show file tree
Hide file tree
Showing 12 changed files with 57 additions and 57 deletions.
2 changes: 1 addition & 1 deletion THIRD-PARTY-NOTICES
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ The following NPM packages may be included in this product:

- @yext/answers-core@1.3.2
- @yext/answers-headless-react@0.3.0-beta.0
- @yext/answers-headless@0.0.5
- @yext/answers-headless@0.1.0-beta.0

These packages each contain the following license and notice below:

Expand Down
14 changes: 7 additions & 7 deletions package-lock.json

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

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"dependencies": {
"@reduxjs/toolkit": "^1.6.2",
"@types/react": "^17.0.15",
"@yext/answers-headless": "^0.0.5",
"@yext/answers-headless": "^0.1.0-beta.0",
"typescript": "^4.3.5"
},
"devDependencies": {
Expand Down
8 changes: 4 additions & 4 deletions sample-app/package-lock.json

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

2 changes: 1 addition & 1 deletion sample-app/src/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ import App from './App';
test('renders learn react link', () => {
const { getByText } = render(<App />);

expect(getByText(/Stateful Core React Test/i)).toBeInTheDocument();
expect(getByText(/Answers Headless React Test/i)).toBeInTheDocument();
});
2 changes: 1 addition & 1 deletion sample-app/src/components/Facet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export default function Facet(props: FacetProps): JSX.Element {
});

const facetOptions = searchable
? answersActions.searchThroughFacet(facet, filterValue).options
? answersActions.utilities.searchThroughFacet(facet, filterValue).options
: facet.options;

return (
Expand Down
4 changes: 2 additions & 2 deletions src/AnswersActionsContext.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { StatefulCore } from '@yext/answers-headless';
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 AnswersActionsContext = createContext<StatefulCore>({} as StatefulCore);
export const AnswersActionsContext = createContext<AnswersHeadless>({} as AnswersHeadless);
8 changes: 4 additions & 4 deletions src/AnswersActionsProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ReactChild, ReactChildren } from 'react';
import { provideStatefulCore, StatefulCore } from '@yext/answers-headless';
import { provideAnswersHeadless, AnswersHeadless } from '@yext/answers-headless';
import { AnswersConfig } from '@yext/answers-core';
import { AnswersActionsContext } from './AnswersActionsContext';

Expand All @@ -10,10 +10,10 @@ interface Props extends AnswersConfig {

export function AnswersActionsProvider(props: Props): JSX.Element {
const { children, verticalKey, ...answersConfig } = props;
const statefulCore: StatefulCore = provideStatefulCore(answersConfig);
verticalKey && statefulCore.setVerticalKey(verticalKey);
const answers: AnswersHeadless = provideAnswersHeadless(answersConfig);
verticalKey && answers.setVerticalKey(verticalKey);
return (
<AnswersActionsContext.Provider value={statefulCore}>
<AnswersActionsContext.Provider value={answers}>
{children}
</AnswersActionsContext.Provider>
);
Expand Down
12 changes: 6 additions & 6 deletions src/subscribeToStateUpdates.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,22 @@ export function subscribeToStateUpdates(
const generateSubscriberHOC: SubscriberGenerator = WrappedComponent => {
/**
* Keep manual track of the props mapped from state instead of storing
* it in the StatefulCoreSubscriber's state. This avoids react's batching
* it in the AnswersHeadlessSubscriber'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 stateful-core listener invocations.
* This can, in turn, result in extra answers-headless listener invocations.
*/
let previousPropsFromState = {};
return function StatefulCoreSubscriber(props: Record<string, unknown>) {
const statefulCore = useContext(AnswersActionsContext);
return function AnswersHeadlessSubscriber(props: Record<string, unknown>) {
const answers = useContext(AnswersActionsContext);
const [mergedProps, dispatch] = useReducer(() => {
return {
...props,
...previousPropsFromState
};
}, { ...props, ...mapStateToProps(statefulCore.state) });
}, { ...props, ...mapStateToProps(answers.state) });

useEffect(() => {
return statefulCore.addListener({
return answers.addListener({
valueAccessor: (state: State) => mapStateToProps(state),
callback: newPropsFromState => {
if (!isShallowEqual(previousPropsFromState, newPropsFromState)) {
Expand Down
4 changes: 2 additions & 2 deletions src/useAnswersActions.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { StatefulCore } from '@yext/answers-headless';
import { AnswersHeadless } from '@yext/answers-headless';
import { useContext } from 'react';
import { AnswersActionsContext } from './AnswersActionsContext';

export type AnswersActions = StatefulCore;
export type AnswersActions = AnswersHeadless;

export function useAnswersActions(): AnswersActions {
return useContext(AnswersActionsContext);
Expand Down
16 changes: 8 additions & 8 deletions src/useAnswersState.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,41 +9,41 @@ export type StateSelector<T> = (s: State) => T;
* Very similar to useSelector in react-redux.
*/
export function useAnswersState<T>(stateSelector: StateSelector<T>): T {
const statefulCore = useContext(AnswersActionsContext);
const answers = useContext(AnswersActionsContext);

// useRef stores values across renders without triggering additional ones
const storedStoreState = useRef<State>(statefulCore.state);
const storedStoreState = useRef<State>(answers.state);
const storedSelector = useRef<StateSelector<T>>(stateSelector);
const storedSelectedState = useRef<T>();
/**
* Guard execution of {@link stateSelector} for initializing storedSelectedState.
* Otherwise it's run an additional time every render, even when storedSelectedState is already initialized.
*/
if (storedSelectedState.current === undefined) {
storedSelectedState.current = stateSelector(statefulCore.state);
storedSelectedState.current = stateSelector(answers.state);
}

/**
* The currently selected state - this is the value returned by the hook.
* Tries to use {@link storedSelectedState} when possible.
*/
const selectedStateToReturn: T = (() => {
if (storedStoreState.current !== statefulCore.state || storedSelector.current !== stateSelector) {
return stateSelector(statefulCore.state);
if (storedStoreState.current !== answers.state || storedSelector.current !== stateSelector) {
return stateSelector(answers.state);
}
return storedSelectedState.current;
})();

const [, triggerRender] = useState<T>(storedSelectedState.current);
useLayoutEffect(() => {
storedSelector.current = stateSelector;
storedStoreState.current = statefulCore.state;
storedStoreState.current = answers.state;
storedSelectedState.current = selectedStateToReturn;
});

useLayoutEffect(() => {
let unsubscribed = false;
const unsubscribe = statefulCore.addListener({
const unsubscribe = answers.addListener({
valueAccessor: state => state,
callback: (state: State) => {
// prevent React state update on an unmounted component
Expand All @@ -61,7 +61,7 @@ export function useAnswersState<T>(stateSelector: StateSelector<T>): T {
unsubscribed = true;
unsubscribe();
};
}, [statefulCore]);
}, [answers]);

return selectedStateToReturn;
}
40 changes: 20 additions & 20 deletions tests/useAnswersState.test.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { act, render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { Result } from '@yext/answers-core';
import { provideStatefulCore } from '@yext/answers-headless';
import { provideAnswersHeadless } from '@yext/answers-headless';
import { State } from '@yext/answers-headless/lib/esm/models/state';
import React, { useCallback, useReducer } from 'react';
import { AnswersActionsContext, useAnswersActions, useAnswersState } from '../src';
Expand Down Expand Up @@ -43,13 +43,13 @@ it('does not perform extra renders/listener registrations for nested components'
);
}

const statefulCore = createStatefulCore();
const addListenerSpy = jest.spyOn(statefulCore, 'addListener');
const answers = createAnswersHeadless();
const addListenerSpy = jest.spyOn(answers, 'addListener');
expect(addListenerSpy).toHaveBeenCalledTimes(0);
expect(parentStateUpdates).toHaveLength(0);
expect(childStateUpdates).toHaveLength(0);
render(
<AnswersActionsContext.Provider value={statefulCore}>
<AnswersActionsContext.Provider value={answers}>
<Test />
</AnswersActionsContext.Provider>
);
Expand Down Expand Up @@ -88,16 +88,16 @@ it('does not trigger render on unmounted component', async () => {
return <div>child component</div>;
}

const statefulCore = createStatefulCore();
const answers = createAnswersHeadless();
render(
<AnswersActionsContext.Provider value={statefulCore}>
<AnswersActionsContext.Provider value={answers}>
<ParentComponent/>
</AnswersActionsContext.Provider>
);
act(() => statefulCore.setQuery('resultsWithFilter'));
await act( () => statefulCore.executeUniversalQuery());
act(() => statefulCore.setQuery('default'));
await act( () => statefulCore.executeUniversalQuery());
act(() => answers.setQuery('resultsWithFilter'));
await act( () => answers.executeUniversalQuery());
act(() => answers.setQuery('default'));
await act( () => answers.executeUniversalQuery());
expect(consoleSpy).not.toHaveBeenCalledWith(
expect.stringMatching('Can\'t perform a React state update on an unmounted component'),
expect.anything(),
Expand All @@ -120,9 +120,9 @@ describe('uses the most recent selector',() => {
);
}

const statefulCore = createStatefulCore();
const answers = createAnswersHeadless();
render(
<AnswersActionsContext.Provider value={statefulCore}>
<AnswersActionsContext.Provider value={answers}>
<Test />
</AnswersActionsContext.Provider>
);
Expand Down Expand Up @@ -153,11 +153,11 @@ describe('uses the most recent selector',() => {
);
}

const statefulCore = createStatefulCore();
statefulCore.setQuery('initial value');
const answers = createAnswersHeadless();
answers.setQuery('initial value');
expect(stateUpdates).toHaveLength(0);
render(
<AnswersActionsContext.Provider value={statefulCore}>
<AnswersActionsContext.Provider value={answers}>
<Test />
</AnswersActionsContext.Provider>
);
Expand All @@ -173,18 +173,18 @@ describe('uses the most recent selector',() => {
expect(stateUpdates).toEqual(['initial value', 1]);

act(() => {
statefulCore.setContext('trigger a state update that would not update the initial selector');
answers.setContext('trigger a state update that would not update the initial selector');
});
expect(stateUpdates).toEqual(['initial value', 1, 3]);
});
});

function createStatefulCore() {
const statefulCore = provideStatefulCore({
function createAnswersHeadless() {
const answers = provideAnswersHeadless({
apiKey: 'fake api key',
experienceKey: 'fake exp key',
locale: 'en',
});
statefulCore.setVerticalKey('fakeVerticalKey');
return statefulCore;
answers.setVerticalKey('fakeVerticalKey');
return answers;
}

0 comments on commit 74c10a5

Please sign in to comment.