Hi GoodData Support Team, We have added a filter...
# gooddata-cloud
s
Hi GoodData Support Team, We have added a filter to our GoodData dashboard, which contains multiple charts. When we change the filter, the data in the charts updates accordingly—this part is working as expected. Now, we need to dynamically pass filter values via code. We’ve created a single dashboard with a filter supporting multiple projects. Our goal is to embed this same dashboard across different projects in our application, changing only the filter value based on the selected project. We have already reviewed the GoodData documentation, but unfortunately, it did not resolve our issue. We are currently stuck and would appreciate your assistance on this as a priority. For reference, we’ve attached: • A screenshot of the dashboard with the filter. • A screenshot showing how the dashboard is embedded in our application. Please review the attachments and advise us on how we can achieve dynamic filter passing programmatically. Looking forward to your prompt support.
j
Hi Sunil, First of all, I would like to ask about some context here. You have mentioned that: "_We’ve created a single dashboard with a filter supporting multiple projects._" Does that mean that we are working with multiple projects (meaning Workspaces) in this case? Or are you referring to projects within your data in a single Workspace. Meaning - you are just trying to embed same dashboard in same workspace just with different filter set?
s
Let me explain this with an example: Suppose we have a dashboard that shows pollution data across the world. There are two filters on the dashboard—one for country and another for state within the selected country. The structure and layout of the dashboard remain the same; only the values for country and state change. Based on these selected filter values, the data displayed in the charts updates accordingly. In our React application, we need to pass these filter values dynamically. I hope this helps clarify the context.
j
Hi Sunil, While it is technically possible to preselect dashboard filters during embedding, this is not a security feature. End users would still be able to: • Change filter values in the UI. • Access other values present in the filter (e.g., other countries or customers). • Potentially view data outside of their intended scope, depending on the underlying data model. This method is therefore not secure or isolating, and not recommended for multitenant or access-controlled use cases. We also don’t officially document this pattern as it leads to misuse in production deployments where stricter access control is expected.
For similar use cases, we strongly recommend one of the following secured and scalable approaches depending on your architecture: 1. Workspace Data Filters (WDFs) If you have all your customer or country data stored in a single workspace, and want to show each user only a slice of the data based on their identity or embedding context: • Use Workspace Hierarchy (parent-child model). • Use Workspace Data Filters to enforce data-level filtering for each child workspace. • Automatically inherit dashboards/metrics from the parent. • Secure, scalable, and well-suited for multi-tenant setups. Docs: Workspace Hierarchy Workspace Data Filters 2. User Data Filters (UDFs) If different users within the same workspace need personalized views (e.g., different countries or customer accounts): • Use User Data Filters to tie specific filter values to each user (via email or user ID). • The dashboard automatically applies the right filter value at runtime. • Fully secure — users cannot access data beyond their scope. Docs: User Data Filters
s
Would it be possible to provide a working example where filters are dynamically passed to the dashboard in React code?
j
Hi Sunil, I'm really sorry but unfortunately, we do not have an official or documented implementation of this approach that we could share. This technique is not part of our recommended or supported embedding patterns, especially due to the potential security risks it introduces in multi-tenant environments.
s
What is the preferred way for gooddata integration with the React app?
j
Hi Sunil, could you be a bit more specific with your question? Maybe tell us more about your use-case? If we are talking about multi-tenant environment, the WDFs are the best way to go about this. If you need to show different filtered data per user or user groups, then I would recommend to use UDFs as per above.
s
We are currently integrating a GoodData dashboard into our React application using the
@gooddata/sdk-ui-dashboard
package. The integration is working fine and the dashboard renders successfully. Below is the React component we are using for this integration:
Copy code
import React, { useState } from 'react';
import { Dashboard } from "@gooddata/sdk-ui-dashboard";
import tigerBackendFactory, { TigerTokenAuthProvider } from "@gooddata/sdk-backend-tiger";
import { BackendProvider, WorkspaceProvider } from "@gooddata/sdk-ui";

function Report() {
  const [dashboardId] = useState('dummy_dashboard_id'); // example: "ad56b32abc1243d78e2aa03e905c70e7"
  const [workspaceId] = useState('dummy_workspace_id'); // example: "acme_workspace_123"

  const personalToken = process.env.REACT_APP_PERSONAL_TOKEN;  
  const goodDataUrl = process.env.REACT_APP_GOOD_DATA;

  const backend = tigerBackendFactory({
    hostname: goodDataUrl
  }).withAuthentication(new TigerTokenAuthProvider(personalToken));

  return (
    <BackendProvider backend={backend}>
      <WorkspaceProvider workspace={workspaceId}>
        <Dashboard dashboard={dashboardId} />
      </WorkspaceProvider>
    </BackendProvider>
  );
}

