iT邦幫忙

0

React hook 的使用問題

請問我使用react寫了個Login.js component以及Message.js這個共用的component,當在Login時Message會根據response去顯示訊息,useState(hook)去控制Message是否要顯示,可是useState只會在第一次render初始狀態時用到,第二次要render時,我想說利用Login傳來的props.open去判斷訊息顯示,可是這樣碰到兩個問題:

1.官方建議不要在判斷式中使用hook
2.顯示Too many re-renders. React limits the number of renders to prevent an infinite loop.(Error)


Login.js(省略部分code)

const [response, setResponse] = useState('')

const handleSubmit = async e => {
    e.preventDefault()

    let response = await instance('post', `/api/user/login`, '', values)
    setResponse(response)
  }


 {response ?
        <Message open={true}
                 message={response.data.status === 400 ? response.data.error[0].message : 'Login successfully!'}
                 timeout={3000}
        />
        : null}

Message.js

export default props => {
  const [open, setOpen] = useState(props.open)

  if (props.open === true) setOpen(true)
  
  return (
    <Snackbar open={open}
              onClose={() => setOpen(false)}
      // TransitionComponent={transition}
              autoHideDuration={props.timeout}
              ContentProps={{
                'aria-describedby': 'message-id',
              }}
              message={props.message ? props.message : ''}
    />
  )
}
0
Andy Chang
iT邦新手 5 級 ‧ 2019-10-29 15:30:50
最佳解答

不確定你想做什麼,如果是單就這段的話:

useState只會在第一次render初始狀態時用到,第二次要render時,我想說利用Login傳來的props.open去判斷訊息顯示

import React, {useState,useRef,useEffect} from 'react';
const mounted=useRef();

// 利用useEffect處理每次render時要做的事情,並用useRef紀錄是否為第一次render

useEffect(()=>{ 
    if(mounted.current===false){
        mounted.current=true;
    }
    else{
        if (props.open === true) setOpen(true)
    }
        
},[props.open])

但看了一下,你好像是希望初始時和props.open被改變時就要setOpen(props.open)。如果是這樣的話這樣就好了。

useEffect(()=>{ 
    setOpen(props.open)        
},[props.open])
看更多先前的回應...收起先前的回應...
coder iT邦新手 5 級 ‧ 2019-10-29 16:09:31 檢舉

我的想法只是想要讓他有訊息時,open為true,可是我有使用autoHideDuration這個屬性,時間到了會去跑onClose,這時open就變為false了,那我再送出一次表單,想要再顯示一次message時,不知在哪加setOpen(true)比較好,大大的方法我試了好像也不行

「再送出一次表單」和「有訊息」這兩個動作發生時,去改變在綁在Message上的open。
然後不是在useEffect中的判斷式要刪掉

你可以把props.open在第一次之後當作是一個「觸發useEffect」的開關。它的值不再具有意義,而是讓useEffect偵測到它被改變之後,顯示你的訊息。

useEffect(()=>{ 
    setOpen(true)        
},[props.open])

而在Login.js中,也用一個state去綁定open(假設是controlOpen),當你有新回應進來,呼叫此函式:

setControlOpen(!controlOpen);
coder iT邦新手 5 級 ‧ 2019-10-29 16:56:29 檢舉

我在Login.js裡加

const [controlOpen, setControlOpen] = useState(false)

const handleSubmit = async e => {
    e.preventDefault()

    let response = await instance('post', `/api/user/login`, '', values)
    setResponse(response)
    setControlOpen(!controlOpen)
  }

可是我這樣按submit之後第一次有訊息,如果馬上再按一次submit controlOpen不是就變回false了?

對。因為假設你是在Login.js接收新回應/submit。你想在「接收新回應/submit」時更改message,實質上這個動作等同於父元件對子元件的主動溝通。也不一定要用open這個props,只是因為剛好你的props.open在第一次設定完初始值後就沒啥用了,所以才用它。你要綁在另外一個新的props也可以。總之我們只是需要一個用來改變的props,然後讓message裡的useEffect能偵測這個props並藉由這個props的改變而得知「我們要打開message」並執行這個動作。 而controlOpen作為這個props,它第一次之後是啥值都沒差,只要有改變就好。

意思是這樣也是沒差,只是這樣你open後面都沒用到。

const [controlOpen, setControlOpen] = useState(1)

