আনমাউন্ট থাকা উপাদানগুলিতে প্রতিক্রিয়া স্থিতি আপডেট করতে পারে না


122

সমস্যা

আমি প্রতিক্রিয়াতে একটি অ্যাপ্লিকেশন লিখছি এবং একটি সুপার সাধারণ ঝুঁকি এড়াতে অক্ষম ছিল, যা setState(...)পরে কল করছে componentWillUnmount(...)

আমি আমার কোডটি খুব যত্ন সহকারে দেখেছি এবং কিছু রক্ষণাবেক্ষণের শর্তগুলি জায়গায় রাখার চেষ্টা করেছি, তবে সমস্যাটি অব্যাহত রয়েছে এবং আমি এখনও সতর্কতাটি পর্যবেক্ষণ করছি।

অতএব, আমি দুটি প্রশ্ন পেয়েছি:

  1. স্ট্যাক ট্রেস থেকে আমি কীভাবে সনাক্ত করব , কোন নির্দিষ্ট উপাদান এবং ইভেন্ট হ্যান্ডলার বা লাইফাইসাইকেল হুক নিয়ম লঙ্ঘনের জন্য দায়ী?
  2. ঠিক আছে, কীভাবে সমস্যাটি নিজেই সমাধান করবেন, কারণ আমার কোডটি এই সমস্যাটি মাথায় রেখেই লেখা হয়েছিল এবং ইতিমধ্যে এটি প্রতিরোধের চেষ্টা করা হচ্ছে, তবে কিছু অন্তর্নিহিত উপাদান এখনও সতর্কতা উত্পন্ন করছে।

ব্রাউজার কনসোল

Warning: Can't perform a React state update on an unmounted component.
This is a no-op, but it indicates a memory leak in your application.
To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount
method.
    in TextLayerInternal (created by Context.Consumer)
    in TextLayer (created by PageInternal) index.js:1446
d/console[e]
index.js:1446
warningWithoutStack
react-dom.development.js:520
warnAboutUpdateOnUnmounted
react-dom.development.js:18238
scheduleWork
react-dom.development.js:19684
enqueueSetState
react-dom.development.js:12936
./node_modules/react/cjs/react.development.js/Component.prototype.setState
react.development.js:356
_callee$
TextLayer.js:97
tryCatch
runtime.js:63
invoke
runtime.js:282
defineIteratorMethods/</prototype[method]
runtime.js:116
asyncGeneratorStep
asyncToGenerator.js:3
_throw
asyncToGenerator.js:29

এখানে চিত্র বর্ণনা লিখুন

কোড

Book.tsx

import { throttle } from 'lodash';
import * as React from 'react';
import { AutoWidthPdf } from '../shared/AutoWidthPdf';
import BookCommandPanel from '../shared/BookCommandPanel';
import BookTextPath from '../static/pdf/sde.pdf';
import './Book.css';

const DEFAULT_WIDTH = 140;

class Book extends React.Component {
  setDivSizeThrottleable: () => void;
  pdfWrapper: HTMLDivElement | null = null;
  isComponentMounted: boolean = false;
  state = {
    hidden: true,
    pdfWidth: DEFAULT_WIDTH,
  };

  constructor(props: any) {
    super(props);
    this.setDivSizeThrottleable = throttle(
      () => {
        if (this.isComponentMounted) {
          this.setState({
            pdfWidth: this.pdfWrapper!.getBoundingClientRect().width - 5,
          });
        }
      },
      500,
    );
  }

  componentDidMount = () => {
    this.isComponentMounted = true;
    this.setDivSizeThrottleable();
    window.addEventListener("resize", this.setDivSizeThrottleable);
  };

  componentWillUnmount = () => {
    this.isComponentMounted = false;
    window.removeEventListener("resize", this.setDivSizeThrottleable);
  };

