• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

repetere / jsonx / 28067088318

24 Jun 2026 12:42AM UTC coverage: 85.483%. Remained the same
28067088318

push

github

web-flow
Merge pull request #1088 from repetere/codex/update-deps-close-issues

fix: support React 19 dependency updates

746 of 947 branches covered (78.78%)

Branch coverage included in aggregate %.

14 of 14 new or added lines in 1 file covered. (100.0%)

25 existing lines in 1 file now uncovered.

785 of 844 relevant lines covered (93.01%)

17.27 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

80.38
/src/components.ts
1
// @spec JSONX-COMP-001 JSONX-COMP-002 JSONX-COMP-003 JSONX-COMP-004 JSONX-COMP-005 JSONX-COMP-006 JSONX-COMP-007 JSONX-COMP-008 JSONX-COMP-009 JSONX-COMP-010 JSONX-COMP-011 JSONX-COMP-012
2
// @intent docs/intent/component-factories/component-factories-specs.md
3
import React, {
5✔
4
  useState,
5
  useEffect,
6
  useContext,
7
  useReducer,
8
  useCallback,
9
  useMemo,
10
  useRef,
11
  useImperativeHandle,
12
  useLayoutEffect,
13
  useDebugValue,
14
  Fragment,
15
  Suspense,
16
  lazy,
17
  createContext,
18
  ReactElement,
19
  FunctionComponent
20
} from "react";
21
import { ReactElementLike } from "prop-types";
22
import * as memoryCache from "memory-cache";
5✔
23
import { useForm, useController, useFieldArray, useWatch, Controller, } from 'react-hook-form';
5✔
24
import { ErrorMessage } from '@hookform/error-message';
5✔
25

26
// import {cache} from 'memory-cache';
27
// import cache from 'memory-cache';
28
//@ts-ignore
29
import { default as ReactDOMElements } from "react-dom-factories";
5✔
30
import { getAdvancedBinding, fetchJSON } from "./utils";
5✔
31
//@ts-ignore
32
import createReactClass from "create-react-class";
5✔
33
import { getReactElementFromJSONX } from ".";
5✔
34

35
import * as defs from "./types/jsonx/index";
36
import { ReactComponentLike } from "prop-types";
37
import { jsonxComponent } from "./types/jsonx/index";
38
declare global {
39
  interface window {
40
    [index: string]: any;
41
  }
42
  interface Window {
43
    [index: string]: any;
44
  }
45
  namespace NodeJS {
46
    interface Global {
47
      document: Document;
48
      window: Window;
49
      navigator: Navigator;
50
    }
51
  }
52
}
53

54
const cache = new memoryCache.Cache();
5✔
55
export const ReactHookForm = { ErrorMessage, Controller }
5✔
56
export const generatedCustomComponents:Map< string, defs.jsonx["jsonxComponents"] | Map<string,defs.jsonx["jsonxComponents"]>> = new Map();
5✔
57
// if (typeof window === 'undefined') {
58
//   var window = window || global.window || {};
59
// }
60

61
//@ts-ignore
62
export let advancedBinding = getAdvancedBinding();
5✔
63
// require;
64
/**
65
 * object of all react components available for JSONX
66
 
67
 */
68
//@ts-ignore
69
export let componentMap = Object.assign(
5✔
70
  { Fragment, Suspense },
71
  ReactDOMElements,
72
  typeof window === "object" && window ? window.__jsonx_custom_elements : {}
15!
73
);
74

75
/**
76
 * getBoundedComponents returns reactComponents with certain elements that have this bounded to select components in the boundedComponents list 
77
 
78
 * @param {Object} options - options for getBoundedComponents 
79
 * @param {Object} options.reactComponents - all react components available for JSONX
80
 * @param {string[]} boundedComponents - list of components to bind JSONX this context (usually helpful for navigation and redux-router)
81
 * @returns {Object} reactComponents object of all react components available for JSONX
82
 */
83
export function getBoundedComponents(
5✔
84
  this: defs.Context,
85
  options: {
×
86
    advancedBinding?: boolean;
87
    reactComponents?: defs.jsonx["jsonxComponents"];
88
    boundedComponents?: string[];
89
  } = {}
90
) {
91
  const { reactComponents, boundedComponents = [] } = options;
6!
92
  if ( 
6✔
93
    (typeof options.advancedBinding === 'boolean' && options.advancedBinding) || (typeof options.advancedBinding==='undefined' && 
17✔
94
    advancedBinding)) {
95
    return Object.assign(
5✔
96
      {},
97
      reactComponents,
98
      boundedComponents.reduce(
99
        (result: defs.jsonx["jsonxComponents"], componentName) => {
100
          result[componentName] = reactComponents[componentName].bind(this);
10✔
101
          return result;
10✔
102
        },
103
        {}
104
      )
105
    );
106
    // reactComponents.ResponsiveLink = ResponsiveLink.bind(this);
107
  } else return reactComponents;
1✔
108
}
109