const handleSubmit = async e => {
    e.preventDefault()

    let response = await instance('post', `/api/user/login`, '', values)
    setResponse(response)
    setControlOpen(controlOpen+1)
  }
  
  return (
     {response ?
        <Message open={true} control={controlOpen}
                 message={response.data.status === 400 ? response.data.error[0].message : 'Login successfully!'}
                 timeout={3000}
        />
        : null}
    />
  )

message

useEffect(()=>{ 
      setOpen(true)        
  },[props.control])
coder iT邦新手 5 級 ‧ 2019-10-29 18:32:26 檢舉

我懂您的意思,就是在props中隨便加入一個屬性值,就大大的範例來說(props.controlOpen),然後每次submit時去改變它,接著在Message裡useEffect就會根據props.controlOpen的改變去作動

Login.js

const [open, setOpen] = useState(false)

const handleSubmit = async e => {
    e.preventDefault()

    let responses = await instance('post', `/api/user/login`, '', values)
    setResponse(responses)
    setOpen(!open)
  }
  
  {response ?
        <Message message={response.data.status === 400 ? response.data.error[0].message : 'Login successfully!'}
                 timeout={3000}
                 control={open}
        />
        : null}

Message.js

export default props => {
  const [open, setOpen] = useState(true)

  useEffect(() => {
    setOpen(true)
  }, [props.control])

  return (
    <Snackbar open={open}
              onClose={() => setOpen(false)}
      // TransitionComponent={transition}
              autoHideDuration={props.timeout}
              ContentProps={{
                'aria-describedby': 'message-id',
              }}
              message={props.message ? props.message : ''}
    />
  )
}

Yep 這樣應該可以解決你想實現的東西(吧?)

0
sion
iT邦新手 4 級 ‧ 2019-10-29 14:37:33

最近剛好也在學react
不過我覺得class比較好用/images/emoticon/emoticon39.gif

你的問題應該是在這裡

if (props.open === true) setOpen(true)

不太懂你useState()的時候就已經綁 props.open了
為何後面還要再加這個判斷式呢?

初始=props.open => 當props.open===true時 => props.open=true

這樣不就無窮迴圈了嗎?

coder iT邦新手 5 級 ‧ 2019-10-29 14:54:22 檢舉

我的流程是這樣:
1.表單送出(登入失敗)
2.跳出Message
3.autoHideDuration這個屬性時間到會去執行onClose裡的fun,也就是() => setOpen(false)
4.再送出一次表單(還是登入失敗),第二次不會再去執行useState(props.open)

我要怎麼再把open的值變成true呢?
就是setOpen(true)

sion iT邦新手 4 級 ‧ 2019-10-29 15:19:55 檢舉

但你第一次render的時候就進無窮迴圈啦
依你的需求改成useState(false)就好了
而你之後的if (props.open === true) setOpen(true)
會幫你改狀態的

coder iT邦新手 5 級 ‧ 2019-10-29 15:56:59 檢舉

改了還是噴Too many re-renders. React limits the number of renders to prevent an infinite loop.

1
dragonH
iT邦大師 1 級 ‧ 2019-10-29 15:53:57

不熟 react

不知道這樣寫有沒有問題

codepen

等待高手指點

我還是比較喜歡 Vue /images/emoticon/emoticon07.gif

看更多先前的回應...收起先前的回應...
coder iT邦新手 5 級 ‧ 2019-10-29 16:46:05 檢舉

看了大大的範例,跟我的有點不太一樣
MainApp => Login
SnackBar => Meaasge
大大的作法是在MainApp裡,有submit就去改變訊息的顯示
我的是在Message裡控制

dragonH iT邦大師 1 級 ‧ 2019-10-29 16:54:46 檢舉

是在MainApp裡,有submit就去改變訊息的顯示

你的不也是從 login.js(MainApp)

以 props 控制顯示與否 跟 內容嗎

coder iT邦新手 5 級 ‧ 2019-10-29 17:07:58 檢舉

是的,可是我在Message.js裡也有控制顯示(幾秒後自動關閉)的函數onClose

onClose={() => setOpen(false)}

大大您的都是拿props傳入的值
可是我的Message還會有onClose去改變open

dragonH iT邦大師 1 級 ‧ 2019-10-29 17:15:48 檢舉

這應該是因為你的 message.js

裡面還有再包一個 component 吧

我的 SnackBar 裡面直接就是 html dom 了

我要發表回答

立即登入回答