Closed zackgp closed 2 years ago
Hi @zackgp thanks for reporting the issue, let me dig into this a little further, I will get back to you before end of day. Thanks.
Hi @zackgp, I did my best to try and reproduce your issue but I couldn't successfully reproduce it (everything worked as expected), see my attempt below and let me know if i'm missing anything. I tried this using launchdarkly-react-client-sdk
version 2.23.0
and 2.22.0
.
const clientSideID = 'my-client-id';
let _LDProvider;
const getLDProvider = async () => {
if (_LDProvider) {
return _LDProvider;
}
_LDProvider = await asyncWithLDProvider({
clientSideID: clientSideID,
deferInitialization: true
});
return _LDProvider;
};
// Using function component to try and reproduce issue
const MyComponent = ({ flags, ldClient }) => {
console.log(flags.exampleText); // true <--- expected
console.log(ldClient.allFlags()['example-text']); // true. <--- expected
return <div>This is a test from function component</div>
}
const WrappedComponent = withLDConsumer()(MyComponent)
// Using class component to try and reproduce issue
class MyClassComponent extends React.Component {
render() {
const { flags, ldClient } = this.props;
console.log(flags.exampleText); // true <--- expected
console.log(ldClient.allFlags()['example-text']); // true. <--- expected
return <div>This is a test from class component</div>
}
}
const WrappedClassComponent = withLDConsumer()(MyClassComponent)
const renderApp = async () => {
const LDProvider = await getLDProvider();
ReactDOM.render(
<React.StrictMode>
<LDProvider>
<WrappedClassComponent />
</LDProvider>
</React.StrictMode>,
document.getElementById('root')
)
}
// I used setInterval here to try to simulate re-rendering multiple times as you explained
setInterval(() => {
renderApp();
}, 5000);
Hi @ctawiah , thanks for your attempt and I'm sorry that I mentioned one important thing very inconspicuous, that is: do the identify.
Please let me do a slight change to the MyClassComponent
:
// Using class component to try and reproduce issue
class MyClassComponent extends React.Component {
componentDidMount() {
const { ldClient } = this.props;
ldClient.identify({ key: 'test-user' })
}
render() {
const { flags, ldClient } = this.props;
console.log(flags.exampleText);
console.log(ldClient.allFlags()['example-text']);
return <div>This is a test from class component</div>
}
}
const WrappedClassComponent = withLDConsumer()(MyClassComponent)
And please also let the flag exampleText
evaluated to false
by default, and evaluated to true
only for the user test-user
.
By the way, could you please able to invite me to the test project you created for reproducing the issue on the LaunchDarkly system. (My LD account email address is: zack.qiu@globalpay.com)
Hi @zackgp, I modified the example with ldClient.identify({ key: 'test-user' })
but still couldn't reproduce the issue. I created a repo with the example I used and added you as an external contributor (please check your email for the invitation). Let me know if theres anything else I need to modify in the example to reproduce the issue.
Hi @ctawiah, thanks a lot for the invitation and the example repo so I could clone it down and do some tests locally 🙏
The example code is almost close to the condition to reproduce the case. Since in our real system the SubC3
is frequently been removed and added back to the DOM tree, to simulate this condition, I added one line to the callback passed to setInterval
:
const timer = setInterval(() => {
ReactDOM.unmountComponentAtNode(document.getElementById('root')) // simulate the node been removed
renderApp();
}, 5000);
Then, the issue comes out again! I guess you can have a try and will see it in this case. A lot of Thanks!
Hi @zackgp, thanks for providing the additional details, I was able to successfully reproduce the issue. Your suggested solution should help resolve it, we will need to make some small tweaks to it to filter targeted flags
when specified. We will work on getting a fix out for this - I'll keep you posted when the fix is out. Thanks.
This is resolved in version 2.23.1.
Description We have a "partially" React app. "Partially" here means the React root node is a sub-node of the app's DOM tree.
In this case, our SubC3 (React root) node will mount/unmount many times in our app's lifecycle.
The implementation of React root is almost like the code below:
The problem comes out when any time our
reactRoot
re-renders.For example:
Analysis
As dive into the implementation of
asyncWithLDProvider
, we find the problem might because theLDProvider
use theflags
fetched at the initialize time (const { flags: fetchedFlags, ldClient } = await initLDClient(clientSideID, user, reactOptions, options, flags);
).When the first time
LDProvider
renders andldClient.identify
be called, theldClient
fetched the latest flags, it found the latest flags are different than the flags fetched at the initialize time, so theldClient.on('change')
callback will be called.When the second time and after the
LDProvider
renders, no matter how many timesldClient.identify
be called again, theldClient
will found it's flags are the same as the latest fetched flags, so theldClient.on('change')
callback will not be called again. TheLDProvider
's flags will fallback tofetchedFlags
got at the initialize time.Suggestion From our view, a minor change could fix this problem while still keeping the code semantically correct, that is:
Any time,
LDProvider
renders/re-renders, it will pop out the latest flags from theldClient
, so theLDProvider
/LDConsumer
will always got the latest flags no matter how many times it mounted/unmounted or renders/re-renders.Expected behavior The
flags
extract fromLDProvider
/LDConsumer
keeps the same as it inldClient
.SDK version launchdarkly-js-client-sdk: 2.19.0 launchdarkly-react-client-sdk: 2.22.0
Not familiar with the issue format here, please let me know and I will fix it if I have something missed or wrong.