110
/**
111
 * returns a react component from a component library
112
 
113
 * @param {Object} options - options for getComponentFromLibrary
114
 * @param {Object} [options.componentLibraries={}] - react component library like bootstrap
115
 * @param {Object} [options.jsonx={}] - any valid JSONX JSON
116
 * @returns {function|undefined} react component from react library like bootstrap, material design or bulma
117
 */
118
export function getComponentFromLibrary(
5✔
119
  options: {
1✔
120
    jsonx: defs.jsonx;
121
    componentLibraries?: defs.jsonx["jsonxLibrary"];
122
  } = { jsonx: {} }
123
) {
124
  const { componentLibraries = {}, jsonx = {} } = options;
7!
125
  const libComponent = Object.keys(componentLibraries)
7✔
126
    .map(libraryName => {
127
      //@ts-ignore
128
      const cleanLibraryName = jsonx.component.replace(`${libraryName}.`, "");
10✔
129
      const libraryNameArray = cleanLibraryName.split(".");
10✔
130
      if (
10✔
131
        libraryNameArray.length === 2 &&
19✔
132
        componentLibraries[libraryName] &&
133
        componentLibraries[libraryName][libraryNameArray[0]] &&
134
        typeof componentLibraries[libraryName][libraryNameArray[0]][
135
          libraryNameArray[1]
136
        ] !== "undefined"
137
      ) {
138
        return componentLibraries[libraryName][libraryNameArray[0]][
1✔
139
          libraryNameArray[1]
140
        ];
141
      } else if (
9✔
142
        typeof componentLibraries[libraryName][cleanLibraryName] !== "undefined"
143
      ) {
144
        return componentLibraries[libraryName][cleanLibraryName];
5✔
145
      }
146
    })
147
    .filter(val => val)[0];
10✔
148
  return libComponent;
7✔
149
}
150

151
/**
152
 * returns a react element from jsonx.component
153
 
154
 * @example
155
 * // returns react elements
156
 * getComponentFromMap({jsonx:{component:'div'}})=>div
157
 * getComponentFromMap({jsonx:{component:'MyModal'},reactComponents:{MyModal:MyModal extends React.Component}})=>MyModal
158
 * getComponentFromMap({jsonx:{component:'reactBootstap.nav'},componentLibraries:{reactBootstrap,}})=>reactBootstap.nav
159
 * @param {Object} options - options for getComponentFromMap
160
 * @param {object} [options.jsonx={}] - any valid JSONX JSON object
161
 * @param {Object} [options.reactComponents={}] - react components to render
162
 * @param {Object} [options.componentLibraries={}] - react components to render from another component library like bootstrap or bulma
163
 * @param {function} [options.logError=console.error] - error logging function
164
 * @param {boolean} [options.debug=false] - use debug messages
165
 * @returns {string|function|class} valid react element
166
 */
167
export function getComponentFromMap(
5✔
168
  options: {
1✔
169
    jsonx?: defs.jsonx;
170
    reactComponents?: defs.jsonx["jsonxComponents"];
171
    componentLibraries?: defs.jsonx["jsonxLibrary"];
172
    logError?: any;
173
    debug?: boolean;
174
  } = {}
175
): any {
176
  //ReactElementLike | ReactComponentLike | ReactElement | ReactComponentLike
177
  // eslint-disable-next-line
178
  const {
179
    jsonx = {},
1✔
180
    reactComponents = {},
8✔
181
    componentLibraries = {},
8✔
182
    logError = console.error,
9✔
183
    debug
184
  } = options;
114✔
185

186
  try {
114✔
187
    if (
114✔
188
      typeof jsonx.component !== "string" &&
117✔
189
      typeof jsonx.component === "function"
190
    ) {
191
      return jsonx.component;
1✔
192
      //@ts-ignore
193
    } else if (jsonx.component && ReactDOMElements[jsonx.component]) {
113✔
194
      return jsonx.component;
99✔
195
      //@ts-ignore
196
    } else if (reactComponents[jsonx.component]) {
14✔
197
      //@ts-ignore
198
      return reactComponents[jsonx.component];
5✔
199
    } else if (
9✔
200
      typeof jsonx.component === "string" &&
18✔
201
      jsonx.component.indexOf(".") > 0 &&
202
      getComponentFromLibrary({ jsonx, componentLibraries })
203
    ) {
204
      return getComponentFromLibrary({ jsonx, componentLibraries });
2✔
205
    } else {
206
      throw new ReferenceError(`Invalid React Component (${jsonx.component})`);
7✔
207
    }
208
  } catch (e) {
209
    if (debug) logError(e, (e as Error).stack ? (e as Error).stack : "no stack");
7!
210
    throw e;
7✔
211
  }
212
}
213

