مشکلات تغییر path و بارگذاری پست های مختلف

22 بهمن 1399
مشکلات تغییر path و بارگذاری پست های مختلف

مشکل تغییر path

در قسمت قبل با Route های تو در تو یا Nested Routes آشنا شدیم اما به شما گفتیم که برنامه مشکل خاصی دارد که مربوط به path ها و تغییر آن است. در واقع اگر فرض کنیم که به جای / خالی، چیزی شبیه به posts/ داشته باشیم (یا هر پسوند دیگری):

<Route path="/posts" component={Posts} />

و سپس به Posts.js برویم و آدرس های آن را نیز تصحیح کنیم:

postSelectedHandler = (id) => {
    // this.props.history.push({ pathname: '/posts/' + id });
    this.props.history.push('/posts/' + id);
}

من حالت اول را نیز به صورت کامنت برایتان نگه داشته ام تا با آن نیز آشنا باشید. همچنین اگر می خواستید در این کامپوننت از <Link> ها استفاده کنید باید آدرس آن ها را نیز تصحیح می کردید:

<Link to={'/posts/' + post.id} key={post.id}>

در آخر وارد فایل Blog.js شده و لینک nav را نیز تصحیح کنید:

<li><NavLink
    exact
    to="/posts/"
    activeClassName="my-active"
    activeStyle={{
        color: '#fa923f',
        textDecoration: 'underline'
    }}
>Posts</NavLink></li>

حالا اگر وارد برنامه ی خود شده و روی یکی از پست ها کلیک کنیم به مشکل برمی خوریم. این خطا از سمت سرور تمرینی JSONPlaceholder می آید. چرا؟ در حال حاضر قسمت JSX فایل Posts.js بدین شکل است:

return (
    <div>
        <section className="Posts">
            {posts}
        </section>
        <Route path="/:id" exact component={FullPost} />
    </div>
);

بنابراین مسیر id:/ مستقیما به localhost میچسبد و به جای اینکه /posts/:id را داشته باشیم، مستقیما id:/ را داریم. راه حل ساده است:

<Route path="/posts/:id" exact component={FullPost} />

اما انجام این کار برای تمام <Route> های برنامه ی ما کار طاقت فرسایی است مخصوصا اگر برنامه ی بسیار بزرگی داشته باشیم. راه حل صحیح برای دریافت پویای آدرس صحیح بدین شکل است:

<Route path={this.props.match.url + '/:id'} exact component={FullPost} />

بنابراین همیشه آدرس فعلی خود را آدرس قبلی می چسبانیم تا در Route های تو در تو دچار مشکل نشویم. این راه حل، یک راه حل پایدار برای کار با Route های تو در تو است.

مشکل بارگذاری پست های مختلف

مشکل دیگری که برنامه ی ما دارد تغییر بارگذاری پست ها است. در حال حاضر اگر روی یکی از پست ها کلیک کنید، آن پست بدون مشکل بارگذاری می شود اما پس از بارگذاری آن اگر روی پست دیگری کلیک کنید، با اینکه URL تغییر میکند، اما آن پست بارگذاری نمی شود. آیا دلیل آن را میدانید؟

در React-router اگر در کامپوننتی باشید و بخواهید آن را دوباره بارگذاری کنید، react-router از رخ دادن آن جلوگیری می کند. در واقع کامپوننت ما از نظر react-router تغییری نکرده بنابراین componentDidMount دوباره اجرا نخواهد شد. چرا کامپوننت را بی جهت Mount و سپس unmount کند؟ کد componentDidMount بدین شکل است:

componentDidMount() {
    console.log(this.props);
    axios.get('/posts')
        .then(response => {
            const posts = response.data.slice(0, 4);
            const updatedPosts = posts.map(post => {
                return {
                    ...post,
                    author: 'Max'
                }
            });
            this.setState({ posts: updatedPosts });
            // console.log( response );
        })
        .catch(error => {
            console.log(error);
            // this.setState({ error: true });
        });
}

