Code Monkey home page Code Monkey logo

react-client's Introduction

Split SDK for React

npm version Build Status

Overview

This SDK is designed to work with Split, the platform for controlled rollouts, which serves features to your users via feature flag to manage your complete customer experience.

Twitter Follow

Compatibility

This SDK is compatible with React 16.3.0 and above, since it uses React Context API.

Some features, such as useSplitClient and useSplitTreatments, use React Hooks API that requires React 16.8.0 or later.

Getting started

Below is a simple example that describes the instantiation and most basic usage of our SDK:

import React from 'react';

// Import SDK functions
import { SplitFactoryProvider, useSplitTreatments } from '@splitsoftware/splitio-react';

// Define your config object
const CONFIG = {
  core: {
    authorizationKey: 'YOUR_SDK_KEY',
    key: 'CUSTOMER_ID'
  }
};

function MyComponent() {
  // Evaluate feature flags with useSplitTreatments hook
  const { treatments: { FEATURE_FLAG_NAME }, isReady } = useSplitTreatments({ names: ['FEATURE_FLAG_NAME'] });

  // Check SDK readiness using isReady prop
  if (!isReady) return <div>Loading SDK ...</div>;

  if (FEATURE_FLAG_NAME.treatment === 'on') {
    // return JSX for on treatment
  } else if (FEATURE_FLAG_NAME.treatment === 'off') {
    // return JSX for off treatment
  } else {
    // return JSX for control treatment
  };
}

function MyApp() {
  return (
    // Use SplitFactoryProvider to instantiate the SDK and makes it available to nested components
    <SplitFactoryProvider config={CONFIG} >
      <MyComponent />
    </SplitFactoryProvider>
  );
}

Please refer to our official docs to learn about all the functionality provided by our SDK and the configuration options available for tailoring it to your current application setup.

Submitting issues

The Split team monitors all issues submitted to this issue tracker. We encourage you to use this issue tracker to submit any bug reports, feedback, and feature enhancements. We'll do our best to respond in a timely manner.

Contributing

Please see Contributors Guide to find all you need to submit a Pull Request (PR).

License

Licensed under the Apache License, Version 2.0. See: Apache License.

About Split

Split is the leading Feature Delivery Platform for engineering teams that want to confidently deploy features as fast as they can develop them. Split’s fine-grained management, real-time monitoring, and data-driven experimentation ensure that new features will improve the customer experience without breaking or degrading performance. Companies like Twilio, Salesforce, GoDaddy and WePay trust Split to power their feature delivery.

To learn more about Split, contact [email protected], or get started with feature flags for free at https://www.split.io/signup.

Split has built and maintains SDKs for:

For a comprehensive list of open source projects visit our Github page.

Learn more about Split:

Visit split.io/product for an overview of Split, or visit our documentation at help.split.io for more detailed information.

react-client's People

Contributors

davidchouinard avatar dependabot[bot] avatar drewmendenhall avatar emilianofant-split avatar emilianosanchez avatar emmaz90 avatar github-actions[bot] avatar gpochettino avatar israphel avatar lfender6445 avatar nicozelaya avatar sanzmauro avatar splitadricejas avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

react-client's Issues

CORS issue

I am getting CORS issues while using this package in our application.
All requests just throw this error:

Access to fetch at 'https://sdk.split.io/api/mySegments/xxx' from origin 'https://xxx.abc' has been blocked by CORS policy: Request header field traceparent is not allowed by Access-Control-Allow-Headers in preflight response.

How do I go about sorting this out?

isReady prop passed to children of SplitTreatments is never true during unit tests

Summary

During normal rendering in localhost mode, SplitTreatments first passes isReady: false to its children and returns the control treatment, but shortly after returns isReady: true and returns the treatment defined in config.features as expected. But during testing using jest and testing-library/react, isReady is always false.

I've set up a simple repository to reproduce the issue here: https://github.com/brentcklein/split-tests

Environment

yarn: 1.21.1
node: v14.4.0
react: 16.13.1
react-scripts: 3.4.1
jest: 24.9.0
testing-library/react: 9.5.0
splitio-react: 1.1.0

Example code

// App.jsx

import React from 'react';
import './App.css';

import { SplitFactory } from "@splitsoftware/splitio-react";

import ExampleSplitComponent from "./ExampleSplitComponent";

function App() {
  const splitConfig = {
    core: {
      authorizationKey: "localhost",
      key: "default",
    },
    features: {
      [`prod-feature-1`]: "on",
      [`prod-feature-2`]: "off",
    },
  };

  

  return (
    <SplitFactory config={splitConfig}>
      <ExampleSplitComponent splits={["prod-feature-1", "prod-feature-2"]} />
    </SplitFactory>
  );
}

export default App;
// ExampleSplitComponent.jsx

import React from "react";

import { SplitTreatments } from "@splitsoftware/splitio-react";

const ExampleSplitComponent = ({ splits }) => {
  return splits.map(split => {
    return (
      <SplitTreatments names={[split]} key={split}>
        {({ treatments, isReady }) => {
          return isReady && treatments[split].treatment === "on"
            ? <div>Split {split} is on</div>
            : <div>Split {split} is off</div>;
        }}
      </SplitTreatments>
    );
  });
}

export default ExampleSplitComponent;
// ExampleSplitComponent.test.jsx

import React from "react";
import { SplitFactory } from "@splitsoftware/splitio-react";
import ExampleSplitComponent from "./ExampleSplitComponent";
import { render, waitForElement, getByText } from "@testing-library/react";

it("renders the proper output based on the Split treatment", async () => {
  const splitConfig = {
    core: {
      authorizationKey: "localhost",
      key: "default",
    },
    features: {
      [`test-feature-on`]: "on",
      [`test-feature-off`]: "off",
    },
  };

  const { container } = render(
    <SplitFactory config={splitConfig}>
      <ExampleSplitComponent splits={["test-feature-on", "test-feature-off"]} />
    </SplitFactory>
  );

  const [ featureOn, featureOff ] = await waitForElement(
    () => [
      getByText(container, "Split test-feature-on is on"),
      getByText(container, "Split test-feature-off is off"),
    ],
    { container }
  );

  expect(featureOn).toBeTruthy();
  expect(featureOff).toBeTruthy();
});