214
/**
215
 * Returns a new function from an options object
216
 
217
 * @param {Object} options 
218
 * @param {String} [options.body=''] - Function string body
219
 * @param {String[]} [options.args=[]] - Function arguments
220
 * @returns {Function} 
221
 */
222
export function getFunctionFromEval(options: any = {}) {
5!
223
  if (typeof options === "function") return options;
31✔
224
  const { body = "", args = [], name } = options;
27!
225
  const argus: string[] = [].concat(args);
27✔
226
  argus.push(body);
27✔
227
  const evalFunction = Function.prototype.constructor.apply({ name }, argus);
27✔
228
  if (name) {
27✔
229
    Object.defineProperty(evalFunction, "name", { value: name });
1✔
230
  }
231
  return evalFunction;
27✔
232
}
233

234
function createReactComponentFactory(reactComponentClass: any) {
235
  const reactFactory = function jsonxReactComponentFactory(
8✔
236
    props: any,
237
    ...children: any[]
238
  ) {
239
    return React.createElement(reactComponentClass, props, ...children);
1✔
240
  };
241
  Object.defineProperty(reactFactory, "type", {
8✔
242
    value: reactComponentClass
243
  });
244
  return reactFactory;
8✔
245
}
246

247
/**
248
 * Returns a new React Component
249

250
 * @param {Boolean} [options.returnFactory=true] - returns a React component if true otherwise returns Component Class 
251
 * @param {Object} [options.resources={}] - asyncprops for component
252
 * @param {String} [options.name ] - Component name
253
 * @param {Function} [options.lazy ] - function that resolves {reactComponent,options} to lazy load component for code splitting
254
 * @param {Boolean} [options.use_getState=true] - define getState prop
255
 * @param {Boolean} [options.bindContext=true] - bind class this reference to render function components
256
 * @param {Boolean} [options.passprops ] - pass props to rendered component
257
 * @param {Boolean} [options.passstate] - pass state as props to rendered component
258
 * @param {Object} [reactComponent={}] - an object of functions used for create-react-class
259
 * @param {Object} reactComponent.render.body - Valid JSONX JSON
260
 * @param {String} reactComponent.getDefaultProps.body - return an object for the default props
261
 * @param {String} reactComponent.getInitialState.body - return an object for the default state
262
 * @returns {Function} 
263
 * @see {@link https://reactjs.org/docs/react-without-es6.html} 
264
 */
