CALENDAR DIALOG
Here is our current pattern for rendering a calendar datepicker from a list cell. Any recommendations, tips or improvements or other thoughts are welcome.
I wanted to share for feedback. The toast does not work on successful submission. Setting the function interface as (values: any) => any
in the children does not seem like the best practice. And probably ten other things could be improved. It was my first time using hooks like this to manage state updates between parent and child elements.
Thanks for taking the time to read. I hope it helps others.
I am really enjoying working with Redwood. Thanks to the team for being so open and helpful. Also, thanks to @dthyresson for help with the aging calculations using services and sdl.
LIST CELL / PARENT ITEM
The List Cell renders two main components: the list item component and then a dialog popup component.
Two hooks in the List Cell help manage and track state between the components.
export const Success = ({
bushhogItems,
}: CellSuccessProps<BushhogItemsQuery>) => {
const [isOpen, setIsOpen] = useState<boolean>(false)
const [bhItemNumber, setItemNumber] = useState<number>(0)
const [bhItemName, setItemName] = useState<string>('')
const updateIsOpen = () => {
setIsOpen(!isOpen)
}
return (
<div className="bg-white overflow-hidden rounded-md">
<ul className="divide-y divide-gray-200">
<li>
{bushhogItems.map((bushhogItem) => (
<BushhogItem
key={bushhogItem.id}
bushhogItem={bushhogItem}
updateIsOpen={updateIsOpen}
setItemNumber={setItemNumber}
setItemName={setItemName}
/>
))}
</li>
</ul>
<CalendarDialog
isOpen={isOpen}
bhItemNumber={bhItemNumber}
bhItemName={bhItemName}
updateIsOpen={updateIsOpen}
/>
</div>
)
}
LIST ITEM COMPONENT / CHILD 1
The list item component is a basic display component. It accepts a visibility toggle function and two hook setter functions from the parent.
Button onClick
uses the hook setters to update two variables and also fires the visibility function passed from the parent. This toggles the dialog to open.
interface Props {
bushhogItem: {
id: number
name: string
lastCutDate: string
age: number
color: string
}
setItemNumber: (values: any) => any
setItemName: (values: any) => any
updateIsOpen: () => void
}
const BushhogItem = ({
bushhogItem,
setItemNumber,
setItemName,
updateIsOpen,
}: Props) => {
function openDialog() {
setItemNumber(bushhogItem.id)
setItemName(bushhogItem.name)
updateIsOpen()
}
return (
<div className="px-4 py-4 sm:px-6">
<div className="flex items-center flex-wrap justify-between">
<... code shortened ...>
<button
type="button"
onClick={openDialog}
className="inline-flex items-center shadow-sm px-2.5 py-0.5 border border-gray-300 text-sm leading-5 font-medium rounded-full text-gray-700 bg-white hover:bg-gray-50"
>
Update
</button>
</div>
</div>
<div className="mt-2 sm:flex sm:justify-between">
<... code shortened ...>
</div>
</div>
)
}
DATEPICKER DIALOG / CHILD 2
The datepicker dialog component uses react-day-picker out of the box and tailwind headlessui for the modal dialog. It also uses date-fns to format the dates.
The visibility toggle function, as well as two hook variables, are passed to this component. It uses the variables in the graphql mutation to update the record.
Code shortened to the calendar selection and submit/cancel buttons.
interface Props {
isOpen: boolean
bhItemNumber: number
bhItemName: string
updateIsOpen: () => void
}
const CalendarDialog = ({
isOpen,
bhItemNumber,
bhItemName,
updateIsOpen,
}: Props) => {
const [selected, setSelected] = useState<Date>(new Date())
const cancelButtonRef = useRef(null)
const [updateItem] = useMutation(UPDATE, {
onCompleted: () => {
toast.success('Update Successful')
},
})
function closeModal() {
isOpen = false
updateIsOpen()
}
const handleDaySelect = (date: Date) => {
setSelected(date)
}
function submitUpdate(id: number) {
const input = {
lastCutDate: format(selected, 'yyyy-MM-dd') + 'T23:59:59Z',
}
updateItem({ variables: { id, input } })
closeModal()
}
return (
<>
<Transition appear show={isOpen} as={Fragment}>
<Dialog as="div" className="relative z-10" onClose={closeModal}>
<Transition.Child>
<... code shortened ...>
<div className="mt-2">
<DayPicker
onSelect={handleDaySelect}
selected={new Date()}
mode="single"
/>
</div>
<div className="mt-4">
<div className="mt-5 sm:mt-6 sm:grid sm:grid-cols-2 sm:gap-3 sm:grid-flow-row-dense">
<button
type="button"
className="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-indigo-600 text-base font-medium text-white hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:col-start-2 sm:text-sm"
onClick={() => submitUpdate(bhItemNumber)}
>
Confirm
</button>
<button
type="button"
className="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:col-start-1 sm:text-sm"
onClick={closeModal}
ref={cancelButtonRef}
>
Cancel
</button>
</div>
<... code shortened ...>
</Dialog>
</Transition>
</>
)
}