anu-verzum 1.2.0 → 1.4.0

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
@@ -171,7 +171,10 @@ npm run format # Format all source files with Prettier
171
171
  <a href="#avoiding-unnecessary-wrapper-elements">Avoiding unnecessary wrapper elements</a>
172
172
  </li>
173
173
  <li>
174
- <a href="#refs">Refs</a>
174
+ <a href="#list-keys">Keys for dynamic lists</a>
175
+ </li>
176
+ <li>
177
+ <a href="#refs">The refs</a>
175
178
  </li>
176
179
  <li>
177
180
  <a href="#rendering-application">Rendering application</a>
@@ -546,14 +549,14 @@ It takes <strong>ONE</strong> argument which can either be:
546
549
  // Explicit form:
547
550
  const ElemList = ({ somethingToLoop }: ListProps): AnuElement => (
548
551
  <Anu.Fragment>
549
- {somethingToLoop.map(prop => <li>{prop}</li>)}
552
+ {somethingToLoop.map((prop, i) => <li key={`list-item-${i}`}>{prop}</li>)}
550
553
  </Anu.Fragment>
551
554
  );
552
555
 
553
556
  // Shorthand form — identical result:
554
557
  const ElemList = ({ somethingToLoop }: ListProps): AnuElement => (
555
558
  <>
556
- {somethingToLoop.map(prop => <li>{prop}</li>)}
559
+ {somethingToLoop.map((prop, i) => <li key={`list-item-${i}`}>{prop}</li>)}
557
560
  </>
558
561
  );
559
562
 
@@ -571,6 +574,40 @@ It takes <strong>ONE</strong> argument which can either be:
571
574
  };