265
export function getReactClassComponent(
5✔
266
  this: defs.Context,
267
  reactComponent = {},
2✔
268
  options: any = {}
4✔
269
): unknown {
270
  // const util = require('util');
271
  // console.log(util.inspect({ reactComponent },{depth:20}));
272
  // console.log('reactComponent',reactComponent)
273
  if (options.lazy) {
13✔
274
    //@ts-ignore
275
    return lazy(() =>
1✔
UNCOV
276
      options
×
277
        .lazy(reactComponent, Object.assign({}, options, { lazy: false }))
278
        .then((lazyComponent: any) => {
UNCOV
279
          return {
×
280
            //@ts-ignore
281
            default: getReactClassComponent(...lazyComponent)
282
          };
283
        })
284
    );
285
  }
286
  const context: defs.Context = this || {};
12✔
287
  const {
288
    returnFactory = true,
11✔
289
    resources = {},
12✔
290
    use_getState = true,
12✔
291
    bindContext = true,
12✔
292
    disableRenderIndexKey = true
12✔
293
  } = options;
12✔
294
  const rjc: any = {
12✔
295
    //mounting
296
    getDefaultProps: {
297
      body: "return {};"
298
    },
299
    // (unsupported) getDerivedStateFromProps: undefined, // {body:'return null;', args:['props','state',]}
300
    getInitialState: {
301
      body: "return {};"
302
    },
303
    componentDidMount: undefined,
304
    UNSAFE_componentWillMount: undefined,
305
    
306
    //updating
307
    // (unsupported) getDerivedStateFromProps 
308
    shouldComponentUpdate: undefined, // {body:'return true;', args:['nextProps','nextState',]}
309
    getSnapshotBeforeUpdate: undefined, // {body:'return snapshot;', args:['prevProps', 'prevState)',]}
310
    componentDidUpdate: undefined, // {body:'', args:['prevProps', 'prevState','snapshot')',]}
311
    UNSAFE_componentWillUpdate: undefined, // {body:';', args:['nextProps','nextState',]}
312
    UNSAFE_componentWillReceiveProps: undefined, // {body:';', args:['nextProps',]}
313

314
    //unmounting
315
    componentWillUnmount: undefined,
316

317
    //error handling
318
    // (unsupported) componentDidCatch:undefined, // { body:'return ;', args:['error','info'] }
319
    // (unsupported) getDerivedStateFromError: undefined, // {body:' return { hasError:true}', args:['error')',]}
320
    
321
    //body
322
    ...reactComponent
323
  };
324
  const rjcKeys = Object.keys(rjc);
12✔
325
  if (rjcKeys.includes("render") === false) {
12✔
326
    throw new ReferenceError("React components require a render method");
3✔
327
  }
328
  const classOptions = rjcKeys.reduce((result: any, val: string) => {
9✔
329
    if (!rjc[val]) return result;
99✔
330
    if (typeof rjc[val] === "function") rjc[val] = { body: rjc[val] };
35✔
331
    const args = rjc[val].arguments;
35✔
332
    const body = rjc[val].body;
35✔
333
    if (!body) {
35!
UNCOV
334
      console.warn({ rjc });
×
UNCOV
335
      throw new SyntaxError(`Function(${val}) requires a function body`);
×
336
    }
337
    if (
35!
338
      args &&
42!
339
      !Array.isArray(args) &&
340
      args.length &&
341
      args.length &&
UNCOV
342
      args.filter((arg: string) => typeof arg === "string").length
×
343
    ) {
UNCOV
344
      throw new TypeError(
×
345
        `Function(${val}) arguments must be an array or variable names`
346
      );
347
    }
348
    if (val === "render") {
35✔
349
      result[val] = function() {
9✔
350
        //@ts-ignore
351
        if (options.passprops && this && this.props)
1!
UNCOV
352
          body.props = Object.assign({}, body.props, this.props);
×
353
        //@ts-ignore
354
        if (options.passstate && this.state)
1!
355
          body.props = Object.assign({}, body.props, this.state);
×
356
        return getReactElementFromJSONX.call(
1✔
357
          Object.assign(
358
            {},
359
            context,
360
            bindContext ? this : { props: {} },
1!
361
            { disableRenderIndexKey },
362
            {
363
              props:
364
                use_getState && this && this.props
4!
365
                  ? //@ts-ignore
366
                    Object.assign({}, this.props, {
367
                      getState: () => this.state
1✔
368
                    })
369
                  : //@ts-ignore
370
                    this.props
371
            }
372
          ),
373
          body,
374
          resources
375
        );
376
      };
377
    } else {
378
      //@ts-ignore
379
      result[val] =
26✔
380
        typeof body === "function"
26✔
381
          ? body
382
          : getFunctionFromEval({
383
              body,
384
              args
385
            });
386
    }
387

388
    return result;
35✔
389
  }, {});
390
  const reactComponentClass = createReactClass(classOptions);
9✔
391
  if (options.name) {
9✔
392
    Object.defineProperty(reactComponentClass, "name", {
5✔
393
      value: options.name
394
    });
395
  }
396
  const reactClass = returnFactory
9✔
397
    ? createReactComponentFactory(reactComponentClass)
398
    : reactComponentClass;
399
  return reactClass;
9✔
400
}
401

402
/**
403
 * A helper component that allows you to create forms with [react-hook-form](https://react-hook-form.com/) without needed to add external form libraries
404
 * @param this 
405
 * @param props 
406
 */
