Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reset query params to the initial params when un-mounting. #3

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 34 additions & 6 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,54 @@
import { document, history } from 'global';
import qs from 'qs';

import { makeDecorator, StoryContext, StoryGetter } from '@storybook/addons';
import { makeDecorator, StoryContext, StoryGetter, useRef, useEffect } from '@storybook/addons';

import { PARAM_KEY } from './constants';

/** Update our `location.search` values with given query params. */
const updateLocationSearch = (query: Record<string, any>) => {
const newLocation = new URL(document.location.href);
newLocation.search = qs.stringify(query);

history.replaceState({}, document.title, newLocation.toString());
};

export const withQuery = makeDecorator({
name: 'withQuery',
parameterName: PARAM_KEY,
skipIfNoParametersOrOptions: true,
wrapper: (getStory: StoryGetter, context: StoryContext, { parameters }) => {
const { location } = document;
const currentQuery = qs.parse(location.search, { ignoreQueryPrefix: true });
/**
* We use this during unmount to revert to the original `location.search` when your Story mounted.
* The `useRef` initialValue will never change in subsequent renders. This is locked in.
*/
const initialSearch = useRef<string>(document.location.search);

/**
* On ever render, we merge the current `location.search` with the Story's `parameters.query`.
* This has to happen in render, not inside of a `useEffect` as our `location.search` needs to be set synchronously.
*/
const currentQuery = qs.parse(document.location.search, { ignoreQueryPrefix: true });
const additionalQuery =
typeof parameters === 'string'
? qs.parse(parameters, { ignoreQueryPrefix: true })
: parameters;

const newLocation = new URL(document.location.href);
newLocation.search = qs.stringify({ ...currentQuery, ...additionalQuery });
updateLocationSearch({ ...currentQuery, ...additionalQuery });

history.replaceState({}, document.title, newLocation.toString());
/**
* This useEffect is purely for the cleanup callback.
* As we're unmounting the component, revert to the very first render's `location.search`.
*/
useEffect(
() => {
return () => {
const searchAsQuery = qs.parse(initialSearch.current, { ignoreQueryPrefix: true });
updateLocationSearch(searchAsQuery);
}
},
[]
);

return getStory(context);
},
Expand Down
80 changes: 73 additions & 7 deletions stories/addon-queryparams.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,81 @@ import React from "react";

export default {
title: "Addons/Queryparams",
parameters: {
query: {
mock: "Hello world!",
},
};

export const SingleParam = () => {
const urlParams = new URLSearchParams(document.location.search);
const entries = Object.fromEntries(urlParams);

const { id, viewMode } = entries;
delete entries.id;
delete entries.viewMode;

return (
<div>
<p>
<u>Single param:</u>
<br />{JSON.stringify(entries)}
</p>

<u>Storybook params:</u>
<br />id = {id}
<br />viewMode = {viewMode}
</div>
);
};
SingleParam.parameters = {
query: {
name: "MockedParam",
},
};

export const WithMockedSearch = () => {
export const MultipleParams = () => {
const urlParams = new URLSearchParams(document.location.search);
const mockedParam = urlParams.get("mock");
return <div>Mocked value: {mockedParam}</div>;
const entries = Object.fromEntries(urlParams);

const { id, viewMode } = entries;
delete entries.id;
delete entries.viewMode;

return (
<div>
<p>
<u>Complex params:</u>
<br />{JSON.stringify(entries)}
</p>

<u>Storybook params:</u>
<br />id = {id}
<br />viewMode = {viewMode}
</div>
);
};
MultipleParams.parameters = {
query: {
name: "MockedParamsComplex",
'array[]': [1, 2],
},
};

export const NoParams = () => {
const urlParams = new URLSearchParams(document.location.search);
const entries = Object.fromEntries(urlParams);

const { id, viewMode } = entries;
delete entries.id;
delete entries.viewMode;

return (
<div>
<p>
<u>No params:</u>
<br />{JSON.stringify(entries)}
</p>

<u>Storybook params:</u>
<br />id = {id}
<br />viewMode = {viewMode}
</div>
);
}