Skip to content

Commit 3730075

Browse files
committed
feat(auto-effect): add autoEffect and clearEffect
1 parent a95d578 commit 3730075

5 files changed

Lines changed: 175 additions & 3 deletions

File tree

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import React, { Component } from 'react';
2+
import { render, cleanup } from '@testing-library/react/pure';
3+
// eslint-disable-next-line import/no-unresolved
4+
import { view, store, autoEffect } from 'react-easy-state';
5+
6+
describe('AutoEffect edge cases and errors', () => {
7+
afterEach(cleanup);
8+
9+
test(`Using autoEffect in a function component ${
10+
process.env.NOHOOK
11+
? 'with a version of react that has no hooks should'
12+
: 'should not'
13+
} throw an error`, () => {
14+
const someEffect = () => {};
15+
16+
const MyComp = view(() => {
17+
const person = store({ name: 'Bob' });
18+
autoEffect(() => someEffect(person.name));
19+
return <div>{person.name}</div>;
20+
});
21+
22+
if (process.env.NOHOOK) {
23+
expect(() => render(<MyComp />)).toThrow(
24+
'You cannot use autoEffect inside a function component with a pre-hooks version of React. Please update your React version to at least v16.8.0 to use this feature.',
25+
);
26+
} else {
27+
expect(() => render(<MyComp />)).not.toThrow();
28+
}
29+
});
30+
31+
test('Using autoEffect inside a render of a class component should throw an error', () => {
32+
const someEffect = () => {};
33+
const person = store({ name: 'Bob' });
34+
35+
const MyComp = view(
36+
class extends Component {
37+
render() {
38+
autoEffect(() => someEffect(person.name));
39+
return <div>{person.name}</div>;
40+
}
41+
},
42+
);
43+
44+
expect(() => render(<MyComp />)).toThrow(
45+
'You cannot use autoEffect inside a render of a class component. Please use it in the constructor or lifecycle methods instead.',
46+
);
47+
});
48+
});

__tests__/autoEffect.test.jsx

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import React from 'react';
2+
import { render, cleanup, act } from '@testing-library/react/pure';
3+
import {
4+
view,
5+
store,
6+
autoEffect,
7+
clearEffect,
8+
// eslint-disable-next-line import/no-unresolved
9+
} from 'react-easy-state';
10+
11+
describe.only('autoEffect', () => {
12+
afterEach(cleanup);
13+
14+
test('should auto run global effects', () => {
15+
let documentTitle = '';
16+
const app = store({ name: 'Online Store' });
17+
18+
const effect = autoEffect(() => {
19+
documentTitle = app.name;
20+
});
21+
expect(documentTitle).toBe('Online Store');
22+
23+
act(() => {
24+
app.name = 'Learning Platform';
25+
});
26+
expect(documentTitle).toBe('Learning Platform');
27+
28+
clearEffect(effect);
29+
act(() => {
30+
app.name = 'Social Platform';
31+
});
32+
expect(documentTitle).toBe('Learning Platform');
33+
});
34+
35+
test('should auto run local effects in function components', () => {
36+
let documentTitle = '';
37+
38+
const app = store({ name: 'Online Store' });
39+
40+
const MyComp = view(() => {
41+
autoEffect(() => {
42+
documentTitle = app.name;
43+
});
44+
return <div>{app.name}</div>;
45+
});
46+
47+
const { container, unmount } = render(<MyComp />);
48+
expect(container).toHaveTextContent('Online Store');
49+
expect(documentTitle).toBe('Online Store');
50+
51+
act(() => {
52+
app.name = 'Learning Platform';
53+
});
54+
expect(container).toHaveTextContent('Learning Platform');
55+
expect(documentTitle).toBe('Learning Platform');
56+
57+
unmount();
58+
act(() => {
59+
app.name = 'Social Platform';
60+
});
61+
expect(documentTitle).toBe('Learning Platform');
62+
});
63+
64+
test('should be recreated on dependency changes in function components', () => {
65+
let documentTitle = '';
66+
67+
const app = store({ name: 'Store' });
68+
69+
const MyComp = view(({ name }) => {
70+
autoEffect(() => {
71+
documentTitle = `${name} ${app.name}`;
72+
}, [name]);
73+
return (
74+
<div>
75+
{name}
76+
{app.name}
77+
</div>
78+
);
79+
});
80+
81+
const { container, rerender } = render(<MyComp name="Online" />);
82+
expect(container).toHaveTextContent('Online Store');
83+
expect(documentTitle).toBe('Online Store');
84+
85+
rerender(<MyComp name="Awesome" />);
86+
expect(container).toHaveTextContent('Awesome Store');
87+
expect(documentTitle).toBe('Awesome Store');
88+
89+
act(() => {
90+
app.name = 'Page';
91+
});
92+
expect(container).toHaveTextContent('Awesome Page');
93+
expect(documentTitle).toBe('Awesome Page');
94+
});
95+
});

__tests__/store.no-hook.test.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { render, cleanup } from '@testing-library/react/pure';
33
// eslint-disable-next-line import/no-unresolved
44
import { view, store } from 'react-easy-state';
55

6-
describe('Using an old react version', () => {
6+
describe('Store edge cases and errors', () => {
77
afterEach(cleanup);
88

99
test(`Using local state in a function component ${

src/autoEffect.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { useEffect } from 'react';
2+
import { observe, unobserve } from '@nx-js/observer-util';
3+
4+
import {
5+
isInsideFunctionComponent,
6+
isInsideClassComponentRender,
7+
isInsideFunctionComponentWithoutHooks,
8+
} from './view';
9+
10+
export function autoEffect(fn, deps = []) {
11+
if (isInsideFunctionComponent) {
12+
return useEffect(() => {
13+
const observer = observe(fn);
14+
return () => unobserve(observer);
15+
}, deps);
16+
}
17+
if (isInsideFunctionComponentWithoutHooks) {
18+
throw new Error(
19+
'You cannot use autoEffect inside a function component with a pre-hooks version of React. Please update your React version to at least v16.8.0 to use this feature.',
20+
);
21+
}
22+
if (isInsideClassComponentRender) {
23+
throw new Error(
24+
'You cannot use autoEffect inside a render of a class component. Please use it in the constructor or lifecycle methods instead.',
25+
);
26+
}
27+
return observe(fn);
28+
}
29+
30+
export { unobserve as clearEffect };

src/index.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
export { observe, unobserve } from '@nx-js/observer-util';
2-
31
export { view } from './view';
42
export { store } from './store';
53
export { batch } from './scheduler';
4+
export { autoEffect, clearEffect } from './autoEffect';

0 commit comments

Comments
 (0)