407
export function FormComponent(
5✔
408
  this: defs.Context,
409
  props: defs.formComponentProps = {}) {
×
410
  function FormComponentFunction(this: defs.Context, componentProps:any){
411
    const {
412
      hookFormOptions = {},
2✔
413
      // formComponent = { component: "div", children: "empty form" },
414
      onSubmit,
415
      formWrapperComponent,
416
      formKey,
417
      formWrapperProps,
418
    } = props;
2✔
419
    const formComponent = {
2✔
420
      component: "div", 
421
      children: "empty form",
422
      ...props.formComponent,
423
    }
424
    formComponent.props = {...formComponent.props,...componentProps};
2✔
425
    // const { register, unregister, errors, watch, handleSubmit, reset, setError, clearError, setValue, getValues, triggerValidation, control, formState, } = useForm(hookFormOptions);
426
    const reactHookForm = useForm(hookFormOptions);
2✔
427
    const context = {
2✔
428
      ...this || {},
2!
429
      ...{ reactHookForm, },
430
    };
431
    if (!context.componentLibraries || !context.componentLibraries.ReactHookForm) {
2!
432
      context.componentLibraries = {
2✔
433
      ...context.componentLibraries,
434
      ...{
435
          ReactHookForm: {
436
            Controller, ErrorMessage,
437
          }
438
        }
439
      }
440
    }
441
    const formWrapperJXM = formWrapperComponent||{
2✔
442
      component: 'form',
443
      props: {
444
        onSubmit: onSubmit ? reactHookForm.handleSubmit(onSubmit) : undefined,
2!
445
        key: formKey ? `formWrapperJXM-${formKey}` : undefined,
2!
446
        ...formWrapperProps,
447
      }
448
    };
449
    formWrapperJXM.children = Array.isArray(formComponent) ? formComponent : [formComponent];
2!
450

451
    const renderJSONX = useMemo(() => getReactElementFromJSONX.bind(context), [
2✔
452
      context
453
    ]);
454
    return renderJSONX(formWrapperJXM)||null;
2!
455
  }
456
  if (props.name) {
4✔
457
    Object.defineProperty(FormComponentFunction, "name", {
2✔
458
      value: props.name
459
    });
460
  }
461
  return FormComponentFunction.bind(this);
4✔
462
}
463

464
/**
465
 * A helper component that allows you to create components that load data and render asynchronously. 
466
 * @param this 
467
 * @param props 
468
 */
469
export function DynamicComponent(
5✔
470
  this: defs.Context,
471
  props: defs.dynamicComponentProps = {}
×
472
) {
473
  function DynamicComponentFunction(this: defs.Context, componentProps:any){
474
    //@ts-ignore
475
    const {
476
      useCache = true,
2✔
477
      cacheTimeout = 60 * 60 * 5,
2✔
478
      loadingJSONX = { component: "div", children: "...Loading" },
2✔
479
      //@ts-ignore
480
      loadingErrorJSONX = {
2✔
481
        component: "div",
482
        children: [
483
          { component: "span", children: "Error: " },
484
          {
485
            component: "span",
486
            resourceprops: { _children: ["error", "message"] }
487
          }
488
        ]
489
      },
490
      cacheTimeoutFunction = () => {},
2✔
UNCOV
491
      transformFunction = (data: any) => data,
✔
492
      fetchURL,
493
      fetchOptions,
494
      fetchFunction
495
    } = props;
2✔
496
    const jsonx = {
2✔
497
      ...props.jsonx,
498
    }
499
    jsonx.props = {...jsonx.props,...componentProps};
2✔
500
    const context = this || {};
2!
501
    const [state, setState] = useState({
2✔
502
      hasLoaded: false,
503
      hasError: false,
504
      resources: {},
505
      error: undefined
506
    });
507
    const transformer = useMemo(() => getFunctionFromEval(transformFunction), [
2✔
508
      transformFunction
509
    ]);
510
    const timeoutFunction = useMemo(
2✔
511
      () => getFunctionFromEval(cacheTimeoutFunction),
2✔
512
      [cacheTimeoutFunction]
513
    );
514
    const renderJSONX = useMemo(() => getReactElementFromJSONX.bind(context), [
2✔
515
      context
516
    ]);
517
    const loadingComponent = useMemo(() => renderJSONX(loadingJSONX), [
2✔
518
      loadingJSONX
519
    ]);
520
    const loadingError = useMemo(
2✔
521
      () => renderJSONX(loadingErrorJSONX, { error: state.error }),
2✔
522
      [loadingErrorJSONX, state.error]
523
    );
524

525
    useEffect(() => {
2✔
526
      async function getData() {
UNCOV
527
        try {
×
528
          //@ts-ignore
529
          let transformedData: unknown;
UNCOV
530
          if (useCache && cache.get(fetchURL)) {
×
UNCOV
531
            transformedData = cache.get(fetchURL);
×
532
          } else {
533
            let fetchedData;
UNCOV
534
            if (fetchFunction) {
×
UNCOV
535
              fetchedData = await fetchFunction(fetchURL, fetchOptions);
×
UNCOV
536
            } else fetchedData = await fetchJSON(fetchURL, fetchOptions);
×
UNCOV
537
            transformedData = await transformer(fetchedData);
×
UNCOV
538
            if (useCache)
×
UNCOV
539
              cache.put(fetchURL, transformedData, cacheTimeout, timeoutFunction);
×
540
          }
541
          //@ts-ignore
UNCOV
542
          setState(prevState =>
×
543
            Object.assign({}, prevState, {
×
544
              hasLoaded: true,
545
              hasError: false,
546
              resources: { DynamicComponentData: transformedData }
547
            })
548
          );
549
        } catch (e) {
550
          if (context.debug) console.warn(e);
×
551
          //@ts-ignore
552
          setState({ hasError: true, error: e });
×
553
        }
554
      }
555
      if (fetchURL) getData();
1!
556
    }, [fetchURL, fetchOptions]);
557
    if (!fetchURL) return null;
2✔
558
    else if (state.hasError) {
1!
UNCOV
559
      return loadingError;
×
560
    } else if (state.hasLoaded === false) {
1!
561
      return loadingComponent;
1✔
UNCOV
562
    } else return renderJSONX(jsonx, state.resources);
×
563
  }
564
  if (props.name) {
2!
565
    Object.defineProperty(DynamicComponentFunction, "name", {
2✔
566
      value: props.name
567
    });
568
  }
569
  return DynamicComponentFunction.bind(this);
2✔
570
}
571

