React For Experience Builder Developer Edition

6252
15
11-07-2023 01:56 PM
JeffreyThompson2
MVP Regular Contributor
26 15 6,252

This post is only for people using Developer Edition, if you are on the Online or Enterprise Edition, go away. Go away and be grateful that you do not have to read on, only madness lies ahead.

Hi coders, if you are reading this, you have decided to use Experience Builder Developer Edition. I commend your bravery, if not necessarily your wisdom. Have you used React before? If not, getting started in Developer Edition is going to be a rough ride. My number one tip for anyone booting up Developer Edition for the first time is to stop and spend several days studying React before ever trying to install Developer Edition. And yes, I do mean days. It takes that long to get enough of a grasp on the basics to begin to be able to actually use it. It is different enough from regular javascript that I think it is fair to classify it as an entirely separate language. React operates very differently than any programing language you have used before with the possible exception of Angular or Vue.js, which are other React-like frameworks. Using React is sort of like dancing, if you and your gorgeous bride React are in sync, it will be a beautiful experience and if not, you will have several broken toes and a visit to a divorce attorney.

I cannot teach you React. It is too big a subject and I am not an expert. Here is the official React guide. Start learning here. But I will share what I think are the most important concepts to keep in mind. Some of the concepts in React get a bit circular, but I will try to organize this post the best I can. Just trust that if I throw out a new term, I will get around to explaining it later. I am not sure if it would be better to read this or the official React guide first. Maybe make it a sandwich. Read this. Then, read the official guide and then read this again when it sounds slightly less insane.

React: A History

We will start with a little history lesson. I swear this is relevant. React was invented by Facebook in 2013 and became open-source in 2014. React was invented to solve a problem Facebook had users would scroll through their newsfeed and when they hit the bottom of the page, they might visit another website or even worse, turn off their computers. React was designed to continuously get or send data to or from an API without ever needing to reload the page, so now scroll all you want, you will never see the bottom of your Facebook feed.

Which brings me to the first Experience Builder relevant fact about React, when within an Experience, the page never reloads. You may have multiple pages within your Experience, but React is kind of lying to you, everything is happening in a single page and just showing you different things. This is generally speaking a good thing as it usually results in a faster, smoother user experience, but it may become an issue when combined with a quirk of Experience Builder. Typically on a React page, if you as a user click an X to close a popup, that popup would be destroyed and a new one created if you reopened the popup. But Experience Builder never destroys widgets, even when switching between pages. This can lead to some unexpected behavior, if you do not anticipate it.

Back to the history lesson.

In the beginning, React allowed for components to be made within classes or functions. Using classes had some minor advantages, so they were the preferred method, until 2018 when React Hooks came out. All of the React methods starting with use, like useState or useEffect, are Hooks and Hooks made React so much better and easier. But, Hooks cannot be used in class based components. So, over the next couple of years function based components became the preferred method to write React. Class based components are still 100% valid and functional and the React team swears they will never be removed, but you should consider them depreciated. Always write function based components, if you can. When looking for learning resources, check that they are 2020 or later and if they start talking about classes, move on to something else. The class based React documentation has been buried in the official React docs, but you can find it here.

I'd like to tell you, you can just ignore class based React completely. I'd like to say that, but... look at the dates in that last paragraph, 2018 to 2020. The exact timeframe that Experience Builder was being developed. Much of Experience Builder is in class based components, including most of the widget coding examples provided by ESRI. You may not need to write in class based React, but you will probably need to be able to read it. Sorry. 😢

JSX and the Virtual DOM

When a user loads a React application, the HTML contains only a single node. Everything that happens on the screen is the result of React manipulating what is known as the Virtual DOM. You should not, in standard React, ever attempt to directly manipulate the actual DOM, using methods like innerHTML or appendChild, etc.. Bad things happen to React developers who mess with the DOM.

JSX is a mashup language of HTML and Javascript. All of the HTML elements and React components are valid JSX elements. Every React component must resolve to a single return statement that returns a single JSX node. Like HTML, JSX nodes can contain any number of elements.

A React fragment (it looks like this, <></>) can be used for a component that should not paint something to the screen directly, but will add items through direct DOM manipulation. Direct DOM manipulation should be avoided at all cost, but when using the ESRI Javascript API there is often no way around doing it. Experience Builder 1.13 will be based on the 4.28 version of the API and includes map components that were specifically designed to help deal with this problem. For versions 1.13 or later of Experience Builder, you should never need to directly manipulate the DOM.

