Contributing to react-simplikit
react-simplikit is designed to encourage contributions from anyone. If you'd like to contribute, please follow the guide below.
Scope
react-simplikit provides hooks, components and utilities that work in every React environment — browser, server rendering and React Native — plus hooks that solve problems specific to browsers and mobile web browsers, such as the on-screen keyboard, safe-area insets and the visual viewport. Anything that interferes with React's lifecycle or depends on another library is out of scope; see the Design Principles.
Source lives under packages/react-simplikit/src in hooks/, components/ and utils/.
Implementation Contribution
When contributing implementations, add them to the appropriate directory based on their type (components, hooks, or utils). Each implementation must include the following elements:
- Implementation
- Test Code
- JSDoc
TIP
Do I need to write documentation?
No, you don't need to write documentation separately. Instead, please write detailed JSDoc comments, then run yarn docs:gen <name> to generate the English documentation from them and commit the result with your PR. Translations are maintained separately; until one exists, the page is shown in English with a notice.
Writing Implementations
You must follow react-simplikit's Design Principles. We don't provide implementations that depend on specific libraries or are tightly coupled with React's lifecycle. Please write implementations that adhere to these design principles.
Writing JSDoc
All implementations must include JSDoc comments. These provide hints when using the implementation and play a crucial role in generating documentation. JSDoc comments must include @description and @example, and if there are parameters or return values, they should include @param and @returns.
JSDoc writing rules must be followed for accurate documentation generation. If JSDoc validation fails, CI might fail.
JSDoc must be written in English.
@description: A required tag that clearly explains the implementation's functionality or role.@example: A required tag that shows example code demonstrating how to use the implementation.@param: Write the parameter's name and description. Must be included if the implementation has parameters.For required parameters:
@param {<type>} <parameter name> - <parameter description>For optional parameters:
@param {<type>} [<parameter name>] - <parameter description>For object parameters, both the object itself and its properties need
@paramtags.If you want to write a list under a description, use
--instead of-.tstype Props = { name: string; age: number; nickname?: string; company: { name: string; address?: string; }; paymentMethod?: { type: 'card' | 'account'; number?: string; }; }; /** * @param {string} name - Name of the user. * @param {number} age - Age of the user. * @param {string} [nickname] - Nickname of the user. * @param {Object} company - Company information of the user. * @param {string} company.name - Name of the company. * @param {string} [company.address] - Address of the company. * @param {Object} [paymentMethod] - Payment information of the user. * @param {string} [paymentMethod.type] - Payment method. * @param {string} [paymentMethod.number] - Card or account number. * -- Card or account number without `-`. * -- If the number is a card number, it should be 15 or 16 digits. */This JSDoc will be converted into the following documentation.
- namerequired · string
Name of the user.
- agerequired · number
Age of the user.
- nicknamestring
Nickname of the user.
- companyrequired · Object
Company information of the user.
- company.namerequired · string
Name of the company.
- company.addressstring
Address of the company.
- company.namerequired · string
- paymentMethodObject
Payment information of the user.
- paymentMethod.typerequired · string
Payment method.
- paymentMethod.numberstring
Card or account number.
- Card or account number without `-`.
- If the number is a card number, it should be 15 or 16 digits.
- paymentMethod.typerequired · string
- namerequired · string
@returns: Write the return value's name and description. Must be included if the implementation has return values.Format:
@returns {<type>} <return value description>For object or tuple return values, include descriptions for each member.
If additional details are needed for each member, please use
:.tstype ReturnValue = [Object, () => void]; /** * @returns {[Object, () => void]} A tuple containing: * - obj `Object` - An object containing: * : label `string` - The label of the input. * : value `string` - The value of the input. * - onChange `() => void` - A function to update the value. */This JSDoc will be converted into the following documentation.
- [obj: Object, onChange: () => void]
A tuple containing:
- objObject
An object containing:
: labelstring- The label of the input.
: valuestring- The value of the input. - onChange() => void
A function to update the value.
- objObject
Object-type return values can be written in a similar way.
tstype ReturnValue = { value: string; onChange: () => void }; /** * @returns {Object} An object containing: * - value `string` - The value of the input. * - onChange `() => void` - A function to update the value. */This JSDoc will be converted into the following documentation.
- Object
An object containing:
- valuestring
The value of the input.
- onChange() => void
A function to update the value.
- valuestring
- [obj: Object, onChange: () => void]
Writing Test Code
All implementations must include test code, written with the same name as the implementation. Test coverage must always reach 100%. Use the following command to verify coverage:
yarn test:coveragePlease verify safe operation in SSR environments
All react-simplikit implementations use special rendering functions to verify safe operation in SSR environments.
Component Testing
tsxit('is safe on server side rendering', () => { // renderSSR.serverOnly is a method that renders the component in the server environment. // In this environment, hooks like useEffect are not executed, and objects like window or document are not available, causing errors. renderSSR.serverOnly(() => ( <Component> <div>Test Content</div> </Component> )); expect(screen.getByText('Test Content')).toBeInTheDocument(); }); it('should render children correctly', async () => { // renderSSR is a method that renders the component in the client environment. // However, if the HTML rendered on the server and the HTML rendered on the client are different, hydration mismatch errors will occur. await renderSSR(() => ( <Component> <div>Test Content</div> </Component> )); expect(screen.getByText('Test Content')).toBeInTheDocument(); }); it('should hydration mismatch error occurred', async () => { // This test code will fail due to a hydration mismatch error. await renderSSR(() => ( <Component> <div>Test Content</div> <div>{Math.random()}</div> </Component> )); expect(screen.getByText('Test Content')).toBeInTheDocument(); });Hook Testing
tsit('is safe on server side rendering', () => { // renderHookSSR.serverOnly is a method that renders the hook in the server environment. // In this environment, hooks like useEffect are not executed, and objects like window or document are not available, causing errors. const result = renderHookSSR.serverOnly(() => useToggle(true)); const [bool] = result.current; expect(bool).toBe(true); }); it('should initialize with the default value true', async () => { const { result } = await renderHookSSR(() => useToggle(true)); const [bool] = result.current; expect(bool).toBe(true); });
Creating a Changeset
When your code changes affect the package, you need to create a changeset. Changesets are a tool that automates version management and changelog generation.
How to Create a Changeset
- After implementing your changes, run the following command:
yarn changesetSelect the type of change:
patch: Bug fixes or minor changesminor: New features (maintaining backward compatibility)major: Breaking changes (breaking backward compatibility)
Write a brief summary of your changes.
TIP
The package is in the 0.x stage. During this phase, most changes should use patch. If you're unsure about the version type, please discuss with the maintainers.
- Commit the generated changeset file with your PR.
TIP
Changeset files are created in the .changeset folder and must be committed with your PR. When the PR is merged, the version will be automatically updated and a changelog will be generated.
Release
When changes are merged into the main branch, the release process happens automatically:
- When a PR is merged into the
mainbranch, GitHub Actions will run. - If there are changesets, a version update PR will be automatically created.
- When the version update PR is merged, the new version will be published to npm.
You can view the release results in GitHub Actions.
Documentation Contribution
There are no specific conditions for contributing to documentation. If you find incorrect information, poor translations, or have additional content to add, feel free to make edits. Please write documentation clearly and concisely from the reader's perspective.
Scaffolding
There's a command that creates the minimum skeleton for contributions. Use the following command to create an implementation folder with a basic structure:
yarn run scaffold <name> --type <type>type: Implementation type, must be one ofcomponent,hook, orutil.name: Name of the implementation.
Example
yarn run scaffold Button --type componentThis command creates three files in the src/components/Button folder:
/**
* @description
* <description-here>
*
* @param {<param-type>} <param-name> - <param-description>
* @param {<param-type>} [<param-name>] - <optional-param-description>
*
* @returns {<return-type>} <return-description>
* - <member-description> `<member-name>` - <member-description>
*
* @example
* <example-code>
*/
export function Button() {
// TODO: Implement Button
}import { describe, expect, it } from 'vitest';
import { renderSSR } from '../../_internal/test-utils/renderSSR.tsx';
import { Button } from './Button.tsx';
describe('Button', () => {
it('is safe on server side rendering', async () => {
const result = renderSSR.serverOnly(() => <Button />);
expect(true).toBe(true);
});
it('should work', async () => {
const result = renderSSR.serverOnly(() => <Button />);
expect(true).toBe(true);
});
});export { Button } from './Button.tsx';TIP
You can also use these shortcuts:
yarn run scaffold Button --t c // Create component
yarn run scaffold useButton --t h // Create hook
yarn run scaffold getButton --t u // Create utilContribution Workflow
Scaffold → Implementation → Testing → Documentation → Review → Changeset → Merge
Coverage Checklist
- [ ] All if/else branches
- [ ] All switch cases
- [ ] All early returns
- [ ] Cleanup functions (useEffect return)
Implementation Rules
- Named exports only
- Maximize TypeScript inference
- Required parameters first, optional ones last; use an options object once there are three or more optional parameters
- Return a single value when there is one, a
[state, action]tuple for a pair, and an object when there are more members or when the shape is expected to grow (browser measurements such as keyboard height or safe-area insets) - Apply the SSR safety pattern below
SSR Safety Pattern
Never read a browser API while rendering — the server has no window, and a client value that differs from the server's causes a hydration mismatch. Start from a fixed value and synchronize in an effect:
const [state, setState] = useState(FIXED_INITIAL_VALUE);
useEffect(function syncBrowserState() {
if (isServer()) {
return;
}
setState(readBrowserApi());
}, []);Browser and Mobile Web Hooks
Throttle high-frequency events (
scroll,resize,visualViewportchanges) at about 16ms, skip updates when the value has not changed, and usestartTransitionfor non-urgent updatesUse passive event listeners where the handler never calls
preventDefaultAccount for platform differences instead of picking one platform:
Feature iOS Android visualViewport.offsetTopBecomes negative when keyboard appears Typically remains 0 Keyboard behavior Viewport is pushed up Resizes the layout jsdom cannot reproduce the visual viewport or the on-screen keyboard, so verify these hooks on a real iOS Safari and Android Chrome device as well as in tests