Skip to content
Merged
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
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ jobs:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 9
version: 10
- uses: actions/setup-node@v4
with:
node-version: 20
node-version: 22
cache: pnpm
- run: pnpm install
- run: pnpm run lint
Expand Down
15 changes: 10 additions & 5 deletions demos/components/Loading.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import {css} from '@linaria/core';
import {type CSSProperties, type ReactPortal, useMemo, useEffect, useState} from 'react';
import {
type CSSProperties,
type ReactPortal,
useMemo,
useEffect,
useState
} from 'react';
import {createPortal} from 'react-dom';
import {cancel, useLoading, useRouter} from 'native-router-react';

Expand All @@ -15,7 +21,6 @@ export default function Loading(): ReactPortal | null {
setPercent(0);
}, [key]);

// eslint-disable-next-line consistent-return
useEffect(() => {
const remove = () => {
if (el.parentElement) document.body.removeChild(el);
Expand Down Expand Up @@ -47,9 +52,9 @@ export default function Loading(): ReactPortal | null {

return createPortal(
<button
data-testid="loading"
type="button"
title="Click to cancel!"
data-testid='loading'
type='button'
title='Click to cancel!'
onClick={() => cancel(router)}
className={css`
position: fixed;
Expand Down
4 changes: 2 additions & 2 deletions demos/components/RouterError.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ export default function RouterError({error}: Props) {
<section>
<h1>Error</h1>
<pre>{error.stack}</pre>
<button type="button" onClick={() => refresh(router)}>
<button type='button' onClick={() => refresh(router)}>
Refresh
</button>
<Link to="/">home</Link>
<Link to='/'>home</Link>
</section>
);
}
1 change: 0 additions & 1 deletion demos/util/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
/* eslint-disable import/prefer-default-export */
export function sleep(interval: number): Promise<void> {
return new Promise((resolve) => {
setTimeout(resolve, interval);
Expand Down
1 change: 0 additions & 1 deletion demos/util/styles.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
/* eslint-disable import/prefer-default-export */
import {css} from '@linaria/core';

export const center = css`
Expand Down
1 change: 0 additions & 1 deletion demos/views/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ export default function App() {
return (
<HashRouter
routes={routes}
// eslint-disable-next-line react/no-unstable-nested-components
errorHandler={(e) => <RouterError error={e} />}
>
<View />
Expand Down
67 changes: 67 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
const path = require('path');
const eslintPluginPrettier = require('eslint-plugin-prettier');
const eslintPluginImport = require('eslint-plugin-import');
const eslintPluginReact = require('eslint-plugin-react');
const eslintPluginReactHooks = require('eslint-plugin-react-hooks');
const eslintPluginJsxA11y = require('eslint-plugin-jsx-a11y');
const tsEslintPlugin = require('@typescript-eslint/eslint-plugin');
const tsEslintParser = require('@typescript-eslint/parser');

module.exports = [
{
ignores: ['dist', 'node_modules', 'coverage', 'eslint.config.js']
},
{
files: ['**/*.{js,jsx,ts,tsx}'],
languageOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
globals: {
browser: true,
node: true,
es6: true,
__DEV__: true
},
parser: tsEslintParser,
parserOptions: {
project: './tsconfig.json'
}
},
plugins: {
prettier: eslintPluginPrettier,
import: eslintPluginImport,
react: eslintPluginReact,
'react-hooks': eslintPluginReactHooks,
'jsx-a11y': eslintPluginJsxA11y,
'@typescript-eslint': tsEslintPlugin
},
rules: {
'prettier/prettier': 'error',
'react/jsx-props-no-spreading': 'off',
'no-return-assign': ['error', 'except-parens'],
'no-sequences': 'off',
'no-shadow': 'off',
'no-plusplus': 'off',
'no-param-reassign': 'off',
'no-void': 'off',
'react/require-default-props': 'off',
'react/react-in-jsx-scope': 'off',
'@typescript-eslint/no-use-before-define': ['error', {functions: false}],
'no-use-before-define': ['error', {functions: false}],
'import/no-extraneous-dependencies': [
'error',
{devDependencies: ['demos/**/*', 'test/**/*']}
],
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'warn',
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
},
settings: {
react: {
version: 'detect'
},
'import/resolver': {
typescript: {}
}
}
}
];
6 changes: 4 additions & 2 deletions src/memo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,10 @@ export function memoBase<P extends object>(
) {
const MemoComponent = reactMemo(WrappedComponent, propsAreEqual);

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const FixedComponent = forwardRef(function FixedComponent(props: any, ref: any) {
const FixedComponent = forwardRef(function FixedComponent(
props: any,
ref: any
) {
const memoEventsRef = useRef<Record<string, any>>({});
const memoEvents = memoEventsRef.current;
const newMemoEvents = Object.keys(props)
Expand Down
64 changes: 32 additions & 32 deletions test/async.test.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,33 @@
import { describe, it, expect, vi } from 'vitest';
import { render, screen, waitFor } from '@testing-library/react';
import { useState, useEffect } from 'react';
import { useRun, useInjectable, createMemoryCacheProvider } from '../src/async';
import {describe, it, expect, vi} from 'vitest';
import {render, screen, waitFor} from '@testing-library/react';
import {useState, useEffect} from 'react';
import {useRun, useInjectable, createMemoryCacheProvider} from '../src/async';

describe('async hooks', () => {
describe('useRun', () => {
it('should run function on mount', () => {
const fn = vi.fn(() => 'result');

function TestComponent() {
useRun(fn, []);
return <div>done</div>;
}

render(<TestComponent />);
expect(fn).toHaveBeenCalled();
});

it('should re-run when dependencies change', () => {
const fn = vi.fn(() => 'result');
function TestComponent({ deps }: { deps: number[] }) {

function TestComponent({deps}: {deps: number[]}) {
useRun(fn, deps);
return <div>done</div>;
}
const { rerender } = render(<TestComponent deps={[1]} />);

const {rerender} = render(<TestComponent deps={[1]} />);
expect(fn).toHaveBeenCalledTimes(1);

rerender(<TestComponent deps={[2]} />);
expect(fn).toHaveBeenCalledTimes(2);
});
Expand All @@ -36,20 +36,20 @@ describe('async hooks', () => {
describe('useInjectable', () => {
it('should create injectable function', async () => {
const fetchData = vi.fn(async (id: number) => `result ${id}`);

function TestComponent() {
const injectable = useInjectable(fetchData);
const [result, setResult] = useState('');

useEffect(() => {
injectable(1).then(setResult);
}, [injectable]);

return <div>{result}</div>;
}

render(<TestComponent />);

await waitFor(() => {
expect(screen.getByText('result 1')).toBeDefined();
});
Expand All @@ -60,9 +60,9 @@ describe('async hooks', () => {
it('should create cache provider', () => {
const provider = createMemoryCacheProvider<string, number[]>({
cacheTime: 60000,
hash: (key) => JSON.stringify(key),
hash: (key) => JSON.stringify(key)
});

expect(provider).toBeDefined();
expect(provider.get).toBeDefined();
expect(provider.set).toBeDefined();
Expand All @@ -74,61 +74,61 @@ describe('async hooks', () => {
it('should cache and retrieve data', async () => {
const provider = createMemoryCacheProvider<string, [string]>({
cacheTime: 60000,
hash: (key) => JSON.stringify(key),
hash: (key) => JSON.stringify(key)
});

provider.set(['test'], 'cached value');
const result = await provider.get(['test']);

expect(result).toEqual(['cached value', expect.any(Number)]);
});

it('should return undefined for missing key', async () => {
const provider = createMemoryCacheProvider<string, [string]>({
cacheTime: 60000,
hash: (key) => JSON.stringify(key),
hash: (key) => JSON.stringify(key)
});

const result = await provider.get(['missing']);
expect(result).toBeUndefined();
});

it('should delete cache entry', () => {
const provider = createMemoryCacheProvider<string, [string]>({
cacheTime: 60000,
hash: (key) => JSON.stringify(key),
hash: (key) => JSON.stringify(key)
});

provider.set(['test'], 'value');
provider.delete(['test']);

expect(provider.get(['test'])).toBeUndefined();
});

it('should clear all cache', () => {
const provider = createMemoryCacheProvider<string, [string]>({
cacheTime: 60000,
hash: (key) => JSON.stringify(key),
hash: (key) => JSON.stringify(key)
});

provider.set(['a'], 'value a');
provider.set(['b'], 'value b');
provider.clear();

expect(provider.get(['a'])).toBeUndefined();
expect(provider.get(['b'])).toBeUndefined();
});

it('should use hook correctly', () => {
const provider = createMemoryCacheProvider<string, [string]>({
cacheTime: 60000,
hash: (key) => JSON.stringify(key),
hash: (key) => JSON.stringify(key)
});

const cleanup = provider.use();
expect(cleanup).toBeDefined();
expect(typeof cleanup).toBe('function');

cleanup();
});
});
Expand Down
Loading
Loading