572
/**
573
 * Returns new React Function Component
574
 
575
 * @todo set 'functionprops' to set arguments for function
576
 * @param {*} reactComponent - Valid JSONX to render
577
 * @param {String} functionBody - String of function component body
578
 * @param {String} options.name - Function Component name 
579
 * @returns {Function}
580
 * @see {@link https://reactjs.org/docs/hooks-intro.html}
581
 * @example
582
  const jsonxRender = {
583
   component:'div',
584
   passprops:'true',
585
   children:[ 
586
     {
587
      component:'input',
588
      thisprops:{
589
          value:['count'],
590
        },
591
     },
592
      {
593
        component:'button',
594
       __dangerouslyBindEvalProps:{
595
        onClick:function(count,setCount){
596
          setCount(count+1);
597
          console.log('this is inline',{count,setCount});
598
        },
599
        // onClick:`(function(count,setCount){
600
        //   setCount(count+1)
601
        //   console.log('this is inline',{count,setCount});
602
        // })`,
603
        children:'Click me'
604
      }
605
   ]
606
  };
607
  const functionBody = 'const [count, setCount] = useState(0); const functionprops = {count,setCount};'
608
  const options = { name: IntroHook}
609
  const MyCustomFunctionComponent = jsonx._jsonxComponents.getReactFunctionComponent({jsonxRender, functionBody, options});
610
   */