The splits render correctly when run with yarn start, but running the unit test results in Unable to find an element with the text: Split test-feature-on is on. with the container output being

<div>
  <div>
    Split test-feature-on is off
  </div>
  <div>
    Split test-feature-off is off
  </div>
</div>

Issue with SplitClient undefined attributes

Hi,

I have a question related to SplitClient and undefined attributes. Basically if any of the attributes is undefined, attributes are not being passed at all. Is this expected or bug?

e.g. this works fine(attribute someAttribute is passed):

                <SplitClient
                    splitKey="someKey"
                    attributes={{
                        someAttribute: 'test'
                    }}
                >

and this doesn't work, no attributes are being passed at all:

                <SplitClient
                    splitKey="someKey"
                    attributes={{
                        someAttribute: 'test'
                        someAttribute2: undefined,
                    }}
                >

Issue with SplitFactoryProvider

Upgrading to version 1.11.0 with SplitFactoryProvider causes the application to hang indefinitely. However, the same version (1.11.0) works with the deprecated SplitFactory. My custom provider code is as follows:

import * as React from 'react'
import { SplitFactoryProvider } from '@splitsoftware/splitio-react'
import { FullPageSpinner } from 'components/Loader'
import { useUser } from 'context/UserProvider'

const SplitProvider = ({ children }: React.PropsWithChildren) => {
  const user = useUser()
  return user.id ? (
    <SplitFactoryProvider config={{
    core: {
      authorizationKey: SPLIT_API_KEY,
      key: user.id
    },
    impressionListener: {
      logImpression(impressionData) {
        datadogRum.addFeatureFlagEvaluation(impressionData.impression.feature, impressionData.impression.treatment)
      }
    }
  }}>
     {children}
    </SplitFactoryProvider>
  ) : (
    <FullPageSpinner />
  )
}

export default SplitProvider

The child component listens to Split client events, and with the new provider, client.Event.SDK_READY is never called, nor is client.Event.SDK_READY_TIMED_OUT. Since these events are used to determine the loading state, the application hangs. The child component code is as follows:

import React from 'react'
import { useSplitClient } from '@splitsoftware/splitio-react'

const FeatureFlagsProvider = ({ children }: React.PropsWithChildren) => {
  const { client } = useSplitClient()
  const [splitReady, setSplitReady] = React.useState(false)

  React.useEffect(() => {

      client.once(client.Event.SDK_READY, () => {
        // doing some other stuff here....
        setSplitReady(true)
      })

      client.once(client.Event.SDK_READY_TIMED_OUT, () => {
         // doing some other stuff here....
        setSplitReady(true)
      })

      client.on(client.Event.SDK_UPDATE, someUpdateFunction)
    }
  }, [client])
  
  return splitReady ? <>{children}</> : <FullPageSpinner />
}

export { FeatureFlagsProvider }

Switching from SplitFactory to SplitFactoryProvider causes the application to hang indefinitely because splitReady is never set to true. No console errors or visible failures are apparent in the network.

EventEmitter memory leak warning

I receive this warning in console when running the react-client:

MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 init::cache-ready listeners added. Use emitter.setMaxListeners() to increase limit

Screenshot 2024-04-12 at 6 58 39 PM

Update `peerDependencies` to include React 18

❯ npm i
npm ERR! code ERESOLVE
npm ERR! ERESOLVE could not resolve
npm ERR!
npm ERR! While resolving: @splitsoftware/[email protected]
npm ERR! Found: [email protected]
npm ERR! node_modules/react
npm ERR!   react@"18.0.0" from the root project
npm ERR!   peer react@"^16.11.0 || ^17 || ^18" from @auth0/[email protected]
npm ERR!   node_modules/@auth0/auth0-react
npm ERR!     @auth0/auth0-react@"1.10.0" from the root project
npm ERR!   13 more (@emotion/react, @testing-library/react, react-dom, ...)
npm ERR!
npm ERR! Could not resolve dependency:
npm ERR! peer react@"^16.3.0 || ^17.0.0" from @splitsoftware/[email protected]
npm ERR! node_modules/@splitsoftware/splitio-react
npm ERR!   @splitsoftware/splitio-react@"1.4.0" from the root project

can peerDependencies be updated to include "^18.0.0"?

  "peerDependencies": {
    "react": "^16.3.0 || ^17.0.0"
  },

[Feature request] Lazy init of Split client

Hi,

