Skip to content

Commit

Permalink
Fix involuntary form reset (#355)
Browse files Browse the repository at this point in the history
This PR removes react-hook-form, and rework the logic behind this form
to useReducer. Numerous glitches would happen by using react-hook-form
logic and trying to work with their internals/validations.

- **Rework the UI to not use react-hook-form**

Before:


https://github.com/user-attachments/assets/49700260-2668-449b-8d6d-494435b993c9

After:


https://github.com/user-attachments/assets/6a97a68e-e759-4573-a836-08ff0240cd2c
  • Loading branch information
xjunior authored Jul 18, 2024
1 parent 2849f69 commit 6a12e5e
Show file tree
Hide file tree
Showing 21 changed files with 402 additions and 336 deletions.
7 changes: 4 additions & 3 deletions audiences-react/package.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
{
"name": "audiences",
"version": "1.1.0",
"version": "1.2.0",
"description": "Audiences SCIM client",
"files": [
"dist"
"dist/*.*",
"docs/README.md",
"docs/CHANGELOG.md"
],
"main": "./dist/audiences.umd.js",
"module": "./dist/audiences.es.js",
Expand Down Expand Up @@ -35,7 +37,6 @@
"license": "MIT",
"dependencies": {
"lodash": "^4.17.21",
"react-hook-form": "^7.45.2",
"use-http": "^1.0.28"
},
"prettier": "@powerhome/eslint-config/prettier",
Expand Down
51 changes: 0 additions & 51 deletions audiences-react/src/AudienceForm/AllToggle.tsx

This file was deleted.

10 changes: 7 additions & 3 deletions audiences-react/src/AudienceForm/CriteriaActions.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
import { Button, Icon, PbReactPopover, List, ListItem } from "playbook-ui"
import { useState } from "react"
import { MembersModalButton } from "./MembersModal"
import { MembersModalButton } from "./MembersModalButton"
import { GroupCriterion } from "../types"
import { CriteriaDescription } from "./CriteriaDescription"
import { useAudiences } from "../audiences"

type CriteriaActionsProps = {
viewUsers: boolean
criterion: GroupCriterion
onRequestRemove: () => void
fetchUsers: ReturnType<typeof useAudiences>["fetchUsers"]
onRequestEdit: () => void
onRequestRemove: () => void
viewUsers: boolean
}
export function CriteriaActions({
viewUsers,
criterion,
fetchUsers,
onRequestRemove,
onRequestEdit,
}: CriteriaActionsProps) {
Expand Down Expand Up @@ -59,6 +62,7 @@ export function CriteriaActions({
padding="xs"
text="View Members"
title={<CriteriaDescription groups={criterion.groups} />}
fetchUsers={fetchUsers}
total={criterion.count}
criterion={criterion}
/>
Expand Down
10 changes: 7 additions & 3 deletions audiences-react/src/AudienceForm/CriteriaCard.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
import { Card, Flex, FlexItem, Caption } from "playbook-ui"
import isEmpty from "lodash/isEmpty"
import { isEmpty } from "lodash"

import type { GroupCriterion } from "../types"
import { CriteriaDescription } from "./CriteriaDescription"
import { CriteriaActions } from "./CriteriaActions"
import { useAudiences } from "../audiences"

type CriteriaCardProps = {
viewUsers: boolean
criterion?: GroupCriterion
onRequestRemove: () => void
fetchUsers: ReturnType<typeof useAudiences>["fetchUsers"]
onRequestEdit: () => void
onRequestRemove: () => void
viewUsers: boolean
}
export function CriteriaCard({
criterion,
fetchUsers,
viewUsers,
onRequestRemove,
onRequestEdit,
Expand All @@ -34,6 +37,7 @@ export function CriteriaCard({
<FlexItem>
<CriteriaActions
criterion={criterion}
fetchUsers={fetchUsers}
onRequestRemove={onRequestRemove}
onRequestEdit={onRequestEdit}
viewUsers={viewUsers}
Expand Down
6 changes: 3 additions & 3 deletions audiences-react/src/AudienceForm/CriteriaDescription.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from "react"
import { Fragment } from "react"
import { Body, Title } from "playbook-ui"
import { isEmpty, map } from "lodash"

Expand All @@ -20,14 +20,14 @@ export function CriteriaDescription({ groups }: CriteriaDescriptionProps) {
<Body tag="span" text="All" />
{map(Prepositions, (prep, key) =>
isEmpty(groups[key]) ? null : (
<React.Fragment key={`groups-${key}`}>
<Fragment key={`groups-${key}`}>
<Body tag="span" text={` ${prep} `} />
<Title
tag="span"
size={4}
text={toSentence(map(groups[key], "displayName"))}
/>
</React.Fragment>
</Fragment>
),
)}
<Body tag="span">{"."}</Body>
Expand Down
49 changes: 32 additions & 17 deletions audiences-react/src/AudienceForm/CriteriaForm.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,39 @@
import { flatten, isEmpty, keys, values } from "lodash"
import { Button, Card, Flex, IconValue } from "playbook-ui"
import { useFormContext } from "react-hook-form"
import { flatten, isEmpty, keyBy, mapValues, values } from "lodash"

import { CriteriaDescription } from "./CriteriaDescription"
import { ScimResourceTypeahead } from "./ScimResourceTypeahead"
import { useMemo } from "react"
import { GroupCriterion } from "../types"
import useFormReducer from "../useFormReducer"

export type CriteriaFormProps = {
current: string
onClose: () => void
resources: string[]
criterion: GroupCriterion | undefined
onSave: (criterion: GroupCriterion) => void
onCancel: () => void
}
export function CriteriaForm({ current, onClose }: CriteriaFormProps) {
const { setValue, watch } = useFormContext()
const value = watch(`${current}.groups`)
const initialValue = useMemo(() => ({ ...value }), [current])
function buildCriterion(resources: string[]) {
const emptyGroups = mapValues(keyBy(resources), () => [])
return { groups: emptyGroups }
}
export function CriteriaForm({
resources,
criterion,
onSave,
onCancel,
}: CriteriaFormProps) {
const { value, change } = useFormReducer<GroupCriterion>(
criterion || buildCriterion(resources),
)

const emptyCriteria = isEmpty(flatten(values(value)))
const emptyCriteria = isEmpty(flatten(values(value.groups)))

const handleSave = () => {
onSave(value)
}

const handleCancel = () => {
setValue(`${current}.groups`, initialValue)
onClose()
onCancel()
}

return (
Expand All @@ -37,19 +51,20 @@ export function CriteriaForm({ current, onClose }: CriteriaFormProps) {
</Card.Header>

<Card.Body>
{keys(value).map((resourceId) => (
{resources.map((resourceId) => (
<ScimResourceTypeahead
resourceId={resourceId}
key={`${current}.${resourceId}`}
key={`criterion.groups.${resourceId}`}
label={resourceId}
name={`${current}.groups.${resourceId}`}
value={value.groups[resourceId]}
onChange={(values) => change(`groups.${resourceId}`, values)}
/>
))}

{emptyCriteria || <CriteriaDescription groups={value} />}
{emptyCriteria || <CriteriaDescription groups={value.groups} />}

<Flex justify="between" marginTop="md">
<Button onClick={onClose} text="Save" disabled={emptyCriteria} />
<Button onClick={handleSave} text="Save" disabled={emptyCriteria} />
<Button onClick={handleCancel} text="Cancel" variant="link" />
</Flex>
</Card.Body>
Expand Down
28 changes: 13 additions & 15 deletions audiences-react/src/AudienceForm/CriteriaList.tsx
Original file line number Diff line number Diff line change
@@ -1,36 +1,34 @@
import { Button, Flex, FlexItem } from "playbook-ui"
import { useFormContext } from "react-hook-form"

import { GroupCriterion } from "../types"

import { AudienceContext } from "../types"
import { CriteriaCard } from "./CriteriaCard"
import { useAudiences } from "../audiences"

type CriteriaListFieldsProps = {
type CriteriaListProps = {
addCriteriaLabel: string
context: AudienceContext
fetchUsers: ReturnType<typeof useAudiences>["fetchUsers"]
onAddCriteria: () => void
onRemoveCriteria: (index: number) => void
onEditCriteria: (index: number) => void
onRemoveCriteria: (index: number) => void
}
export function CriteriaList({
context,
fetchUsers,
addCriteriaLabel,
onAddCriteria,
onRemoveCriteria,
onEditCriteria,
}: CriteriaListFieldsProps) {
const { watch, getFieldState } = useFormContext()
const currentCriteria = (watch("criteria") || []) as GroupCriterion[]
const isCriterionDirty = (index: number) =>
getFieldState(`criteria.${index}`).isDirty

}: CriteriaListProps) {
return (
<Flex orientation="column" align="stretch">
{currentCriteria.map((criterion, index: number) => (
{context.criteria.map((criterion, index: number) => (
<CriteriaCard
key={`criterion-${criterion.id}`}
criterion={criterion}
viewUsers={!isCriterionDirty(index)}
fetchUsers={fetchUsers}
key={`criterion-${index}`}
onRequestEdit={() => onEditCriteria(index)}
onRequestRemove={() => onRemoveCriteria(index)}
viewUsers={criterion.count !== undefined}
/>
))}

Expand Down
45 changes: 0 additions & 45 deletions audiences-react/src/AudienceForm/Header.tsx

This file was deleted.

Loading

0 comments on commit 6a12e5e

Please sign in to comment.