export default Report;
Now, we’d like to dynamically apply filters to the dashboard — for example: • A specific project ID stored in sessionStorage • A date range selected by the user from the UI • Other user-specific or context-driven filters We would like to know: 1. How can we pass these filters to the dashboard at runtime? 2. Do we need to use
DashboardView
instead of
Dashboard
to inject filters? 3. Are there any official code examples or best practices to follow using the React SDK? We’d appreciate detailed guidance or code snippets for implementing this correctly. Hope this helps.
r
Hi Sunil, Radek here! As discussed on the call, I'd refrain from separating data like this using Dashboard filters only since that's not considered secure data-wise (the filters can still be worked around), I would opt for User Data Filters instead. However, for the "default filter" behavior, there are still ways to go about this - the most self-contained one I could think of is using a small local Dashboard plugin with dispatch commands. Here's an example of setting a filter on load (in TypeScript):
Copy code
import React, { useEffect, useState } from "react";
import {
  changeAttributeFilterSelection,
  CustomDashboardWidgetComponent,
  DashboardConfig,
  DashboardContext,
  DashboardPluginV1,
  IDashboardCustomizer,
  IDashboardEventHandling,
  newCustomWidget,
  newDashboardItem,
  newDashboardSection,
  resetAttributeFilterSelection,
  selectFilterContextAttributeFilterByDisplayForm,
  useDashboardSelector,
  useDispatchDashboardCommand,
} from "@gooddata/sdk-ui-dashboard";
import { idRef } from "@gooddata/sdk-model";
import { useDashboardLoader } from "@gooddata/sdk-ui-loaders";
import { LoadingComponent } from "@gooddata/sdk-ui";

const dashboardRef = import.meta.env.VITE_GD_DASHBOARD;
const config: DashboardConfig = { isReadOnly: true };

// Custom widget with default filter applied on mount
const MyCustomWidget: CustomDashboardWidgetComponent = () => {
  const changeAttributeFilterSelectionCmd = useDispatchDashboardCommand(
    changeAttributeFilterSelection
  );
  const resetAttributeFilter = useDispatchDashboardCommand(
    resetAttributeFilterSelection
  );

  const filterLocalId = useDashboardSelector(
    selectFilterContextAttributeFilterByDisplayForm(idRef("customer_country"))
  )?.attributeFilter.localIdentifier;

  // Avoid infinite loops by only running once
  const [initialized, setInitialized] = useState(false);

  useEffect(() => {
    if (!initialized && filterLocalId) {
      changeAttributeFilterSelectionCmd(
        filterLocalId,
        { values: ["Canada"] },
        "IN"
      );
      setInitialized(true);
    }
  }, [filterLocalId, initialized, changeAttributeFilterSelectionCmd]);

  // Helper function to apply filter values
  const applyFilter = (values: string[]) => {
    if (!filterLocalId) return;
    changeAttributeFilterSelectionCmd(filterLocalId, { values }, "IN");
  };

  const resetFilterSelection = () => {
    if (filterLocalId) {
      resetAttributeFilter(filterLocalId);
    }
  };

  return (
    <div>
      <button onClick={() => applyFilter(["United States"])}>
        Filter: United States
      </button>
      <button onClick={() => applyFilter(["Canada"])}>Filter: Canada</button>
      <button onClick={() => applyFilter(["United States", "Canada"])}>
        Filter: Both
      </button>
      <button onClick={resetFilterSelection}>Reset Filter</button>
    </div>
  );
};

// Dashboard plugin registering the widget and layout
class LocalPlugin extends DashboardPluginV1 {
  public readonly author = "GD Support";
  public readonly displayName = "Sample filter plugin";
  public readonly version = "1.0";

  public register(
    _ctx: DashboardContext,
    customize: IDashboardCustomizer,
    _handlers: IDashboardEventHandling
  ): void {
    customize.customWidgets().addCustomWidget("myCustomWidget", MyCustomWidget);
    customize.layout().customizeFluidLayout((_layout, customizer) => {
      customizer.addSection(
        0,
        newDashboardSection(
          "Buttons to dispatch filter commands",
          newDashboardItem(newCustomWidget("myWidget1", "myCustomWidget"), {
            xl: {
              gridWidth: 12,
              gridHeight: 3,
            },
          })
        )
      );
    });
  }
}

const LocalExtraPlugin = {
  factory: () => new LocalPlugin(),
};

const App: React.FC = () => {
  const { status, result, error } = useDashboardLoader({
    dashboard: dashboardRef,
    loadingMode: "staticOnly",
    extraPlugins: LocalExtraPlugin,
  });

  if (status === "loading" || status === "pending") {
    return <LoadingComponent />;
  }

  if (error) {
    return <div>Error loading dashboard...</div>;
  }

  const { DashboardComponent, props: dashboardProps } = result!;
  return (
    <div>
      <DashboardComponent {...dashboardProps} config={config} />
    </div>
  );
};