بنابراین اگر componentDidMount دوباره اجرا نشود، اطلاعات داخل کامپوننت ما نیز به روز رسانی نمی شوند چرا که درخواست HTTP ما دوباره اجرا نمی شوند. برای حل این مشکل باید componentDidUpdate را تعریف کنیم چرا که کامپوننت unmount نمی شود (از DOM حذف نمی شود) اما بروزرسانی می شود بنابراین برخلاف componentDidMount تابع componentDidUpdate اجرا خواهد شد.

برای تعریف componentDidUpdate به همان فایل FullPost.js می رویم و سپس تمام شرط if موجود در componentDidMount (که همان درخواست HTTP ما است) را درون یک متد جدید به نام loadData قرار می دهیم:

loadData() {
    if (this.props.match.params.id) {
        if (!this.state.loadedPost || (this.state.loadedPost && this.state.loadedPost.id !== this.props.id)) {
            axios.get('/posts/' + this.props.match.params.id)
                .then(response => {
                    // console.log(response);
                    this.setState({ loadedPost: response.data });
                });
        }
    }
}

سپس آن را هم درون componentDidMount و هم درون componentDidUpdate فراخوانی می کنیم. باقی محتویات componentDidMount بدین شکل خواهد بود:

componentDidMount() {
    console.log(this.props);
    this.loadData();
}

و محتویات componentDidUpdate نیز به شکل زیر است:

componentDidUpdate() {
    this.loadData();
}

اگر در حال حاضر کدها را ذخیره کرده و به مرورگر برویم با مشکل جدیدی مواجه می شویم! قسمت کنسول مرورگر خود را باز کرده و سپس روی یکی از پست ها کلیک کنید. سریعا مشاهده می کنید که برنامه در یک حلقه ی بی نهایت گیر افتاده است و درخواست های HTTP ما مکررا ارسال می شوند تا زمانی که برنامه را متوقف کنیم.

پاسخ این مشکل در شرطی در تابع loadData است:

if (!this.state.loadedPost || (this.state.loadedPost && this.state.loadedPost.id !== this.props.id)) {
    axios.get('/posts/' + this.props.match.params.id)
        .then(response => {
            // console.log(response);
            this.setState({ loadedPost: response.data });
        });
}

اگر به شرط این if نگاه کنید، ما this.props.id را بررسی کرده ایم در حالی که گفتیم باید props.match.params.id را بررسی کنیم. بنابراین به راحتی آن را اصلاح می کنیم:

if (!this.state.loadedPost || (this.state.loadedPost && this.state.loadedPost.id !== this.props.match.params.id)) {

اما هنوز مشکلی وجود دارد. Match.params.id همیشه یک رشته خواهد بود (به صورت فایل json ارسال می شود که مقادیر آن همه رشته هستند). اما درون شرط if بالا از ==! استفاده کرده ایم که علاوه بر مقدار، نوع متغیرها را نیز چک می کند. از طرفی مقدار state.loadedPost.id هیچگاه یک رشته نیست بنابراین شرط همیشه غلط است. دو راه حل داریم: یا اینکه شرط را از ==! به =! تغییر دهیم و یا اینکه با type casting مقدار props.match.params.id را از رشته به عدد تبدیل کنیم. من روش اول را انتخاب می کنم:

if (!this.state.loadedPost || (this.state.loadedPost && this.state.loadedPost.id != this.props.match.params.id)) {

همچنین باید به متد deletePostHandler برویم و آن را نیز اصلاح کنیم (تبدیل this.props.id به this.props.match.params.id):

deletePostHandler = () => {
    axios.delete('/posts/' + this.props.match.params.id)
        .then(response => {
            console.log(response);
        });
}

همچنین در قسمت render و به شکل زیر:

render() {
    let post = <p style={{ textAlign: 'center' }}>Please select a Post!</p>;
    if (this.props.match.params.id) {
        post = <p style={{ textAlign: 'center' }}>Loading...!</p>;
    }

    // بقیه ی کدها 

حالا اگر به مرورگر بروید، برنامه ی ما بدون مشکل اجرا خواهد شد.

دانلود کدهای این فصل تا این جلسه

تمام فصل‌های سری ترتیبی که روکسو برای مطالعه‌ی دروس سری دوره جامع آموزش ری اکت توصیه می‌کند:
نویسنده شوید
دیدگاه‌های شما

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.