611
export function getReactFunctionComponent(
5✔
612
  this: defs.Context,
613
  reactComponent = {},
1✔
614
  functionBody: string | defs.functionParam = "",
2✔
615
  options: any = {}
×
616
) {
617
  if (options.lazy) {
20✔
618
    //@ts-ignore
619
    return lazy(() =>
1✔
UNCOV
620
      options
×
621
        .lazy(
622
          reactComponent,
623
          functionBody,
624
          Object.assign({}, options, { lazy: false })
625
        )
626
        .then((lazyComponent: any) => {
UNCOV
627
          return {
×
628
            //@ts-ignore
629
            default: getReactFunctionComponent(...lazyComponent)
630
          };
631
        })
632
    );
633
  }
634
  if (typeof options === "undefined" || typeof options.bind === "undefined")
19✔
635
    options.bind = true;
18✔
636
  const { resources = {}, args = [] } = options;
19✔
637
  //@ts-ignore
638
  const props = Object.assign({}, reactComponent.props);
19✔
639
  const functionArgs = [
19✔
640
    React,
641
    useState,
642
    useEffect,
643
    useContext,
644
    useReducer,
645
    useCallback,
646
    useMemo,
647
    useRef,
648
    useImperativeHandle,
649
    useLayoutEffect,
650
    useDebugValue,
651
    getReactElementFromJSONX,
652
    reactComponent,
653
    resources,
654
    props,
655
    useForm, 
656
    useController, 
657
    useFieldArray, 
658
    useWatch,
659
  ];
660
  //@ts-ignore
661
  if (typeof functionBody === "function")
19✔
662
    functionBody = functionBody.toString();
1✔
663
  
664
  const functionComponent = Function(
19✔
665
    "React",
666
    "useState",
667
    "useEffect",
668
    "useContext",
669
    "useReducer",
670
    "useCallback",
671
    "useMemo",
672
    "useRef",
673
    "useImperativeHandle",
674
    "useLayoutEffect",
675
    "useDebugValue",
676
    "getReactElementFromJSONX",
677
    "reactComponent",
678
    "resources",
679
    "props",
680
    "useForm", 
681
    "useController", 
682
    "useFieldArray", 
683
    "useWatch",
684
    `
685
    'use strict';
686
    const self = this || {};
687

688
    return function ${options.name || "Anonymous"}(props){
21✔
689
      try {
690
        ${functionBody}
691
        if(typeof exposeprops==='undefined' || exposeprops){
692
          reactComponent.props = Object.assign({},props,typeof exposeprops==='undefined'?{}:exposeprops);
693
          if(typeof exposeprops!=='undefined') reactComponent.__functionargs = Object.keys(exposeprops);
694
        } else{
695
          reactComponent.props =  props;
696
        }
697
        if(!props?.children) {
698
        //  delete props.children;
699
        }
700
        const context = ${options.bind ? "Object.assign(self,this||{})" : "this"};
19!
701
        return getReactElementFromJSONX.call(context, reactComponent);
702

703
      } catch(e){
704
        if(self.debug) return e.toString()
705
        else throw e
706
      }
707
    }
708
  `
709
  );
710
  if (options.name) {
18✔
711
    Object.defineProperty(functionComponent, "name", {
16✔
712
      value: options.name
713
    });
714
  }
715
  return options.bind
18!
716
    ? functionComponent.call(this, ...functionArgs)
717
    : functionComponent(...functionArgs);
718
}
719

720
/**
721
 * Returns the string of a function, the difference between calling .toString() on a function definition is, the toString method will return the entire function definition (with paramaters and the name, etc)
722
 * @param {Function} - The function you want the body string for
723
 * @returns {String}
724
 * @example
725
function myFunc(){
726
  const a = 1;
727
  const b = 3;
728
  return a + b;
729
} 
730
getFunctionBody(myFunc) => `
731
  const a = 1;
732
  const b = 3;
733
  return a + b;
734
`
735
 */
736
export function getFunctionBody(func:()=>any){
5✔
737
  const functionString = func.toString()
6✔
738
  if(functionString.includes('return')===false) throw new EvalError('JSONX Function Components can not use implicit returns')
6!
739
  return functionString.substring(
6✔
740
    functionString.indexOf("{") + 1,
741
    functionString.lastIndexOf("}")
742
  )
743
}
744

745
/**
746
 * A helpful function that lets you write a regular JavaScript function and passes the appropriate arguments to getReactFunctionComponent
747
 * @param {Function} func - function definition to turn into React Component Function 
748
 * @property {object} this - options for getReactElementFromJSONX
749
 * @returns {Function} - React Component Function
750
 */
751
export function makeFunctionComponent(
5✔
752
  this: defs.Context,
753
  func:()=>any,
754
  options:any
755
  ){
756
  const scopedEval = eval; 
6✔
757
  const fullFunctionBody = getFunctionBody(func)
6✔
758
  const [functionBody,] = fullFunctionBody.split('return');
6✔
759
  let reactComponentString = fullFunctionBody.replace(functionBody,'').trim()
6✔
760
  const reactComponent = scopedEval(`(()=>{${reactComponentString}})()`);
6✔
761
  const functionOptions = {name:func.name,...options};
6✔
762
  return getReactFunctionComponent.call(this,reactComponent,functionBody,functionOptions)
6✔
763
}
764
/**
765
 *
766
 */
767
export function getReactContext(options: any = {}) {
5!
768
  return createContext(options.value);
1✔
769
}
770

771
/**
772
 * generates react function components from a json definition
773
 * @property {object} this 
774
 * @param customComponent 
775
 * @returns {function} returns react functional component
776
 */
777
export function getCustomFunctionComponent(this: defs.Context, customComponent: Partial<defs.jsonxCustomComponent>): defs.genericComponent{
5✔
778
  const { options, functionBody, functionComponent, jsonxComponent, } = customComponent;
10✔
779
  if (functionComponent) {
10✔
780
    return makeFunctionComponent.call(this,functionComponent,options);
3✔
781
  } else {
782
    return getReactFunctionComponent.call(this,
7✔
783
      jsonxComponent,
784
      functionBody,
785
      options
786
    );
787
  }
788
}
789