export default App;
In my case, it's important to note that the App component itself is wrapped by the Backend and Workspace providers, and I'm using Vite env variables for the references - so don't forget to replace those in testing 🙂 I think that if you were to use sessionStorage, you can use the value from there in the useEffect filter. If you do that, and change
idRef("customer_country")
to the correct filter ID in
filterLocalId
, you're most of the way there.
Hi there @Sunil Katkar, was the above helpful? 🙂 Anything more I can help you with?
s
Hey @Radek Novacek No luck yet, am trying to look around the code sample you send. But it is not working for me so looking into it
r
If you need a hand, feel free to share any errors that you're getting and I can check as well!
s
Hi @Radek Novacek If possible can you please provide the zip file of working code which includes components & package.json?
Any updates @Radek Novacek?
r
Hi @Sunil Katkar, apologies for the delay, we were discussing this internally. Unfortunately, I can't really provide you full working code of this - I would be more than happy to help you troubleshoot the errors that you're seeing if you share them, however code-wise, I can only share snippets and directions. Any implementation work or writing custom code is out of scope of the Support team. But just to reiterate, if you share the errors you have, I can point you in the right direction - I tested the code before I shared it and can confirm that it is working, provided you already have a baseline app with authentication, and fix the references to point to the right attributes in your solution.
s
Hi Radek, Thanks, I can understand but the code sample you provided. Was unable to integrate it directly. So instead of full working code can you please provide the code in separate components, which I can easily understood.
r
Hi Sunil, there's unfortunately not much that can be separated here - the setup in the snippet all relates to the Dashboard, so let me try to explain the code a little better: First, we set up the references and create a custom widget - this is done in order to be able to access the Dashboard context values and set filters properly via dispatch commands. The useEffect inside listens to first load and filter changes, and sets the filter to a preset value - for this example, "*Canada*", since our filter reference is the customer_country attribute. It's also used to set up the buttons for changing the filter - again, done here to have direct access to the Dashboard context.
Copy code
const dashboardRef = import.meta.env.VITE_GD_DASHBOARD;
const config: DashboardConfig = { isReadOnly: true };

// Custom widget with default filter applied on mount
const MyCustomWidget: CustomDashboardWidgetComponent = () => {
  const changeAttributeFilterSelectionCmd = useDispatchDashboardCommand(
    changeAttributeFilterSelection
  );
  const resetAttributeFilter = useDispatchDashboardCommand(
    resetAttributeFilterSelection
  );

  const filterLocalId = useDashboardSelector(
    selectFilterContextAttributeFilterByDisplayForm(idRef("customer_country"))
  )?.attributeFilter.localIdentifier;

  // Avoid infinite loops by only running once
  const [initialized, setInitialized] = useState(false);

  useEffect(() => {
    if (!initialized && filterLocalId) {
      changeAttributeFilterSelectionCmd(
        filterLocalId,
        { values: ["Canada"] },
        "IN"
      );
      setInitialized(true);
    }
  }, [filterLocalId, initialized, changeAttributeFilterSelectionCmd]);

  // Helper function to apply filter values
  const applyFilter = (values: string[]) => {
    if (!filterLocalId) return;
    changeAttributeFilterSelectionCmd(filterLocalId, { values }, "IN");
  };

  const resetFilterSelection = () => {
    if (filterLocalId) {
      resetAttributeFilter(filterLocalId);
    }
  };

  return (
    <div>
      <button onClick={() => applyFilter(["United States"])}>
        Filter: United States
      </button>
      <button onClick={() => applyFilter(["Canada"])}>Filter: Canada</button>
      <button onClick={() => applyFilter(["United States", "Canada"])}>
        Filter: Both
      </button>
      <button onClick={resetFilterSelection}>Reset Filter</button>
    </div>
  );
};
The section below creates the Dashboard plugin - used to place the widget we created above into the layout of the Dashboard itself. It adds a full width section to the Dashboard to place the buttons into.
Copy code
// Dashboard plugin registering the widget and layout
class LocalPlugin extends DashboardPluginV1 {
  public readonly author = "GD Support";
  public readonly displayName = "Sample filter plugin";
  public readonly version = "1.0";

  public register(
    _ctx: DashboardContext,
    customize: IDashboardCustomizer,
    _handlers: IDashboardEventHandling
  ): void {
    customize.customWidgets().addCustomWidget("myCustomWidget", MyCustomWidget);
    customize.layout().customizeFluidLayout((_layout, customizer) => {
      customizer.addSection(
        0,
        newDashboardSection(
          "Buttons to dispatch filter commands",
          newDashboardItem(newCustomWidget("myWidget1", "myCustomWidget"), {
            xl: {
              gridWidth: 12,
              gridHeight: 3,
            },
          })
        )
      );
    });
  }
}

