Dynamics - React Glue Components

Dynamics - React Glue Components

We saw the use of runAfterSave as a function that set the entity id and entityname on our components using method calls. This approach is not very react oriented which is much more about state being passed down as props from the parent component. While redux and other toolkits suggest injecting state directly into the component is Ok, e.g. an observable changes and the props on a component changes, we can do something a bit simpler.

We'll leave it as an exercise on how to change the runAfterSave into an observable. Here, we will create a simple parent component that passes down entityId and entityName to a single child component when values are available. We'll also create a simple component that provides the Xrm context as a context that any sub-component can access directly.

Dynamics Wrapper

Much like the redux wrapper that provides a store to child components, we can provide Xrm as part of a context. This allows components to use a consistent approach to obtaining Xrm when needed.

// import a definition of Xrm...

export interface Notifier {
  add: (p: {message:string, removeAfter:number, level: string}) => string
  remove: (token: string) => void
}

export interface ErrorHandler {
   // or whatever...
   report: (message: string) => void
}

export interface DynamicsContext {
    Xrm: Xrm | null
    notifier: Notifier
    errorHandler: ErrorHandler
}

export interface DynamicsProps extends Partial<DynamcisContext> {}

class Dynamics<P extends DynamicsProps=DynamicsProps,S={}> extends React.Component<Props, S> {

    constructor(props,context) {
     super(props, context)
    }

    private defaultErrorHandler: ErrorHandler = new ConsoleErrorHandler()
    private defaultNotifier: Notifier = new NotificationManager(() => this.getXrm())

    getChildContext(): DynamicsContext {
        console.log("getChildContext")
        return {
            notifier: this.props.notifier ? (this.props.notifier as Notifier) : this.defaultNotifier,
            Xrm: this.getXrm() || null,
            errorHandler: this.props.errorHandler ? (this.props.errorHandler as ErrorHandler):
                          this.defaultErrorHandler

        }
    }

    /** Get Xrm from the props or the global environment. */
    protected getXrm(): Xrm | null {
        if(typeof this.props.Xrm !== "undefined" && this.props.Xrm !== null)
            return this.props.Xrm as Xrm
        return Utils.getXrm()
    }

    static childContextTypes = {
        notifier: PropTypes.object,
        Xrm: PropTypes.object,
        errorHandler: PropTypes.object
    }

    render() {
        const { children } = this.props;
        return React.Children.only(children)
    }

}

The use of default types in Dynamics allows this to be properly extended or used directly without specifying the property type. Your class could subclass this, but its better to be used as a container. You could also use recompose to make this into something composable using just a function.

You would use this like:

<Fabric>
  <Dynamics>
    <YourComponent
      prop1={}
      prop2={} 
    />
  </Dynamics>
</Fabric>

Then just to access the context within YourComponent, declare the context types as per the react manual setting the type DynamicsContext. Then:

protected method() {
  ...
  this.context.Xrm.Page...
  ...
}

The notifier can be used simply as well:

protected method() {
  this.getData().
  then(list => ... ).
  catch(e => {
     this.context.notifier.addMessage({message:"Fetch failed. Refresh browser", 
       level: "ERROR", removeAfter: 30})
  })
}

Passing EntityId and EntityName Component

We can easily extend Dynamics to include entityid logic and use this when we are working with entities:

class EntityForm<P,S> extends Dynamics<P,S> {

[cut and paste]

}

Your component would just use the standard componentWillReceiveProps to nab the new "entityId" state on its change, for example if you are on a "create" form and the entity is saved, we need to have the "entity" be consumed.

class MyComponent {
    ...
    constructor(props) {
      // store initial value in state if needed...
      this.state = { entityId: props.entityId || null }
    }

    // Grab entityId for the state if case you need it for your state
    // you may not need it in your state.
    public componentWillReceiveProps(nextProps,nextContext) {
      if(nextProps.entityId !== this.state.entityId) {
          this.setState({entityId: nextProps.entityId})
          // fetch some data and update data state based on the entityId and entityName
          // ... 
    }

    render() {
        // You could use props or state here.
        if(!this.props.entityId) return (<div>No id!</div>)
        return(<div>{this.props.entityId}</
    }
}

Simple Notifier

We mentioned the notifier above. Since we have a react component, we can maintain the state of a notification in our component state then remove the message after a certain time...or do other tricks.

...

Last updated