State and Re-renders

Components in React frequently re-render. During a re-render a component will clear its memory and forget everything it knows unless a variable is stored in state or a ref. The three triggers for a re-render are a change to state, a change to props, or a re-render higher up in the React tree.

Here is a widget designed to add and remove a layer from a map. It doesn't work quite right. It will add the layer, but it can't remove it.

 

import { React, AllWidgetProps } from 'jimu-core'
import { JimuMapViewComponent, JimuMapView } from 'jimu-arcgis'
import FeatureLayer from 'esri/layers/FeatureLayer'

const { useState } = React

const Widget = (props: AllWidgetProps<any>) => {
  const [jimuMapView, setJimuMapView] = useState<JimuMapView>()
  const layer = new FeatureLayer({
      url: 'URL1'
    })

  const activeViewChangeHandler = (jmv: JimuMapView) => {
    if (jmv) {
      setJimuMapView(jmv)
    }
  }

  const formSubmit = (evt) => {
    evt.preventDefault()
    jimuMapView.view.map.add(layer)
  }
  
const formSubmit3 = (evt) => {
    evt.preventDefault()

    jimuMapView.view.map.remove(layer)
  }
  
  
  return (
    <div className="widget-starter jimu-widget">
      {
        props.useMapWidgetIds &&
        props.useMapWidgetIds.length === 1 && (
          <JimuMapViewComponent
            useMapWidgetId={props.useMapWidgetIds?.[0]}
            onActiveViewChange={activeViewChangeHandler}
          />
        )
      }

      <form onSubmit={formSubmit}>
        <div>
          <button>Add Layer</button>
        </div>
      </form>
	   <form onSubmit={formSubmit3}>
        <div>
          <button>Delete</button>
        </div>
      </form> 
	  
    </div>
  )
}

export default Widget

 

The fundamental problem is that the widget will forget the reference to layer between re-renders, so will not be able to find the layer later to remove it. Here is this widget updated to use state. It works as expected.

 

import { React, AllWidgetProps } from 'jimu-core'
import { JimuMapViewComponent, JimuMapView } from 'jimu-arcgis'
import FeatureLayer from 'esri/layers/FeatureLayer'

const { useState } = React

const Widget = (props: AllWidgetProps<any>) => {
  const [jimuMapView, setJimuMapView] = useState<JimuMapView>()
  const item = new FeatureLayer({
      url: 'URL1'
    })
  const [layer, setLayer] = useState(item)

  const activeViewChangeHandler = (jmv: JimuMapView) => {
    if (jmv) {
      setJimuMapView(jmv)
    }
  }

  const formSubmit = (evt) => {
    evt.preventDefault()
    jimuMapView.view.map.add(layer)
  }
  
const formSubmit3 = (evt) => {
    evt.preventDefault()

    jimuMapView.view.map.remove(layer)
    setLayer(null)
  }
  
  
  return (
    <div className="widget-starter jimu-widget">
      {
        props.useMapWidgetIds &&
        props.useMapWidgetIds.length === 1 && (
          <JimuMapViewComponent
            useMapWidgetId={props.useMapWidgetIds?.[0]}
            onActiveViewChange={activeViewChangeHandler}
          />
        )
      }

      <form onSubmit={formSubmit}>
        <div>
          <button>Add Layer</button>
        </div>
      </form>
	   <form onSubmit={formSubmit3}>
        <div>
          <button>Delete</button>
        </div>
      </form> 
	  
    </div>
  )
}

export default Widget

 

State is not live data. It is a snapshot of data at the last re-render. This can result in all sorts of confusing errors. To make things more confusing, calling useState() does not instantly update state. React cues all of the useState() calls and runs them in order during the re-render.

 

const [num, setNum] = useState(0)
setNum(num + 1) //num would be 1 on the next render.
setNum(num + 5) //Now, num would be 5 on the next render.
setNum(num - 1) //Now, num will be -1 on the next render.
console.log(num) //Output is 0

const [num, setNum] = useState(0)
setNum(num + 1) //num would be 1 on the next render.
setNum(5) //Now, num would be 5 on the next render.
setNum(num - 1) //Now, num will be 4 on the next render.
console.log(num) //Output is 0

 

Confusing, isn't it? Welcome to the wonderful world of React.

React must know what to render at every microsecond. If at any point React does not know what to render, the component will break and display an error message. The most difficult render to handle is often the very first one as it often occurs before the required data is loaded. This is often accomplished with a ternary statement, like the one below. Ternary statements, deconstructing arrays and various ways of testing for truthiness are rarely used features of Javascript that are incredibly important to writing good React.

 