const LocalExtraPlugin = {
  factory: () => new LocalPlugin(),
};
Last but not least, the App itself, which then loads the Dashboard with the plugin (and with simple loading logic):
Copy code
const App: React.FC = () => {
  const { status, result, error } = useDashboardLoader({
    dashboard: dashboardRef,
    loadingMode: "staticOnly",
    extraPlugins: LocalExtraPlugin,
  });

  if (status === "loading" || status === "pending") {
    return <LoadingComponent />;
  }

  if (error) {
    return <div>Error loading dashboard...</div>;
  }

  const { DashboardComponent, props: dashboardProps } = result!;
  return (
    <div>
      <DashboardComponent {...dashboardProps} config={config} />
    </div>
  );
};
Hope this helps! As I mentioned, if you run into specific errors trying to implement this, please do share them here and I will see if I can point you in the right direction.
s
Thanks @Radek Novacek for detail walkthrough. Could you please take a look at below issue am facing.
Copy code
import React from "react";
import GoodDataDashboard from "./GoodDataDashboard";
import tigerBackendFactory, {
  TigerTokenAuthProvider
} from "@gooddata/sdk-backend-tiger";
import { BackendProvider, WorkspaceProvider } from "@gooddata/sdk-ui";

const personalToken = 'cmFtX3ZpZ25lc2g6cmFtc190b2tlbjpwYVE1czBQU241L3QxcjdsSG80aThqR2lNU20r======';
const goodDataUrl = '<https://sustain360-dev.cloud.gooddata.com>';

const backend = tigerBackendFactory({
    hostname: goodDataUrl
}).withAuthentication(
    new TigerTokenAuthProvider(personalToken)
);

const workspace = "your_workspace_id";

const App: React.FC = () => {
  return (
    <BackendProvider backend={backend}>
      <WorkspaceProvider workspace={workspace}>
        <h1>Custom Dashboard</h1>
        <GoodDataDashboard />
      </WorkspaceProvider>
    </BackendProvider>
  );
};

export default App;
In App component for backend am facing below issue,
Copy code
Type 'import("c:/Users/DELL/Downloads/gooddata-dashboard-webpack/gooddata-dashboard-webpack/node_modules/@gooddata/sdk-backend-base/node_modules/@gooddata/sdk-backend-spi/esm/backend/index").IAnalyticalBackend' is not assignable to type 'import("c:/Users/DELL/Downloads/gooddata-dashboard-webpack/gooddata-dashboard-webpack/node_modules/@gooddata/sdk-ui-loaders/node_modules/@gooddata/sdk-backend-spi/esm/backend/index").IAnalyticalBackend'.
  Types of property 'withAuthentication' are incompatible.
    Type '(provider: import("c:/Users/DELL/Downloads/gooddata-dashboard-webpack/gooddata-dashboard-webpack/node_modules/@gooddata/sdk-backend-base/node_modules/@gooddata/sdk-backend-spi/esm/backend/index").IAuthenticationProvider) => import("c:/Users/DELL/Downloads/gooddata-dashboard-webpack/gooddata-dashboard-webpack/node_mo...' is not assignable to type '(provider: import("c:/Users/DELL/Downloads/gooddata-dashboard-webpack/gooddata-dashboard-webpack/node_modules/@gooddata/sdk-ui-loaders/node_modules/@gooddata/sdk-backend-spi/esm/backend/index").IAuthenticationProvider) => import("c:/Users/DELL/Downloads/gooddata-dashboard-webpack/gooddata-dashboard-webpack/node_modu...'.
      Types of parameters 'provider' and 'provider' are incompatible.
        Property 'disablePrincipalCache' is missing in type 'import("c:/Users/DELL/Downloads/gooddata-dashboard-webpack/gooddata-dashboard-webpack/node_modules/@gooddata/sdk-ui-loaders/node_modules/@gooddata/sdk-backend-spi/esm/backend/index").IAuthenticationProvider' but required in type 'import("c:/Users/DELL/Downloads/gooddata-dashboard-webpack/gooddata-dashboard-webpack/node_modules/@gooddata/sdk-backend-base/node_modules/@gooddata/sdk-backend-spi/esm/backend/index").IAuthenticationProvider'.ts(2322)
index.d.ts(235, 5): 'disablePrincipalCache' is declared here.
BackendContext.d.ts(11, 5): The expected type comes from property 'backend' which is declared here on type 'IntrinsicAttributes & IBackendProviderProps'
Am also adding my gooddata packages version here for your reference, please take a look,
Copy code
"@gooddata/api-client-bear": "^9.9.0",
    "@gooddata/api-client-tiger": "^10.4.0",
    "@gooddata/sdk-backend-tiger": "^10.4.0",
    "@gooddata/sdk-ui": "^10.4.0",
    "@gooddata/sdk-ui-charts": "^10.4.0",
    "@gooddata/sdk-ui-dashboard": "^10.4.0",
    "@gooddata/sdk-ui-kit": "^10.4.0",
    "@gooddata/sdk-ui-loaders": "^10.34.0",
    "@gooddata/sdk-ui-theme-provider": "^10.4.0",
