react-store-input 0.1.0 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -8,6 +8,8 @@ At the same time, it allows you to use all the attributes originally provided by
8
8
 
9
9
  ## Get Started
10
10
 
11
+ This is a simple example of how to use this package.
12
+
11
13
  ```tsx
12
14
  import { useFormStore } from "dn-react-input";
13
15
 
@@ -37,3 +39,184 @@ export default function App() {
37
39
  );
38
40
  }
39
41
  ```
42
+
43
+ ## How to define state?
44
+
45
+ You can define any state you want as an object when calling `useStore`.
46
+
47
+ ```tsx
48
+ function Component() {
49
+ ...
50
+
51
+ const store = useStore({
52
+ email: "",
53
+ password: "",
54
+ rememberMe: false,
55
+ });
56
+
57
+ ...
58
+ }
59
+ ```
60
+
61
+ It's a single source of truth for your form state.
62
+
63
+ ## How to get input values?
64
+
65
+ You can access the current values of the input elements through the `state` property of the store.
66
+
67
+ ```tsx
68
+ function Component() {
69
+ ...
70
+
71
+ const submit = () => {
72
+ const { email, password, rememberMe } = store.state;
73
+ };
74
+
75
+ ...
76
+ }
77
+ ```
78
+
79
+ ## How to add input elements?
80
+
81
+ You can add input elements using the `Input` component provided by the store. There are 'Select' and 'Textarea' components as well.
82
+
83
+ ```tsx
84
+ import { Input } from "dn-react-input";
85
+
86
+ function Component() {
87
+ ...
88
+
89
+ return (
90
+ <form>
91
+ <Input store={store} name="email" type="email" />
92
+ <Input store={store} name="password" type="password" />
93
+ <Input store={store} name="rememberMe" type="checkbox" />
94
+ </form>
95
+ );
96
+ }
97
+ ```
98
+
99
+ If you want to avoid passing the store to each input component, use `useStoreInput`. This hook provides input components that are already connected to the store.
100
+
101
+ ```tsx
102
+ import { useStoreInput } from "dn-react-input";
103
+
104
+ function Component() {
105
+ ...
106
+ const Input = useStoreInput(store);
107
+
108
+ return (
109
+ <form>
110
+ <Input.input name="email" type="email" />
111
+ <Input.input name="password" type="password" />
112
+ <Input.input name="rememberMe" type="checkbox" />
113
+ </form>
114
+ );
115
+ }
116
+ ```
117
+
118
+ `useFormStore` is a facade that combines `useStore` and `useStoreInput` for convenience.
119
+
120
+ ```tsx
121
+ import { useFormStore } from "dn-react-input";
122
+
123
+ function Component() {
124
+ ...
125
+ const store = useFormStore({
126
+ email: "",
127
+ password: "",
128
+ rememberMe: false,
129
+ });
130
+
131
+ return (
132
+ <form>
133
+ <store.input name="email" type="email" />
134
+ <store.input name="password" type="password" />
135
+ <store.input name="rememberMe" type="checkbox" />
136
+ </form>
137
+ );
138
+ }
139
+ ```
140
+
141
+ ## How to render components on state changes?
142
+
143
+ If you want to render a component only when specific parts of the state change, use the `useSelector` hook.
144
+
145
+ ```tsx
146
+ import { useSelector } from "dn-react-input";
147
+
148
+ function Component() {
149
+ ...
150
+ const email = useSelector(store, (state) => state.email);
151
+
152
+ return <div>Your email is: {email}</div>;
153
+ }
154
+ ```
155
+
156
+ If you want to render components in an inline manner, use the `createRender` function. By using this, you can avoid creating separate components for each part of the state you want to track.
157
+
158
+ ```tsx
159
+ import { createRender } from "dn-react-input";
160
+
161
+ function Component() {
162
+ ...
163
+ return (
164
+ <div>
165
+ {createRender(store, (state) => <p>{state.email}</p>)}
166
+ {createRender(store, (state) => <p>{state.password}</p>)}
167
+ </div>
168
+ );
169
+ }
170
+ ```
171
+
172
+ ## How to subscribe to state changes?
173
+
174
+ You can subscribe to state changes using the `subscribe` method of the store.
175
+
176
+ ```tsx
177
+ function Component() {
178
+ ...
179
+ useEffect(() => {
180
+ const unsubscribe = store.subscribe((state) => {
181
+ console.log(`State changed`, state);
182
+ });
183
+
184
+ return () => {
185
+ unsubscribe();
186
+ };
187
+ }, []);
188
+
189
+ ...
190
+ }
191
+ ```
192
+
193
+ ## How to update state manually?
194
+
195
+ You can update the state manually using the `dispatch` method of the store.
196
+
197
+ ```tsx
198
+ function Component() {
199
+ ...
200
+ const updateEmail = () => {
201
+ store.dispatch({ email: "ohjinsu98@icloud.com" });
202
+ };
203
+
204
+ return <button onClick={updateEmail}>Update Email</button>;
205
+ }
206
+ ```
207
+
208
+ The `dispatch` method uses immerjs internally to update the state, so you can also use a function to update the state based on the previous state.
209
+
210
+ ```tsx
211
+ function Component() {
212
+ ...
213
+
214
+ const updateEmail = () => {
215
+ store.dispatch((state) => {
216
+ state.email = "ohjinsu98@icloud.com";
217
+ });
218
+ };
219
+
220
+ return <button onClick={updateEmail}>Update Email</button>;
221
+ }
222
+ ```
@@ -1,7 +1,8 @@
1
- import { ReactNode } from 'react';
1
+ import React__default, { ReactNode } from 'react';
2
2
  import { Store } from './use_store.mjs';
