Skip to content

Commit

Permalink
fix(useIsActive): Support relative state names
Browse files Browse the repository at this point in the history
  • Loading branch information
christopherthielen committed Jan 7, 2020
1 parent d14075d commit 311f1f3
Show file tree
Hide file tree
Showing 2 changed files with 37 additions and 7 deletions.
19 changes: 19 additions & 0 deletions src/hooks/__tests__/useIsActive.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as React from 'react';
import { RawParams } from '@uirouter/core';
import { makeTestRouter } from '../../__tests__/util';
import { UIView } from '../../components';
import { useIsActive } from '../useIsActive';

const state1 = { name: 'state1', url: '/state1' };
Expand Down Expand Up @@ -52,6 +53,24 @@ describe('useIsActive', () => {
expect(wrapper.find('div').props().className).toBe('notactive');
});

it('works with relative states', async () => {
const parent = { name: 'parent', component: () => <TestComponent state=".child" params={null} exact={false} /> };
const child = { name: 'parent.child', component: () => <div /> };
router.stateRegistry.register(parent);
router.stateRegistry.register(child);
await routerGo('parent');
const wrapper = mountInRouter(<UIView />);
expect(wrapper.find('div').props().className).toBe('notactive');

await routerGo('parent.child');
expect(
wrapper
.update()
.find('div')
.props().className
).toBe('yesactive');
});

it('updates when the desired state changes', async () => {
await routerGo('state2');
const wrapper = mountInRouter(<TestComponent state="state1" params={null} exact={false} />);
Expand Down
25 changes: 18 additions & 7 deletions src/hooks/useIsActive.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,39 @@
import { StateService } from '@uirouter/core';
import { StateDeclaration } from '@uirouter/core';
import { useEffect, useMemo, useState } from 'react';
import { UIRouterReact } from '../core';
import { useDeepObjectDiff } from './useDeepObjectDiff';
import { useOnStateChanged } from './useOnStateChanged';
import { useRouter } from './useRouter';
import { useViewContextState } from './useViewContextState';

function checkIfActive(stateService: StateService, stateName: string, params: object, exact: boolean) {
return exact ? stateService.is(stateName, params) : stateService.includes(stateName, params);
function checkIfActive(
router: UIRouterReact,
stateName: string,
params: object,
relative: StateDeclaration,
exact: boolean
) {
return exact
? router.stateService.is(stateName, params, { relative })
: router.stateService.includes(stateName, params, { relative });
}

export function useIsActive(stateName: string, params = null, exact = false) {
const { stateService } = useRouter();
const router = useRouter();
const relative = useViewContextState(router);
// Don't re-compute initialIsActive on every render
const initialIsActive = useMemo(() => checkIfActive(stateService, stateName, params, exact), []);
const initialIsActive = useMemo(() => checkIfActive(router, stateName, params, relative, exact), []);
const [isActive, setIsActive] = useState(initialIsActive);

const checkIfActiveChanged = () => {
const newIsActive = checkIfActive(stateService, stateName, params, exact);
const newIsActive = checkIfActive(router, stateName, params, relative, exact);
if (newIsActive !== isActive) {
setIsActive(newIsActive);
}
};

useOnStateChanged(checkIfActiveChanged);
useEffect(checkIfActiveChanged, [stateService, stateName, useDeepObjectDiff(params), exact]);
useEffect(checkIfActiveChanged, [router, stateName, useDeepObjectDiff(params), exact]);

return isActive;
}

0 comments on commit 311f1f3

Please sign in to comment.