790
/**
791
 * returns a cache key of custom components names
792
 * @param customComponents 
793
 * @returns {string} cachekey
794
 */
795
export function getCustomComponentsCacheKey(customComponents:defs.jsonxCustomComponent[]):string{
5✔
796
  return customComponents.map(({name})=>name).join('')
56✔
797
}
798

799
/**
800
 * 
801
 * @param this 
802
 * @param customComponents 
803
 * @returns 
804
 * @example
805
 const customComponents = [
806
   {
807
      type: 'library',
808
      name: 'someLib',
809
      jsonx?: {
810
        Header: {
811
          type:'function',
812
          jsonxComponent: {p:'sample'},
813
          functionBody:'console.log(44)',
814
        },
815
        Footer: {
816
          type:'function',
817
          jsonxComponent: {p:'sample'},
818
          functionBody:'console.log(44)',
819
        }
820
      }
821
   },
822
   {
823
      type: 'component'|'function'|'library';
824
      name: string;
825
      jsonx?: jsonxDefinitionLibrary | jsonx;
826
      jsonxComponent?: jsonx;
827
      options?: {};
828
      functionBody?: (string);
829
      functionComponent?: ((props?:any)=>any);
830
   },
831
  ]
832
 */
833
export function getReactLibrariesAndComponents(this: defs.Context, customComponents: defs.jsonxCustomComponent[]): defs.jsonxLibrariesAndComponents {
5✔
834
  const customComponentsCacheKey = getCustomComponentsCacheKey(customComponents);
13✔
835
  if(generatedCustomComponents.has(customComponentsCacheKey)) return generatedCustomComponents.get(customComponentsCacheKey);
13✔
836
  const cxt = {
2✔
837
    componentLibraries:{},
838
    reactComponents:{},
839
    ...this,
840
  };
841

842
  const customComponentLibraries: defs.jsonxComponentLibraries = {};
2✔
843
  const customReactComponents: defs.jsonxComponent = {};
2✔
844

845
  if (customComponents && customComponents.length) {
2!
846
    customComponents.forEach(customComponent => {
2✔
847
      const { type, name, jsonx, options, functionBody, functionComponent, jsonxComponent, } = customComponent;
8✔
848
      if (type === "library") {
8✔
849
        if (jsonx) {
2!
850
          customComponentLibraries[name] = Object
2✔
851
            .keys(jsonx as defs.jsonxLibrary)
852
            .reduce(
853
            (result: defs.jsonxLibrary, prop: string) => {
854
              const libraryComponent:Partial<defs.jsonxCustomComponent> = (jsonx as defs.jsonxDefinitionLibrary )[prop];
6✔
855
              const {
856
                type,
857
                name,
858
                jsonxComponent,
859
                options,
860
                functionBody
861
              } = libraryComponent;
6✔
862
              if (type === "component") {
6✔
863
                result[name as string] = getReactClassComponent.call(this,
2✔
864
                  jsonxComponent,
865
                  options
866
                ) as defs.genericComponent;
867
              } else {
868
                result[name as string] = getCustomFunctionComponent.call(this, { options, functionBody, functionComponent, jsonxComponent, });
4✔
869
              }
870
              return result;
6✔
871
            },
872
            {}
873
          );
UNCOV
874
        } else customComponentLibraries[name] = window[name];
×
875
        cxt.componentLibraries[name] = customComponentLibraries[name];
2✔
876
      } else if (type === "component") {
6✔
877
        if (jsonx) {
2!
878
          customReactComponents[name] = getReactClassComponent.call(this,
2✔
879
            jsonx,
880
            options
881
          ) as defs.genericComponent;
UNCOV
882
        } else customReactComponents[name] = window[name];
×
883
        cxt.reactComponents[name] = customReactComponents[name];
2✔
884
      } else if (type === "function" ) {
4!
885
        if (functionComponent || functionBody) {
4!
886
          customReactComponents[
4✔
887
            name
888
          ] = getCustomFunctionComponent.call(this, { options, functionBody, functionComponent, jsonxComponent:jsonx, });
UNCOV
889
        } else customReactComponents[name] = window[name];
×
890
        cxt.reactComponents[name] = customReactComponents[name];
4✔
891
      }
892
    });
893
  }
894

895
  generatedCustomComponents.set(customComponentsCacheKey,{
2✔
896
    customComponentLibraries,
897
    customReactComponents
898
  });
899
  return {
2✔
900
    customComponentLibraries,
901
    customReactComponents
902
  };
903
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc