@tma.js/sdk-react
React bindings for the client SDK. It includes hooks, components, and other useful tools that enable the use of React alongside the Mini Apps client SDK. It automatically tracks changes to SDK components.
Installation
Before anything else, it is assumed that you have already installed the react
package, as it is a peer dependency of this package. The installation of the SDK itself is not required, as it is already included in @tma.js/sdk-react
.
pnpm i @tma.js/sdk-react
pnpm i @tma.js/sdk-react
npm i @tma.js/sdk-react
npm i @tma.js/sdk-react
yarn add @tma.js/sdk-react
yarn add @tma.js/sdk-react
Using SDK provider
According to the @tma.js/sdk
documentation, it consists of a set of components that are not initialized by default. Developers are responsible for creating these components themselves. However, the SDK provides the init
function, which simplifies the process of creating the components and using the standard TWA flow. It handles all the necessary steps for developers.
To make the SDK functionality available to the application and allow the initialization of newly created components, we need to use the SDKProvider
component.
import React from 'react';
import { SDKProvider } from '@tma.js/sdk-react';
function Root() {
return (
<SDKProvider>
<div>My application!</div>
</SDKProvider>
);
}
import React from 'react';
import { SDKProvider } from '@tma.js/sdk-react';
function Root() {
return (
<SDKProvider>
<div>My application!</div>
</SDKProvider>
);
}
Internally, the SDKProvider
utilizes the init
function from @tma.js/sdk
. It accepts an optional list of parameters through the initOptions
property, which is described here.
import React from 'react';
import { SDKProvider, InitOptions } from '@tma.js/sdk-react';
/**
* Root component for the whole project.
*/
export function Root() {
const options: InitOptions = {
acceptScrollbarStyle: true,
checkCompat: true,
debug: true
};
return (
<SDKProvider initOptions={options}>
<div>My application!</div>
</SDKProvider>
);
}
import React from 'react';
import { SDKProvider, InitOptions } from '@tma.js/sdk-react';
/**
* Root component for the whole project.
*/
export function Root() {
const options: InitOptions = {
acceptScrollbarStyle: true,
checkCompat: true,
debug: true
};
return (
<SDKProvider initOptions={options}>
<div>My application!</div>
</SDKProvider>
);
}
Most of the time, there is no need to use initOptions
unless you have specific logic in your application. Typically, the SDK handles everything necessary for developers, so there is no need for additional configuration.
Getting SDK context
By using the SDKProvider
component, the child elements are able to utilize the useSDK
hook (or the withSDK
higher-order component) to access core SDK information.
import React from 'react';
import { SDKProvider, SDKContext, useSDK, withSDK } from '@tma.js/sdk-react';
function App() {
const sdk = useSDK();
// Here, we can use SDK information.
return <div>My application!</div>;
}
// or
interface Props {
sdk: SDKContext
}
function AppPure({ sdk }: Props) {
return <div>My application!</div>;
}
const AppWrapped = withSDK(AppPure);
function Root() {
return (
<SDKProvider>
<App/>
{/* or */}
<AppWrapped/>
</SDKProvider>
);
}
import React from 'react';
import { SDKProvider, SDKContext, useSDK, withSDK } from '@tma.js/sdk-react';
function App() {
const sdk = useSDK();
// Here, we can use SDK information.
return <div>My application!</div>;
}
// or
interface Props {
sdk: SDKContext
}
function AppPure({ sdk }: Props) {
return <div>My application!</div>;
}
const AppWrapped = withSDK(AppPure);
function Root() {
return (
<SDKProvider>
<App/>
{/* or */}
<AppWrapped/>
</SDKProvider>
);
}
Let's enhance the previous example and introduce crucial logic associated with the SDK lifecycle:
import React, { PropsWithChildren, useEffect } from 'react';
import { SDKProvider, useSDK, useBackButton, useWebApp } from '@tma.js/sdk-react';
/**
* Part of the application which doesn't know anything about SDK initialization
* and which should be rendered only in case, SDK is already initialized and
* could provide Telegram Mini Apps components.
*/
function App() {
const backButton = useBackButton();
const webApp = useWebApp();
// When App is attached to DOM, lets show back button and
// add "click" event handler, which should close current application.
useEffect(() => {
const listener = () => webApp.close();
backButton.on('click', listener);
backButton.show();
return () => {
backButton.off('click', listener);
backButton.hide();
};
// We know, that backButton and webApp will never change,
// but let's follow React rules.
}, [backButton, webApp]);
return <div>My application!</div>;
}
/**
* This component is the layer controlling the application display. It displays
* application in case, the SDK is initialized, displays an error if something
* went wrong, and a loader if the SDK is warming up.
*/
function Loader({ children }: PropsWithChildren<{}>) {
const { didInit, components, error } = useSDK();
// There were no calls of SDK's init function. It means, we did not
// even try to do it.
if (!didInit) {
return <div>SDK init function is not yet called.</div>;
}
// Error occurred during SDK init.
if (error !== null) {
return <div>Something went wrong.</div>;
}
// If components is null, it means, SDK is not ready at the
// moment and currently initializing. Usually, it takes like
// several milliseconds or something like that, but we should
// have this check.
if (components === null) {
return <div>Warming up SDK.</div>;
}
// Safely render application.
return <>{children}</>;
}
/**
* Root component of the whole project.
*/
export function Root() {
return (
<SDKProvider>
<Loader>
<App/>
</Loader>
</SDKProvider>
);
}
import React, { PropsWithChildren, useEffect } from 'react';
import { SDKProvider, useSDK, useBackButton, useWebApp } from '@tma.js/sdk-react';
/**
* Part of the application which doesn't know anything about SDK initialization
* and which should be rendered only in case, SDK is already initialized and
* could provide Telegram Mini Apps components.
*/
function App() {
const backButton = useBackButton();
const webApp = useWebApp();
// When App is attached to DOM, lets show back button and
// add "click" event handler, which should close current application.
useEffect(() => {
const listener = () => webApp.close();
backButton.on('click', listener);
backButton.show();
return () => {
backButton.off('click', listener);
backButton.hide();
};
// We know, that backButton and webApp will never change,
// but let's follow React rules.
}, [backButton, webApp]);
return <div>My application!</div>;
}
/**
* This component is the layer controlling the application display. It displays
* application in case, the SDK is initialized, displays an error if something
* went wrong, and a loader if the SDK is warming up.
*/
function Loader({ children }: PropsWithChildren<{}>) {
const { didInit, components, error } = useSDK();
// There were no calls of SDK's init function. It means, we did not
// even try to do it.
if (!didInit) {
return <div>SDK init function is not yet called.</div>;
}
// Error occurred during SDK init.
if (error !== null) {
return <div>Something went wrong.</div>;
}
// If components is null, it means, SDK is not ready at the
// moment and currently initializing. Usually, it takes like
// several milliseconds or something like that, but we should
// have this check.
if (components === null) {
return <div>Warming up SDK.</div>;
}
// Safely render application.
return <>{children}</>;
}
/**
* Root component of the whole project.
*/
export function Root() {
return (
<SDKProvider>
<Loader>
<App/>
</Loader>
</SDKProvider>
);
}
You might wonder why we need a component like Loader
. The reason is that the SDK initialization process is asynchronous. Some of its components need to send requests to the Telegram application to fetch their current state. Due to this, we cannot determine the required properties for these components until the initialization is completed.
As a result, all hooks that return component instances will throw an error because they cannot retrieve the necessary component from the components
property. Therefore, these hooks should not be called until the SDK is fully initialized.
When init is done
Once the initialization is successfully completed, developers should call the webApp.ready
function. This function notifies the Telegram application that the current Mini App is ready to be displayed.
import React, { useEffect } from 'react';
import { useWebApp } from '@tma.js/sdk-react';
function App() {
const webApp = useWebApp();
useEffect(() => {
webApp.ready();
}, [webApp]);
return <div>Here is my App</div>;
}
import React, { useEffect } from 'react';
import { useWebApp } from '@tma.js/sdk-react';
function App() {
const webApp = useWebApp();
useEffect(() => {
webApp.ready();
}, [webApp]);
return <div>Here is my App</div>;
}
Hooks and HOCs
Launch parameters
There may be cases where a developer needs to retrieve launch parameters without initializing the entire SDK. For example, they might want to access current theme parameters stored in window.location
. In such cases, SDK initialization may not be necessary.
To retrieve Mini App launch parameters, the useLaunchParams
hook (or the withLaunchParams
higher-order component) can be used.
import React from 'react';
import { useLaunchParams, withLaunchParams, LaunchParams } from '@tma.js/sdk-react';
function DisplayLaunchParams() {
const launchParams = useLaunchParams();
return (
<pre>
<code>{JSON.stringify(launchParams, null, ' ')}</code>
</pre>
);
}
// or
interface Props {
launchParams: LaunchParams;
}
function DisplayLaunchParamsPure({ launchParams }: Props) {
return (
<pre>
<code>{JSON.stringify(launchParams, null, ' ')}</code>
</pre>
);
}
const DisplayLaunchParamsWrapped = withLaunchParams(DisplayLaunchParams);
import React from 'react';
import { useLaunchParams, withLaunchParams, LaunchParams } from '@tma.js/sdk-react';
function DisplayLaunchParams() {
const launchParams = useLaunchParams();
return (
<pre>
<code>{JSON.stringify(launchParams, null, ' ')}</code>
</pre>
);
}
// or
interface Props {
launchParams: LaunchParams;
}
function DisplayLaunchParamsPure({ launchParams }: Props) {
return (
<pre>
<code>{JSON.stringify(launchParams, null, ' ')}</code>
</pre>
);
}
const DisplayLaunchParamsWrapped = withLaunchParams(DisplayLaunchParams);
It will return the result of the retrieveLaunchParams function.
Other
The library provides a collection of simple hooks and higher-order components (HOCs) for each SDK component. The returned instances of these components remain the same, but force updates will be triggered if any changes occur in a component.
WARNING
If you are using higher-order components (HOCs), it's important to note that the passed components will always be the same instances. This can cause issues with React's PureComponent
and memo
components, as they won't detect any changes in the component references. To avoid problems, refrain from creating new component instances, as it can disrupt event listeners established during the SDK initialization process.
List of hooks and HOCs of components:
useBackButton
(withBackButton
)useBridge
(withBridge
)useClosingConfirmation
(withClosingConfirmation
)useHapticFeedback
(withHapticFeedback
)useInitData
(withInitData
)useLayout
(withLayout
)useMainButton
(withMainButton
)usePopup
(withPopup
)useQRScanner
(withQRScanner
)useThemeParams
(withThemeParams
)useViewport
(withViewport
)useWebApp
(withWebApp
)