3
3
 
4
4
  type CreateRender<TState> = (selector: (state: TState) => ReactNode, compare?: (a: TState, b: TState) => boolean) => ReactNode;
5
- declare function createRender<TState>(store: Store<TState>): CreateRender<TState>;
5
+ declare function createRender<TState>(store: Store<TState>, selector: (state: TState) => ReactNode, compare?: (a: TState, b: TState) => boolean): React__default.JSX.Element;
6
+ declare function createRenderWithStore<TState>(store: Store<TState>): CreateRender<TState>;
6
7
 
7
- export { type CreateRender, createRender };
8
+ export { type CreateRender, createRender, createRenderWithStore };
@@ -1,7 +1,8 @@
1
- import { ReactNode } from 'react';
1
+ import React__default, { ReactNode } from 'react';
2
2
  import { Store } from './use_store.js';
3
3
 
4
4
  type CreateRender<TState> = (selector: (state: TState) => ReactNode, compare?: (a: TState, b: TState) => boolean) => ReactNode;
5
- declare function createRender<TState>(store: Store<TState>): CreateRender<TState>;
5
+ declare function createRender<TState>(store: Store<TState>, selector: (state: TState) => ReactNode, compare?: (a: TState, b: TState) => boolean): React__default.JSX.Element;
6
+ declare function createRenderWithStore<TState>(store: Store<TState>): CreateRender<TState>;
6
7
 
7
- export { type CreateRender, createRender };
8
+ export { type CreateRender, createRender, createRenderWithStore };
@@ -30,7 +30,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/create_render.tsx
31
31
  var create_render_exports = {};
32
32
  __export(create_render_exports, {
33
- createRender: () => createRender
33
+ createRender: () => createRender,
34
+ createRenderWithStore: () => createRenderWithStore
34
35
  });
35
36
  module.exports = __toCommonJS(create_render_exports);
36
37
 
@@ -63,7 +64,15 @@ function useSelector(store, selector, compare = (a, b) => a === b) {
63
64
 
64
65
  // src/create_render.tsx
65
66
  var import_react3 = __toESM(require("react"));
66
- function createRender(store) {
67
+ function createRender(store, selector, compare) {
68
+ function Component() {
69
+ const result = useSelector(store, (state) => state, compare);
70
+ const content = selector(result);
71
+ return /* @__PURE__ */ import_react3.default.createElement(import_react3.default.Fragment, null, content);
72
+ }
73
+ return /* @__PURE__ */ import_react3.default.createElement(Component, null);
74
+ }
75
+ function createRenderWithStore(store) {
67
76
  return function createRender2(selector, compare) {
68
77
  function Component() {
69
78
  const result = useSelector(store, (state) => state, compare);
@@ -75,5 +84,6 @@ function createRender(store) {
75
84
  }
76
85
  // Annotate the CommonJS export names for ESM import in node:
77
86
  0 && (module.exports = {
78
- createRender
87
+ createRender,
88
+ createRenderWithStore
79
89
  });
@@ -27,7 +27,15 @@ function useSelector(store, selector, compare = (a, b) => a === b) {
27
27
 
28
28
  // src/create_render.tsx
29
29
  import React from "react";
30
- function createRender(store) {
30
+ function createRender(store, selector, compare) {
31
+ function Component() {
32
+ const result = useSelector(store, (state) => state, compare);
33
+ const content = selector(result);
34
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, content);
35
+ }
36
+ return /* @__PURE__ */ React.createElement(Component, null);
37
+ }
38
+ function createRenderWithStore(store) {
31
39
  return function createRender2(selector, compare) {
32
40
  function Component() {
33
41
  const result = useSelector(store, (state) => state, compare);
@@ -38,5 +46,6 @@ function createRender(store) {
38
46
  };
39
47
  }
40
48
  export {
41
- createRender
49
+ createRender,
50
+ createRenderWithStore
42
51
  };
package/dist/index.d.mts CHANGED
@@ -1,8 +1,10 @@
1
1
  export { Store, StoreSubscriber, useStore } from './use_store.mjs';
2
2
  export { useSelector } from './use_selector.mjs';
3
3
  export { useSubscribe } from './use_subscribe.mjs';
4
- export { Input, Select, StoreInputPropsWithStore, Textarea, useStoreInput } from './use_store_input.mjs';
5
- export { useStoreInputProps } from './use_store_input_props.mjs';
4
+ export { StoreControllerProps, useStoreController } from './use_store_controller.mjs';
5
+ export { useStoreInputWithName } from './use_store_input_with_name.mjs';
6
+ export { StoreInputProps, useStoreInput } from './use_store_input.mjs';
7
+ export { Input, Select, StoreComponentProps, StoreComponentPropsWithStore, Textarea, useStoreComponent } from './use_store_component.mjs';
6
8
  export { FormStore, useFormStore } from './use_form_store.mjs';
7
9
  export { deserialize, serialize } from './serialize.mjs';
8
10
  import 'react';
package/dist/index.d.ts CHANGED
@@ -1,8 +1,10 @@
1
1
  export { Store, StoreSubscriber, useStore } from './use_store.js';
2
2
  export { useSelector } from './use_selector.js';
3
3
  export { useSubscribe } from './use_subscribe.js';
4
- export { Input, Select, StoreInputPropsWithStore, Textarea, useStoreInput } from './use_store_input.js';
5
- export { useStoreInputProps } from './use_store_input_props.js';
4
+ export { StoreControllerProps, useStoreController } from './use_store_controller.js';
5
+ export { useStoreInputWithName } from './use_store_input_with_name.js';
6
+ export { StoreInputProps, useStoreInput } from './use_store_input.js';
7
+ export { Input, Select, StoreComponentProps, StoreComponentPropsWithStore, Textarea, useStoreComponent } from './use_store_component.js';
6
8
  export { FormStore, useFormStore } from './use_form_store.js';
7
9
  export { deserialize, serialize } from './serialize.js';
8
10
  import 'react';
package/dist/index.js CHANGED
@@ -38,8 +38,10 @@ __export(index_exports, {
38
38
  useFormStore: () => useFormStore,
39
39
  useSelector: () => useSelector,
40
40
  useStore: () => useStore,
41
+ useStoreComponent: () => useStoreComponent,
42
+ useStoreController: () => useStoreController,
41
43
  useStoreInput: () => useStoreInput,
42
- useStoreInputProps: () => useStoreInputProps,
44
+ useStoreInputWithName: () => useStoreInputWithName,
43
45
  useSubscribe: () => useSubscribe
44
46
  });
45
47
  module.exports = __toCommonJS(index_exports);
@@ -115,14 +117,52 @@ function useSelector(store, selector, compare = (a, b) => a === b) {
115
117
  return state;
116
118
  }
117
119
 
120
+ // src/use_store_controller.tsx
121
+ var import_react4 = require("react");
122
+ function useStoreController(store, props) {
123
+ const ref = (0, import_react4.useRef)(null);
124
+ const dispatchKey = (0, import_react4.useId)();
125
+ (0, import_react4.useEffect)(() => {
126
+ const element = ref.current;
127
+ if (!element) {
128
+ return;
129
+ }
130
+ if (typeof props.ref === "function") {
131
+ props.ref(element);
132
+ } else if (props.ref) {
133
+ props.ref.current = element;
134
+ }
135
+ }, []);
136
+ useSubscribe(store, (state, key) => {
137
+ if (key === dispatchKey) {
138
+ return;
139
+ }
140
+ if (!ref.current) return;
141
+ props.onSubscribe(state, ref.current);
142
+ });
143
+ const onChange = () => {
144
+ store.dispatch(
145
+ (state) => {
146
+ const element = ref.current;
147
+ if (!element) return;
148
+ props.onDispatch(state, element);
149
+ },
150
+ {
151
+ key: dispatchKey
152
+ }
153
+ );
154
+ };
155
+ return {
156
+ dispatchKey,
157
+ ref,
158
+ onChange
159
+ };
160
+ }
161
+
118
162
  // src/use_store_input.tsx
119
163
  var import_react5 = require("react");
120
- var import_react6 = __toESM(require("react"));
121
-
122
- // src/use_store_input_props.tsx
123
- var import_react4 = require("react");
124
164
  var import_date_fns = require("date-fns");
125
- function useStoreInputProps(store, props) {
165
+ function useStoreInput(store, props) {
126
166
  const toInputValue = (value) => {
127
167
  if (value === void 0 || value === null) {
128
168
  return "";
@@ -144,7 +184,7 @@ function useStoreInputProps(store, props) {
144
184
  if (props.type === "checkbox" || props.type === "radio") {
145
185
  return void 0;
146
186
  }
147
- return toInputValue(store.state[props.name]);
187
+ return toInputValue(props.getter(store.state));
148
188
  };
149
189
  const toInputChecked = (value) => {
150
190
  if (props.type === "radio") {
@@ -159,98 +199,93 @@ function useStoreInputProps(store, props) {
159
199
  if (props.type !== "checkbox" && props.type !== "radio") {
160
200
  return void 0;
161
201
  }
162
- return toInputChecked(store.state[props.name]);
202
+ return toInputChecked(props.getter(store.state));
163
203
  };
164
- function useSubscriptionRef() {
165
- const ref2 = (0, import_react4.useRef)(null);
166
- (0, import_react4.useEffect)(() => {
167
- return store.subscribe((state, key) => {
168
- if (key === dispatchKey) {
169
- return;
170
- }
171
- const input = ref2.current;
172
- if (!input) {
173
- return;
174
- }
175
- if (props.type === "checkbox" || props.type === "radio") {
176
- const checked = toInputChecked(state[props.name]);
177
- if (input.checked === checked) {
178
- return;
179
- }
180
- input.checked = checked;
181
- } else {
182
- const value = toInputValue(state[props.name]);
183
- if (input.value === value) {
184
- return;
185
- }
186
- input.value = value;
187
- }
188
- const event = new Event("input", { bubbles: true });
189
- input.dispatchEvent(event);
190
- });
191
- }, []);
192
- return ref2;
193
- }
194
204
  function toStateValue(value) {
195
- if (typeof store.state[props.name] === "number") {
205
+ const selected = props.getter(store.state);
206
+ if (typeof selected === "number") {
196
207
  return Number(value);
197
208
  }
198
- if (store.state[props.name] instanceof Date) {
209
+ if (selected instanceof Date) {
199
210
  return new Date(value);
200
211
  }
201
212
  return value;
202
213
  }
203
- function createChangeEventHandler() {
204
- return (e) => {
205
- const target = e.target;
206
- if (props.type === "checkbox") {
207
- store.dispatch((state) => {
208
- state[props.name] = target.checked;
209
- }, {
210
- key: dispatchKey
211
- });
214
+ const inputProps = useStoreController(store, {
215
+ ref: props.ref,
216
+ onSubscribe: (state, element) => {
217
+ if ("checked" in element && (props.type === "checkbox" || props.type === "radio")) {
218
+ const checked = toInputChecked(props.getter(state));
219
+ if (element.checked === checked) {
220
+ return;
221
+ }
222
+ element.checked = checked;
212
223
  } else {
213
- store.dispatch((state) => {
214
- state[props.name] = toStateValue(target.value);
215
- }, {
216
- key: dispatchKey
217
- });
224
+ const value = toInputValue(props.getter(state));
225
+ if (element.value === value) {
226
+ return;
227
+ }
228
+ element.value = value;
218
229
  }
219
- props.onChange?.(e);
220
- };
221
- }
222
- const dispatchKey = (0, import_react4.useId)();
223
- const ref = useSubscriptionRef();
224
- const name = String(props.name);
225
- const defaultValue = getDefaultValue();
226
- const defaultChecked = getDefaultChecked();
227
- const onChange = createChangeEventHandler();
230
+ const event = new Event("input", { bubbles: true });
231
+ element.dispatchEvent(event);
232
+ },
233
+ onDispatch: (state, element) => {
234
+ if ("checked" in element && props.type === "checkbox") {
235
+ props.setter(state, element.checked);
236
+ } else {
237
+ props.setter(state, toStateValue(element.value));
238
+ }
239
+ }
240
+ });
228
241
  return {
229
- key: dispatchKey,
230
- ref,
231
- name,
232
- defaultValue,
233
- defaultChecked,
234
- onChange
242
+ ref: inputProps.ref,
243
+ dispatchKey: inputProps.dispatchKey,
244
+ defaultValue: getDefaultValue(),
245
+ defaultChecked: getDefaultChecked(),
246
+ onChange: (event) => {
247
+ inputProps.onChange();
248
+ props.onChange?.(event);
249
+ }
235
250
  };
236
251
  }
237
252
 
238
- // src/use_store_input.tsx
239
- function useStoreInput(store) {
240
- const input = (0, import_react5.useCallback)(
241
- function Component(props) {
242
- return /* @__PURE__ */ import_react6.default.createElement(Input, { store, ...props });
243
- },
244
- []
245
- );
246
- const select = (0, import_react5.useCallback)(
247
- function Component(props) {
248
- return /* @__PURE__ */ import_react6.default.createElement(Select, { store, ...props });
253
+ // src/use_store_input_with_name.tsx
254
+ function useStoreInputWithName(store, props) {
255
+ const inputProps = useStoreInput(store, {
256
+ ...props,
257
+ getter: (state) => {
258
+ if (props.getter) {
259
+ return props.getter(state);
260
+ }
261
+ return state[props.name];
249
262
  },
250
- []
251
- );
252
- const textarea = (0, import_react5.useCallback)(function Component(props) {
253
- return /* @__PURE__ */ import_react6.default.createElement(Textarea, { store, ...props });
263
+ setter: (state, value) => {
264
+ if (props.setter) {
265
+ props.setter(state, value);
266
+ return;
267
+ }
268
+ state[props.name] = value;
269
+ }
270
+ });
271
+ return {
272
+ ...inputProps,
273
+ name: "name" in props ? String(props.name) : void 0
274
+ };
275
+ }
276
+
277
+ // src/use_store_component.tsx
278
+ var import_react6 = require("react");
279
+ var import_react7 = __toESM(require("react"));
280
+ function useStoreComponent(store) {
281
+ const input = (0, import_react6.useCallback)(function Component(props) {
282
+ return /* @__PURE__ */ import_react7.default.createElement(Input, { store, ...props });
283
+ }, []);
284
+ const select = (0, import_react6.useCallback)(function Component(props) {
285
+ return /* @__PURE__ */ import_react7.default.createElement(Select, { store, ...props });
286
+ }, []);
287
+ const textarea = (0, import_react6.useCallback)(function Component(props) {
288
+ return /* @__PURE__ */ import_react7.default.createElement(Textarea, { store, ...props });
254
289
  }, []);
255
290
  return {
256
291
  input,
@@ -258,44 +293,53 @@ function useStoreInput(store) {
258
293
  textarea
259
294
  };
260
295
  }
261
- function Input({ store, ...props }) {
262
- const storeProps = useStoreInputProps(store, props);
263
- return /* @__PURE__ */ import_react6.default.createElement("input", { ...props, ...storeProps });
296
+ function Input({
297
+ store,
298
+ ...props
299
+ }) {
300
+ const storeProps = useStoreInputWithName(store, props);
301
+ return /* @__PURE__ */ import_react7.default.createElement("input", { ...props, ...storeProps });
264
302
  }
265
- function Select({ store, ...props }) {
266
- const storeProps = useStoreInputProps(store, props);
267
- return /* @__PURE__ */ import_react6.default.createElement("select", { ...props, ...storeProps });
303
+ function Select({
304
+ store,
305
+ ...props
306
+ }) {
307
+ const storeProps = useStoreInputWithName(store, props);
308
+ return /* @__PURE__ */ import_react7.default.createElement("select", { ...props, ...storeProps });
268
309
  }
269
- function Textarea({ store, ...props }) {
270
- const storeProps = useStoreInputProps(store, props);
271
- return /* @__PURE__ */ import_react6.default.createElement("textarea", { ...props, ...storeProps });
310
+ function Textarea({
311
+ store,
312
+ ...props
313
+ }) {
314
+ const storeProps = useStoreInputWithName(store, props);
315
+ return /* @__PURE__ */ import_react7.default.createElement("textarea", { ...props, ...storeProps });
272
316
  }
273
317
 
274
318
  // src/create_render.tsx
275
- var import_react7 = __toESM(require("react"));
276
- function createRender(store) {
277
- return function createRender2(selector, compare) {
319
+ var import_react8 = __toESM(require("react"));
320
+ function createRenderWithStore(store) {
321
+ return function createRender(selector, compare) {
278
322
  function Component() {
279
323
  const result = useSelector(store, (state) => state, compare);
280
324
  const content = selector(result);
281
- return /* @__PURE__ */ import_react7.default.createElement(import_react7.default.Fragment, null, content);
325
+ return /* @__PURE__ */ import_react8.default.createElement(import_react8.default.Fragment, null, content);
282
326
  }
283
- return /* @__PURE__ */ import_react7.default.createElement(Component, null);
327
+ return /* @__PURE__ */ import_react8.default.createElement(Component, null);
284
328
  };
285
329
  }
286
330
 
287
331
  // src/use_form_store.tsx
288
332
  function useFormStore(initialState) {
289
333
  const store = useStore(initialState);
290
- const storeInput = useStoreInput(store);
334
+ const storeComponent = useStoreComponent(store);
291
335
  return {
292
336
  get state() {
293
337
  return store.state;
294
338
  },
295
339
  dispatch: store.dispatch,
296
340
  subscribe: store.subscribe,
297
- render: createRender(store),
298
- ...storeInput
341
+ render: createRenderWithStore(store),
342
+ ...storeComponent
299
343
  };
300
344
  }
301
345
 
@@ -395,7 +439,9 @@ function deserialize(data) {
395
439
  useFormStore,
396
440
  useSelector,
397
441
  useStore,
442
+ useStoreComponent,
443
+ useStoreController,
398
444
  useStoreInput,
399
- useStoreInputProps,
445
+ useStoreInputWithName,
400
446
  useSubscribe
401
447
  });