Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 48 additions & 11 deletions biome.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
"$schema": "https://biomejs.dev/schemas/2.3.5/schema.json",
"vcs": { "enabled": false, "clientKind": "git", "useIgnoreFile": false },
"files": {
"ignoreUnknown": false,
"ignore": ["src/__generated__/**/*", ".cache/**/*", "dist/**/*"]
"includes": ["**", "!**/src/__generated__/**/*", "!**/.cache/**/*", "!**/dist/**/*"]
},
"formatter": {
"enabled": true,
Expand All @@ -15,9 +15,9 @@
"lineWidth": 80,
"attributePosition": "auto",
"bracketSpacing": true,
"ignore": ["**/__generated__", "hokusai/*.yml", ".cache/**/*", "dist/**/*"]
"includes": ["**", "!**/__generated__", "!**/hokusai/**/*.yml", "!**/.cache/**/*", "!**/dist/**/*"]
},
"organizeImports": { "enabled": true },
"assist": { "actions": { "source": { "organizeImports": "on" } } },
"linter": {
"enabled": true,
"rules": {
Expand All @@ -32,7 +32,8 @@
"noForEach": "off",
"noUselessFragments": "off",
"useLiteralKeys": "off",
"useOptionalChain": "off"
"useOptionalChain": "off",
"noArguments": "off"
},
"performance": {
"noAccumulatingSpread": "off",
Expand All @@ -42,10 +43,28 @@
"noDangerouslySetInnerHtml": "off"
},
"style": {
"noArguments": "off",
"noNonNullAssertion": "off",
"noUselessElse": "off",
"useNodejsImportProtocol": "off"
"useNodejsImportProtocol": "off",
"noRestrictedImports": {
"level": "error",
"options": {
"paths": {
"superagent": "Please use native fetch instead.",
"sharify": "Please use getENV instead.",
"unstated": "Please just use React state.",
"react-waypoint": "Please use `useIntersectionObserver`",
"relay-runtime": {
"importNames": ["graphql"],
"message": "Please import `graphql` from `react-relay`."
},
"@unleash/proxy-client-react": {
"importNames": ["useFlag", "useVariant"],
"message": "Please import useFlag from `System/FeatureFlags/useFlag.ts` and useVariant from `System/FeatureFlags/useVariant.ts` instead."
}
}
}
}
},
"suspicious": {
"noArrayIndexKey": "off",
Expand All @@ -54,7 +73,7 @@
"noImplicitAnyLet": "off"
}
},
"ignore": ["src/__generated__/**/*", ".cache/**/*", "dist/**/*"]
"includes": ["**", "!**/src/__generated__/**/*", "!**/.cache/**/*", "!**/dist/**/*"]
},
"javascript": {
"formatter": {
Expand All @@ -70,14 +89,32 @@
}
},
"overrides": [
{ "include": ["*.cy.js", "*.cy.ts"], "linter": { "rules": {} } },
{ "includes": ["**/*.cy.js", "**/*.cy.ts"], "linter": { "rules": {} } },
{
"include": ["webpack/**/*"],
"includes": ["**/webpack/**/*"],
"linter": { "rules": { "suspicious": { "noConsole": "off" } } }
},
{
"include": ["*.test.ts", "*.test.tsx", "*.jest.ts", "**/*.jest.tsx"],
"includes": [
"**/*.test.ts",
"**/*.test.tsx",
"**/*.jest.ts",
"**/*.jest.tsx"
],
"linter": { "rules": { "style": { "noNonNullAssertion": "off" } } }
},
{
"includes": [
"**/System/FeatureFlags/useFlag.ts",
"**/System/FeatureFlags/useVariant.ts"
],
"linter": {
"rules": {
"style": {
"noRestrictedImports": "off"
}
}
}
}
]
}
10 changes: 4 additions & 6 deletions docs/using_unleash.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,14 @@ By following these steps, you can safely manage feature rollouts and experiments

3. **Use the flag in your code**: Refer to the examples in this guide to implement the feature flag in your application.

