react-store-input 0.1.1 → 0.1.4

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
@@ -14,29 +14,29 @@ This is a simple example of how to use this package.
14
14
  import { useFormStore } from "dn-react-input";
15
15
 
16
16
  export default function App() {
17
- const store = useFormStore({
18
- email: "",
19
- password: "",
20
- });
21
-
22
- const submit = async () => {
23
- const { email, password } = store.state;
24
-
25
- alert(`Email: ${email}\nPassword: ${password}`);
26
- };
27
-
28
- return (
29
- <form
30
- onSubmit={(e) => {
31
- e.preventDefault();
32
- submit();
33
- }}
34
- >
35
- <store.input name="email" type="email" />
36
- <store.input name="password" type="password" />
37
- <button type="submit">Submit</button>
38
- </form>
39
- );
17
+ const store = useFormStore({
18
+ email: "",
19
+ password: "",
20
+ });
21
+
22
+ const submit = async () => {
23
+ const { email, password } = store.state;
24
+
25
+ alert(`Email: ${email}\nPassword: ${password}`);
26
+ };
27
+
28
+ return (
29
+ <form
30
+ onSubmit={(e) => {
31
+ e.preventDefault();
32
+ submit();
33
+ }}
34
+ >
35
+ <store.input name="email" type="email" />
36
+ <store.input name="password" type="password" />
37
+ <button type="submit">Submit</button>
38
+ </form>
39
+ );
40
40
  }
41
41
  ```
42
42
 
@@ -96,20 +96,20 @@ function Component() {
96
96
  }
97
97
  ```
98
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.
99
+ If you want to avoid passing the store to each input component, use `useStoreComponent`. This hook provides input components that are already connected to the store.
100
100
 
101
101
  ```tsx
102
- import { useStoreInput } from "dn-react-input";
102
+ import { useStoreComponent } from "dn-react-input";
103
103
 
104
104
  function Component() {
105
105
  ...
106
- const Input = useStoreInput(store);
106
+ const component = useStoreComponent(store);
107
107
 
108
108
  return (
109
109
  <form>
110
- <Input.input name="email" type="email" />
111
- <Input.input name="password" type="password" />
112
- <Input.input name="rememberMe" type="checkbox" />
110
+ <component.input name="email" type="email" />
111
+ <component.input name="password" type="password" />
112
+ <component.input name="rememberMe" type="checkbox" />
113
113
  </form>
114
114
  );
115
115
  }
@@ -169,6 +169,28 @@ function Component() {
169
169
  }
170
170
  ```
171
171
 
172
+ `Store.render` is a shortcut for `createRender` when you use `useFormStore`.
173
+
174
+ ```tsx
175
+ function Component() {
176
+ const store = useFormStore({
177
+ email: "",
178
+ password: "",
179
+ });
180
+
181
+ return (
182
+ <div>
183
+ {store.render((state) => (
184
+ <p>{state.email}</p>
185
+ ))}
186
+ {store.render((state) => (
187
+ <p>{state.password}</p>
188
+ ))}
189
+ </div>
190
+ );
191
+ }
192
+ ```
193
+
172
194
  ## How to subscribe to state changes?
173
195
 
174
196
  You can subscribe to state changes using the `subscribe` method of the store.
@@ -220,3 +242,44 @@ function Component() {
220
242
  return <button onClick={updateEmail}>Update Email</button>;
221
243
  }
222
244
  ```
245
+
246
+ ## How to create custom input components?
247
+
248
+ You can create custom input components using the `useStoreInput` hook. This hook provides the necessary props to connect your custom input component to the store: `name`, `value`, `defaultValue`, `defaultChecked`, `onChange`, and `ref` which already subscribed to the store.
249
+
250
+ ```tsx
251
+ import { useStoreInput } from "dn-react-input";
252
+
253
+ function CustomInput({ store }: { store: Store<{ email: string }> }) {
254
+ const inputProps = useStoreInput(store, {
255
+ name: "email",
256
+ });
257
+
258
+ return <input {...inputProps} />;
259
+ }
260
+ ```
261
+
262
+ ## How to creatre custom controller components?
263
+
264
+ If your custom component is not an html input element, you can use the `useStoreController` hook. This hook provides the necessary props to connect your custom controller component to the store: `ref`, `onSubscribe`, and `onDispatch`.
265
+
266
+ ```tsx
267
+ import { useStoreController } from "dn-react-input";
268
+
269
+ type State = {
270
+ count: number;
271
+ };
272
+
273
+ function CustomController({ store }: { store: Store<State> }) {
274
+ const controllerProps = useStoreController<HTMLDivElement, State>(store, {
275
+ onSubscribe: (state, element) => {
276
+ element.textContent = `Count: ${state.count}`;
277
+ },
278
+ onDispatch: (state, element) => {
279
+ state.count += Number(element.textContent.replace("Count: ", ""));
280
+ },
281
+ });
282
+
283
+ return <div {...controllerProps} />;
284
+ }
285
+ ```
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,51 @@ 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
+ ref,
157
+ onChange
158
+ };
159
+ }
160
+
118
161
  // src/use_store_input.tsx
119
162
  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
163
  var import_date_fns = require("date-fns");
125
- function useStoreInputProps(store, props) {
164
+ function useStoreInput(store, props) {
126
165
  const toInputValue = (value) => {
127
166
  if (value === void 0 || value === null) {
128
167
  return "";
@@ -144,7 +183,7 @@ function useStoreInputProps(store, props) {
144
183
  if (props.type === "checkbox" || props.type === "radio") {
145
184
  return void 0;
146
185
  }
147
- return toInputValue(store.state[props.name]);
186
+ return toInputValue(props.getter(store.state));
148
187
  };
149
188
  const toInputChecked = (value) => {
150
189
  if (props.type === "radio") {
@@ -159,103 +198,92 @@ function useStoreInputProps(store, props) {
159
198
  if (props.type !== "checkbox" && props.type !== "radio") {
160
199
  return void 0;
161
200
  }
162
- return toInputChecked(store.state[props.name]);
201
+ return toInputChecked(props.getter(store.state));
163
202
  };
164
- function useSubscriptionRef() {
165
- const ref2 = (0, import_react4.useRef)(null);
166
- (0, import_react4.useEffect)(() => {
167
- const input = ref2.current;
168
- if (!input) {
169
- return;
170
- }
171
- if (typeof props.ref === "function") {
172
- props.ref(input);
173
- } else if (props.ref) {
174
- props.ref.current = input;
175
- }
176
- return store.subscribe((state, key) => {
177
- if (key === dispatchKey) {
178
- return;
179
- }
180
- if (props.type === "checkbox" || props.type === "radio") {
181
- const checked = toInputChecked(state[props.name]);
182
- if (input.checked === checked) {
183
- return;
184
- }
185
- input.checked = checked;
186
- } else {
187
- const value = toInputValue(state[props.name]);
188
- if (input.value === value) {
189
- return;
190
- }
191
- input.value = value;
192
- }
193
- const event = new Event("input", { bubbles: true });
194
- input.dispatchEvent(event);
195
- });
196
- }, []);
197
- return ref2;
198
- }
199
203
  function toStateValue(value) {
200
- if (typeof store.state[props.name] === "number") {
204
+ const selected = props.getter(store.state);
205
+ if (typeof selected === "number") {
201
206
  return Number(value);
202
207
  }
203
- if (store.state[props.name] instanceof Date) {
208
+ if (selected instanceof Date) {
204
209
  return new Date(value);
205
210
  }
206
211
  return value;
207
212
  }
208
- function createChangeEventHandler() {
209
- return (e) => {
210
- const target = e.target;
211
- if (props.type === "checkbox") {
212
- store.dispatch(
213
- (state) => {
214
- state[props.name] = target.checked;
215
- },
216
- {
217
- key: dispatchKey
218
- }
219
- );
213
+ const inputProps = useStoreController(store, {
214
+ ref: props.ref,
215
+ onSubscribe: (state, element) => {
216
+ if ("checked" in element && (props.type === "checkbox" || props.type === "radio")) {
217
+ const checked = toInputChecked(props.getter(state));
218
+ if (element.checked === checked) {
219
+ return;
220
+ }
221
+ element.checked = checked;
220
222
  } else {
221
- store.dispatch(
222
- (state) => {
223
- state[props.name] = toStateValue(target.value);
224
- },
225
- {
226
- key: dispatchKey
227
- }
228
- );
223
+ const value = toInputValue(props.getter(state));
224
+ if (element.value === value) {
225
+ return;
226
+ }
227
+ element.value = value;
229
228
  }
230
- props.onChange?.(e);
231
- };
232
- }
233
- const dispatchKey = (0, import_react4.useId)();
234
- const ref = useSubscriptionRef();
235
- const name = String(props.name);
236
- const defaultValue = getDefaultValue();
237
- const defaultChecked = getDefaultChecked();
238
- const onChange = createChangeEventHandler();
229
+ const event = new Event("input", { bubbles: true });
230
+ element.dispatchEvent(event);
231
+ },
232
+ onDispatch: (state, element) => {
233
+ if ("checked" in element && props.type === "checkbox") {
234
+ props.setter(state, element.checked);
235
+ } else {
236
+ props.setter(state, toStateValue(element.value));
237
+ }
238
+ }
239
+ });
239
240
  return {
240
- key: dispatchKey,
241
- ref,
242
- name,
243
- defaultValue,
244
- defaultChecked,
245
- onChange
241
+ ref: inputProps.ref,
242
+ defaultValue: getDefaultValue(),
243
+ defaultChecked: getDefaultChecked(),
244
+ onChange: (event) => {
245
+ inputProps.onChange();
246
+ props.onChange?.(event);
247
+ }
246
248
  };
247
249
  }
248
250
 
249
- // src/use_store_input.tsx
250
- function useStoreInput(store) {
251
- const input = (0, import_react5.useCallback)(function Component(props) {
252
- return /* @__PURE__ */ import_react6.default.createElement(Input, { store, ...props });
251
+ // src/use_store_input_with_name.tsx
252
+ function useStoreInputWithName(store, props) {
253
+ const inputProps = useStoreInput(store, {
254
+ ...props,
255
+ getter: (state) => {
256
+ if (props.getter) {
257
+ return props.getter(state);
258
+ }
259
+ return state[props.name];
260
+ },
261
+ setter: (state, value) => {
262
+ if (props.setter) {
263
+ props.setter(state, value);
264
+ return;
265
+ }
266
+ state[props.name] = value;
267
+ }
268
+ });
269
+ return {
270
+ ...inputProps,
271
+ name: "name" in props ? String(props.name) : void 0
272
+ };
273
+ }
274
+
275
+ // src/use_store_component.tsx
276
+ var import_react6 = require("react");
277
+ var import_react7 = __toESM(require("react"));
278
+ function useStoreComponent(store) {
279
+ const input = (0, import_react6.useCallback)(function Component(props) {
280
+ return /* @__PURE__ */ import_react7.default.createElement(Input, { store, ...props });
253
281
  }, []);
254
- const select = (0, import_react5.useCallback)(function Component(props) {
255
- return /* @__PURE__ */ import_react6.default.createElement(Select, { store, ...props });
282
+ const select = (0, import_react6.useCallback)(function Component(props) {
283
+ return /* @__PURE__ */ import_react7.default.createElement(Select, { store, ...props });
256
284
  }, []);
257
- const textarea = (0, import_react5.useCallback)(function Component(props) {
258
- return /* @__PURE__ */ import_react6.default.createElement(Textarea, { store, ...props });
285
+ const textarea = (0, import_react6.useCallback)(function Component(props) {
286
+ return /* @__PURE__ */ import_react7.default.createElement(Textarea, { store, ...props });
259
287
  }, []);
260
288
  return {
261
289
  input,
@@ -265,43 +293,49 @@ function useStoreInput(store) {
265
293
  }
266
294
  function Input({
267
295
  store,
296
+ getter,
297
+ setter,
268
298
  ...props
269
299
  }) {
270
- const storeProps = useStoreInputProps(store, props);
271
- return /* @__PURE__ */ import_react6.default.createElement("input", { ...props, ...storeProps });
300
+ const storeProps = useStoreInputWithName(store, { ...props, getter, setter });
301
+ return /* @__PURE__ */ import_react7.default.createElement("input", { ...props, ...storeProps });
272
302
  }
273
303
  function Select({
274
304
  store,
305
+ getter,
306
+ setter,
275
307
  ...props
276
308
  }) {
277
- const storeProps = useStoreInputProps(store, props);
278
- return /* @__PURE__ */ import_react6.default.createElement("select", { ...props, ...storeProps });
309
+ const storeProps = useStoreInputWithName(store, { ...props, getter, setter });
310
+ return /* @__PURE__ */ import_react7.default.createElement("select", { ...props, ...storeProps });
279
311
  }
280
312
  function Textarea({
281
313
  store,
314
+ getter,
315
+ setter,
282
316
  ...props
283
317
  }) {
284
- const storeProps = useStoreInputProps(store, props);
285
- return /* @__PURE__ */ import_react6.default.createElement("textarea", { ...props, ...storeProps });
318
+ const storeProps = useStoreInputWithName(store, { ...props, getter, setter });
319
+ return /* @__PURE__ */ import_react7.default.createElement("textarea", { ...props, ...storeProps });
286
320
  }
287
321
 
288
322
  // src/create_render.tsx
289
- var import_react7 = __toESM(require("react"));
323
+ var import_react8 = __toESM(require("react"));
290
324
  function createRenderWithStore(store) {
291
325
  return function createRender(selector, compare) {
292
326
  function Component() {
293
327
  const result = useSelector(store, (state) => state, compare);
294
328
  const content = selector(result);
295
- return /* @__PURE__ */ import_react7.default.createElement(import_react7.default.Fragment, null, content);
329
+ return /* @__PURE__ */ import_react8.default.createElement(import_react8.default.Fragment, null, content);
296
330
  }
297
- return /* @__PURE__ */ import_react7.default.createElement(Component, null);
331
+ return /* @__PURE__ */ import_react8.default.createElement(Component, null);
298
332
  };
299
333
  }
300
334
 
301
335
  // src/use_form_store.tsx
302
336
  function useFormStore(initialState) {
303
337
  const store = useStore(initialState);
304
- const storeInput = useStoreInput(store);
338
+ const storeComponent = useStoreComponent(store);
305
339
  return {
306
340
  get state() {
307
341
  return store.state;
@@ -309,7 +343,7 @@ function useFormStore(initialState) {
309
343
  dispatch: store.dispatch,
310
344
  subscribe: store.subscribe,
311
345
  render: createRenderWithStore(store),
312
- ...storeInput
346
+ ...storeComponent
313
347
  };
314
348
  }
315
349
 
@@ -409,7 +443,9 @@ function deserialize(data) {
409
443
  useFormStore,
410
444
  useSelector,
411
445
  useStore,
446
+ useStoreComponent,
447
+ useStoreController,
412
448
  useStoreInput,
413
- useStoreInputProps,
449
+ useStoreInputWithName,
414
450
  useSubscribe
415
451
  });