Just to highlight, I’m using the PAT (Personal Access Token) approach to load the dashboard. Please let me know if you need anything else from my end. Thank you in advance!!!
r
Hi @Sunil Katkar, I will proceed with checking the rest of the code, but please first edit your post immediately to remove the token reference from the code, and delete the token from GoodData. You can generate a new one after, but exposing your token like this is an heavy security risk, as anyone else could use it to gain access to your environment!
Ok, the errors come from the fact that
sdk-backend-spi
is being loaded from two different places - there could be multiple reasons for this, but the most likely is that your SDK versions are very much mismatched; can you try to update all of them to 10.34? Also,
api-client-bear
is not a necessary package to include unless you also had a GoodData Platform instance you wanted to interact with, so you can safely get rid of that one 🙂
Moreover, when you delete the API token you accidentally published here, if you share the ID of it with me (the name you set for it in GoodData), I will be able to check the logs for you and make sure it wasn't used without your knowledge.
s
Hi @Radek Novacek Token is not valid, that one is dummy token added while adding code here
❤️ 1
Hi @Radek Novacek I was trying below code for same purpose,
Copy code
import React, { useState, useEffect, useCallback } from 'react';
import { Dashboard } from '@gooddata/sdk-ui-dashboard';
import tigerBackendFactory, {
    TigerTokenAuthProvider
} from '@gooddata/sdk-backend-tiger';
import { BackendProvider, WorkspaceProvider } from '@gooddata/sdk-ui';
// --- CORRECTED IMPORT FOR FILTERS ---
import { newPositiveAttributeFilter, uriRef } from '@gooddata/sdk-model'; // Use newPositiveAttributeFilter
// --- END CORRECTED IMPORT ---


const GOODDATA_HOST = '<https://test-dev.cloud.gooddata.com/>';
const GOODDATA_WORKSPACE_ID = '62dd165920bd44feb45fb89def254196';
const GOODDATA_DASHBOARD_ID = '2a6e103a-6b29-483d-9e0c-95651d24ca12';
const GOODDATA_PAT = 'bmF2ZWVuX2NoYXVyYXNpYToxMjM0OjRUOTBKWm===========EZWNWtzNTNHN3BzRlls';

// Define the identifiers for your Organization and Sub-organization attributes/labels
const ORGANIZATION_ATTRIBUTE_IDENTIFIER = 'label.organization_id';
const SUB_ORGANIZATION_ATTRIBUTE_IDENTIFIER = 'label.sub_organization_id'; // <-- VERIFY THIS AGAINST YOUR GOODDATA LDM

const GoodDataDashboardEmbed = ({ selectedOrganization, selectedSubOrganization }) => {
    const [backend, setBackend] = useState(null);
    const [isLoadingBackend, setIsLoadingBackend] = useState(true);
    const [backendError, setBackendError] = useState(null);

    
    useEffect(() => {
        const initializeBackend = async () => {
            try {
                const authProvider = new TigerTokenAuthProvider(GOODDATA_PAT);

                const newBackend =  tigerBackendFactory({
                        hostname: GOODDATA_HOST
                    }).withAuthentication(
                        new TigerTokenAuthProvider(GOODDATA_PAT)
                    );

                setBackend(newBackend);
                setIsLoadingBackend(false);
            } catch (error) {
                console.error("Failed to initialize GoodData backend:", error);
                setBackendError("Failed to initialize dashboard. Check console for details.");
                setIsLoadingBackend(false);
            }
        };

        initializeBackend();
    }, []);

    const dashboardFilters = useCallback(() => {
        const filters = [];

        if (selectedOrganization) {
            const values = selectedOrganization.split(',').map(item => item.trim()).filter(Boolean);
            if (values.length > 0) {
                // --- CORRECTED FILTER CREATION ---
                filters.push(
                    newPositiveAttributeFilter(
                        uriRef(ORGANIZATION_ATTRIBUTE_IDENTIFIER), // Attribute ObjRef
                        values,                                   // Array of selected values
                        true                                      // 'in' operator (include)
                    )
                );
                // --- END CORRECTED FILTER CREATION ---
            }
        }

        if (selectedSubOrganization) {
            const values = selectedSubOrganization.split(',').map(item => item.trim()).filter(Boolean);
            if (values.length > 0) {
                // --- CORRECTED FILTER CREATION ---
                filters.push(
                    newPositiveAttributeFilter(
                        uriRef(SUB_ORGANIZATION_ATTRIBUTE_IDENTIFIER), // Attribute ObjRef
                        values,                                        // Array of selected values
                        true                                           // 'in' operator (include)
                    )
                );
                // --- END CORRECTED FILTER CREATION ---
            }
        }
        return filters;
    }, [selectedOrganization, selectedSubOrganization]);

    if (isLoadingBackend) {
        return <div style={{ textAlign: 'center', padding: '20px' }}>Loading GoodData services...</div>;
    }

    if (backendError) {
        return <div style={{ color: 'red', textAlign: 'center', padding: '20px' }}>Error: {backendError}</div>;
    }

    if (!backend) {
        return <div style={{ textAlign: 'center', padding: '20px' }}>Backend not available.</div>;
    }

    return (
        <div style={{ height: '800px', width: '100%', border: '1px solid #ddd', borderRadius: '8px', overflow: 'hidden' }}>
            <BackendProvider backend={backend}>
                <WorkspaceProvider workspace={GOODDATA_WORKSPACE_ID}>
                    <Dashboard
                        dashboard={GOODDATA_DASHBOARD_ID}
                        filters={dashboardFilters()}
                        config={{
                            menuVisible: false,
                        }}
                        onLoadingChanged={(loading) => console.log('Dashboard loading:', loading)}
                        onError={(error) => console.error('Dashboard error:', error)}
                    />
                </WorkspaceProvider>
            </BackendProvider>
        </div>
    );
};

