galhavivi / cofi

JavaScript Form Solutions
https://galhavivi.github.io/cofi
Other
2 stars 1 forks source link

How to init state of component when init form? #11

Closed WangLarry closed 3 years ago

WangLarry commented 3 years ago

Is your feature request related to a problem? Please describe. For some components, for example multiselect, it is normal that select's options come from backend server. How to init state(options or items) of these components when init form? I notice that react-editor custom codes do it when init model data.

Describe the solution you'd like Can configure component in model, set initComponentState function, then async call it in beforeInitForm.

WangLarry commented 3 years ago

can init state of component in stateChange function.

     stateChange: (props) => {    

        if (!props.prevState) {
          return new Promise((resolve) => {
               // init state ....
            });
          });
        }

        // no more changes needed
        return undefined;
      },
WangLarry commented 3 years ago

above code can also fix AsyncMultiSelect display 'no options' #47

   AsyncMultiSelect: {
      renderer: AsyncMultiSelect,
      stateChange: (props) => {    
        // if search filter has changed - return object with cleared items and 
        // change isLoading indication to true
        if (!props.prevState || (props.prevState && (props.prevState.searchQuery !== props.state.searchQuery))) {
          return { ...props.state, items: [], isLoading: true };
        }

        // else, if isLoading changes to true,
        // begin search and return promise which resolves an object of: { items: [ ... ], isLoading: false }
        if (props.state.isLoading) {
          return new Promise((resolve) => {
            // mock server call
            mockService.search(props.state.searchQuery || '').then((items) => {
              const viewItems = items.map(x => { return { label: x.name, value: x.id }; });
              resolve({ ...props.state, items: viewItems, isLoading: false }); // items should contain objects that has { label, value }
            });
          });
        }

        // else - no more changes needed
        return undefined;
      },
    },
galhavivi commented 3 years ago

@WangLarry True :) There are more ways, lets share them:

  1. You can wait until all your necessary data for the form configuration return form the server (for example enums items form Dropdowns). In this time you will not render the Form object. When you have all the data - create the form configuration object, pass it to the react Form component and render it.

Upside - you init all your form configuration just one time in a single point - which is easy to track and change. Downside - the user might experience a latency in viewing the UI, so if the server taking time to respond this will not be the best UX experience.

  1. You render the Form component with the partial form configuration (i.e with empty items array for the checkboxes). When you get the respond from the server - create a new model object and pass it to the Form component - this will trigger a new init for the form.

Upside - better UX experience (the user immediately see the ui of the form when the page is loaded). Downside - 2 inits, and the user might be quick enough to do some action on the form which might be overwritten. Also might affect tests as well.

  1. As you mentioned - you can use "stateChange" component to init the component state items - which is also run during the init process. Here is a demo example: https://galhavivi.github.io/cofi/demo-react-form.html#/basics/async-state/html

Upside - better UX experience (the user immediately see the ui of the form when the page is loaded). Also all the component "logic" and configuration remains in the component scope. Downside - if you want a single point in the system which will have all the initial form configuration - this will not serve it. Also - if you have multiple fields components which require initial data from the server - this might cause multiple calls to the server, instead of just one (But actually if your'e being smart you can have a server which fetch you all the components data in one call and cache it. And all the component use it to get the data, and only the first component which called it will cause the actual call to the server to occur. the rest will wait for the response.)

Note: Hooks of Side effects like "beforeInitForm" (before and after actions) are not meant to trigger any change in the form structure. For that we have the "actions" objects. Side effects are for stuff like - logging to the DB, google analytics or other stuff. This is why we don't pass the "actions" object to these hooks.

Choose the best practice for your app :)

WangLarry commented 3 years ago

Also all the component "logic" and configuration remains in the component scope. It's important because we can config form, not write 'custom' codes for every form.

galhavivi commented 3 years ago

@WangLarry True and you can use the same form configuration for multiple forms :) For example if you have the field of "lastUpdated" for multiple forms you can define it only once - in a shared location.

WangLarry commented 3 years ago

I think that performance is considered lately.

  1. This library focus enterprise program
  2. Can use cache to improve performance.
  3. Can use server render ...