I recently had support ticket open about dynamic keys in the SplitFactory (#2644 if it's relevant to you), and was told that the use case I was looking for wasn't supported, so I've come up with a simple solution.

Use case

On initial load of a client-side application, the Split key is not always directly available. However, you want to right away use SplitFactory and useClient, otherwise you'd conditionally call useClient which isn't allowed (rules of React hooks.) This means that you have to call SplitFactory and useClient on the initial render, which means that with the current setup you have to initiate the Split client with a key you don't have yet.

Proposal

Allow to delay the initialisation of the SplitClient until the ready prop (open for suggestions here 🙂) on SplitFactory is true. Until that is the case, the context would return a mocked client (isReady is false, getTreatment returns control, etc). Here is a basic simplified example:

const ProposedNewSplitFactory = ({ ready, splitKey, children }) => {
  // if ready is not true, return a client with a getTreatment
  // function that always returns control
  const mockClient = {
    isReady: false,
    getTreatment: () => "control",
    description: "a mocked client"
  };
  return (
    <ProposedSplitContext.Provider
      value={ready ? createSplitClient(splitKey) : mockClient}
    >
      {children}
    </ProposedSplitContext.Provider>
  );
};

And here it is in action in a Codesandbox: https://codesandbox.io/s/exciting-wilbur-7tkt2?file=/src/App.js:624-1092.

  • What do you think of the idea?
  • Are you able to implement this?
  • If not, do you have an alternative solution to the problem?

IE 11 support

Does this library support IE 11? I've found "reduce" method in lib/constants.js
image

Do you support NextJS ?

Hi all,

wanted to know if the current react implementation can be used on NextJS projects? or if have to implement the javascript sdk directly to support SSR runtime, I have seen in the past that some react libraries needs access to browser features, so when running on NextJS they are broken because code has to be somehow isomorphic,

Thanks,

Count url not found

"@splitsoftware/splitio-react": "1.2.1",
Screenshot 2020-10-21 at 09 28 40

I get same problem with 1.2.0.

I'm using create-react-app with craco.
When i'm using latest versions then i get error:

./node_modules/@splitsoftware/splitio/es/impressions/hasher/hashImpression32.js
Attempted import error: 'buildKey' is not exported from '../../../../../../src'.

That is because buildKey is imported from '.' and babel-loader compiles it to my root directory.
If i add @splitsoftware to loaderOptions ignore then everything is working except split is starting to make request to this url that does not exist.

Any ideas ?

Programmatically Set Treatments

Hello all,

Is there a way to programmatically set a specific treatment for a given user? So instead of using useTreatments([]) to receive the calculated treatment a user is going to receive, is it possible to set a specific treatment based on, let's say a URL parameter flag (that we set manually)?

Ultimately, we are trying to split test a home landing page that is located at a subdomain, where our users will then be redirected to our root domain to perform the tracked events (add to cart, purchases, etc). It is unclear to me how these events will know the user came from the subdomain landing page and not the root domain landing page.

I'm most likely missing something in the docs.

Thanks,
Skeen

There was a problem configuring the storybook project

Hi✋,I have some problem need to help
When I introduced @splitsoftware/splitio-react into storybook, I add .storybook/preview file. decorators are configured in JS.

#preview.js
const MainDecorator = story => {
  return (
    <SplitFactory config={splitConfiguration()}>
      <CobaltRoot>{story()}</CobaltRoot>
    </SplitFactory>
  )
}
export const decorators = [MainDecorator]

image
When I switch pages, storybook will update the decorators every time I switch pages, and the data will be restored from on to control.
image
wecom-temp-db7aacc876bd2127c453c5da16775fb6
image
wecom-temp-c64762736b272883eda3bc48513933ff

I found it in @splitsoftware/split-react/es/split/services/splitChanges/offline/browser.js, the first data is always cached, As a result, mockupdated returns false after the second time, which makes my page unable to display correctly
Do you have any good attention?Thank you very much

localhost mode is not working in testing

Hi,

We are using react-client in our app, while testing we used localhost mode as mentioned here https://help.split.io/hc/en-us/articles/360020448791-JavaScript-SDK#localhost-mode, but its always returning isReady property to false.

This is the splitprops we are getting, :

splitProps {
        factory: {
          client: [Function: client],
          manager: [Function: manager],
          Logger: {
            enable: [Function: enable],
            setLogLevel: [Function: setLogLevel],
            disable: [Function: disable],
            LogLevel: [Object]
          },
          settings: {
            mode: 'localhost',
            core: [Object],
            scheduler: [Object],
            urls: [Object],
            storage: [Object],
            version: 'react-1.2.1',
            streamingEnabled: true,
            sync: [Object],
            startup: [Object],
            features: [Object],
            runtime: [Object],
            integrations: []
          },
          sharedClientInstances: Set {},
          config: { core: [Object], features: [Object], scheduler: [Object] }
        },
        client: EventEmitter {
          getTreatment: [Function: getTreatment],
          getTreatmentWithConfig: [Function: getTreatmentWithConfig],
          getTreatments: [Function: getTreatments],
          getTreatmentsWithConfig: [Function: getTreatmentsWithConfig],
          track: [Function: track],
          isBrowserClient: false,
          destroy: [Function: destroy]
        },
        isReady: false,
        isReadyFromCache: false,
        isTimedout: false,
        hasTimedout: false,
        isDestroyed: false,
        lastUpdate: 0,
        treatments: { billing_updates: { treatment: 'control', config: null } }
      }

And config being passed is :

{
        core: {
          authorizationKey: 'localhost',
        },
        features: {
          billing_updates: { treatment: 'visa', config: '{ "color": "blue" }' }, //example of a defined config
        },
      }

Any help regarding how to test react-client ?

Tried mocking http requests which is very difficult though as object being returned is very very complex to mock.

Using split for user who are not logged in?

From what i read the key is required and it must be unique key.

In my case, I want to use the split even when my user are not logged in or when new user signing up?

How can i achieve that since this user will not have a unique key?

ISplitFactoryProps.children type is overly restrictive

The type is defined as

/**
 * Children of the SplitFactory component. It can be a functional component (child as a function) or a React element.
 */
children: ((props: ISplitFactoryChildProps) => JSX.Element | null) | JSX.Element | null;

That doesn't include ReactNode, which makes it incompatible with, e.g. PropsWithChildren and FC:

// React types:
type ReactNode = ReactChild | ReactFragment | ReactPortal | boolean | null | undefined;
type PropsWithChildren<P> = P & { children?: ReactNode };

This won't pass type-checking:

const MySplitContext: FC = ({ children }) => {
  const user = useContext(UserContext);

  const splitConfig: SplitIO.IBrowserSettings = {
    core: {
      authorizationKey: process.env.SPLIT_AUTHORIZATION_KEY,
      key: user.email,
    }
  }

  return (
    <SplitFactory config={splitConfig} attributes={user}>
      {children}
    </SplitFactory>
  )
}
Screenshot 2023-10-10 at 14 30 02

One workaround is to wrap children (a ReactNode, per FC and PropsWithChildren) in a Fragment:

return (
  <SplitFactory config={splitConfig} attributes={user}>
    <>{children}</>
  </SplitFactory>
)

The customary practice is to include ReactNode in the list of possible children unless you absolutely need to restrict the possible children, e.g. because you're copying or manipulating them. In this case, I suspect the following would work well:

children: ((props: ISplitFactoryChildProps) => ReactNode) | ReactNode

WebSocket error when pages are refreshed in Firefox

Summary

When refreshing a page wrapped in the SplitFactory provider in Firefox an error is logged to the console:

The connection to https://streaming.split.io/sse... was interrupted while the page was loading. 

There appear to be Firefox specific WebSocket requirements that aren't being covered by the library:

https://bugzilla.mozilla.org/show_bug.cgi?id=712329

Some similar issues:

Environment

Yarn: 1.22.11
Node: 14.17.4
React: 16.13.1
@stripe/react-stripe-js: 1.4.1
Firefox: 91.0.2

Updating the split key

Hello, I'm working on a single-page application where my coworker has implement split to control feature flagging!
We're encountering a problem where when a user first signs in since the split factory has already been rendered once, the client will not update when the configuration updates. This causes a problem as it uses a guest key as the split key until the page is refreshed.

Here's a nonverbatim idea of what we're dealing with

Split Version: 1.1.0

    const SplitFactoryWrapper = () => {
        const { user } = useContext(AuthCtx);
        const splitKey = user.email || "guest_user";

        // getSplitConfig just overwrites baseConfig.core.key
        return <SplitFactory config={getSplitConfig(splitKey)}  updateOnSdkUpdate={true}>{children}</SplitFactory>; 
    };
    const App = () => ( <AuthCtx><SplitFactoryWrapper>{children}</SplitFactoryWrapper></AuthCtx> )

So the solutions I know that will probably work

  1. Delay factory first render until the user is signed-in. (seems hacky as you may truly want to feature flag guest features)
  2. Implementing shouldComponentUpdate & componentDidUpdate lifecycle methods to unsubscribe events & create a new factory and client on configuration change in a pull request.
    --
  3. Refactor code for more appropriate SDK utilization*
  • *I don't know if he did or did not implement this ideally, I have limited working experience with Split's SDK.

Let me know what the best course of action would be here,
Thanks!

LocalStorage in a React-Native App

Hi. I'm testing Split.io services. It works really well, but I'm in doubt about storage.
When we initialize configs we follow the Browser interface. What happens when we select storage type: 'LocalStorage' in a React-Native environment? Should it be able to pass the AsyncStorage module to deal with it?

image

In another service named Apptimize it is possible in the following way:
image

SplitFactory cannot be used as a JSX component

Running @types/react v18.0.15 (or any v18) with @splitsoftware/splitio-react v1.7.1 results in this error.

const rootElement = document.getElementById('root');

if (!rootElement) throw new Error('Failed to find the root element');

ReactDOM.createRoot(rootElement).render(
  <SplitFactory factory={factory} updateOnSdkTimedout={true}>
    <App />
  </SplitFactory>,
);
'SplitFactory' cannot be used as a JSX component.
  Its instance type 'SplitFactory' is not a valid JSX element.
    Types of property 'refs' are incompatible.
      Type '{ [key: string]: import("node_modules/@types/react/index").ReactInstance; }' is not assignable to type '{ [key: string]: React.ReactInstance; }'.
        'string' index signatures are incompatible.
          Type 'import("/node_modules/@types/react/index").ReactInstance' is not assignable to type 'React.ReactInstance'.
            Type 'Component<any, {}, any>' is not assignable to type 'ReactInstance'.
              Type 'import("/node_modules/@types/react/index").Component<any, {}, any>' is not assignable to type 'React.Component<any, {}, any>'.
                The types returned by 'render()' are incompatible between these types.
                  Type 'import("/node_modules/@types/react/index").ReactNode' is not assignable to type 'React.ReactNode'.
                    Type '{}' is not assignable to type 'ReactNode'.ts(2786)

using withSplitFactory() works though.

[Feature Request] Support Suspense

Hi,

I hope you are having a great day where you are.

I've been trying the React SDK, and there's something I think SDK could really benefit from.

Problem

Currently, all the hooks of the client require the consumer to wait until the client is ready before trying to use their values.

This forces consumers of the library to code very defensively and add checks to isReady in every single component that uses Split.

Some use-cases, like setting a default value from a feature flag, become very difficult because of this.

function Component() {
  const { treatments, isReady } = useSplitTreatments({ names: ["isDefaultEnabled"] });
  
  // 💫 Oops! We forgot to check `isReady`! This inital value might not be what we expect!
  const [enabled, setEnabled] = useState(treatments["isDefaultEnabled"] === "on");
 
  // ...
}

Proposal

Suspsense data-fetching could solves this problem by making the component suspend until the client is ready. This way, the user never has to check if the client is ready -- if the component successfully renders, the client is guaranteed to be ready.

This could be implemented in a new hooks, called useSplitTreatmentsSuspense.

function Component() {
  const { treatments } = useSplitTreatmentsSuspense({ names: ["isDefaultEnabled"] });
  
  // If the component didn't suspend here, it means the client is ready, and we can use treatments directly.
  
  // ✅ This becomes valid!
  const [enabled, setEnabled] = useState(treatments["isDefaultEnabled"] === "on");
 
  // ...
}

Under the hood, the useSplitTreatmentsSuspense would throw a promise and suspend when the client is not ready. This promise would resolve once the client becomes ready, resuming the rendering of the component.

Unfortunately, there is currently no way to implement Suspense data-fetching in user-land when using the React SDK. This is because the Split client is completely hidden from the consumers of the library until the client is ready, preventing the user from listening to the activation event of the client and implementing useSplitTreatmentsSuspense themselves. This forces users who want to use Suspense to fallback to the JavaScript SDK.

Let me know what you think about this proposal, and let me know if I can help with anything.

[Feature request] useTreatment and useTreatmentWithConfig hooks

For our team's use case, we often want to get a single split and its treatments. It seems that getTreatment and getTreatmentWithConfig from the underlying javascript-SDK are not currently available as React hooks, but I may have overlooked it in the codebase!

Here's the API I'm thinking of.

const treatment = useTreatment('my_split');

if (treatment === 'on') {
  return <OnComponent />
}

return <OffComponent />

Would you be open to me submitting a PR for this? Thank you!

isTimedout never reports true

I'm trying to send an event to an internal tracking API whenever the Split SDK client times out, but I'm having no luck using the isTimedout flag.

I've deployed a little reusable test case right here: https://splitio-timeout-hciu0go4e.now.sh/. In the app, either:

  • paste in a fake API key
  • or block the Split.io domain in devtools and paste a real API key

The client will timeout, and client.once(client.Event.SDK_READY_TIMED_OUT) will be triggered, but the isTimedout flag never turns true. I've been fiddling around a bit with the updateOnSdkTimedout flag, but the docs aren't super clear as to where to input it... I've tried to put it on config and as a prop directly on SplitFactory, but neither works.

Here is the code for this example (application here: https://github.com/JCB-K/splitio-timeout):

import {
  useClient,
  withSplitTreatments,
  SplitFactory
} from "@splitsoftware/splitio-react";

const Hooks = () => {
  const client = useClient();
  const { isReady, isTimedout } = client;
  const [timeoutCallBackCalled, setTimedout] = React.useState();

  React.useEffect(() => {
    console.log("split.io: adding client listener");
    client.once(client.Event.SDK_READY_TIMED_OUT, function() {
      setTimedout(true);
    });
  }, [client]);

  return (
    <>
      <h2>Hooks</h2>
      <pre>{JSON.stringify({ isReady, isTimedout })}</pre>
      <h2>Client</h2>
      <pre>{JSON.stringify({ "client.isTimedout": client.isTimedout })}</pre>
      <h2>Client.once</h2>
      <pre>{JSON.stringify({ timeoutCallBackCalled })}</pre>
    </>
  );
};

const Hoc = withSplitTreatments(["abc"])(({ isReady, isTimedout }) => {
  return (
    <>
      <h2>Hoc</h2>
      <pre>{JSON.stringify({ isReady, isTimedout })}</pre>
    </>
  );
});

export default function App() {
  const [apiKey, setApiKey] = React.useState("");
  const [ready, setReady] = React.useState(false);
  return (
    <>
      <input
        placeholder="paste API key here"
        onChange={e => setApiKey(e.target.value)}
        value={apiKey}
      />
      <button type="submit" onClick={() => setReady(true)}>
        Submit
      </button>
      {ready ? (
        <SplitFactory
          config={{
            core: {
              authorizationKey: apiKey,
              key: "somekey"
            },
            startup: {
              readyTimeout: 5 // 5 seconds
            }
            // updateOnSdkTimedout: true
          }}
          //updateOnSdkTimedout={true}
        >
          <Hooks />
          <Hoc />
        </SplitFactory>
      ) : (
        <p>Split Key not loaded</p>
      )}
    </>
  );
}

SSR support with no memory leaks

I've been following @NicoZelaya's useClient suggestion to solve the memory leak reported in #2 without success.

I have a [email protected] application and I want to take advantage of SSR without using Redux, but I'm getting a console message at server-time, with the additional downside that no children component is rendered in the client-side:

Shared Client not supported by the storage mechanism. Create isolated instances instead.

This HOC wraps App:

// in withSplit.tsx
const unmountSplitClient = (client: SplitIO.IClient | null) => {
  if (client) {
    client.destroy();
  }
}

// Need a class to implement componentWillUnmount
class ServerSplitDestroyer extends React.Component {
  private client: SplitIO.IClient;

  constructor(props: { client: SplitIO.IClient }) {
    super(props);
    this.client = props.client;
  }

  // server-side unmount
  componentWillUnmount() {
    unmountSplitClient(this.client);
  }

  render() {
    return (<></>)
  }
}

const ClientDestroyer: NextPage<AType> = ({ splitioKey, children }) => {
  const client = useClient(splitioKey);

  // client-side unmount
  React.useEffect(() => {
    return function _unmountSplitClient() {
      unmountSplitClient(client);
    }
  }, [client]);

  return (
    <>
    <ServerSplitDestroyer />
    {children}
    </>
  );
}

export function withSplit() {
  return function withSplitFactoryHOC<OuterProps>(WrappedComponent: React.ComponentType<OuterProps>) {
    return (props: OuterProps) => {
      return (
        <SplitFactory config={setConfig(key)} >
          {(splitProps) => {
            return (
              <ClientDestroyer splitioKey={key}>
                <WrappedComponent {...props} {...splitProps} />
              </ClientDestroyer>
            )
          }}
        </SplitFactory>
      )
    }
  }
}

How can I use split.io in a next.js application with SSR support?

Make ISplitTreatmentChildProps available in exported types

It seems that the ISplitTreatmentChildProps interface is not currently being included in the React SDKs main type exports. Is that intentional?

In order to work around this so that I could correctly type my application integrating split I ended up having to import it from the src/types directory.

Should this interface be exported or is there a different type I should be using?

Screenshot 2023-09-12 at 22 14 46

Screenshot 2023-09-12 at 22 14 54

Thanks!

SplitTreatments will not re-render when external state changes

The issue is here

shouldComponentUpdate(nextProps: Readonly<ISplitTreatmentsProps>) {

in the following code...

  shouldComponentUpdate(nextProps: Readonly<ISplitTreatmentsProps>) {
    return !shallowEqual(this.props.names, nextProps.names) ||
      !shallowEqual(this.props.attributes, nextProps.attributes);
  }

You intentionally do not re-render, even though your component was given a newly constructed render function. The issue this causes is that if that render function is a closure around a local state value, it will not be reflected appropriately...

Here's a quick pseudo-example...

function MyThing() {
  const [test, setTest] = useState("not ready");
  setTimeout(() => setTest("ready"), 1000);
  return (
    <SplitTreatments names={features} attributes={attributes}>
      {(response): JSX.Element | null => (<div>{test}</div>)
    </SplitTreatments>
}

The rendered div content should change after 1s to ready "ready"; however, due to the above code in shouldComponentUpdate, the child is not re-rendered even though the state has changed.

Function returned by useTrack is recreated on every render

Hi there, first of all thanks for creating and maintaining this codebase!

In my code-base i create a custom hook that basically looks like this (simplified)

import {useTrack } from '@splitsoftware/splitio-react'

const useExperimentTrack = () => {
    const track = useTrack()

    // some other logic

    return useCallback(
        (event) => {

            // some logic to add data to the event

            // track event to split queue
            track(event)

          // do some other stuff
        },
        [track]
    )
}

I deliberately use useCallback in order to not unnecessarily recreate the function and, so i can use it as a dependency in useEffect in other places in the code and trust its not triggered.

Something like this very contrived example, where i would expect the useEffect to only trigger when isImportantBoolean changes.

import React from 'react'
import { useTaskEventTracking } from './useTaskEventTracking'

const Example = () => {
    const [isImportantBoolean, setIsImportantBoolean] = React.useState(false)
    const { trackTaskEvent } = useTaskEventTracking()

    React.useEffect(() => {
        if (isImportantBoolean) {
            trackTaskEvent({'magic':' 'has happened')
        }
    }, [trackTaskEvent, isImportantBoolean])

    return (
        <div>
            <button onClick={() => setIsImportantBoolean(!isImportantBoolean)}>
                rererender
            </button>
        </div>
    )
}

export default Example

My issue is that in useTrack(here), the function is (i think) always recreated and thus the function in my custom hook is always recreated and thus the useEffect in the Example component triggers on every re-render.

I'm not 100% familiar with the inner workings of the split client but do you think it would be possible to change the code in such a way that the function is not always recreated?

Maybe something like:

const useTrack = (key, trafficType) => {
  const client = checkHooks(ERROR_UTRACK_NO_USECONTEXT) ? useClient(key, trafficType) : null;
  return React.useCallback(()=> client ? client.track.bind(client) : noOpFalse,[client]);
};

Any other suggestions would be appreciated as well!

SplitTreatments does not set set isReady to true

The SplitTreatments component is broken on v1.2.0 and up. As per this article
, you should be able to consume the isReady flag from the render props of this component. However, on v1.2.0 and v.1.2.1 the isReady flag never turns from false to true, leaving our components in a fallback state permanently.

However, the isReady flag from the SplitContext still works fine.

Can't resolve dependencies

after adding the React SDK via

yarn add @splitsoftware/[email protected]

the app build fails with the following errors

Critical dependency: the request of a dependency is an expression
 @ ./node_modules/encoding/lib/encoding.js
 @ ./node_modules/@splitsoftware/splitio/node_modules/node-fetch/lib/index.js
 @ ./node_modules/@splitsoftware/splitio/lib/services/getFetch/node.js
 @ ./node_modules/@splitsoftware/splitio/lib/services/transport/index.js
 @ ./node_modules/@splitsoftware/splitio/lib/services/impressions/index.js
 @ ./node_modules/@splitsoftware/splitio/lib/metrics/index.js
 @ ./node_modules/@splitsoftware/splitio/lib/factory/online.js
 @ ./node_modules/@splitsoftware/splitio/lib/index.js
 @ ./node_modules/@splitsoftware/splitio-react/lib/index.js
 @ ./src/components/split-provider/split-provider.tsx
 @ ./src/components/split-provider/index.ts
 @ ./src/ng/react-bridge/index.ts
 @ ./src/ng/components/attack-monitor/index.ts
 @ multi (webpack)-dev-server/client?http://localhost:19090 ./src/org ./src/ng/components/syslog ./src/ng/components/preferences ./src/ng/components/attack-monitor ./src/ng/components/attack-status ./src/ng/components/promise-service ./src/ng/components/superadmin-general-settings ./src/ng/components/discussion ./src/ng/components/loading ./src/ng/components/vulnerability-trend-chart ./src/ng/components/service-utils

ERROR in ./node_modules/ioredis/built/cluster/ClusterOptions.js
Module not found: Error: Can't resolve 'dns' in 'S:\IdeaProjects\team-ui\node_modules\ioredis\built\cluster'
 @ ./node_modules/ioredis/built/cluster/ClusterOptions.js 3:14-28
 @ ./node_modules/ioredis/built/cluster/index.js
 @ ./node_modules/ioredis/built/index.js
 @ ./node_modules/@splitsoftware/splitio/lib/storage/RedisAdapter.js
 @ ./node_modules/@splitsoftware/splitio/lib/storage/node.js
 @ ./node_modules/@splitsoftware/splitio/lib/index.js
 @ ./node_modules/@splitsoftware/splitio-react/lib/index.js
 @ ./src/components/split-provider/split-provider.tsx
 @ ./src/components/split-provider/index.ts
 @ ./src/ng/react-bridge/index.ts
 @ ./src/ng/components/attack-monitor/index.ts
 @ multi (webpack)-dev-server/client?http://localhost:19090 ./src/org ./src/ng/components/syslog ./src/ng/components/preferences ./src/ng/components/attack-monitor ./src/ng/components/attack-status ./src/ng/components/promise-service ./src/ng/components/superadmin-general-settings ./src/ng/components/discussion ./src/ng/components/loading ./src/ng/components/vulnerability-trend-chart ./src/ng/components/service-utils

ERROR in ./node_modules/@splitsoftware/splitio/lib/services/splitChanges/offline/node.js
Module not found: Error: Can't resolve 'fs' in 'S:\IdeaProjects\team-ui\node_modules\@splitsoftware\splitio\lib\services\splitChanges\offline'
 @ ./node_modules/@splitsoftware/splitio/lib/services/splitChanges/offline/node.js 10:33-46
 @ ./node_modules/@splitsoftware/splitio/lib/producer/updater/SplitChangesFromFileSystem.js
 @ ./node_modules/@splitsoftware/splitio/lib/producer/offline/node.js
 @ ./node_modules/@splitsoftware/splitio/lib/factory/offline.js
 @ ./node_modules/@splitsoftware/splitio/lib/index.js
 @ ./node_modules/@splitsoftware/splitio-react/lib/index.js
 @ ./src/components/split-provider/split-provider.tsx
 @ ./src/components/split-provider/index.ts
 @ ./src/ng/react-bridge/index.ts
 @ ./src/ng/components/attack-monitor/index.ts
 @ multi (webpack)-dev-server/client?http://localhost:19090 ./src/org ./src/ng/components/syslog ./src/ng/components/preferences ./src/ng/components/attack-monitor ./src/ng/components/attack-status ./src/ng/components/promise-service ./src/ng/components/superadmin-general-settings ./src/ng/components/discussion ./src/ng/components/loading ./src/ng/components/vulnerability-trend-chart ./src/ng/components/service-utils

ERROR in ./node_modules/ioredis/built/cluster/util.js
Module not found: Error: Can't resolve 'net' in 'S:\IdeaProjects\team-ui\node_modules\ioredis\built\cluster'
 @ ./node_modules/ioredis/built/cluster/util.js 4:14-28
 @ ./node_modules/ioredis/built/cluster/index.js
 @ ./node_modules/ioredis/built/index.js
 @ ./node_modules/@splitsoftware/splitio/lib/storage/RedisAdapter.js
 @ ./node_modules/@splitsoftware/splitio/lib/storage/node.js
 @ ./node_modules/@splitsoftware/splitio/lib/index.js
 @ ./node_modules/@splitsoftware/splitio-react/lib/index.js
 @ ./src/components/split-provider/split-provider.tsx
 @ ./src/components/split-provider/index.ts
 @ ./src/ng/react-bridge/index.ts
 @ ./src/ng/components/attack-monitor/index.ts
 @ multi (webpack)-dev-server/client?http://localhost:19090 ./src/org ./src/ng/components/syslog ./src/ng/components/preferences ./src/ng/components/attack-monitor ./src/ng/components/attack-status ./src/ng/components/promise-service ./src/ng/components/superadmin-general-settings ./src/ng/components/discussion ./src/ng/components/loading ./src/ng/components/vulnerability-trend-chart ./src/ng/components/service-utils

ERROR in ./node_modules/ioredis/built/connectors/StandaloneConnector.js
Module not found: Error: Can't resolve 'net' in 'S:\IdeaProjects\team-ui\node_modules\ioredis\built\connectors'
 @ ./node_modules/ioredis/built/connectors/StandaloneConnector.js 3:14-28
 @ ./node_modules/ioredis/built/connectors/index.js
 @ ./node_modules/ioredis/built/redis/index.js
 @ ./node_modules/ioredis/built/index.js
 @ ./node_modules/@splitsoftware/splitio/lib/storage/RedisAdapter.js
 @ ./node_modules/@splitsoftware/splitio/lib/storage/node.js
 @ ./node_modules/@splitsoftware/splitio/lib/index.js
 @ ./node_modules/@splitsoftware/splitio-react/lib/index.js
 @ ./src/components/split-provider/split-provider.tsx
 @ ./src/components/split-provider/index.ts
 @ ./src/ng/react-bridge/index.ts
 @ ./src/ng/components/attack-monitor/index.ts
 @ multi (webpack)-dev-server/client?http://localhost:19090 ./src/org ./src/ng/components/syslog ./src/ng/components/preferences ./src/ng/components/attack-monitor ./src/ng/components/attack-status ./src/ng/components/promise-service ./src/ng/components/superadmin-general-settings ./src/ng/components/discussion ./src/ng/components/loading ./src/ng/components/vulnerability-trend-chart ./src/ng/components/service-utils

ERROR in ./node_modules/ioredis/built/connectors/SentinelConnector/index.js
Module not found: Error: Can't resolve 'net' in 'S:\IdeaProjects\team-ui\node_modules\ioredis\built\connectors\SentinelConnector'
 @ ./node_modules/ioredis/built/connectors/SentinelConnector/index.js 3:14-28
 @ ./node_modules/ioredis/built/index.js
 @ ./node_modules/@splitsoftware/splitio/lib/storage/RedisAdapter.js
 @ ./node_modules/@splitsoftware/splitio/lib/storage/node.js
 @ ./node_modules/@splitsoftware/splitio/lib/index.js
 @ ./node_modules/@splitsoftware/splitio-react/lib/index.js
 @ ./src/components/split-provider/split-provider.tsx
 @ ./src/components/split-provider/index.ts
 @ ./src/ng/react-bridge/index.ts
 @ ./src/ng/components/attack-monitor/index.ts
 @ multi (webpack)-dev-server/client?http://localhost:19090 ./src/org ./src/ng/components/syslog ./src/ng/components/preferences ./src/ng/components/attack-monitor ./src/ng/components/attack-status ./src/ng/components/promise-service ./src/ng/components/superadmin-general-settings ./src/ng/components/discussion ./src/ng/components/loading ./src/ng/components/vulnerability-trend-chart ./src/ng/components/service-utils

ERROR in ./node_modules/ioredis/built/connectors/StandaloneConnector.js
Module not found: Error: Can't resolve 'tls' in 'S:\IdeaProjects\team-ui\node_modules\ioredis\built\connectors'
 @ ./node_modules/ioredis/built/connectors/StandaloneConnector.js 4:14-28
 @ ./node_modules/ioredis/built/connectors/index.js
 @ ./node_modules/ioredis/built/redis/index.js
 @ ./node_modules/ioredis/built/index.js
 @ ./node_modules/@splitsoftware/splitio/lib/storage/RedisAdapter.js
 @ ./node_modules/@splitsoftware/splitio/lib/storage/node.js
 @ ./node_modules/@splitsoftware/splitio/lib/index.js
 @ ./node_modules/@splitsoftware/splitio-react/lib/index.js
 @ ./src/components/split-provider/split-provider.tsx
 @ ./src/components/split-provider/index.ts
 @ ./src/ng/react-bridge/index.ts
 @ ./src/ng/components/attack-monitor/index.ts
 @ multi (webpack)-dev-server/client?http://localhost:19090 ./src/org ./src/ng/components/syslog ./src/ng/components/preferences ./src/ng/components/attack-monitor ./src/ng/components/attack-status ./src/ng/components/promise-service ./src/ng/components/superadmin-general-settings ./src/ng/components/discussion ./src/ng/components/loading ./src/ng/components/vulnerability-trend-chart ./src/ng/components/service-utils

ERROR in ./node_modules/ioredis/built/connectors/SentinelConnector/index.js
Module not found: Error: Can't resolve 'tls' in 'S:\IdeaProjects\team-ui\node_modules\ioredis\built\connectors\SentinelConnector'
 @ ./node_modules/ioredis/built/connectors/SentinelConnector/index.js 5:14-28
 @ ./node_modules/ioredis/built/index.js
 @ ./node_modules/@splitsoftware/splitio/lib/storage/RedisAdapter.js
 @ ./node_modules/@splitsoftware/splitio/lib/storage/node.js
 @ ./node_modules/@splitsoftware/splitio/lib/index.js
 @ ./node_modules/@splitsoftware/splitio-react/lib/index.js
 @ ./src/components/split-provider/split-provider.tsx
 @ ./src/components/split-provider/index.ts
 @ ./src/ng/react-bridge/index.ts
 @ ./src/ng/components/attack-monitor/index.ts
 @ multi (webpack)-dev-server/client?http://localhost:19090 ./src/org ./src/ng/components/syslog ./src/ng/components/preferences ./src/ng/components/attack-monitor ./src/ng/components/attack-status ./src/ng/components/promise-service ./src/ng/components/superadmin-general-settings ./src/ng/components/discussion ./src/ng/components/loading ./src/ng/components/vulnerability-trend-chart ./src/ng/components/service-utils
Child html-webpack-plugin for "ng\admin_index.html":
     1 asset
    Entrypoint undefined = ng/admin_index.html
    [./node_modules/html-webpack-plugin/lib/loader.js!./admin_index.html] 1.89 KiB {0} [built]
    [./node_modules/html-webpack-plugin/node_modules/lodash/lodash.js] 527 KiB {0} [built]
    [./node_modules/webpack/buildin/global.js] (webpack)/buildin/global.js 472 bytes {0} [built]
    [./node_modules/webpack/buildin/module.js] (webpack)/buildin/module.js 497 bytes {0} [built]
Child html-webpack-plugin for "ng\index.html":
     1 asset
    Entrypoint undefined = ng/index.html
    [./node_modules/html-webpack-plugin/lib/loader.js!./index.html] 1.91 KiB {0} [built]
    [./node_modules/html-webpack-plugin/node_modules/lodash/lodash.js] 527 KiB {0} [built]
    [./node_modules/webpack/buildin/global.js] (webpack)/buildin/global.js 472 bytes {0} [built]
    [./node_modules/webpack/buildin/module.js] (webpack)/buildin/module.js 497 bytes {0} [built]
i ?wdm?: Failed to compile.
  • react version -> "react": "16.9.0"
  • split version -> "@splitsoftware/splitio-react": "1.2.0"

impressionListener called multiple times for the same key and attributes

Hi,

Following the documentation I implemented the impressionListener, but the function was called multiple times for the same key and attributes.

Is that expected? How could I treat this situation to call once by evaluation?

I am trying to send the impression data to the fullstory, but it's necessary call once by session key and attributes or evaluation.

Code:

import { SplitFactory } from '@splitsoftware/splitio-react';

const withFeatureFlag = Component => props => {

  ... omit code ...

  function logImpression(impressionData) {
    console.log("splitio Sending impressions", impressionData)
  }

  const [sdkConfig, setSdkConfig] = useState({
    core: {
      authorizationKey: API_KEY,
      key: "anon-1671743490536"
    },
    impressionListener: {
      logImpression
    }
  });

  const [attributes, setAttributes] = useState({});

  useEffect(() => {

    setSdkConfig({
      core: {
        authorizationKey: API_KEY,
        key: "anon-1671743490536"
      }
    });

  }, [authentication.isSignedIn]);

  useEffect(() => {

    setAttributes({
      realm: application.realm ? application.realm.realm : null
    });

  }, [name]);

  return (
    <SplitFactory config={sdkConfig} attributes={attributes} updateOnSdkTimedout>
      <Component {...props } />;
    </SplitFactory>
  );
};
export default withFeatureFlag;

The usage code:

const App = () => (
    <MyComponents />
);

export default withFeatureFlag(App);

Thanks!

IClient type not defined

When trying to call client.destroy() when created from factory as documented here, I receive type errors in Typescript that destroy is not a method of IClient. It appears to be because that type is not defined in the declaration that comes with the SDK.

site specific feature flags loading after refresh

I have a Context in react for featureFlags which loads feature flags for specific sites or domains. Currently I have to switch domains in order to load that domains featureFlag. Here's a code snippet

const ProvideFeatureFlagsRaw = ({ children }: IProvideFeatureFlagsProps) => {
  const featureFlagsContextValue = useFeatureFlagsProvider();
  return (
    <FeatureFlagsContext.Provider value={featureFlagsContextValue}>
      {children}
    </FeatureFlagsContext.Provider>
  );
};

const SDK_CONFIG = {
  core: {
    authorizationKey: process.env.REACT_APP_SPLIT_API_KEY,
    key: cookie.getJSON(TENANT_COOKIE_NAME)?.domain,
  },
  debug: false,
} as SplitIO.IBrowserSettings;

const withSplit = withSplitFactory(SDK_CONFIG);

const ProvideFeatureFlags = withSplit(ProvideFeatureFlagsRaw);

export default ProvideFeatureFlags;

The useFeatureFlagsProvider() basically is hook which returns all the feature flags with the state either they are on or off

Set client key dynamically not working

Hi,

We are using react sdk, and trying to set key after user is logged in but treatments are not getting updated from component.

Step 1. Initialising sdk with initial config

// where uid = undefined
spliConfig = {
  core: {
    authorizationKey: process.env.SPLIT_IO_KEY,
    key: uid,
    trafficType: 'user',
  }
}

function SplitProvider(splitConfig) {
  return (<SplitFactory config={{splitConfig }}>
      <SplitTreatments names={flagNames}>
    .......
}

Step 2. updating uid to 1233312312

But we are not getting updated treatments even after updating configuration with new value of key with new user id.

Is there any way we can update splitConfig and get new treatments updated ?

We already whitelisted user 1233312312 in split console.

Destroying split clients on `key` changes.

Hello!
So I have an "advanced use case" according to your docs, we currently split traffic in a few ways depending on the feature we're testing out. This could be scoped to a user, a set of users within a team, etc.

This has required me to implement a custom useTreatment hook which uses useSplitClient under the hood. However, I'm running into a few issues with my implementation:

  • when a user signs out, and signs into a separate account I now have two split clients, with no easy way to destroy the previous split client. This leads to eventEmitter warnings.
  • If a user switches teams, again we have the same issue.

It looks like the destroy method on a client also destroys the factory instance. This doesn't sound good

There's also no way to update the key prop either, as far as I can tell. What's the recommend way of doing this within the react library?

SSE connection takes too long to open

Hi,

I'm trying to update the React SDK in my app but I noticed that the SSE connection takes more time to open in the latest version, look at this:

"@splitsoftware/splitio-react": "1.2.6"
With a fresh refresh the connection appears immediately
image

"@splitsoftware/splitio-react": "1.11.0" Latest
With the latest version an a fresh refresh the connection isn't opened immediately
image

But after 1 minute there it is
image

Any ideas on why this is happening?
Can this behavior be changed to get the connection ready almost immediately?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.