export default GoodDataDashboardEmbed;
but getting below issue,
Copy code
CorrelationContext.tsx:94  Uncaught TypeError: effectiveBackend.withCorrelation is not a function
    at CorrelationContext.tsx:94:1
    at mountMemo (react-dom.development.js:17225:1)
    at Object.useMemo (react-dom.development.js:17670:1)
    at useMemo (react.development.js:1650:1)
    at useBackendWithCorrelation (CorrelationContext.tsx:90:1)
    at useBackendWithDashboardCorrelation (Dashboard.tsx:59:1)
    at Dashboard (Dashboard.tsx:22:1)
    at renderWithHooks (react-dom.development.js:16305:1)
    at mountIndeterminateComponent (react-dom.development.js:20074:1)
    at beginWork (react-dom.development.js:21587:1)
Could you please help me here?
r
Sure thing - this still looks a bit like a SDK package version mismatch, since the code itself looks fine at a first look. Can you confirm whether you've updated your GD.UI SDK packages already (and ideally share the updated package.json)? If you have, and this is still an issue, I'd try to wipe the node_modules folder alongside package-lock.json and run npm install again, could help!
s
Hi @Radek Novacek, Thanks for the support. As per your instructions, I did the re-install pacjages. Now facing issue like dashboard is loading but without charts & looks like CSS is missing. Please check screenshot for reference
r
Are you importing the necessary CSS at some level of the app? 🙂
s
Hi @Radek Novacek, We don't need to add any separate css right as we are integrating the GooDData dashboard as it is. Screenshot I have attached is the gooddata dashboard integrated in react app. Please let me know if I missed anything.
Copy code
import React, { useState, useEffect, useCallback } from 'react';
import { Dashboard } from '@gooddata/sdk-ui-dashboard';
import tigerBackendFactory, { // Default export for the backend factory
    TigerTokenAuthProvider      // Named export for PAT authentication
} from '@gooddata/sdk-backend-tiger';
import { BackendProvider, WorkspaceProvider } from '@gooddata/sdk-ui';
// Import newPositiveAttributeFilter as it was found in your possible exports list
import { newPositiveAttributeFilter, uriRef } from '@gooddata/sdk-model'; 


const GOODDATA_HOST = '<https://test-dev.cloud.gooddata.com>';
const GOODDATA_WORKSPACE_ID = 'WorkspaceId';
const GOODDATA_DASHBOARD_ID = 'Dashboard ID';
const GOODDATA_PAT = 'TOken';

// --- Attribute Identifiers for Filtering ---
// Based on your previous JSON, 'label.organization_id' is correct for Organization.
// !!! VERIFY 'label.sub_organization_id' against your GoodData Logical Data Model !!!
// You can find these in GoodData UI (e.g., Analytical Designer) or API Explorer.
const ORGANIZATION_ATTRIBUTE_IDENTIFIER = 'label.organization_id'; 
const SUB_ORGANIZATION_ATTRIBUTE_IDENTIFIER = 'label.suborganization_id'; // Example, please verify!