{value ? <MyComponent></MyComponent> : <Loading></Loading>}

 

If you have ever wondered why so many modern websites show a shimmery, data-free outline of the website design before the actual site loads, this is why. (Also, psychological research finds that users that are shown shiny things are more patient with load times and think websites load faster.) The UI StoryBook includes a Loading component and many other useful UI elements.

UseRef()

Other than state, the other way to store data between re-renders is the useRef() Hook. Changes to a ref occur instantly and do not trigger a re-render. It looks something like this.

 

 

const num = useRef(0)
//Use the .current property to access the value of a ref.
console.log(num.current) //Output is 0
num.current = 5
console.log(num.current) //Output is 5

 

UseEffect

UseEffect is the React Hook I use most often in Experience Builder. With useEffect(), you can create a block of code that runs once when the component is mounted, every time a component re-renders, when a specific value or set of values is changed, or when the component is unmounted. But remember that widgets do not fully unmount in Experience Builder, so that last option will not work in the highest level of an Experience Builder widget. Each of these uses of useEffect() has a different syntax. I keep this page bookmarked because I use it all the time to find the proper useEffect() syntax.

Props and One-Way Data Flow

A React application is structured like a tree or at least the part of a tree that is above ground. The stuff closest to the root is referred to as being "high in the React tree" and we get "lower or deeper in the React tree" as we get into sub-components. (I guess people at Facebook don't get outside very often.) Water flows up the trunk, through the branches and out the leaves. Water never goes back down the tree. If a squirrel, starts jumping up and down on a branch, it will (or should) only affect that branch, not the trunk or a totally separate branch. If the whole tree starts shaking when a squirrel jumps on one branch, run away, that tree is about to fall over.

Another common React metaphor is that of children and parents. With the key phrase being, parents are allowed to alter their children, but children are not allowed to alter their parents. (The people at Facebook sound like pretty lousy parents.) 

It's not often shown in the ESRI coding samples, but your widgets can and often should call in sub-components. If you are using any elements from the UI StoryBook, you are already doing this. Adding additional levels and branches within your widgets is often necessary to get your desired outcome. My custom List widget is five levels deep it branches in the middle and comes back together at the lowest level.  Data in React should always* flow from the root, which is much higher in the React tree than anything you should be doing in Experience Builder to the lowest levels, which are your widgets. (Developer Edition comes with a folder called "your-extensions", if you are doing anything outside of that folder, you are probably doing something wrong.) Trying to fight one-directional flow is a React anti-pattern that will infuriate you and probably result in undesired outcomes.

One-way data flow is managed through props. Props must be explicitly passed from a parent component to the next child down in the React tree. The props the child receives are immutable. If the props are changed by the parent, the component will re-render. Children must receive props by passing them as an argument into the component function. It looks something like this.

 

//Passing props down
render(
 <MyComponent myProp={'value'}></MyComponent>
)

//Recieving props
const MyComponent = (props) => {
  console.log(props.myProp) //Output is 'value'
}

 

*Redux Or Breaking One-Way Data Flow

Experience Builder comes packaged with a state manager called Redux. As you read through the React docs, you will encounter a design pattern called "lifting up state".  In lifting up state, we rewrite a set of components containing a parent and two or more children so that state is managed by the parent and passed to the children as props, effectively allowing the children to communicate with each other. State managers take this concept to the logical extreme. They sit at the top of the React tree thus allowing any component to send data up to the state manager and retrieve it in any other component regardless of what branch it is on. ESRI has kindly done all the heavy lifting in setting up Redux, so all you need to know is how to send and receive messages. Note that the messages are received through props, so getting a message will cause the widget to re-render.

 

//To send a message, with a widget id of 'widget_id', use:

//Dispatch- This should be your preferred method, as it is immutiable.
getAppStore().dispatch(appActions.widgetStatePropChange('widget_id','nameOfMessage', message))

//MutableStoreManager- Use this only if you need to send a complex object.
MutableStoreManager.getInstance().updateStateValue('widget_id', 'nameOfMessage', message)

//To read a message

//From Dispatch:
props.stateProps?.nameOfmessage

//From MutableStoreManager:
props.mutableStateProps?.nameOfmessage

 

I think that covers all of the basic information you need to know about React to use Experience Builder Developer Edition and yes, the React iceberg goes much deeper.

Good Luck and May Your Toes Never Be Broken!

15 Comments
AValenski
Occasional Contributor

Great write-up! This is probably more informative and useful than all the Esri-produced trainings that don't cost $1,900.

I think you illustrated a really important point about ExperienceBuilder: it was not designed for the existing GIS community and userbase as it necessitates an understanding of its underlying technology and framework that many users and organizations simply cannot commit to learning, especially when Esri implements significant, breaking changes or forces users to implement and support workarounds while they continue working on this pretty clearly beta product. 

Sometimes it seems that Esri doesn't recognize that many (read: most?) organizations don't have hoards of GIS Developers/Admins/Technical staff available. I've been a GIS consultant for the last six years and worked with hundreds of organizations: most of the time, its one or two folks responsible for system administration, GIS management, web and backend development, database administration, and user-support. The lack of free, legitimately useful/instructive resources on ExB provided by Esri is noticeable and the foisting of ExB on the community before its in a stable state is cause for concern (and  warrants considering alterative platforms/solutions).

Thanks for your write up, @JeffreyThompson2!

JeffreyThompson2
MVP Regular Contributor

Thank you, @AValenski 

I would like to remind you and everyone else that we are trying to keep the Tips and Tricks community a positive space. Complaints about ESRI or Experience Builder are generally unhelpful. And more practically, ESRI has carved out a special place for us here on their own website and I am concerned that if it becomes too negative, they may choose to take it away.

I know I took a couple digs at Experience Builder, really React, in my post, but there are genuinely many great benefits to working in React. There is a reason it is the most popular web framework in the world right now. But one thing it is not is "beginner-friendly" which was the point I was trying to make.

MichaelGaiggEsri
Deactivated User

@AValenski - Esri has various products that don't require any development knowledge, they are considered no-code solutions (ArcGIS Instant Apps, ArcGIS Dashboards, ArcGIS Storymaps,...); they all have their own space of existence and come with their own pro's and con's, but mostly they are limited in terms of customization. 

For users that want more than that, ArcGIS Experience Builder is a great way to design and build apps (experiences) that go beyond "configuration", yet again, with it's own pro's and con's. One advantage is flexibility, but that's often also a disadvantage because one needs some level of training (eye for design) to build something usable. In 80% of cases, this out of the box (no-code) builder tool will get you want you want, but in the remaining case that's not enough, and that's when custom widgets and custom themes come into play. Will that require training? Yes. Is it as simple as it maybe could be? Probably not. BUT, if that's what you need, then you can do it.

Lastly, I think ExB is pretty stable and actually pretty powerful once you wrap your head around it. You can do some amazing things in little time. I'm glad you found your way into this community here, this is our effort to fill in the gaps and help each other. Hope you join in.

Cheers, Mike

MichaelGaiggEsri
Deactivated User

Oh yes, @JeffreyThompson2, amazing writeup!!

AValenski
Occasional Contributor

@JeffreyThompson2 I don't quite prescribe to the same philosophy regarding Esri's community site, nor this particular forum; no one should moderate their opinions out of fear that Esri may "take the site away." If people are being honest and acting in good faith with their feedback, thoughts, suggestions, etc., then the perceived 'positiveness' shouldn't matter. This shouldn't become an echo-chamber to celebrate Esri, but I digress. And to be clear, I'm not looking for an argument or anything, I am just clarifying my position. As I said, I think your write-up is legitimately more instructive and useful than Esri's free training, and I stand by that.

@MichaelGaiggEsri , I've been working with ExB since it was in beta so I don't believe its a function of needing to take time to learn the system. I think there are underlying issues with the system, particularly as it relates to the existing WebApp Builder Developer Edition community and their apps (which ExB is intended to replace in a few months), which are deeply concerning. There are significant, prohibitive gaps in the product, which are exasperated by the lack of the free (or reasonably cheap) Esri-provided training/resources. 

I'm sure you have the Salesforce dashboard on this, but in case anyone else doesn't: here is the list of known bug/enhancement entries for Experience Builder (as of 13-11-2023), which doesn't contain all of the issues, but is pretty instructive nonetheless.

I'm not sharing this to bash Esri for the sake of it; I am sharing my feedback to hopefully spur Esri into action as they haven't hit the standard that their customers expect and/or require. I feel a bit disappointed to see Esri replace one of its cornerstone products (WAB) in a few months, yet haven't published new, free training on ExB since 2021*.

In fact, Esri has removed many of the training materials on Experience Builder (ArcGIS Experience Builder: Introduction, retired in April; Design a Layout for a Thematic Map in ArcGIS Experience Builder, retired in March; ArcGIS Experience Builder: Designing Apps with Style and Layout, retired in March) from their support and documentation sites, leading to many of the blogs on ExB leading to dead-links

If you're genuinely interested, I'm happy to organize a compendium of ExB issues/concerns I've organized myself and found through the community and share. 

*distributers excluding

MichaelGaiggEsri
Deactivated User

@AValenski I am interested to hear your feedback. Do you want to jump on a call with me so you can walk me through? I don't work for the Experience Builder product team, but I may be able to point you to helpful resources and relay your feedback to team.

QuantitativeFuturist
Frequent Contributor

@AValenski Fully agree with your assessment of the current situation and I support people giving feedback (negative or otherwise) on any and every platform available to them. All of these bugs and issues are something that you would expect in an open source platform but NOT in an Enterprise grade software platform. Documentation has got significantly worse and some of the instructional videos are laughable in their simplicity. There is a huge delta between what esri considers to be rigorous testing and production ready applications vs the real world. 

JeffreyThompson2
MVP Regular Contributor

@AValenski @QuantitativeFuturist To clarify why I called negativity unhelpful, it is because this is a User Group and not an official ESRI board. There are plenty of spaces on this website and other forums where you can lodge complaints directly to ESRI personnel. On the Tips and Tricks boards the only person I can guarantee will read your complaints is me. I am not in a position to help you and I personally do not want my inbox flooded with the kind of toxic vitriol that is routinely posted on the Experience Builder Blogs.

Personally, my feelings about ESRI and Experience Builder are not 100% positive. My biggest complaint about Experience Builder is insufficient documentation, hence the existence of this group. A more sneaky motivation for this group is that many of the posts highlight known limitations and common complaints and if anyone from ESRI is reading them, they may be motivated to work on these issues. And I feel they may be more receptive to do so, if the overall tone of the group is more 'constrictive criticism' than 'angry complaints'.

MichaelLev
Frequent Contributor

You wrote: "Experience Builder 1.13 will be based on the 4.28 version of the API and includes map components".

I looked at "What's New in ArcGIS Experience Builder Developer Edition (version 1.13) " and I have not seen any talk about this, and I also searched the code of 1.13 and failed to detect any usage of web components. Have I missed something? What is really the current situation in 1.13? and in case I want for example to insert "coordinate conversion" widget component in 1.13, I'll appreciate info about how to do it. 

JeffreyThompson2
MVP Regular Contributor

I have not use 1.13 yet. My best guess for using map components goes like this.

  1. In the terminal, run

 

npm install @ArcGIS/map-components

 

  • Inside the widget.tsx of a custom component, add import { ArcgisCoordinateConversion } from "@arcgis/map-components-react" to your import statements.
  • In the JSX of this widget, use <arcgis-coordinate-conversion />
MichaelLev
Frequent Contributor

@JeffreyThompson2 Thank yoo, I'll check in the next 2 weeks.

MichaelLev
Frequent Contributor

You wrote (in bold): "Much of Experience Builder is in class based components, including most of the widget coding examples provided by ESRI".

I searched in the code of EXB 1.12 and 1.13 (in dist/widgets) and it seems that all widgets *.tsx files are Function... As I'm newbie to React and Typescript, I am bewildered. Am I missing something important? Do I really need to understand react class documentation in order to learn and modify/extend EXB?

JeffreyThompson2
MVP Regular Contributor

ESRI has been writing their new widgets in the function based React style. I don't know how much of an effort they are making at refactoring the older widgets, but I'm sure if you dig deeply enough you will find some class-based stuff. Here is the the official set of ESRI sample widgets and they are almost exclusively class-based.

It is not critical to learn class-based React, but it is helpful.

WDominguez-Zarate
Emerging Contributor

Does anyone have any experience publishing a custom widget with external components? I want to use JZip but there was component so I installed it. My widget to take a column with URL's download them and send them to a compressed zip file works in my local ExB, will ArcGIS Enterprise accept this added component? 

TimWestern
Frequent Contributor

@WDominguez-Zarate

When you say external components are you referring to other NPM components in the NodeJS Echosystem perhaps?  I know you can install them as norm locally when developing.  The documentation of what to do for them when deployed I haven't encountered as of yet.

About the Author
A frequently confused rock-hound that writes ugly, but usually functional code.