4

I'm working on a spfx app to be used as a SharePoint list form. The app will be used to edit ha a LOT of lookup columns. the lookup columns are rendered as dropdowns in the react component. I am currently fetching the list item to be rendered as well as all the data from all the lookup columns in the render method of the web part, and then passing the item to be edited and the values of all the lookup columns(the complete list) as state to the react component when I call ReactDom.render so that it can render the dropdowns.

This is slow. Its waiting to get all possible values for all columns before rendering the component. I have users in Australia and our tenant is in US-East.

What I want to do is fetch just the item to be edited and render the component. The component should render with the dropdowns having only one value-- the value currently selected on the item to be edited, I can achieve this by using the expands on pnp-js-core. Then, asynchronously, I want to fetch the full set of lookup column values to be passed to the react component and send them down later. That way the page displays quickly, and by the time the user clicks a dropdown, the values will have hopefully been loaded. I see the react component has a componentWillReceiveProps method. How do I pass updated props to the react component from the web part after it has been mounted/rendered?

I can put code it the compnentDidMount, bit that would get ugly (lookup values belong in the state)

Current state:

public render(): void {
    let formProps: ITrFormProps = {
      customers: [], //lookup info
      techSpecs: [], // lookup info
      requestors: [], // lookup info
      tr: new TR(), // the thing i am editing
    };
    let batch = pnp.sp.createBatch();
    get customers using the batch
    get techSpecs using the batch
    get requestors using the batch
    get the data i want to edit using the batch
    batch.execute().then((value) => {
      this.reactElement = React.createElement(TrForm, formProps);
}

Desired state:

public render(): void {
    let formProps: ITrFormProps = {
      customers: [], //lookup info
      techSpecs: [], // lookup info
      requestors: [], // lookup info
      tr: new TR(), // the thing i am editing
    };
    let batch = pnp.sp.createBatch();
    get the data i want to edit using the batch
    batch.execute().then((value) => {
     set the LOOKUPS to be an aray of one element, just the value in the item i am editing
      this.reactElement = React.createElement(TrForm, formProps).then((value) => {
        let batch2 = pnp.sp.createBatch();
        get customers using the batch
        get techSpecs using the batch
        get requestors using the batch
        batch.execute().then((value) => {
            /***** HOW DO I UPDATE THE COMPONET WITH THE NEW STATE***
             }

}

UPDATE: I did get it to work by using this code after the initial item was fetched:

batch.execute().then((value) => {// execute the batch to get the item being edited and info REQUIRED for initial display
  this.reactElement = React.createElement(TrForm, formProps);
  var formComponent: TrForm = ReactDom.render(this.reactElement, this.domElement) as TrForm;//render the component
  let batch2 = pnp.sp.createBatch(); // create a second batch to get the lookup columns
  pnp.sp.web.lists.getByTitle(this.properties.partyListName).items.inBatch(batch2).get()// get the lookup info
    .then((items) => {
      formProps.customers = _.map(items, (item) => {
        return new Customer(item["Id"], item["Title"]);
      });
    })
    .catch((error) => {
      console.log("ERROR, An error occured fetching 'Customers'");
      console.log(error.message);
    });
  batch2.execute().then(() => {
    //  formComponent.props = formProps; this did not work
    formComponent.props.customers = formProps.customers;// set the lookup values on the components props
    formComponent.forceUpdate(); // make it redraw
  });
}
);

This is quite ugly. I have no idea why formComponent.props = formProps did not work. There must be a better way.

1
  • I can have the component request the data for the dropdown after it has rendered by using onComponentDidMount but then the data would reside in the components state, not its props. this will definitely work, but I would rather avoid that approach
    – RussGove
    May 7, 2017 at 15:37

2 Answers 2

0

This is a use case where React takes the programmers approach, not unlike the oldskool jQuery approach of messing with DOM elements.

So you need to program the update of all SELECT OPTIONs yourself with

ReactDOM.render(reactElement, domContainerNode)

maybe more complex code than jQuery; but hey..React does Shadow DOM for you....


end of answer


Can't help you further here.

I ditched React for these types of UI elements, in favor of WebComponents which does Shadow DOM and all goodies native in the Browser... who needs a Framework?

(yes,still requires Polymer in older IE browsers)

React is: Load async data from X then stuff it in DOM elements

WebComponents is: You are an async SELECT, get your options from X

You will ofcourse program an Async Select Class in React,
but it still requires a programmer to re-use it.

With WebComponents it is just another HTML tag:

<select async="X"></select)
0

So after a coupe of weeks of working with it, I think the solution proposed in my UPDATE to the original question above is a good solution. It's actually worked out really well for me.

Say you are editing a list foo, which has a lookup column on list bar. the properties passed to your react component are a single foo object and a list of bar object the user can select from(rendered in a select control).

So you can execute a single pnp-js-core statement to get the foo being edited and expand bar. Usiing the results of that query , you can create an array of bar elements, that has ONLY ONE bar, the one used on the foo being edited. You can pass that foo and the array with only one bar down to your react component and let it render.

Then, after (or while) the component has rendered, do another call to get all the bars. Once you have them , pass them down to the component and call forceUpdate.

formComponent.props.bars= bars;
formComponent.forceUpdate(); // make it redraw

This lets the page get rendered quickly, while you go fetch all the reference data in the background.

An added advantage is if you have bars that may be inactivated at some time but should be allowed to be kept on the foo's they are already associated with;

When you go to select the full list of bar's to be passed down to the component, select only the active ones. Then rather than just passing down the array of active bar's, you can use the underscore 'unique' method to pass down all the active foo's plus the one that's already selected.

If you have just one small reference list, this is probably not worth the effort. But if you have large reference lists, or many reference lists, this is a viable way to render your page quickly, and then fetch all the reference data asynchronously. With multiple reference lists the pnp-js-core batching facility should be used to get all the reference data in a single call.

The other alternative is to not pass the reference data down to your component at all, but instead pass it callback methods it can user to get the reference data(maybe using typeahead).

In my case our tenant is in US Easy, and we have users in Singapore. I have half a dozen reference lists the largest being about 7000 items. I reasoned that in this case it was better to take the hit and download all the reference tables while they were looking at the initial display than to have users wait for data as they hit each dropdown box. Which reminds me....

You can send some reference data down on the initial load and the rest of it later.You can even do it in chuncks. If you have a few columns with only a few choices, and can move them to the top of your form, then send that data down on the initial render.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.