572
575
  ```
573
576
 
577
+ <h3 id="list-keys">Keys for dynamic lists</h3>
578
+
579
+ - Add a <code>key</code> prop to each element in a dynamically rendered list. Keys allow the reconciler to match elements across renders by identity rather than position, so items that move, are inserted, or are removed do not cause unrelated siblings to lose their state or be recreated unnecessarily.
580
+ - A key must be unique among siblings. Use a stable, unique identifier from your data (such as a database ID). Only fall back to the array index when the list never reorders and items are never inserted in the middle.
581
+
582
+ ```typescript
583
+ interface Item {
584
+ id: string;
585
+ label: string;
586
+ }
587
+
588
+ interface ItemListProps extends Props {
589
+ items: Item[];
590
+ }
591
+
592
+ // Stable IDs — preferred:
593
+ const ItemList = ({ items }: ItemListProps): AnuElement => (
594
+ <ul>
595
+ {items.map(({ id, label }) => (
596
+ <li key={id}>{label}</li>
597
+ ))}
598
+ </ul>
599
+ );
600
+
601
+ // Index fallback — only when order never changes:
602
+ const StaticList = ({ items }: ItemListProps): AnuElement => (
603
+ <ul>
604
+ {items.map(({ label }, i) => (
605
+ <li key={`list-item-${i}`}>{label}</li>
606
+ ))}
607
+ </ul>
608
+ );
609
+ ```
610
+
574
611
  <h3 id="refs">The refs</h3>
575
612
 
576
613
  - In most cases, there is no need to use refs to manage (HTML) sub-components from the parent (class) component.
@@ -665,7 +702,7 @@ when you want to imperatively modify a child outside of the typical dataflow.
665
702
  />
666
703
  <ul>
667
704
  {files.length > 0 && files.map(({ name, file }) => (
668
- <li>
705
+ <li key={name}>
669
706
  <img src={name} alt={`Uploaded file name: ${file.name}, type: ${file.type}, size: ${file.size}`} />
670
707
  </li>
671
708
  ))}
@@ -775,6 +812,7 @@ when you want to imperatively modify a child outside of the typical dataflow.
775
812
  todoList.forEach(task => {
776
813
  todoStatus[task.status].push(
777
814
  <div
815
+ key={task.name}
778
816
  draggable
779
817
  className="todoListItem"
780
818
  onDragStart={event => this.handleDragStart(event, task.name)}
@@ -879,7 +917,7 @@ when you want to imperatively modify a child outside of the typical dataflow.
879
917
  <div className="container">
880
918
  <div style={{ minHeight: '800px' }}>
881
919
  {this.state.photos.map(user => (
882
- <img src={user.url} height="100px" width="200px" />
920
+ <img key={user.url} src={user.url} height="100px" width="200px" />
883
921
  ))}
884
922
  </div>
885
923
  <div ref={this.loadingRef} style={loadingCSS}>
@@ -1205,13 +1243,13 @@ when you want to imperatively modify a child outside of the typical dataflow.
1205
1243
  <h2>Topics</h2>
1206
1244
  <ul>
1207
1245
  {items.map(({ name, slug }) => (
1208
- <li>
1246
+ <li key={slug}>
1209
1247
  <Anu.History.Link to={`${match.url}/${slug}`}>{name}</Anu.History.Link>
1210
1248
  </li>
1211
1249
  ))}
1212
1250
  </ul>
1213
1251
  {items.map(({ name, slug }) => (
1214
- <Anu.History.Route path={`${match.path ?? ''}/${slug}`} render={() => (
1252
+ <Anu.History.Route key={slug} path={`${match.path ?? ''}/${slug}`} render={() => (
1215
1253
  <Topic topicId={name} />
1216
1254
  )} />
1217
1255
  ))}
@@ -1284,6 +1322,30 @@ when you want to imperatively modify a child outside of the typical dataflow.
1284
1322
 
1285
1323
  The <code>Anu.ServerAPI</code> is basically built on top of <code>Promise</code> and currently has 5 methods <code>get()</code>, <code>post()</code>, <code>put()</code>, <code>delete()</code> and <code>file()</code>.
1286
1324
 
1325
+ The <code>.catch()</code> callback receives <code>{ status, response }</code> where <code>response</code> is always <code>null</code> on failure. <code>status</code> is the HTTP status code for server-side errors (e.g. <code>404</code>, <code>500</code>) and <code>0</code> for network-level failures such as no internet connection, DNS error, or a CORS rejection (i.e. no HTTP response was received at all).
1326
+
1327
+ ```typescript
1328
+ Anu
1329
+ .ServerAPI
1330
+ .get<MyItem>('/app/my-server-url/1234')
1331
+ .then(({ response }) => {
1332
+ // response is MyItem | null
1333
+ })
1334
+ .catch(({ status }) => {
1335
+ if (status === 0) {
1336
+ // Network-level failure — no response was received.
1337
+ // Show a "check your connection" message to the user.
1338
+ } else if (status === 401) {
1339
+ // Server responded with 401 Unauthorized — redirect to login.
1340
+ Anu.History.goTo('/login');
1341
+ } else if (status === 404) {
1342
+ // Server responded with 404 Not Found.
1343
+ } else {
1344
+ // Any other HTTP error (403, 500, etc.).
1345
+ }
1346
+ });
1347
+ ```
1348
+
1287
1349
  <h3 id="get-and-delete-methods">The <strong>GET</strong> and <strong>DELETE</strong> HTTP methods</h3>
1288
1350
 
1289
1351
  - The <code>Anu.ServerAPI.get()</code> and <code>Anu.ServerAPI.delete()</code> perform a <strong>GET</strong> or <strong>DELETE</strong> HTTP method respectively to get data from the server or delete a specific data from the server
@@ -1787,12 +1849,12 @@ it should return that part, with the updated desired values:
1787
1849
 
1788
1850
  <h3 id="creating-the-store">Creating the store</h3>
1789
1851
 
1790
- - The store object stores the global state object (that can be reached using the <code>store.getState()</code> methiod) and it also has the <code>store.dispatch()</code>, <code>store.subscribe()</code> and <code>store.unsubscribe()</code> methods.
1791
- You will likely use the <code>store.getState()</code> and <code>store.dispatch()</code> methods only because subscribing and unsubscribing functionalities are "wired" into the <code>&lt;Anu.Connector.Provider /&gt;</code> and the "connedted"
1852
+ - The store object stores the global state object (that can be reached using the <code>store.getState()</code> method) and it also has the <code>store.dispatch()</code>, <code>store.subscribe()</code> and <code>store.unsubscribe()</code> methods.
1853
+ You will likely use the <code>store.getState()</code> and <code>store.dispatch()</code> methods only because subscribing and unsubscribing functionalities are "wired" into the <code>&lt;Anu.Connector.Provider /&gt;</code> and the "connected"
1792
1854
  (also known as "container") component(s) created by using the <code>Anu.Connector.connect()</code> - see <a href="#connector-api">Connecting components to the global state - The Connector API</a> section.
1793
1855
  - Create a store object using <code>Anu.store.createStore()</code>:
1794
1856
  - The first argument is the <code>rootReducer</code> which is always needed (can be either a "single" reducer or a combination of ("single" and/or combined) reducers),
1795
- - The second <code>initialState</code> argument is optional however, if you don't use it, the initial state will be <code>undefined</code>.
1857
+ - The second <code>initialState</code> argument is required. Pass the initial state object for your store here.
1796
1858
  This argument is useful if you want to have initialized values for your application before dispatching your first action.
1797
1859
  - The third argument is optional, you can use it if you want to apply middleware functionalities like dispatching asynchronous actions (e.g. AJAX calls or delayed calls).
1798
1860
  In this case, the built-in <code>Anu.store.middleware.applyMiddleware()</code> can be passed:
@@ -82,11 +82,13 @@ class Subscription {
82
82
  }
83
83
  }
84
84
  removeNestedSub(listener) {
85
- this.tryUnsubscribe();
86
85
  const index = this.listeners.indexOf(listener);
87
86
  if (index >= 0) {
88
87
  this.listeners.splice(index, 1);
89
88
  }
89
+ if (this.listeners.length === 0) {
90
+ this.tryUnsubscribe();
91
+ }
90
92
  }
91
93
  }
92
94
  const connectHOC = (mapStateToProps, mapDispatchToProps) => WrappedComponent => {
@@ -35,6 +35,7 @@ const createContext = context => {
35
35
  providerContext.value = {
36
36
  ...pureProps
37
37
  };
38
+ providerContext.__notifySub = true;
38
39
  this.setState();
39
40
  }
40
41
  }
@@ -56,6 +57,7 @@ const createContext = context => {
56
57
  class ContextConsumer extends _Component.Component {
57
58
  componentDidUpdate() {
58
59
  if (providerContext.__notifySub) {
60
+ providerContext.__notifySub = false;
59
61
  this.setState();
60
62
  }
61
63
  }
@@ -52,7 +52,8 @@ const matchPath = (pathname, options) => {
52
52
  isExact: true
53
53
  };
54
54
  }
55
- const match = new RegExp(`^${path}`).exec(pathname);
55
+ const escapedPath = path.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
56
+ const match = new RegExp(`^${escapedPath}`).exec(pathname);
56
57
  if (!match) {
57
58
  return null;
58
59
  }
@@ -130,11 +131,9 @@ class HistoryLink extends _Component.Component {
130
131
  const {
131
132
  to,
132
133
  children,
134
+ ariaLabel,
133
135
  ...restProps
134
136
  } = this.props;
135
- const {
136
- ariaLabel
137
- } = restProps;
138
137
  return (0, _elements.createElement)('a', {
139
138
  href: to,
140
139
  ariaLabel: `historyLink${ariaLabel ? `-${ariaLabel}` : ''}`,
@@ -1,4 +1,5 @@
1
1
  import { AnuElement, Props } from './elements';
2
+ export declare const getHTMLValidSvgTag: (fiberType: string) => string;
2
3
  export declare const SVG_ELEMENT_LIST: readonly string[];
3
4
  export declare const updateDomProperties: (dom: HTMLElement | SVGElement | Text, prevProps: Props, nextProps: Props, isSvgElement?: boolean) => void;
4
5
  export declare const createDomElement: (fiber: AnuElement & {
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.updateDomProperties = exports.createDomElement = exports.SVG_ELEMENT_LIST = void 0;
6
+ exports.updateDomProperties = exports.getHTMLValidSvgTag = exports.createDomElement = exports.SVG_ELEMENT_LIST = void 0;
7
7
  var _elements = require("./elements");
8
8
  const getHTMLValidSvgTag = fiberType => {
9
9
  switch (fiberType) {
@@ -17,6 +17,7 @@ const getHTMLValidSvgTag = fiberType => {
17
17
  return fiberType;
18
18
  }
19
19
  };
20
+ exports.getHTMLValidSvgTag = getHTMLValidSvgTag;
20
21
  const SVG_ELEMENT_LIST = exports.SVG_ELEMENT_LIST = ['anchor', 'animate', 'animateMotion', 'animateTransform', 'circle', 'clipPath', 'desc', 'discard', 'ellipse', 'feBlend', 'feColorMatrix', 'feComponentTransfer', 'feComposite', 'feConvolveMatrix', 'feDiffuseLighting', 'feDisplacementMap', 'feDistantLight', 'feDropShadow', 'feFlood', 'feFuncA', 'feFuncB', 'feFuncG', 'feFuncR', 'feGaussianBlur', 'feImage', 'feMerge', 'feMergeNode', 'feMorphology', 'feOffset', 'fePointLight', 'feSpecularLighting', 'feSpotLight', 'feTile', 'feTurbulence', 'filter', 'foreignObject', 'g', 'hatch', 'hatchpath', 'image', 'line', 'linearGradient', 'marker', 'mask', 'mesh', 'meshgradient', 'meshpatch', 'meshrow', 'metadata', 'mpath', 'path', 'pattern', 'polygon', 'polyline', 'radialGradient', 'rect', 'set', 'stop', 'svgStyle', 'svg', 'switch', 'symbol', 'text', 'textPath', 'svgTitle', 'tspan', 'unknown', 'use', 'view'];
21
22
  const updateDomProperties = (dom, prevProps, nextProps, isSvgElement = false) => {
22
23
  const isEvent = name => {
@@ -33,13 +34,12 @@ const updateDomProperties = (dom, prevProps, nextProps, isSvgElement = false) =>
33
34
  dom.removeEventListener(eventType, prevProps[name]);
34
35
  });
35
36
  Object.keys(prevProps).filter(isAttribute).filter(isGone(prevProps, nextProps)).forEach(name => {
36
- const el = dom;
37
37
  if (name === 'className') {
38
- el['class'] = null;
38
+ dom.removeAttribute('class');
39
39
  } else if (name === 'htmlFor') {
40
- el['for'] = null;
40
+ dom.removeAttribute('for');
41
41
  } else {
42
- el[name] = null;
42
+ dom.removeAttribute(name);
43
43
  }
44
44
  });
45
45
  Object.keys(nextProps).filter(isAttribute).filter(isNew(prevProps, nextProps)).forEach(name => {
@@ -28,6 +28,9 @@ const resetNextUnitOfWork = () => {
28
28
  if (!update) {
29
29
  return;
30
30
  }
31
+ if (update.from === CLASS_COMPONENT && !update.instance.__fiber) {
32
+ return;
33
+ }
31
34
  if (update.partialState) {
32
35
  update.instance.__fiber.partialState = update.partialState;
33
36
  }
@@ -90,22 +93,20 @@ const updateHostComponent = wipFiber => {
90
93
  const newChildElements = wipFiber.props.children;
91
94
  reconcileChildrenArray(wipFiber, newChildElements);
92
95
  };
93
- const createFunctionComponent = fiber => {
94
- const instance = fiber.type(fiber.props);
95
- instance.__fiber = fiber;
96
- return instance;
97
- };
98
96
  const updateFunctionComponent = wipFiber => {
99
97
  let instance = wipFiber.stateNode || null;
100
98
  if (instance === null) {
101
- instance = wipFiber.stateNode = createFunctionComponent(wipFiber);
99
+ instance = wipFiber.stateNode = {
100
+ props: wipFiber.props,
101
+ render: wipFiber.type
102
+ };
102
103
  } else if (wipFiber.props === instance.props && !wipFiber.partialState) {
103
104
  cloneChildFibers(wipFiber);
104
105
  return;
105
106
  }
106
107
  instance.props = wipFiber.props;
107
- wipFiber.stateNode.render = wipFiber.type;
108
- const newChildElements = wipFiber.stateNode.render(wipFiber.props);
108
+ instance.render = wipFiber.type;
109
+ const newChildElements = instance.render(wipFiber.props);
109
110
  reconcileChildrenArray(wipFiber, newChildElements);
110
111
  };
111
112
  const createInstance = fiber => {
@@ -147,6 +148,9 @@ const updateClassComponent = wipFiber => {
147
148
  } else {
148
149
  nextState = wipFiber.partialStateCallback(instance.state, nextProps);
149
150
  }
151
+ wipFiber.prevState = {
152
+ ...instance.state
153
+ };
150
154
  instance.props = nextProps;
151
155
  instance.state = nextState;
152
156
  wipFiber.partialState = undefined;
@@ -155,62 +159,100 @@ const updateClassComponent = wipFiber => {
155
159
  reconcileChildrenArray(wipFiber, newChildElements);
156
160
  };
157
161
  const arrify = val => !val ? [] : Array.isArray(val) ? val : [val];
162
+ const getTag = element => {
163
+ if (typeof element.type === 'string') {
164
+ return HOST_COMPONENT;
165
+ }
166
+ if (typeof element.type === 'function' && !element.type.isAnuComponent && !element.type.prototype?.render) {
167
+ return FUNCTION_COMPONENT;
168
+ }
169
+ return CLASS_COMPONENT;
170
+ };
158
171
  const reconcileChildrenArray = (wipFiber, newChildElements) => {
159
172
  const elements = arrify(newChildElements);
160
- let index = 0;
173
+ const oldFiberMap = new Map();
161
174
  let oldFiber = wipFiber.alternate ? wipFiber.alternate.child : undefined;
162
- let newFiber;
163
- while (index < elements.length || oldFiber) {
164
- const prevFiber = newFiber;
165
- const element = index < elements.length && elements[index];
166
- const sameType = oldFiber && element && element.type === oldFiber.type;
167
- if (sameType && oldFiber && element) {
175
+ let oldIndex = 0;
176
+ while (oldFiber) {
177
+ const mapKey = oldFiber.props.key ?? oldIndex;
178
+ oldFiberMap.set(mapKey, oldFiber);
179
+ oldFiber = oldFiber.sibling;
180
+ oldIndex++;
181
+ }
182
+ let prevFiber;
183
+ for (let i = 0; i < elements.length; i++) {
184
+ const element = elements[i];
185
+ const mapKey = element.props.key ?? i;
186
+ const matchedOldFiber = oldFiberMap.get(mapKey);
187
+ const sameType = matchedOldFiber !== undefined && element.type === matchedOldFiber.type;
188
+ let newFiber;
189
+ if (sameType && matchedOldFiber) {
168
190
  newFiber = {
169
- type: oldFiber.type,
170
- tag: oldFiber.tag,
171
- stateNode: oldFiber.stateNode,
191
+ type: matchedOldFiber.type,
192
+ tag: matchedOldFiber.tag,
193
+ stateNode: matchedOldFiber.stateNode,
172
194
  props: element.props,
173
195
  parent: wipFiber,
174
- alternate: oldFiber,
175
- partialState: oldFiber.partialState,
196
+ alternate: matchedOldFiber,
197
+ partialState: matchedOldFiber.partialState,
176
198
  effectTag: UPDATE
177
199
  };
178
- if (oldFiber.partialStateCallback) {
179
- newFiber.partialStateCallback = oldFiber.partialStateCallback;
200
+ if (matchedOldFiber.partialStateCallback) {
201
+ newFiber.partialStateCallback = matchedOldFiber.partialStateCallback;
180
202
  }
181
- }
182
- if (element && !sameType) {
183
- let tag;
184
- if (typeof element.type === 'string') {
185
- tag = HOST_COMPONENT;
186
- } else if (typeof element.type === 'function' && !element.type.isAnuComponent && !Object.prototype.hasOwnProperty.call(element.type.prototype, 'render')) {
187
- tag = FUNCTION_COMPONENT;
188
- } else {
189
- tag = CLASS_COMPONENT;
203
+ oldFiberMap.delete(mapKey);
204
+ } else {
205
+ if (matchedOldFiber) {
206
+ matchedOldFiber.effectTag = DELETION;
207
+ wipFiber.effects = wipFiber.effects || [];
208
+ wipFiber.effects.push(matchedOldFiber);
209
+ oldFiberMap.delete(mapKey);
190
210
  }
191
211
  newFiber = {
192
212
  type: element.type,
193
- tag,
213
+ tag: getTag(element),
194
214
  props: element.props,
195
215
  parent: wipFiber,
196
216
  effectTag: PLACEMENT
197
217
  };
198
218
  }
199
- if (oldFiber && !sameType) {
200
- oldFiber.effectTag = DELETION;
201
- wipFiber.effects = wipFiber.effects || [];
202
- wipFiber.effects.push(oldFiber);
203
- }
204
- if (oldFiber) {
205
- oldFiber = oldFiber.sibling;
206
- }
207
- if (index === 0) {
219
+ if (i === 0) {
208
220
  wipFiber.child = newFiber;
209
- } else if (prevFiber && element) {
221
+ } else if (prevFiber) {
210
222
  prevFiber.sibling = newFiber;
211
223
  }
212
- index++;
224
+ prevFiber = newFiber;
225
+ }
226
+ oldFiberMap.forEach(fiber => {
227
+ fiber.effectTag = DELETION;
228
+ wipFiber.effects = wipFiber.effects || [];
229
+ wipFiber.effects.push(fiber);
230
+ });
231
+ };
232
+ const getFirstHostNode = fiber => {
233
+ if (fiber.tag === HOST_COMPONENT) {
234
+ return fiber.stateNode ? fiber.stateNode : null;
235
+ }
236
+ let child = fiber.child;
237
+ while (child) {
238
+ const node = getFirstHostNode(child);
239
+ if (node) {
240
+ return node;
241
+ }
242
+ child = child.sibling;
213
243
  }
244
+ return null;
245
+ };
246
+ const getNextHostNode = (fiber, domParent) => {
247
+ let sibling = fiber.sibling;
248
+ while (sibling) {
249
+ const node = getFirstHostNode(sibling);
250
+ if (node && node.parentNode === domParent) {
251
+ return node;
252
+ }
253
+ sibling = sibling.sibling;
254
+ }
255
+ return null;
214
256
  };
215
257
  const cloneChildFibers = parentFiber => {
216
258
  const oldFiber = parentFiber.alternate;
@@ -268,7 +310,7 @@ const flushComponentLifecyclesQueue = () => {
268
310
  }
269
311
  };
270
312
  const commitAllWork = fiber => {
271
- fiber.effects.forEach((effect, index, effects) => {
313
+ (fiber.effects || []).forEach((effect, index, effects) => {
272
314
  commitWork(effect);
273
315
  if (index === effects.length - 1) {
274
316
  flushComponentLifecyclesQueue();
@@ -289,7 +331,12 @@ const commitWork = effect => {
289
331
  const domParent = domParentFiber.stateNode;
290
332
  if (effect.effectTag === PLACEMENT) {
291
333
  if (effect.tag === HOST_COMPONENT) {
292
- domParent.appendChild(effect.stateNode);
334
+ const nextDomSibling = getNextHostNode(effect, domParent);
335
+ if (nextDomSibling) {
336
+ domParent.insertBefore(effect.stateNode, nextDomSibling);
337
+ } else {
338
+ domParent.appendChild(effect.stateNode);
339
+ }
293
340
  if (effect.props.ref) {
294
341
  effect.props.ref.current = effect.stateNode;
295
342
  }
@@ -304,6 +351,12 @@ const commitWork = effect => {
304
351
  } else if (effect.effectTag === UPDATE) {
305
352
  if (effect && effect.stateNode && effect.alternate && effect.alternate.props && effect.props) {
306
353
  if (effect.tag === HOST_COMPONENT) {
354
+ const nextDomSibling = getNextHostNode(effect, domParent);
355
+ if (nextDomSibling) {
356
+ domParent.insertBefore(effect.stateNode, nextDomSibling);
357
+ } else {
358
+ domParent.appendChild(effect.stateNode);
359
+ }
307
360
  (0, _domUtils.updateDomProperties)(effect.stateNode, effect.alternate.props, effect.props, _domUtils.SVG_ELEMENT_LIST.indexOf(effect.type) > -1);
308
361
  if (effect.props.ref) {
309
362
  effect.props.ref.current = effect.stateNode;
@@ -313,8 +366,8 @@ const commitWork = effect => {
313
366
  componentLifecyclesQueue.push({
314
367
  fn: effect.stateNode.componentDidUpdate,
315
368
  params: {
316
- prevProps: effect.stateNode.props,
317
- prevState: effect.stateNode.state
369
+ prevProps: effect.alternate?.props,
370
+ prevState: effect.prevState
318
371
  }
319
372
  });
320
373
  }
@@ -325,6 +378,7 @@ const commitWork = effect => {
325
378
  if (effect.stateNode.componentWillUnmount) {
326
379
  effect.stateNode.componentWillUnmount();
327
380
  }
381
+ effect.stateNode.__fiber = null;
328
382
  }
329
383
  commitDeletion(effect, domParent);
330
384
  }
@@ -9,6 +9,18 @@ const CLIENT_ERROR_STATUS_CODES = [400, 401, 402, 403, 404, 405, 406, 407, 408,
9
9
  const SERVER_ERROR_STATUS_CODES = [500, 501, 502, 503, 504, 505, 506, 507, 508, 510, 511];
10
10
  const _setXHR = (successHandler, errorHandler) => {
11
11
  const XHR = new XMLHttpRequest();
12
+ XHR.onerror = () => {
13
+ errorHandler({
14
+ status: 0,
15
+ response: null
16
+ });
17
+ };
18
+ XHR.ontimeout = () => {
19
+ errorHandler({
20
+ status: 0,
21
+ response: null
22
+ });
23
+ };
12
24
  XHR.onload = () => {
13
25
  const {
14
26
  status,
@@ -42,7 +54,7 @@ const _serverGetAPI = (url, params = {}) => (successHandler, errorHandler) => {
42
54
  const urlParamKeys = Object.keys(params);
43
55
  if (urlParamKeys.length > 0) {
44
56
  urlParamKeys.forEach((key, index) => {
45
- urlWithParams += index === 0 ? `?${key}=${params[key]}` : `&${key}=${params[key]}`;
57
+ urlWithParams += index === 0 ? `?${encodeURIComponent(key)}=${encodeURIComponent(params[key])}` : `&${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`;
46
58
  });
47
59
  }
48
60
  XHR.open('GET', urlWithParams, true);
@@ -52,11 +64,10 @@ const _serverPostAPI = (url, data) => (successHandler, errorHandler) => {
52
64
  if (!data) {
53
65
  return;
54
66
  }
55
- const urlParams = `data=${JSON.stringify(data)}`;
56
67
  const XHR = _setXHR(successHandler, errorHandler);
57
68
  XHR.open('POST', url, true);
58
- XHR.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
59
- XHR.send(urlParams);
69
+ XHR.setRequestHeader('Content-type', 'application/json; charset=utf-8');
70
+ XHR.send(JSON.stringify(data));
60
71
  };
61
72
  const _serverPutAPI = (url, data) => (successHandler, errorHandler) => {
62
73
  if (!data) {
@@ -73,7 +84,7 @@ const _serverDeleteAPI = (url, params = {}) => (successHandler, errorHandler) =>
73
84
  const urlParamKeys = Object.keys(params);
74
85
  if (urlParamKeys.length > 0) {
75
86
  urlParamKeys.forEach((key, index) => {
76
- urlWithParams += index === 0 ? `?${key}=${params[key]}` : `&${key}=${params[key]}`;
87
+ urlWithParams += index === 0 ? `?${encodeURIComponent(key)}=${encodeURIComponent(params[key])}` : `&${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`;
77
88
  });
78
89
  }
79
90
  XHR.open('DELETE', urlWithParams, true);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "anu-verzum",
3
- "version": "1.2.0",
3
+ "version": "1.4.0",
4
4
  "description": "A \"React-like\" UI library that supports JSX syntax, Redux-like state management, array-rendering, i18n, routing and many more.",
5
5
  "keywords": [
6
6
  "anu-verzum",