Access to XAML controls in a React Native for Windows application (Part 1)
Published May 19 2022 08:07 AM 1,443 Views
Microsoft

When you build a Windows application using React Native for Windows, typically you don't have to worry about the underline generated application. One of the powerful features of React Native is that it generates a truly native application, which means that the JSX controls you place in the UI are translated with the equivalent native XAML control. This feature helps to deliver great performance and a familiar Fluent look & feel to your applications.

However, there are a few scenarios where you would need to access the underlying XAML infrastructure. Recently, I worked on an engagement with a customer who needed to show a flyout when specific actions occur. However, the flyout didn't have to be displayed in a fixed position, but near the control that generated the action (like the click of a button). Achieving this goal only using JSX isn't feasible for two reasons:

  1. React Native is a technology to build cross-platform applications, while Flyout is a specific Windows control. As such, by default, React Native doesn't expose a JSX equivalent.
  2. When you create a Flyout, you must link it to another XAML control (like a Button) by setting its Flyout property or by passing it as parameter to the ShowAt() method. We can't access this information in JSX.

In this blog post we'll learn how to support this scenario, by combining a native module and the findNodeHandle() React Native function, which is the one that gives you most flexibility but it's also more overhead to maintain.

In the second part, instead, we'll see how to achieve the same goal in a simpler way with the react-native-xaml library.

 

Create the native module

The first step is to create a native module, which we'll need to interact with the native XAML control. You can follow the guidance on the official website to create a new one and add support for Windows. Once you have the basic infrastructure, we can add in the module class a method to display the flyout, as in the following example:

 

namespace ReactNativeInAppNotifications
{
    [ReactModule("inappnotifications")]
    internal sealed class ReactNativeModule
    {

        private ReactContext _reactContext;

        [ReactInitializer]
        public void Initialize(ReactContext reactContext)
        {
            _reactContext = reactContext;
        }

        [ReactMethod("showNotification")]
        public void ShowNotification(int tag, string title)
        {
            _reactContext.Handle.UIDispatcher.Post(() =>
            {
                Flyout flyout = new Flyout
                {
                    Content = new TextBlock { Text = title }
                };

                var control = XamlUIService.FromContext(_reactContext.Handle).ElementFromReactTag(tag) as FrameworkElement;

                flyout.ShowAt(control);
            });
        }
    }
}

First, notice how the class is decorated with the [ReactModule] attribute, while the ShowNotification() method is decorated with the [ReactMethod] attribute. They will enable us to access them from the JavaScript layer of the application. The ShowNotification() method accepts two parameters: one is the title of the notification, while the other one is a tag, which is an index that we can use to reference a JSX control. We'll learn later in the post, when we'll talk about the JavaScript layer, how to pass this information to the native module.

The next step is to create our Flyout control. In the previous snippet, we're using a very simple approach and we're setting the Content property with a new TextBlock control; then we set its Text property with the title that we're passing from the JavaScript layer. Since we are using native code, you have the flexibility to customize the Flyout control as you prefer: you can use a more complex XAML to define the Content; or you can customize other properties like LightDismissOverlayMode or ShowMode.

Now that we have our flyout, we need to display it near the control that triggered the action. As such, we need to use the tag we have received from the JavaScript layer to retrieve a reference to the corresponding XAML control. We can achieve this goal thanks to the XamlUIService class offered by React Native for Windows. First, we get an instance from the current context (which is exposed by the Handle property of the ReactContext object that is set in the Initialize() method of the module). Then, we call the ElemenFromReactTag() method, passing as parameter the tag we have received. In the end, we cast the result into a FrameworkElement object, which is the base class of XAML controls.

Now we have access to underline XAML control which triggered the event, so we can just pass it to ShowAt() method exposed by the Flyout control. This will make sure that the Flyout will be displayed near the control.

Let's see now the code that we have to write in the React Native layer to use the native module we have just written.

 

Invoke the native module from JavaScript

Let's now create a React Native component that we can use to display the notification. Let's see the code first, then we'll comment it.

 

import React from 'react';
import {Button, NativeModules, findNodeHandle} from 'react-native';

class NotificationComponent extends React.Component {

    constructor(props) {
        super(props);
        this.myButton = React.createRef();
    }


  showNotification = async () => {
    await NativeModules.inappnotifications.showNotification(findNodeHandle(this.myButton.current), 'Hello World');
  };

  render() {
    return (
      <Button
        title="Click me"
        onPress={this.showNotification}
        ref={this.myButton} />
    );
  }
}

export default NotificationComponent;

In order to pass to the native module the JSX control which triggered the event, we need to use the concept of reference in React Native. You can think of it like the x:Name property in XAML: it's a way to directly reference a control from code. If this approach is frequently used in other platforms (like WPF or Windows Forms), it's not very common in React Native. In most of the scenarios, in fact, you won't need to directly access to the control, but you're going to use properties, events and state to implement the logic you need.

However, there are scenarios (like this one) in which a reference is the only way to achieve a goal and, as such, React Native provides the infrastructure to implement it. This is achieved with two changes to the code:

  • In the constructor of the component, we define a variable (in this case, myButton) and we initialize it by calling React.createRef().
  • In JSX, we set the ref property of the control we want to reference (in this sample, a Button control) with the same variable we have created in the constructor (in this case, myButton).

Now we can access to the control by using the current property of the myButton object. We have everything we need now, so we can call the showNotification() method exposed by our native module, through the NativeModules object offered by React Native. Remember that the syntax to use a native module is NativeModule.<class name>.<method name>. In our example, it's NativeModules.inappnotifications.showNotification.

Well, we have almost everything we need, there's one last thing to add. Remember that the ShowNotification() method in the native module requires a tag to reference the XAML control? To translate our reference to a tag, we must use the findNodeHandle() method offered by React Native. This is how the implementation looks like:

showNotification = async () => {
await NativeModules.inappnotifications.showNotification(findNodeHandle(this.myButton.current), 'Hello World');
};

To get the tag, we invoke findNodeHandle() passing, as parameter, the current property of the myButton reference we have created earlier.

In the end, we just connect the showNotification() method we have just created to the onPress event of the Button control, so that we can invoke it when it's clicked.

 

 

Wrapping up

That's it! Now, if you launch your React Native for Windows application and you press the button, you will see the flyout being displayed right at the top of the Button:

 

Flyout.png

 

You can test the full code with the sample application on GitHub.

In the second part of this post, we'll see another and more powerful approach to support this scenario: using the react-native-xaml library.

 

Happy coding!

 

[UPDATE] The second part of the post is now live.

Co-Authors
Version history
Last update:
‎May 20 2022 06:32 AM
Updated by: