نحوه ساختن مدال در ریکت فانکشنال کامپوننت و کلاس کامپوننت با قابلیت دکمه بازگشت مرورگر (create modal in functional and class component with back handler )

چند وقت پیش من و همکارم که رو یه پروژه ای کار میکردیم، خیلی گشتیم تو منابع فارسی و انگلیسی و هیچ جا مقاله درست حسابی برای اینکه چجوری میشه وقتی یه popover یا modal باز هست، با زدن دکمه back مرورگر، مدال بسته بشه. ولی خب مقاله مناسبی پیدا نشد. برای همین شروع کردیم به ساختن یه کامپوننت مدال کاستومایز شده و وظیفه خودمون دونستیم تا این مطلب رو با شما به اشتراک بگذاریم که حداقل به دولوپرهای بعد از خودمون کمک کرده باشیم ?❤️. تمرکز این مقاله بر این هست که وقتی مدال باز هست، با کلیک یوزر بر دکمه بازگشت مرورگر، مدال بسته بشه و توی همون پیج بمونیم. یعنی عملا یک backHandler برای دکمه بازگشت مرورگر بسازیم. در زیر میتونید دمو رو برای هر دو حالت functioinal و class کامپوننت ببینید. قراره خوش بگذرونیم ??demo for: functioanl componentdemo for: soon...لازم به ذکر هست که بنده در این مقاله و دمو از typescript, next.js استفاده کردم تا هر حالتی رو پوشش بدم. عزیزانی که از react استفاده میکنن به جای next، کافی هست که از react-router-dom به جای next-router استفاده کنند.برای ساختن مدال از یه پلاگین خیلی خوب توی npm استفاده میکنیم تا بتونیم مدال خودمون رو درست کنیم. اسم پلاگین هست react-responsive-modal که برای نصبش کافیه کد زیر رو در cmd یا terminal خودتون ران کنید:yarn add react-responsive-modalحالا که ما این پکیج رو نصب کردیم، میتونیم اون رو در فایل package.json اپلیکیشن ریکت خودتمون ببینیم، پس بریم سراغ اینکه یه کامپوننت مدال کاستومایز برای خودمون بسازیم. نحوه استفاده از کامپوننت های react-responsive-modal در این لینک کامل توضیح داده شده. در زیر به صورت مجزا برای فانکشنال کامپوننت و کلاس کامپوننت ریکت نحوه استفاده از کامپوونت های react-responsive-modal رو آوردیم. لازم به ذکر هست که ما به جای react-router از next-router استفاده کردیم. تفاوت زیادی در نحوه تغییر روت وجود ندارد.Functional Component// custom-modal.tsx import React, { FC, useState, ReactNode } from 'react'; import { Modal } from 'react-responsive-modal'; import &quotreact-responsive-modal/styles.css&quot interface IProps { name: string hideModal: () => void visible: boolean children: ReactNode } const CustomModal: FC<IProps> = ({name, hideModal, visible, children}) => { const [showModal, setShowModal] = useState<boolean>(visible || false); const open = () => setShowModal(true); const close = () => setShowModal(false); return ( <Modal open={showModal} ={close} onOverlayClick={close } closeOnEsc > {children} </Modal > ) } export default CustomModalClass Component// custom-modal.tsx import React , { Component, ReactNode } from 'react'; import { Modal } from 'react-responsive-modal'; import &quotreact-responsive-modal/styles.css&quot interface IProps { name: string hideModal: () => void visible: boolean children: ReactNode } interface IState { showModal: boolean } class CustomModal extends Component<IProps, IState> { constructor(props: IProps) { super(props) this.state = {showModal: this.props.visible || false} }; close () { this.setState({ showModal: false }) } render () { return ( <Modal open={this.state.showModal} ={this.close} onOverlayClick={this.close } closeOnEsc > {this.props.children} </Modal > ) }; } export default CustomModal ;حالا که کامپوننت custom-modal رو ساختیم، میتونیم توی هر کامپوننتی از اون استفاده کنیم. کافیه با هر ایونتی جاوااسکریپتی که مد نظرمون هست (مثلا کلیک) به کامپوونت custom-modal پراپرتی visiblie = true پاس بدیم. الان دیگه میتونیم بریم سراغ اینکه برای کامپوننت مدالمون backHandler ست کنیم. چجوری؟ ??وقتی مدال باز هست، با کلیک روی دکمه بازگشت مرورگر، باید مدال باز بشه. راهکار چی هست حالا؟ ?? کد زیر رو ببینید که چطور ما custom-modal رو ویرایش کردیم.Functional Component// custom-modal.tsx import React , { FC, memo, useEffect, useState, ReactNode } from 'react'; import { Modal } from 'react-responsive-modal'; import &quotreact-responsive-modal/styles.css&quot import { useRouter } from 'next/router'; interface IProps { name: string hideModal: () => void visible: boolean children: ReactNode } const CustomModal: FC<IProps> = ({name, hideModal, visible, children}) => { const [showModal, setShowModal] = useState<boolean>(visible || false); const router = useRouter(); const hasHash = router.asPath.includes('#') const open = () => { if (!hasHash) { router.push({ pathname: router.pathname, query: { ...router.query }, hash: name + '' }, undefined, { scroll: false }) } setShowModal(true); } const close = () => { hideModal(); setShowModal(false); if (showModal) { router.back() } } const clickOutSide = () => { hideModal(); setShowModal(false); } useEffect(() => { visible && open() }, [visible]) useEffect(() => { if (!hasHash && showModal) hideModal (), setShowModal(false) }, [hasHash]); return ( <Modal open={showModal} ={close} onOverlayClick={close } closeOnEsc > {children} </Modal > ) } export default memo(CustomModal); Class Component// custom-modal import React , { Component, ReactNode } from 'react'; import { Modal } from 'react-responsive-modal'; import &quotreact-responsive-modal/styles.css&quot interface IProps { name: string hideModal: () => void visible: boolean children: ReactNode } interface IState { showModal: boolean } class CustomModal extends Component<IProps, IState> { constructor(props: IProps) { super(props) this.state = {showModal: this.props.visible || false} }; close () { this.setState({ showModal: false }) } render () { return ( <Modal open={this.state.showModal} ={this.close} onOverlayClick={this.close } closeOnEsc > {this.props.children} </Modal > ) }; } export default CustomModal;چی شد؟ ??وقتی که مدال باز باشه، ما یه modalName# به انتهای روتمون اضافه میکنیم. وقتی هم که مدال باید بسته بشه یا یوزر دکمه بازگشت مرورگر رو بزنه، ما # رو بر میداریم. خیلی راحت عملا داریم روتر رو تغییر میدیم و با باز و بسته شدن مدال این روت آپدیت میشه. حالا یک سوال اگر مدال باز باشه و یوزر صفحه رو رفرش کنه، چی میشه؟ باید بگم که با رفرش کردن صفحه روتر با # به یوزر نشون داده میشه. وقتی یوزر روی دکمه باز کردن مدال کلیک کرد، # همون هست ولی خب مدال رو باز هم میبینید. یعنی مشکلی ایجاد نمیکنه.اگر نیاز داشتیم میتونیم با رفرش، به صورت دیفالت روت را بدون # نشون بدیم که این قسمت به عهده خود شما ??. حالا چرا با # مگه با query string نمیشد هندل کرد؟ بله با کوئری استرینگ هم میشه هندل کرد ولی روتر ریکت و نکست دیفالتش اینه وقتی که کوئری استرینگ و پارامتر های روت تغییر میکنه، اسکرول میکنه به بالای صفحه. پس ما درد سر تغییر دیفالت روتر رو به جون نخریدیم و از # استفاده کردیم.با تشکر از همکاری دوست خوبم نیلوفر صادقی https://virgool.io/@niloofar_sadeghi