Loading...

PCF control with React 18 and TypeScript: our project template

Most PCF tutorials stop at 'hello world.' The gap to a production-ready control - with types, tests, reliable builds, and solution packaging - is surprisingly wide. Here is the template repository we reach for on every new PCF, and the decisions baked into it.

PCF control with React 18 and TypeScript: our project template

The Power Apps Component Framework (PCF) lets you drop a custom React component onto a Dataverse form or a Canvas app and treat it as if it were native. The official getting-started docs walk you through a working "hello world" in 20 minutes. The gap from there to something you would put into a managed solution and ship to production is wider than the tutorials admit.

We have built eleven PCF controls across client projects. Each taught us something we baked into a template repo. The current version of that template - React 18, TypeScript strict mode, Jest, ESLint, and a CI pipeline that produces a solution zip - is what we reach for on every new control.

Here is what is in it and why.

The shape of the template

Nothing exotic. The details matter more than the shape.

Why React and TypeScript (strict)

PCF originally shipped with plain TypeScript and DOM-native rendering. React is optional but heavily supported - Microsoft's Fluent UI library assumes React, and the productivity gain for anything beyond a simple input is significant.

We use React 18 with the concurrent rendering features (createRoot rather than ReactDOM.render). This matters for controls that need transitions or suspense boundaries. If you ship with React 17, you opt out of features the newer Fluent UI components assume.

TypeScript strict mode is non-negotiable. PCF exposes ComponentFramework.Context, and the input/output types are generated from the manifest. Without strict mode, you can pass the wrong type to context.parameters.xxx and find out at runtime. With strict mode, the compiler rejects it.

The ControlManifest decisions

Three choices in the manifest make the template shareable across projects:

  1. Virtual control (): the modern shape, React-aware, required for React 18 compatibility.
  2. Dataset-bound or field-bound variants of the template: two separate starter branches because the Context API is subtly different between the two.
  3. Resource declarations include a localization strings file (resx): every user-facing string gets a key, never hardcoded. Internationalization later is free.

Services: the wrapper layer over context.webAPI

PCF exposes context.webAPI for Dataverse calls. The raw API is callback-style, untyped, and verbose. Wrapping it in a typed service with Promise-returning methods is five minutes of work that pays off on every subsequent call.

Components import the service, never the raw webApi. Testing becomes possible because the service is a pure class that can be mocked.

The test harness

PCF's runtime environment is not Node - it is a host page in Dynamics or a Canvas app. You cannot run a PCF control outside of that host directly. But you can test the React components and the service classes in isolation.

Jest plus @testing-library/react covers 80% of what matters:

The pattern: pass services in as props, not as new DataverseService(context.webApi) inside the component. Dependency injection at the top level makes every component test-mockable.

We require 70% line coverage on new PCFs. Not because the number is magic, but because a coverage floor forces the team to structure code so it can be tested.

ESLint rules that earn their slot

The base config we use:

no-floating-promises catches the most common PCF bug: calling webApi.retrieveRecord without awaiting it, which silently fails on error. no-explicit-any forces proper typing of the context API surface. exhaustive-deps catches stale-closure bugs in useEffect.

The CI pipeline

Three jobs run on every pull request:

  1. Install + build: npm ci then npm run build (compiles TypeScript and runs the PCF build step). Fails if build errors.
  2. Test: npm test (Jest) + coverage report. Fails if coverage drops below 70%.
  3. Solution pack: uses pac pcf push or the CreatePowerPlatformSolution pipeline task to produce a solution zip artifact.

The solution zip is the artifact we deploy from. A pull request into main triggers a build that produces a versioned zip; the release pipeline then imports that zip into the target environment.

Things we stopped doing

  • Bundling Fluent UI's entire core library. The size hit on the PCF bundle is noticeable on slow connections. We import only the components we use (Button, TextField, specific icons).
  • Running TypeScript in --strict false mode to "move faster." It was always slower in the end.
  • Skipping manifest versioning. The PCF manifest has a version field. Bumping it on every deploy was annoying until we had a PCF fail to load because the cached version matched the new version number - forcing users to hard-refresh. Now the version bumps automatically in CI.

When to not use PCF at all

Not every customization needs a PCF. Model-driven forms cover 80% of requirements with out-of-the-box components. Canvas apps offer drag-and-drop custom layouts with Power Fx. PCF is for:

  • Interactive visualizations that Power Fx cannot produce (complex charts, map overlays)
  • Third-party JavaScript library integrations (Chart.js, specific mapping SDKs)
  • Custom form controls that replace a native field with richer UX (a date range picker, a search-autocomplete)

If the requirement maps to a Fluent UI or out-of-box component, use the native option. PCF adds a build pipeline, a deployment artifact, and a code surface to maintain. The threshold for choosing PCF should be "no other Power Platform option fits."

The deliverable the client actually cares about

After all the engineering, the client handoff is: "here is a managed solution zip. Import it and your new field control appears on the form." They do not know or care about React 18, strict mode, or Jest.

But every time they ask for a change six months later, the coverage, the linting, and the test suite are what make the change possible in hours instead of days. That is the real value of the template.

If you are starting your first PCF, skip past the hello-world tutorials and invest half a day in this shape. The hello-world code will become the first thing you have to throw away.

Contact Us Now

Share Your Story

We build trust by delivering what we promise – the first time and every time!

We'd love to hear your vision. Our IT experts will reach out to you during business hours to discuss making it happen.

WHY CHOOSE US

"Collaborate, Elevate, Celebrate where Associates - Create Project Excellence"

SapotaCorp beyond the IT industry standard, we are

  • Certificated
  • Assured quality
  • Extra maintenance

Tell us about your project

close