-
Notifications
You must be signed in to change notification settings - Fork 349
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Check if OUIA is set after mounting #2478
Conversation
PatternFly-React preview: https://patternfly-react-pr-2478.surge.sh |
See description on how to use in the library |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wouldn't really change props of children, it is unsafe and if there is going to be an array of children it will break. Same for children wrapped with React.Fragment
.
This is really perfect example for context. We can create context with default value and reuse it everywhere we want to access it. We can also export this context so consumers can use this as well, another benefit is that we could control the value by ourselves in future instead of using localStorage
anybody else can use Provider and set the value there.
const OUIAContext = React.createContext({
isOuia: isOUIAEnvironment()
});
<OUIAContext.Consumer>
{({ isOuia }) => (
<label
//...
</label>
)}
</OUIAContext.Consumer>
@karelhala Context uses render props. Usage would be the same, as it can be inserted at any level. Just like context, it needs a single child. |
I don't really like this approach of checking component render, setting state and rendering again because what you are essentially doing is rendering twice same component. And as we all know rendering is the most expensive operation. If it were used only on staticly rendered pages, I would be fine with that. But this is going to be used on live sites (with JS and window and such) and twice rendering the same elements will take a LOT of time. How about we use Context as I proposed and add decorator as well that would set provider's value after mounting. We'd specifically say that this decorator is mostly for staticly rendered pages. const withOUIA = WrappedComponent => {
class ComponentWithOUIA extends React.Component {
state: WithOUIAState = {
renderWithOUIA: false,
ouiaId: getOUIAUniqueId()
};
componentDidMount() {
const { renderWithOUIA } = this.state;
const isOuia = isOUIAEnvironment();
if (isOuia !== renderWithOUIA) {
this.setState({ renderWithOUIA: isOuia });
}
}
render() {
const { renderWithOUIA, ouiaId } = this.state;
return <OUIAContext.Provider value={{ isOuia: renderWithOUIA, ouiaId }}>
<WrappedComponent {...this.props} />
</OUIAContext.Provider>
}
}
return ComponentWithOUIA;
}; |
@jschuler I agree with @karelhala. Basically what i see here is a mix of of Context with some additional logic of component decorators (like The problem of mixing these two is that you are forcing additional render after initial render (state or props mutation usually seen in decorators). This might be seen as a nit pick but i think we should try to be as efficient as possible. If you are leveraging client side rendering (and most of PF4 users are) you are left with subpar solution. I would prefer having decorator for your static pages and context for client rendering. This would not even mean having duplicate code because you could use the context inside your decorator. And using the React Context comes with all the checks, optimizations etc. |
@karelhala @Hyperkid123 Can you explain what you mean by decorators? Also i am trying to understand how the context version differs all that much. Looks like it's a HOC instead of a render props component. About rendering twice, that would only happen if the local storage key 'ouia' is true, so for normal consumers this wouldn't change how often it gets rendered |
@jschuler It doesn't force a rerender unless the prop is true, but React does have to crawl the tree again and stateless components with OUIA will have to become stateful, so this is a performance hit (although minor). @Hyperkid123 Your suggested decorator is really just setting a flag when we build the docs, which is in #2487 and an alternative to this PR. |
@@ -0,0 +1,40 @@ | |||
import * as React from 'react'; | |||
import { isOUIAEnvironment, getUniqueId as getOUIAUniqueId } from '../helpers/ouia'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
an absolutely critical note - the ouia id should identify an actual object via its uuid or pkey - having a incremented global as data source there is a massive problem
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@RonnyPfannschmidt Why is it a massive problem? It provides unique IDs every page load per-instance of the component that are consistent every page reload. If we were to generate a UUID instead with something like this solution, how could we make sure they're unique without a global data source that keeps a record of them all, is inconsistent between page loads, and has the same problem?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@redallen i believe you completely misunderstood the use for those ids - they are for identifying application object across potentially different pages, different views and different collections - you cannot generate them randomly or from a counter, you have to take database ids or primary keys
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we are now iterating on the ouia spec as we noted that the component id specification is lacking a description of the intent to make this absolutely clear
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@RonnyPfannschmidt We are a component library, have no database IDs or primary keys to map to components we create on the page. How do we comply to the spec *for our docs page at http://patternfly-react.surge.sh/?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@redallen thats why it is critical, a user can pass it in, the user will have the database keys,
for documentation pages/examples, exemplary ids are suitable
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what's the format of these? haven't seen the spec yet. @redallen maybe something that can be passed in as a json string into another local var?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the id should be pretty much expressable as pain string
possible examples are 00000000-0000-0000-0000-000000000000
, flammable
, state-deffered
, john@example.com
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wasn't sure about the auto generated IDs at first and given the spec we can't really generate them. If I understand correctly the spec these IDs are kinda like helpers to identify what is being rendered. So for instance if you have a list of cards on screen each card would have data-ouia-component-type="card"
set, but the data-ouia-component-id
would be probably UUID from database, or whatever to identify which data are actually rendered there.
I think that best way here would be to add uoiaId
prop to every component that is marked by ouia spec. Consumers can then pass this prop to component and if ouia is enabled we will add it to root element. To ilustrate this for set of cards I will have a list of UUIDs from server and render user data in them
import React from 'react';
import { Card, CardBody } from '@patternfly/react-core';
const data = [{
uuid: '00000000-0000-0000-0000-000000000001',
name: 'Foo'
}, {
uuid: '00000000-0000-0000-0000-000000000002',
name: 'Bar'
}];
export default () => (<React.Fragment>
{data.map((user) => <Card key={user.id} ouiaId={user.id}>
<CardBody>{user.name}</CardBody>
</Card>
)}
</React.Fragment>)
The resulting HTML would look like
<div class="pf-c-card" data-ouia-component-id="00000000-0000-0000-0000-000000000001" data-ouia-component-type="Card">
<div class="pf-c-card__body">
Foo
</div>
</div>
<div class="pf-c-card" data-ouia-component-id="00000000-0000-0000-0000-000000000002" data-ouia-component-type="Card">
<div class="pf-c-card__body">
Bar
</div>
</div>
Thanks for this excellent detail description I believe it's often likely that render key and ouia ID can be the same, that may be a chance for convenience |
Ok, how about the new commit? I don't think we want to really advertise any OUIA props in our consumer docs but we can still allow the passing of a
Reading the spec, these ids are optional and can be left off unless passed in. If you wish to always add them (for example for the patternfly-react.surge.sh site) can add another local variable |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This satisfies all the constraints.
render() { | ||
const { renderWithOUIA, ouiaId } = this.state; | ||
const { children } = this.props; | ||
return children({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there any reason why are you using children instead of React context? I think that it is much easier approach and it allows for further improvements.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay I'll update to use context :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpicking. Nice work that should satisfy everyone and statically render correctly.
ouiaId?: number | string; | ||
} | ||
|
||
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about instead import { Omit } from '../../helpers/typeUtils';
?
|
||
const SwitchWithOuiaContext = withOuiaContext(Switch); | ||
|
||
export { SwitchWithOuiaContext as Switch }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
React-docgen is still unphased when grabbing the props for Switch. This is good.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really like the sage of context! just one correction PF shouldn't really take care of OUIA IDs because these will be bound to each application and each screen. Plus consumers should be able to have no ID if there is one component of same type present.
The simplest usage can be <Switch />
there can't be any ID because it's just one switch on screen.
PF should be aware of data-ouia-component-id
and it should be part of rendered DOM only if ouia is enabled (either with localStorage or with context provider) however calculating it is not good. It is possible to pass it from context provider, however the final code will be janky and not good to read.
export const generateOUIAId = (): boolean => typeof window !== 'undefined' && window.localStorage['ouia-generate-id'] && window.localStorage['ouia-generate-id'].toLowerCase() === 'true' || false; | ||
|
||
let id = 0; | ||
export const getUniqueId = (): number => id++; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Patternfly shouldn't really provide these IDs because they will be bound to specific screen inside specific product. On one place the ID will represent inventory ID and on other it can represent for instance ordered products. And if there is going to be just one component of one type we can't have any ID. Plus autogenerated ID would break some UI automated tests where we compare rendered HTML.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
id is only provided if ouia-generate-id
in local storage is true. The main use case i see for this is the PatternFly docs, since our examples won't have manual ouia ids assigned to them. Otherwise no id is added to the component
'data-ouia-component-type': 'Switch', | ||
'data-ouia-component-id': this.ouiaId | ||
'data-ouia-component-id': ouiaContext.ouiaId |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we remove ouiaId
from context and consume it from props instead? It would be kinda repetetive to add id to multiple items trough context provider, plus not all items will have data-ouia-component-id
set. Only if there will be multiple items of same component we will use ouiaId
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok, i'll make it so it can be consumed from props
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
@karelhala I believe i've addressed your comments. If there is anything else I don't mind following up |
Your changes have been released in:
Thanks for your contribution! 🎉 |
Closes: #2477
Adding OUIA capabilities to library components
import { InjectedOuiaProps, withOuiaContext } from '../withOuia';
class Switch extends React.Component<SwitchProps & InjectedOuiaProps>
this.props.ouiaContext
Consumer usage
Case 1: non-ouia users
Case 2: enable ouia through local storage
in local storage ouia: true
Case 3: enable ouia through local storage and generate id
in local storage ouia: true
in local storage ouia-generate-id: true
Case 4: enable ouia through local storage and provide id
in local storage ouia: true
Case 5: enable ouia through context and provide id
Note: If context provided isOuia is true and local storage provided isOuia is false, context will win out. Context will also win if its isOuia is false and local storage's is true. Context > local storage