## Using Feature Flags in React (Client-side)
## Using Feature Flags in React (Isomorphic)

IMPORTANT: `useFlag` is not isomorphic. Meaning you will not have access to the flag on the server side render. Design your UI accordingly.
Our feature flag hooks are **isomorphic** - they work on both server-side render (SSR) and client-side. This prevents hydration mismatches and enables consistent behavior.

### Basic Feature Flag Usage

```tsx
import { useFlag } from "@unleash/proxy-client-react"
import { useFlag } from "System/FeatureFlags/useFlag"

const MyComponent = () => {
const enabled = useFlag("demo-feature")
Expand All @@ -71,10 +71,8 @@ const MyComponent = () => {

### A/B Testing with Variants

IMPORTANT: `useVariant` is not isomorphic. Meaning you will not have access to the variant on the server side render. `variant` will be `"disabled"` until Unleash mounts and fetches.

```tsx
import { useVariant } from "@unleash/proxy-client-react"
import { useVariant } from "System/FeatureFlags/useVariant"
import { useTrackFeatureVariantOnMount } from "System/Hooks/useTrackFeatureVariant"

const MyExperiment = () => {
Expand Down
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
"hokusai": "yarn hokusai dev start --no-build",
"jest": "node_modules/.bin/jest --config jest.config.js",
"jest:debug": "node --inspect node_modules/.bin/jest --runInBand",
"lint": "yarn biome lint --no-errors-on-unmatched",
"lint": "yarn biome lint --no-errors-on-unmatched --fix",
"local-palette-dev": "./scripts/yalc-link-local-palette",
"local-palette-dev:stop": "./scripts/yalc-unlink-local-palette",
"open-consent-modal": "open http://localhost:5000?otreset=false&otpreview=true&otgeo=gb",
Expand Down Expand Up @@ -191,7 +191,7 @@
},
"devDependencies": {
"@artaio/arta-browser": "^2.16.1",
"@biomejs/biome": "1.9.4",
"@biomejs/biome": "^2.3.5",
"@graphql-inspector/core": "1.14.0",
"@graphql-tools/jest-transform": "^2.0.0",
"@loadable/webpack-plugin": "5.15.2",
Expand Down Expand Up @@ -223,6 +223,7 @@
"@types/jest": "27.0.2",
"@types/jwt-decode": "2.2.0",
"@types/loadable__component": "5.13.1",
"@types/loadable__webpack-plugin": "^5.15.1",
"@types/lodash": "4.14.111",
"@types/luxon": "^3.2.0",
"@types/memoize-one": "3.1.1",
Expand All @@ -247,6 +248,7 @@
"@types/testing-library__jest-dom": "^6.0.0",
"@types/testing-library__react": "^10.2.0",
"@types/underscore.string": "0.0.32",
"@types/webpack-node-externals": "^3.0.4",
"@types/yup": "^0.29.13",
"chai": "4.0.0",
"chai-passport-strategy": "^3.0.0",
Expand Down
2 changes: 2 additions & 0 deletions playwright/e2e/collection.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { test, expect } from "@playwright/test"

const artworkGridRenders = async (page: any) => {
// Wait for network to be idle to ensure artwork data has loaded
await page.waitForLoadState("networkidle")
await expect(page.locator("div[data-test='artworkGrid']")).toHaveCount(1)
const gridItems = page.locator("div[data-test='artworkGridItem']")
expect(await gridItems.count()).toBeGreaterThanOrEqual(1)
Expand Down
2 changes: 1 addition & 1 deletion src/Apps/Artwork/Components/ArtworkLightbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
Image,
ResponsiveBox,
} from "@artsy/palette"
import { useFlag } from "@unleash/proxy-client-react"
import { useFlag } from "System/FeatureFlags/useFlag"
import { useSystemContext } from "System/Hooks/useSystemContext"
import { useLocalImage } from "Utils/localImageHelpers"
import { userIsTeam } from "Utils/user"
Expand Down
4 changes: 2 additions & 2 deletions src/Apps/Home/HomeApp.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { Join, Spacer } from "@artsy/palette"
import { MyBidsQueryRenderer } from "Apps/Auctions/Components/MyBids/MyBids"
import { HomeArtworkRecommendationsRailQueryRenderer } from "Apps/Home/Components/HomeArtworkRecommendationsRail"
import { HomeAuctionLotsTabBar } from "Apps/Home/Components/HomeAuctionLotsTabBar"
import { HomeNewWorksFromGalleriesYouFollowRailQueryRenderer } from "Apps/Home/Components/HomeNewWorksFromGalleriesYouFollowRail"
import { HomeRecommendedArtistsRailQueryRenderer } from "Apps/Home/Components/HomeRecommendedArtistsRail"
import { ArtworkGridContextProvider } from "Components/ArtworkGrid/ArtworkGridContext"
import { FlashBannerQueryRenderer } from "Components/FlashBanner"
import type { HomeApp_featuredEventsOrderedSet$data } from "__generated__/HomeApp_featuredEventsOrderedSet.graphql"
Expand All @@ -19,8 +21,6 @@ import { HomeMeta } from "./Components/HomeMeta"
import { HomeStructuredData } from "./Components/HomeStructuredData"
import { HomeTrendingArtistsRailQueryRenderer } from "./Components/HomeTrendingArtistsRail"
import { HomeWorksForYouTabBar } from "./Components/HomeWorksForYouTabBar"
import { HomeRecommendedArtistsRailQueryRenderer } from "Apps/Home/Components/HomeRecommendedArtistsRail"
import { HomeArtworkRecommendationsRailQueryRenderer } from "Apps/Home/Components/HomeArtworkRecommendationsRail"

interface HomeAppProps {
featuredEventsOrderedSet: HomeApp_featuredEventsOrderedSet$data | null
Expand Down
4 changes: 2 additions & 2 deletions src/Apps/Order/Routes/Payment/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ import type { PaymentRouteSetOrderPaymentMutation } from "__generated__/PaymentR
import type { Payment_me$data } from "__generated__/Payment_me.graphql"
import type { Payment_order$data } from "__generated__/Payment_order.graphql"

import { useFlag } from "@unleash/proxy-client-react"
import { useStripePaymentBySetupIntentId } from "Apps/Order/Hooks/useStripePaymentBySetupIntentId"
import { useSetPayment } from "Apps/Order/Mutations/useSetPayment"
import {
type CommitMutation,
injectCommitMutation,
} from "Apps/Order/Utils/commitMutation"
import { getInitialPaymentMethodValue } from "Apps/Order/Utils/orderUtils"
import { useFlag } from "System/FeatureFlags/useFlag"
import { useRouter } from "System/Hooks/useRouter"
// utils, hooks, mutations and system tools
import { extractNodes } from "Utils/extractNodes"
Expand Down Expand Up @@ -386,7 +386,7 @@ export const PaymentRoute: FC<
let title = "An error occurred"
let message =
"Something went wrong. Please try again or contact orders@artsy.net"
let width: undefined | number = undefined
let width: undefined | number

const errorCode = (paymentSetupError as any).code

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
addressWithFallbackValues,
getInitialShippingValues,
} from "Apps/Order/Routes/Shipping/Utils/shippingUtils"
import { useFlag } from "@unleash/proxy-client-react"
import { useFlag } from "System/FeatureFlags/useFlag"
import { useRouter } from "System/Hooks/useRouter"
import { extractNodes } from "Utils/extractNodes"
import createLogger from "Utils/logger"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Box, Clickable, Flex, Join, Spacer, Text } from "@artsy/palette"
import { themeGet } from "@styled-system/theme-get"
import { useFlag } from "@unleash/proxy-client-react"
import { EntityHeaderArtistFragmentContainer } from "Components/EntityHeaders/EntityHeaderArtist"
import { useFlag } from "System/FeatureFlags/useFlag"
import { useRouter } from "System/Hooks/useRouter"
import { extractNodes } from "Utils/extractNodes"
import type { InsightsMedianSalePrice_me$data } from "__generated__/InsightsMedianSalePrice_me.graphql"
Expand Down
2 changes: 1 addition & 1 deletion src/Apps/Settings/Routes/Insights/InsightsRoute.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Join, Spacer } from "@artsy/palette"
import { InsightsCareerHighlightRailFragmentContainer } from "Apps/Settings/Routes/Insights/Components/CareerHighlights/InsightsCareerHighlightRail"
import { InsightsMedianSalePriceFragmentContainer } from "Apps/Settings/Routes/Insights/Components/InsightsMedianSalePrice"
import { MetaTags } from "Components/MetaTags"
import { useFlag } from "@unleash/proxy-client-react"
import { useFlag } from "System/FeatureFlags/useFlag"
import { Media } from "Utils/Responsive"
import type { InsightsRoute_me$data } from "__generated__/InsightsRoute_me.graphql"
import { createFragmentContainer, graphql } from "react-relay"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { ActionType, ContextModule, OwnerType } from "@artsy/cohesion"
import ShareIcon from "@artsy/icons/ShareIcon"
import { Box, Button, Flex, Stack } from "@artsy/palette"
import { useFlag } from "@unleash/proxy-client-react"
import { useMyCollectionTracking } from "Apps/CollectorProfile/Routes/MyCollection/Hooks/useMyCollectionTracking"
import { MyCollectionArtworkGrid } from "Apps/Settings/Routes/MyCollection/Components/MyCollectionArtworkGrid"
import { MetaTags } from "Components/MetaTags"
import { ShareCollectionDialog } from "Components/ShareCollectionDialog"
import { RouterLink } from "System/Components/RouterLink"
import { useFlag } from "System/FeatureFlags/useFlag"
import { cleanLocalImages } from "Utils/localImageHelpers"
import type { MyCollectionRoute_me$data } from "__generated__/MyCollectionRoute_me.graphql"
import { type FC, useEffect, useState } from "react"
Expand Down
2 changes: 1 addition & 1 deletion src/Components/Address/AddressAutocompleteInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
usePrevious,
} from "@artsy/palette"
import type { Address } from "Components/Address/utils"
import { useFlag } from "@unleash/proxy-client-react"
import { useFlag } from "System/FeatureFlags/useFlag"
import { getENV } from "Utils/getENV"
import { throttle, uniqBy } from "lodash"
import { useCallback, useEffect, useReducer } from "react"
Expand Down
6 changes: 2 additions & 4 deletions src/Components/Address/useAddressAutocomplete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import {
} from "@artsy/cohesion"
import type { AutocompleteInputOptionType } from "@artsy/palette"
import type { Address } from "Components/Address/utils"
import { useFlag } from "System/FeatureFlags/useFlag"
import { useAnalyticsContext } from "System/Hooks/useAnalyticsContext"
import { useFlag } from "@unleash/proxy-client-react"
import { getENV } from "Utils/getENV"
import { throttle, uniqBy } from "lodash"
import { useCallback, useEffect, useMemo, useState } from "react"
Expand Down Expand Up @@ -101,9 +101,7 @@ export const useAddressAutocomplete = (
}

