-
Notifications
You must be signed in to change notification settings - Fork 900
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
refactor(core/presentation): Refactor FormField components using hooks #7148
refactor(core/presentation): Refactor FormField components using hooks #7148
Conversation
747f701
to
6659992
Compare
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.
@christopherthielen thank you for the insightful PR description, I definitely do not yet grok the hook mindset so looking over this PR was very helpful!
|
||
public label = () => ifString(this.props.label); | ||
const validate = useMemo(() => props.validate, []); |
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.
In case anyone else looking at this PR had the same n00b question as me of the difference between the second argument to useMemo
being an empty array vs. null, this Stack Overflow answer sums it up nicely.
|
||
public name = () => this.props.name; | ||
const fieldLayoutFromContext = useContext(LayoutContext); |
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'm curious what other Deck state you feel might be good candidate for storing on React 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.
I'm wary of putting too much stuff in the context because it can make the answer to "where does this data come from?" harder to answer. There is a similar smell in AngularJS land -- you can put stuff higher up in the $scope
tree and reference it down below, but tends to be an anti-pattern. It introduces undocumented, context sensitive dependencies.
Where I do think it makes sense is when you want to scope some setting to a portion of the DOM. For example, there may be multiple forms on the screen at the same time (each with different Layouts). Prop-drilling (things like layouts) all the way down to the leaf components would be explicit and easy to trace the data flow, but it would also be tedious and introduce excessive boilerplate to the API.
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.
Makes sense, thanks as always for the thoughtful response! 😻
In prep for some other work, I migrated the
<FormField/>
and<FormikFormField/>
to use react hooks.Here are some things I learned:
useReducer
because the reducer always receives fresh values.const contextValue = useContext(ContextObject)
- this can be used right inside the render, instead of wrapping a<Context.Consumer></>
component around.React.createContext()
, not just theContext.Producer
andContext.Consumer
objects.useState
to store simple local stateconst [name, setName] = useState(initialName)
useEffect
for side effects, which includes fetching data based on user interactions.useEffect(() => api.checkIfNameExists(name).then(nameExists => setNameExists(nameExists)), [name]);
===
.===
equality doesn't change and cause unnecessary work.useMemo
is an easy way to avoid expensive recalculationsconst memoizedValue = useMemo(() => expensiveCalculation(inputString), [inputString])
- Is recomputed wheneverinputString
changes.In general, this style of coding seems to lead to fewer if/then blocks, less imperative code, and more declarative code that defines the state of the component, and its output. I believe this will also replace much of the RxJS code in components, including the
Observable.fromPromise(promise).takeUntil(this.destroy$).subscribe(result => {})
pattern.Hooks will allow us to extract significant chunks of behavior, reducing code duplication. By encapsulating patterns as hooks, I think we can reduce friction for certain classes of problems (see how management of async data fetching seems simpler via useLatestPromise). However, hooks is a different mindset that will need to be grokked by deck developers. It's unfortunate that we are adding yet another paradigm to front end coding in deck (AngularJS, React Classes + lifecycle methods, React Hooks).