  render = () => (
    <div className="Book">
      { this.state.hidden && <div className="Book__LoadNotification centered">Book is being loaded...</div> }

      <div className={this.getPdfContentContainerClassName()}>
        <BookCommandPanel
          bookTextPath={BookTextPath}
          />

        <div className="Book__PdfContent" ref={ref => this.pdfWrapper = ref}>
          <AutoWidthPdf
            file={BookTextPath}
            width={this.state.pdfWidth}
            onLoadSuccess={(_: any) => this.onDocumentComplete()}
            />
        </div>

        <BookCommandPanel
          bookTextPath={BookTextPath}
          />
      </div>
    </div>
  );

  getPdfContentContainerClassName = () => this.state.hidden ? 'hidden' : '';

  onDocumentComplete = () => {
    try {
      this.setState({ hidden: false });
      this.setDivSizeThrottleable();
    } catch (caughtError) {
      console.warn({ caughtError });
    }
  };
}

export default Book;

অটোউইথপিডিএফ.এসএক্স

import * as React from 'react';
import { Document, Page, pdfjs } from 'react-pdf';

pdfjs.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.js`;

interface IProps {
  file: string;
  width: number;
  onLoadSuccess: (pdf: any) => void;
}
export class AutoWidthPdf extends React.Component<IProps> {
  render = () => (
    <Document
      file={this.props.file}
      onLoadSuccess={(_: any) => this.props.onLoadSuccess(_)}
      >
      <Page
        pageNumber={1}
        width={this.props.width}
        />
    </Document>
  );
}

আপডেট 1: থ্রোটলেবল ফাংশন বাতিল করুন (এখনও ভাগ্য নেই)

const DEFAULT_WIDTH = 140;

class Book extends React.Component {
  setDivSizeThrottleable: ((() => void) & Cancelable) | undefined;
  pdfWrapper: HTMLDivElement | null = null;
  state = {
    hidden: true,
    pdfWidth: DEFAULT_WIDTH,
  };

  componentDidMount = () => {
    this.setDivSizeThrottleable = throttle(
      () => {
        this.setState({
          pdfWidth: this.pdfWrapper!.getBoundingClientRect().width - 5,
        });
      },
      500,
    );

    this.setDivSizeThrottleable();
    window.addEventListener("resize", this.setDivSizeThrottleable);
  };

  componentWillUnmount = () => {
    window.removeEventListener("resize", this.setDivSizeThrottleable!);
    this.setDivSizeThrottleable!.cancel();
    this.setDivSizeThrottleable = undefined;
  };

  render = () => (
    <div className="Book">
      { this.state.hidden && <div className="Book__LoadNotification centered">Book is being loaded...</div> }

      <div className={this.getPdfContentContainerClassName()}>
        <BookCommandPanel
          BookTextPath={BookTextPath}
          />

        <div className="Book__PdfContent" ref={ref => this.pdfWrapper = ref}>
          <AutoWidthPdf
            file={BookTextPath}
            width={this.state.pdfWidth}
            onLoadSuccess={(_: any) => this.onDocumentComplete()}
            />
        </div>

        <BookCommandPanel
          BookTextPath={BookTextPath}
          />
      </div>
    </div>
  );

  getPdfContentContainerClassName = () => this.state.hidden ? 'hidden' : '';

  onDocumentComplete = () => {
    try {
      this.setState({ hidden: false });
      this.setDivSizeThrottleable!();
    } catch (caughtError) {
      console.warn({ caughtError });
    }
  };
}

export default Book;

আপনি শ্রোতাদের যোগ এবং সরানোর বিষয়ে মন্তব্য করলে সমস্যাটি কি স্থির থাকে?
ic3b3rg

@ আইসি 3 বি 3 আরজি যদি কোনও ইভেন্টের শোনার কোড না থাকে তবে সমস্যাটি অদৃশ্য হয়ে যায়
ইগোর সলোয়ডেনকো

ঠিক আছে, আপনি গার্ডের this.setDivSizeThrottleable.cancel()পরিবর্তে পরামর্শটি করার চেষ্টা করেছিলেন this.isComponentMounted?
ic3b3rg

4
@ ic3b3rg এখনও একই রান-টাইম সতর্কতা।
ইগোর সলোয়ডেনকো

উত্তর:


63

এখানে একটি প্রতিক্রিয়া হুকস নির্দিষ্ট সমাধান আছে

ত্রুটি

সতর্কতা: আনমাউন্ট থাকা উপাদানগুলিতে প্রতিক্রিয়া স্থিতি আপডেট করতে পারে না।

সমাধান

আপনি let isMounted = trueভিতরে ঘোষণা করতে পারেন useEffect, যা উপাদানটি আনমাউন্ট করার সাথে সাথে ক্লিনআপ কলব্যাকে পরিবর্তিত হবে। রাষ্ট্র আপডেটের আগে, আপনি এখন এই পরিবর্তনশীলটি শর্তযুক্ত পরীক্ষা করুন:

useEffect(() => {
  let isMounted = true; // note this flag denote mount status
  someAsyncOperation().then(data => {
    if (isMounted) setState(data);
  })
  return () => { isMounted = false }; // use effect cleanup to set flag false, if unmounted
});

সম্প্রসারণ: কাস্টম useAsyncহুক

আমরা সমস্ত বয়লারপ্লেটকে কাস্টম হুকের সাথে ক্যাপসুলেট করতে পারি, এটি কেবলমাত্র জানে, কীভাবে উপাদানটি আগে আনমাউন্ট করা যায় সেইজন্য অ্যাসিঙ্ক ফাংশনগুলি কীভাবে মোকাবেলা করতে এবং স্বয়ংক্রিয়ভাবে বাতিল করতে হবে:

function useAsync(asyncFn, onSuccess) {
  useEffect(() => {
    let isMounted = true;
    asyncFn().then(data => {
      if (isMounted) onSuccess(data);
    });
    return () => { isMounted = false };
  }, [asyncFn, onSuccess]);
}


4
আপনার কৌশল কাজ করে! আমি ভাবছি এর পিছনে কি জাদু আছে?
নিয়ঙ্গাবো

4
আমরা এখানে বিল্ট-ইন এফেক্ট ক্লিনআপ বৈশিষ্ট্যটি নিয়েছি , যা নির্ভরতা পরিবর্তনের সময় এবং যখন উপাদানটি আনমসেট হয় তখন চলতে পারে। সুতরাং এটিতে কোনও isMountedপতাকা টগল করার উপযুক্ত জায়গা false, যা পার্শ্ববর্তী প্রভাব কলব্যাক বন্ধের সুযোগ থেকে অ্যাক্সেস করা যায়। আপনি ক্লিনআপ ফাংশনটিকে এর সম্পর্কিত প্রভাবের সাথে সম্পর্কিত হিসাবে ভাবতে পারেন ।
ford04

4
এটা বোঝা যায়! আমি আপনার উত্তর দিয়ে খুশি। আমি এটি থেকে শিখেছি।
নিয়ঙ্গাবো

পবিত্র ধূমপান ... এই isMountedজিনিস কাজ করে। আমি প্রতিক্রিয়া- 10.4.7পরীক্ষা- lib এবং ফর্মিক উপর আছি ^2.1.4। এটি মোট হ্যাক এবং ফর্মিকের সাথে কোনও কিছুর ফলাফল বলে মনে হচ্ছে।
ফিল লাকস

4
@ ভিক্টর মোলিনা না, নিশ্চয়ই ওভারকিল হবে। উপাদানগুলির জন্য এই কৌশলটি বিবেচনা করুন ক) fetchইন useEffectএবং বি এর মতো অ্যাসিনক্রোনাস অপারেশনগুলি ব্যবহার করে ) যা স্থিতিশীল নয়, অর্থাৎ অ্যাসিঙ্ক ফলাফলের ফলাফলের পূর্বে আনমাউন্ট হতে পারে এবং রাষ্ট্র হিসাবে সেট হওয়ার জন্য প্রস্তুত।
ford04

83

অপসারণ করতে - একটি আনমাউন্ট থাকা উপাদান সতর্কতাতে প্রতিক্রিয়া স্থিতি আপডেট করতে পারে না, একটি শর্তের অধীনে घटकডাডমাউন্ট পদ্ধতি ব্যবহার করুন এবং উপাদানটি উইলআউনমઉન્ટ পদ্ধতিতে সেই শর্তটিকে মিথ্যা করুন। উদাহরণ স্বরূপ : -

class Home extends Component {
  _isMounted = false;

  constructor(props) {
    super(props);

    this.state = {
      news: [],
    };
  }

  componentDidMount() {
    this._isMounted = true;

    ajaxVar
      .get('https://domain')
      .then(result => {
        if (this._isMounted) {
          this.setState({
            news: result.data.hits,
          });
        }
      });
  }

  componentWillUnmount() {
    this._isMounted = false;
  }

  render() {
    ...
  }
}

4
এটি কাজ করেছে, তবে কেন এই কাজ করা উচিত? ঠিক এই ত্রুটির কারণ কি? এবং এটি কীভাবে এটি স্থির করেছে: |
অভিনব

এটা ভাল কাজ। এটি সেটস্টেট পদ্ধতির পুনরাবৃত্তি কলটি বন্ধ করে দেয় কারণ এটি সেটস্টেট কলের আগে _আইসমাউন্টযুক্ত মানকে বৈধতা দেয়, তারপরে শেষ পর্যন্ত পুনরায় পুনরায় সেট করে घटকের উইলআউনমઉન્ટ ()। আমার মনে হয়, এটাই কাজ করে।
অভিষেক

7
হুক উপাদানটির জন্য এটি ব্যবহার করুন:const isMountedComponent = useRef(true); useEffect(() => { if (isMountedComponent.current) { ... } return () => { isMountedComponent.current = false; }; });
এক্স-ম্যাগিক্স

@ x-Magix আপনার সত্যিকার অর্থে এর জন্য কোনও রেফের দরকার নেই, আপনি কেবল একটি স্থানীয় ভেরিয়েবল ব্যবহার করতে পারেন যা রিটার্ন ফাংশনটি বন্ধ হতে পারে।
মোরদেখাই

@ অভিনব আমার সর্বোত্তম অনুমান যে এই কাজগুলি কেন _isMountedপ্রতিক্রিয়া দ্বারা পরিচালিত হয় না (বিপরীতে state) এবং তাই প্রতিক্রিয়াটির রেন্ডারিং পাইপলাইনের বিষয় নয় । সমস্যাটি হ'ল যখন কোনও উপাদান আনমাউন্ট করা সেট করা হয়, তখন প্রতিক্রিয়া কোনও কলকে ডেকে আনে setState()(যা একটি 'রি-রেন্ডার' দেয়); অতএব, রাজ্যটি কখনই আপডেট হয় না
লাইটফায়ার 228

25

উপরের সমাধানগুলি যদি কাজ না করে তবে এটি ব্যবহার করে দেখুন এবং এটি আমার জন্য কাজ করে:

componentWillUnmount() {
    // fix Warning: Can't perform a React state update on an unmounted component
    this.setState = (state,callback)=>{
        return;
    };
}

ধন্যবাদ এটি আমার জন্য কাজ করে। এই কোডের টুকরা কি কেউ আমাকে ব্যাখ্যা করতে পারেন?
বদ্রি পৌদেল

@ বাদ্রিপৌডেল যখন এস্কেপস উপাদানটি অচল করবেন তখন ফিরে যাবে, এটি আর কোনও স্মৃতিতে রাখবে না
মে আবহাওয়া ভিএন

এই জন্য আপনাকে অনেক ধন্যবাদ!
তুষার গুপ্ত

কি ফিরি? ঠিক কেমন লেগেছে?
প্লাস

10

setStateএফেক্ট হুক থেকে কল দেওয়ার কারণে আমার এই সতর্কতাটি সম্ভবত ছিল (এটি একত্রে লিঙ্কযুক্ত এই 3 টি বিষয়ে আলোচনা করা হয়েছে )।

যাইহোক, প্রতিক্রিয়া সংস্করণটি আপগ্রেড করা সতর্কতা সরিয়ে ফেলে।


5

পরিবর্তন setDivSizeThrottleableকরার চেষ্টা করুন

this.setDivSizeThrottleable = throttle(
  () => {
    if (this.isComponentMounted) {
      this.setState({
        pdfWidth: this.pdfWrapper!.getBoundingClientRect().width - 5,
      });
    }
  },
  500,
  { leading: false, trailing: true }
);

আমি চেষ্টা করেছিলাম। এখন আমি ধারাবাহিকভাবে সতর্কতাটি দেখছি যা আমি এই পরিবর্তনটি করার আগে উইন্ডোটির আকার পরিবর্তন করার সময় সময়ে সময়ে পর্যবেক্ষণ করছিলাম। ¯_ (ツ) _ / though যদিও এটি চেষ্টা করার জন্য ধন্যবাদ।
ইগোর সলোয়ডেনকো

3

আমি জানি যে আপনি ইতিহাস ব্যবহার করছেন না, তবে আমার ক্ষেত্রে আমি useHistoryপ্রতিক্রিয়া রাউটার ডিওএম থেকে হুক ব্যবহার করছিলাম, যা আমার প্রতিক্রিয়া প্রসঙ্গে সরবরাহকারীর রাজ্যটি স্থির রাখার আগে এই উপাদানটিকে আনমাট করে।

এই সমস্যাটি সমাধান করার জন্য আমি withRouterউপাদানটি আমার ক্ষেত্রে export default withRouter(Login)এবং উপাদানটির অভ্যন্তরে হুক নেস্টিং ব্যবহার করেছি const Login = props => { ...; props.history.push("/dashboard"); ...। আমি অন্যটি props.history.pushউপাদান থেকেও সরিয়েছি , যেমন, if(authorization.token) return props.history.push('/dashboard')কারণ এটি একটি লুপের কারণ, কারণ authorizationরাষ্ট্র।

ইতিহাসে কোনও নতুন আইটেম ঠেলে দেওয়ার বিকল্প ।


2

আপনি যদি অক্ষ থেকে ডেটা আনছেন এবং ত্রুটিটি এখনও দেখা দেয় তবে কেবল অবস্থার ভিতরে সেটারটি জড়িয়ে দিন

let isRendered = useRef(false);
useEffect(() => {
    isRendered = true;
    axios
        .get("/sample/api")
        .then(res => {
            if (isRendered) {
                setState(res.data);
            }
            return null;
        })
        .catch(err => console.log(err));
    return () => {
        isRendered = false;
    };
}, []);

1

সম্পাদনা: আমি ঠিক বুঝতে পেরেছি যে সতর্কতাটি একটি উপাদানটিকে উল্লেখ করছে TextLayerInternal। আপনার বাগটি সম্ভবত এটিই। এটির বাকিগুলি এখনও প্রাসঙ্গিক, তবে এটি আপনার সমস্যার সমাধান করতে পারে না।

1) এই সতর্কতার জন্য কোনও উপাদানটির উদাহরণ পাওয়া শক্ত। দেখে মনে হচ্ছে প্রতিক্রিয়াতে এটি উন্নত করার জন্য কিছু আলোচনা হয়েছে তবে বর্তমানে এটি করার সহজ কোনও উপায় নেই। আমার সন্দেহ, কারণ এটি এখনও তৈরি হয়নি, কারণ সম্ভবত উপাদানগুলি এমনভাবে লেখা যেতে পারে যে আনমাউন্টের পরে সেটস্টেট সম্ভব না হলেও উপাদানটির অবস্থা কী তা বিবেচনা করে। যতক্ষণ প্রতিক্রিয়া দল সম্পর্কিত সমস্যাটি সর্বদা কম্পোনেন্ট কোডে থাকে এবং কম্পোনেন্ট উদাহরণ নয়, এজন্য আপনি কম্পোনেন্ট টাইপের নাম পান।

এই উত্তরটি অসন্তুষ্ট হতে পারে তবে আমি মনে করি আপনার সমস্যাটি আমি ঠিক করতে পারি।

2) লোডাশে থ্রোটলড ফাংশনটির একটি cancelপদ্ধতি রয়েছে। ফোন করুন cancelমধ্যে componentWillUnmountএবং খানা isComponentMounted। বাতিল করা একটি নতুন সম্পত্তি প্রবর্তনের চেয়ে প্রতিক্রিয়া বেশি "মূর্খতার সাথে" is


ইস্যুটি হ'ল, আমি সরাসরি নিয়ন্ত্রণ করি না TextLayerInternal। সুতরাং, আমি জানি না "ডান কে দোষ করেছে setState()"। আমি cancelআপনার পরামর্শ অনুসারে চেষ্টা করব এবং এটি কীভাবে হয় তা দেখুন
Igor Soloydenko

দুর্ভাগ্যক্রমে, আমি এখনও সতর্কতাটি দেখতে পাচ্ছি। আমি জিনিসগুলি সঠিকভাবে করছি তা যাচাই করতে দয়া করে আপডেট 1 বিভাগে কোডটি পরীক্ষা করুন।
ইগোর সলোয়ডেনকো

1

আমার অনুরূপ একটি সমস্যা ছিল ধন্যবাদ @ ফোরড04 আমাকে সাহায্য করেছিল।

তবে, আরও একটি ত্রুটি ঘটেছে।

এনবি। আমি রিএ্যাকটিজেএস হুক ব্যবহার করছি

ndex.js:1 Warning: Cannot update during an existing state transition (such as within `render`). Render methods should be a pure function of props and state.

ত্রুটির কারণ কী?

import {useHistory} from 'react-router-dom'

const History = useHistory()
if (true) {
  history.push('/new-route');
}
return (
  <>
    <render component />
  </>
)

এটি কাজ করতে পারে না কারণ আপনি সমস্ত পৃষ্ঠায় নতুন পৃষ্ঠায় পুনঃনির্দেশ দিচ্ছেন এবং ডমগুলিতে প্রপসগুলি পরিচালনা করা হচ্ছে বা কেবল পূর্ববর্তী পৃষ্ঠায় রেন্ডারিং বন্ধ হয়নি।

আমি কি সমাধান খুঁজে পেয়েছি

import {Redirect} from 'react-router-dom'

if (true) {
  return <redirect to="/new-route" />
}
return (
  <>
    <render component />
  </>
)

0

আমি একই সমস্যা ছিল এবং এটি সমাধান:

আমি রিডেক্সে অ্যাকশন প্রেরণ করে স্বয়ংক্রিয়ভাবে ব্যবহারকারীকে লগ-ইন করছিলাম (রিডেক্স অবস্থায় প্রমাণীকরণের টোকেন রেখে)

এবং তারপরে আমি আমার উপাদানটিতে এই সেট সেট ({Succ_message: "...") দিয়ে একটি বার্তা দেখানোর চেষ্টা করছিলাম।

কনসোলে একই ত্রুটিযুক্ত অংশটি খালি খুঁজছিল: "আনমাউন্টযুক্ত উপাদান" .. "মেমরি ফাঁস" ইত্যাদি etc.

আমি এই থ্রেডে ওয়ালটারের উত্তরটি পড়েছি

আমি লক্ষ্য করেছি যে আমার আবেদনের রাউটিং টেবিলটিতে, ব্যবহারকারী লগ-ইন থাকলে আমার উপাদানগুলির রুটটি বৈধ ছিল না:

{!this.props.user.token &&
        <div>
            <Route path="/register/:type" exact component={MyComp} />                                             
        </div>
}

টোকেন উপস্থিত আছে কি নেই তা আমি রুটটি দৃশ্যমান করে দিয়েছি।


0

@ Ford04 উত্তরের উপর ভিত্তি করে, এখানে কোনও পদ্ধতিতে একই এনপ্যাপুলেটেড রয়েছে:

import React, { FC, useState, useEffect, DependencyList } from 'react';

export function useEffectAsync( effectAsyncFun : ( isMounted: () => boolean ) => unknown, deps?: DependencyList ) {
    useEffect( () => {
        let isMounted = true;
        const _unused = effectAsyncFun( () => isMounted );
        return () => { isMounted = false; };
    }, deps );
} 

ব্যবহার:

const MyComponent : FC<{}> = (props) => {
    const [ asyncProp , setAsyncProp ] = useState( '' ) ;
    useEffectAsync( async ( isMounted ) =>
    {
        const someAsyncProp = await ... ;
        if ( isMounted() )
             setAsyncProp( someAsyncProp ) ;
    });
    return <div> ... ;
} ;
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.