if (!apiKey) return null
const url =
"https://us-autocomplete-pro.api.smarty.com/lookup?" +
new URLSearchParams(params).toString()
const url = `https://us-autocomplete-pro.api.smarty.com/lookup?${new URLSearchParams(params).toString()}`

const response = await fetch(url)
const json = await response.json()
Expand Down
2 changes: 1 addition & 1 deletion src/Components/ArtworkFilter/ArtworkFilterPlaceholder.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import {
SkeletonText,
Spacer,
} from "@artsy/palette"
import { useFlag } from "@unleash/proxy-client-react"
import { AppContainer } from "Apps/Components/AppContainer"
import { HorizontalPadding } from "Apps/Components/HorizontalPadding"
import { ArtworkFilterActiveFilters } from "Components/ArtworkFilter/ArtworkFilterActiveFilters"
Expand All @@ -30,6 +29,7 @@ import {
import { Sticky } from "Components/Sticky"
import { useStickyBackdrop } from "Components/Sticky/useStickyBackdrop"
import { useAnalyticsContext } from "System/Hooks/useAnalyticsContext"
import { useFlag } from "System/FeatureFlags/useFlag"
import { Media } from "Utils/Responsive"

interface ArtworkFilterPlaceholderProps extends BoxProps {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { SelectedFiltersCountsLabels } from "Components/ArtworkFilter/ArtworkFilterContext"
import { ResultsFilter } from "./ResultsFilter"
import { useFlag } from "@unleash/proxy-client-react"
import { getArtworkLocationSearchableText } from "Components/ArtworkFilter/Utils/getArtworkLocationSearchableText"
import { useTracking } from "react-tracking"
import { useAnalyticsContext } from "System/Hooks/useAnalyticsContext"
import type { FilterSelectChangeState } from "@artsy/palette"
import {
ActionType,
type CommercialFilterSelectedAll,
ContextModule,
CommercialFilterSelectedAll,
} from "@artsy/cohesion"
import type { FilterSelectChangeState } from "@artsy/palette"
import { SelectedFiltersCountsLabels } from "Components/ArtworkFilter/ArtworkFilterContext"
import { getArtworkLocationSearchableText } from "Components/ArtworkFilter/Utils/getArtworkLocationSearchableText"
import { useFlag } from "System/FeatureFlags/useFlag"
import { useAnalyticsContext } from "System/Hooks/useAnalyticsContext"
import { useTracking } from "react-tracking"
import { ResultsFilter } from "./ResultsFilter"

export interface ArtworkLocationFilterProps {
expanded?: boolean
Expand Down
3 changes: 2 additions & 1 deletion src/Components/ArtworkFilter/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import {
Spacer,
Text,
} from "@artsy/palette"
import { useFlag, useVariant } from "@unleash/proxy-client-react"
import { AppContainer } from "Apps/Components/AppContainer"
import { HorizontalPadding } from "Apps/Components/HorizontalPadding"
import { ArtworkFilterActiveFilters } from "Components/ArtworkFilter/ArtworkFilterActiveFilters"
Expand All @@ -44,6 +43,8 @@ import {
} from "Components/ProgressiveOnboarding/ProgressiveOnboardingImmersiveView"
import { Sticky } from "Components/Sticky"
import { useStickyBackdrop } from "Components/Sticky/useStickyBackdrop"
import { useFlag } from "System/FeatureFlags/useFlag"
import { useVariant } from "System/FeatureFlags/useVariant"
import { useAnalyticsContext } from "System/Hooks/useAnalyticsContext"
import { useSystemContext } from "System/Hooks/useSystemContext"
import { Jump, useJump } from "Utils/Hooks/useJump"
Expand Down
2 changes: 2 additions & 0 deletions src/System/Contexts/SystemContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { getUser } from "Utils/user"
import type { Router } from "found"
import { createContext, useState } from "react"
import type { Environment } from "react-relay"
import type { IToggle } from "unleash-proxy-client"

export type UserPreferences = {
metric: Metric
Expand All @@ -20,6 +21,7 @@ export interface SystemContextState {
interface FeatureFlags {
isEnabled: (flag: string) => boolean
getVariant: (flag: string) => any
toggles: IToggle[]
}

export interface SystemContextProps extends SystemContextState {
Expand Down
3 changes: 2 additions & 1 deletion src/System/FeatureFlags/FeatureFlagContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { getENV } from "Utils/getENV"
export const FeatureFlagProvider: React.FC<
React.PropsWithChildren<IFlagProvider>
> = ({ children }) => {
const { user } = useSystemContext()
const { user, featureFlags } = useSystemContext()

const unleashConfig: IConfig = {
url: `${getENV("UNLEASH_API_URL")}/frontend`,
Expand All @@ -21,6 +21,7 @@ export const FeatureFlagProvider: React.FC<
userId: user?.id,
sessionId: getENV("SESSION_ID"),
},
bootstrap: featureFlags?.toggles || [],
}

return <FlagProvider config={unleashConfig}>{children}</FlagProvider>
Expand Down
Loading