const GoodDataDashboardEmbed = ({ selectedOrganization, selectedSubOrganization }) => {
    const [backend, setBackend] = useState(null);
    const [isLoadingBackend, setIsLoadingBackend] = useState(true);
    const [backendError, setBackendError] = useState(null);

    useEffect(() => {
        const initializeGoodDataBackend = async () => {
            try {
                const goodDataBackendInstance = tigerBackendFactory({
                        hostname: GOODDATA_HOST
                    }).withAuthentication(
                        new TigerTokenAuthProvider(GOODDATA_PAT)
                    );

                setBackend(goodDataBackendInstance);
                setIsLoadingBackend(false);
            } catch (error) {
                console.error("GoodData Backend Initialization Error:", error);
                setBackendError("Failed to initialize GoodData dashboard. Please check console for details and ensure configuration (host, PAT) is correct.");
                setIsLoadingBackend(false);
            }
        };

        initializeGoodDataBackend();
    }, []); // Empty dependency array ensures this runs only once on component mount

    // Memoize the filters to avoid unnecessary re-renders of the dashboard.
    // This function will re-run only when selectedOrganization or selectedSubOrganization props change.
    const getDashboardFilters = useCallback(() => {
        const filters = [];

        // Apply Organization filter if a value is provided
        if (selectedOrganization) {
            // Split comma-separated values, trim whitespace, and filter out empty strings
            const orgValues = selectedOrganization.split(',').map(item => item.trim()).filter(Boolean);
            if (orgValues.length > 0) {
                filters.push(
                    // Use newPositiveAttributeFilter with uriRef for the attribute and the array of values
                    newPositiveAttributeFilter(
                        uriRef(ORGANIZATION_ATTRIBUTE_IDENTIFIER), // Convert string identifier to ObjRef
                        orgValues,                                 // Array of values to filter by
                        true                                       // 'true' for 'in' (include) operator
                    )
                );
            }
        }

        // Apply Sub-organization filter if a value is provided
        if (selectedSubOrganization) {
            const subOrgValues = selectedSubOrganization.split(',').map(item => item.trim()).filter(Boolean);
            if (subOrgValues.length > 0) {
                filters.push(
                    newPositiveAttributeFilter(
                        uriRef(SUB_ORGANIZATION_ATTRIBUTE_IDENTIFIER), // Convert string identifier to ObjRef
                        subOrgValues,                                  // Array of values to filter by
                        true                                           // 'true' for 'in' (include) operator
                    )
                );
            }
        }
        return filters;
    }, [selectedOrganization, selectedSubOrganization]);

    // --- Loading and Error Handling UI ---
    if (isLoadingBackend) {
        return <div style={{ textAlign: 'center', padding: '20px', fontSize: '1.2em' }}>Loading GoodData services...</div>;
    }

    if (backendError) {
        return <div style={{ color: 'red', textAlign: 'center', padding: '20px', border: '1px solid red', borderRadius: '5px' }}>
            <strong>Error:</strong> {backendError}
        </div>;
    }

    if (!backend) {
        // This case should ideally be caught by isLoadingBackend and backendError, but serves as a final fallback.
        return <div style={{ textAlign: 'center', padding: '20px' }}>GoodData backend could not be initialized.</div>;
    }

    // --- Render the GoodData Dashboard ---
    return (
        <>
        <BackendProvider backend={backend}>
            <WorkspaceProvider workspace={GOODDATA_WORKSPACE_ID}>
                <Dashboard dashboard={GOODDATA_DASHBOARD_ID} />
            </WorkspaceProvider>
        </BackendProvider>
        </>
    );
};

export default GoodDataDashboardEmbed;
r
Hi Sunil, the Dashboard does still need the styling to be imported though - for your reference, the full list of styling that may be needed is here.
s
Thanks @Radek Novacek Charts are loading now but dynamic filters are still not working. Please check code,
Copy code
import React, { useState, useEffect, useCallback } from 'react';
import { Dashboard } from '@gooddata/sdk-ui-dashboard';
import tigerBackendFactory, {
    TigerTokenAuthProvider
} from '@gooddata/sdk-backend-tiger';
import { BackendProvider, WorkspaceProvider } from '@gooddata/sdk-ui';
import { newPositiveAttributeFilter, uriRef } from '@gooddata/sdk-model';



const GOODDATA_HOST = '<https://test-dev.cloud.gooddata.com>';
const GOODDATA_WORKSPACE_ID = '=======workspace id========';
const GOODDATA_DASHBOARD_ID = '=======dashboard id========';
const GOODDATA_PAT = '========personal access token==========';

const ORGANIZATION_ATTRIBUTE_IDENTIFIER = 'label.organization_id'; 
const SUB_ORGANIZATION_ATTRIBUTE_IDENTIFIER = 'label.suborganization_id'; // Example, please verify!

