feat: add helpers to convert between url and state

This commit is contained in:
satyajit.happy
2019-08-16 03:14:53 +05:30
committed by Satyajit Sahoo
parent 8ed54dace4
commit dbe2b9159a
6 changed files with 245 additions and 2 deletions

View File

@@ -0,0 +1,47 @@
import getPathFromState from '../getPathFromState';
it('converts path string to initial state', () => {
expect(
getPathFromState({
routes: [
{
name: 'foo',
state: {
index: 1,
routes: [
{ name: 'boo' },
{
name: 'bar',
state: {
routes: [
{
name: 'baz qux',
params: { author: 'jane & co', valid: true },
},
],
},
},
],
},
},
],
})
).toMatchInlineSnapshot(
`"/foo/bar/baz%20qux?author=%22jane%20%26%20co%22&valid=true"`
);
});
it('handles route without param', () => {
expect(
getPathFromState({
routes: [
{
name: 'foo',
state: {
routes: [{ name: 'bar' }],
},
},
],
})
).toBe('/foo/bar');
});

View File

@@ -0,0 +1,92 @@
import getStateFromPath from '../getStateFromPath';
it('converts path string to initial state', () => {
expect(
getStateFromPath(
'foo/bar/baz%20qux?author=%22jane%20%26%20co%22&valid=true'
)
).toEqual({
stale: true,
routes: [
{
name: 'foo',
state: {
stale: true,
routes: [
{
name: 'bar',
state: {
stale: true,
routes: [
{
name: 'baz qux',
params: { author: 'jane & co', valid: true },
},
],
},
},
],
},
},
],
});
});
it('handles leading slash when converting', () => {
expect(getStateFromPath('/foo/bar/?count=42')).toEqual({
stale: true,
routes: [
{
name: 'foo',
state: {
stale: true,
routes: [
{
name: 'bar',
params: { count: 42 },
},
],
},
},
],
});
});
it('handles ending slash when converting', () => {
expect(getStateFromPath('foo/bar/?count=42')).toEqual({
stale: true,
routes: [
{
name: 'foo',
state: {
stale: true,
routes: [
{
name: 'bar',
params: { count: 42 },
},
],
},
},
],
});
});
it('handles route without param', () => {
expect(getStateFromPath('foo/bar')).toEqual({
stale: true,
routes: [
{
name: 'foo',
state: {
stale: true,
routes: [{ name: 'bar' }],
},
},
],
});
});
it('returns undefined for invalid path', () => {
expect(getStateFromPath('//')).toBe(undefined);
});

View File

@@ -0,0 +1,46 @@
import { NavigationState, PartialState, Route } from './types';
type State = NavigationState | Omit<PartialState<NavigationState>, 'stale'>;
/**
* Utility to serialize a navigation state object to a path string.
*
* @param state Navigation state to serialize.
* @returns Path representing the state, e.g. /foo/bar?count=42.
*/
export default function getPathFromState(state: State): string {
let path = '/';
let current: State | undefined = state;
while (current) {
const index = typeof current.index === 'number' ? current.index : 0;
const route = current.routes[index] as Route<string> & {
state?: State | undefined;
};
path += encodeURIComponent(route.name);
if (route.state) {
path += '/';
} else if (route.params) {
const query = [];
for (const param in route.params) {
const value = (route.params as { [key: string]: any })[param];
query.push(
`${encodeURIComponent(param)}=${encodeURIComponent(
JSON.stringify(value)
)}`
);
}
path += `?${query.join('&')}`;
}
current = route.state;
}
return path;
}

View File

@@ -0,0 +1,52 @@
import { InitialState } from './types';
/**
* Utility to parse a path string to initial state object accepted by the container.
* This is useful for deep linking when we need to handle the incoming URL.
*
* @param path Path string to parse and convert, e.g. /foo/bar?count=42.
*/
export default function getStateFromPath(
path: string
): InitialState | undefined {
const parts = path.split('?');
const segments = parts[0].split('/').filter(Boolean);
const query = parts[1] ? parts[1].split('&') : undefined;
let result: InitialState | undefined;
let current: InitialState | undefined;
while (segments.length) {
const state = {
stale: true as true,
routes: [{ name: decodeURIComponent(segments[0]) }],
};
if (current) {
current.routes[0].state = state;
} else {
result = state;
}
current = state;
segments.shift();
}
if (current == null || result == null) {
return undefined;
}
if (query) {
const params = query.reduce<{ [key: string]: any }>((acc, curr) => {
const [key, value] = curr.split('=');
acc[decodeURIComponent(key)] = JSON.parse(decodeURIComponent(value));
return acc;
}, {});
current.routes[0].params = params;
}
return result;
}

View File

@@ -11,4 +11,7 @@ export { default as useNavigation } from './useNavigation';
export { default as useFocusEffect } from './useFocusEffect';
export { default as useIsFocused } from './useIsFocused';
export { default as getStateFromPath } from './getStateFromPath';
export { default as getPathFromState } from './getPathFromState';
export * from './types';

View File

@@ -27,12 +27,15 @@ export type NavigationState = {
stale?: false;
};
export type InitialState = Partial<Omit<NavigationState, 'routes'>> & {
export type InitialState = Partial<
Omit<NavigationState, 'stale' | 'routes'>
> & {
stale?: boolean;
routes: Array<Omit<Route<string>, 'key'> & { state?: InitialState }>;
};
export type PartialState<State extends NavigationState> = Partial<
Omit<State, 'key' | 'routes' | 'routeNames'>
Omit<State, 'stale' | 'key' | 'routes' | 'routeNames'>
> & {
stale: boolean;
routes: Array<