const GoodDataDashboardEmbed = ({ selectedOrganization, selectedSubOrganization }) => {
    const [backend, setBackend] = useState(null);
    const [isLoadingBackend, setIsLoadingBackend] = useState(true);
    const [backendError, setBackendError] = useState(null);

    useEffect(() => {
        const initializeGoodDataBackend = async () => {
            try {
                const goodDataBackendInstance = tigerBackendFactory({
                        hostname: GOODDATA_HOST
                    }).withAuthentication(
                        new TigerTokenAuthProvider(GOODDATA_PAT)
                    );

                setBackend(goodDataBackendInstance);
                setIsLoadingBackend(false);
            } catch (error) {
                console.error("GoodData Backend Initialization Error:", error);
                setBackendError("Failed to initialize GoodData dashboard. Please check console for details and ensure configuration (host, PAT) is correct.");
                setIsLoadingBackend(false);
            }
        };

        initializeGoodDataBackend();
    }, []); // Empty dependency array ensures this runs only once on component mount

    // Memoize the filters to avoid unnecessary re-renders of the dashboard.
    // This function will re-run only when selectedOrganization or selectedSubOrganization props change.
    const getDashboardFilters = useCallback(() => {
        const filters = [];

        // Apply Organization filter if a value is provided
        if (selectedOrganization) {
            // Split comma-separated values, trim whitespace, and filter out empty strings
            const orgValues = selectedOrganization.split(',').map(item => item.trim()).filter(Boolean);
            if (orgValues.length > 0) {
                filters.push(
                    // Use newPositiveAttributeFilter with uriRef for the attribute and the array of values
                    newPositiveAttributeFilter(
                        uriRef(ORGANIZATION_ATTRIBUTE_IDENTIFIER), // Convert string identifier to ObjRef
                        orgValues,                                 // Array of values to filter by
                        true                                       // 'true' for 'in' (include) operator
                    )
                );
            }
        }

        // Apply Sub-organization filter if a value is provided
        if (selectedSubOrganization) {
            const subOrgValues = selectedSubOrganization.split(',').map(item => item.trim()).filter(Boolean);
            if (subOrgValues.length > 0) {
                filters.push(
                    newPositiveAttributeFilter(
                        uriRef(SUB_ORGANIZATION_ATTRIBUTE_IDENTIFIER), // Convert string identifier to ObjRef
                        subOrgValues,                                  // Array of values to filter by
                        true                                           // 'true' for 'in' (include) operator
                    )
                );
            }
        }
        return filters;
    }, [selectedOrganization, selectedSubOrganization]);

    // --- Loading and Error Handling UI ---
    if (isLoadingBackend) {
        return <div style={{ textAlign: 'center', padding: '20px', fontSize: '1.2em' }}>Loading GoodData services...</div>;
    }

    if (backendError) {
        return <div style={{ color: 'red', textAlign: 'center', padding: '20px', border: '1px solid red', borderRadius: '5px' }}>
            <strong>Error:</strong> {backendError}
        </div>;
    }

    if (!backend) {
        // This case should ideally be caught by isLoadingBackend and backendError, but serves as a final fallback.
        return <div style={{ textAlign: 'center', padding: '20px' }}>GoodData backend could not be initialized.</div>;
    }

    // --- Render the GoodData Dashboard ---
    return (
        <>
        {/* <BackendProvider backend={backend}>
            <WorkspaceProvider workspace={'62dd165920bd44feb45fb89def254196'}>
                <Dashboard dashboard={'2a6e103a-6b29-483d-9e0c-95651d24ca12'} />
            </WorkspaceProvider>
        </BackendProvider> */}

        <BackendProvider backend={backend}>
                <WorkspaceProvider workspace={GOODDATA_WORKSPACE_ID}>
                    <Dashboard
                        dashboard={GOODDATA_DASHBOARD_ID}
                        filters={getDashboardFilters()}
                        onLoadingChanged={(loading) => console.log('Dashboard loading:', loading)}
                        onError={(error) => console.error('Dashboard error:', error)}
                    />
                </WorkspaceProvider>
            </BackendProvider>
        </>
    );
};

export default GoodDataDashboardEmbed;
Also App.js below,
Copy code
import React, { useState } from 'react';
import GoodDataDashboardEmbed from './GoodDataDashboardEmbed';

import "@gooddata/sdk-ui-filters/styles/css/main.css";
import "@gooddata/sdk-ui-charts/styles/css/main.css";
import "@gooddata/sdk-ui-geo/styles/css/main.css";
import "@gooddata/sdk-ui-pivot/styles/css/main.css";
import "@gooddata/sdk-ui-kit/styles/css/main.css";
import "@gooddata/sdk-ui-ext/styles/css/main.css";

function App() {
    // State to hold the dynamic filter values entered by the user
    // You can set default values here, or leave them empty ('')
    const [organizationValue, setOrganizationValue] = useState('ifc'); // Example default: starts with 'ifc'
    const [subOrganizationValue, setSubOrganizationValue] = useState(''); // Example default: empty

    // Handlers to update state when input fields change
    const handleOrgChange = (e) => {
        setOrganizationValue(e.target.value);
    };

    const handleSubOrgChange = (e) => {
        setSubOrganizationValue(e.target.value);
    };

    return (
        <div className="App" style={{ fontFamily: 'Arial, sans-serif', padding: '20px', maxWidth: '1200px', margin: '0 auto' }}>
            <h1 style={{ textAlign: 'center', color: '#2c3e50', marginBottom: '30px' }}>
                GoodData Dashboard with Dynamic React Filters
            </h1>

            {/* The GoodData Dashboard Embed Component */}
            <GoodDataDashboardEmbed
                selectedOrganization='ifc'
                selectedSubOrganization='ifc_boston-metal'
            />
        </div>
    );
}

export default App;
Package.json below,
Copy code
{
  "name": "gooddata-dashboard-webpack",
  "version": "1.0.0",
  "private": true,
  "dependencies": {
    "@gooddata/sdk-backend-tiger": "~10.4.0",
    "@gooddata/sdk-model": "~10.4.0",
    "@gooddata/sdk-ui": "~10.4.0",
    "@gooddata/sdk-ui-dashboard": "~10.4.0",
    "@reduxjs/toolkit": "^2.8.2",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-scripts": "5.0.1",
    "typescript": "^4.9.5"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  },
  "devDependencies": {
    "@types/react-dom": "^19.1.6"
  }
}
Please check and let me know if anything am missing.