LoginSignup
1
0

More than 1 year has passed since last update.

lodashとNextでつまずいたところをかく。

Last updated at Posted at 2021-11-22

私は現在lodash+firebase+next.js+materialUIで簡単な連絡帳アプリを構築しています。
その中でも私が特に躓いた部分を記事にしました。
今回は、連絡先の更新機能で躓いた部分を紹介します。

実際に構築したもの

iPhone 12 Pro Max – 20.png

今回紹介するもの

iPhone 12 Pro Max – 22.png

前提としてデータ構造は下記のような形になっています。

interface Contact{
  id: string
  firstName: string
  lastName: string
  phoneNumber: string
  email?: string
  company?: string
  avatarUrl?: string
  address:{
   zip?: string
   prefecture?: string
   city?: string
   building?: string
   street?: string
   }
}

[STEP:01]useStateでフォームの内容を管理する

この時に、間違って×マークを押して編集画面を閉じてしまっても、編集画面をもう一度クリックしたら、編集されていた内容があるようにしたい。
※一部抜粋

iPhone 12 Pro Max – 21.png

lodashのcloneDeepを使用することでネストが深いところまで見てコピーしてくれる

const [updateContact, setUpdateContact] = useReducer(
    (state: Contact, data: Partial<Contact>) => _.merge({}, state, data),
    _.cloneDeep(contact)
  )

実際にはこのような形でコピーされるが、lodashのcloneDeepを使用することによって1行かける。

const [updateContact, setUpdateContact] = useReducer(
    (state: Contact, data: Partial<Contact>) => _.merge({}, state, data),
    {
      id: contact.id,
      firstName: contact.firstName,
      lastName: contact.lastName,
      email: contact.email,
      phoneNumber: contact.phoneNumber,
      company: contact.company,
      address: {
        zip: contact.address?.zip,
        prefecture: contact.address?.prefecture,
        city: contact.address?.city,
        building: contact.address?.building,
        street: contact.address?.street,
      },
    }
  )

[注意]lodashにはcloneとcloneDeepがあり、大元のデータを変更しないcloneDeepを使用する。

//これだと、大元のcontactの値を変更してしまう恐れがある。
_.clone(contact)

//完全に別物にして、オリジナルのものを壊さない為には、cloneDeepを使う
_.cloneDeep(contact)

clone
https://lodash.com/docs/4.17.15#clone
cloneDeepについて
https://lodash.com/docs/4.17.15#cloneDeep

[STEP:02]フォームの内容が書き換えられたら、useStateを更新する


const onChangeUpdate = (e: React.ChangeEvent<HTMLInputElement>) => {
//ここでuseStateを更新する。
    setUpdateContact(_.set({}, e.target.name, e.target.value))
  }
  return (
    ---省略---
          <TextField
            value={updateContact.firstName}
            name='firstName'
            label='名前'
            onChange={onChangeUpdate}
            fullWidth
            margin='normal'
          />
     ---省略---
          <TextField
            name='address.street'
            value={updateContact.address?.street}
            label='町・番地'
            fullWidth
            onChange={onChangeUpdate}
            margin='normal'
            placeholder='ねこマム番地'
          />
     ---省略---
  )
}

lodashのset関数の第二引数はpathを指定する必要がある。

iPhone 12 Pro Max – 23.png

lodash
https://lodash.com/docs/4.17.15#set

[STEP:03]更新ボタンを押したらcloudfirestoreを更新する

※ withConverterは別で書きます。

 const handleUpdate = async () => {
    const ref = doc(
      db,
      'users',
      currentUser?.uid as string,
      'contacts',
      contact.id
    ).withConverter<Contact>(new IdRemover<Contact>())
    await updateDoc(ref, updateContact)
    router.reload()
  }

全コード(Updateモーダルのみ)


interface UpdateDialogProps {
  contact: Contact
  isUpdateModalOpen: boolean
  setIsUpdateModalOpen: React.Dispatch<React.SetStateAction<boolean>>
}

const UpdateDialog: React.FC<UpdateDialogProps> = ({
  contact,
  isUpdateModalOpen,
  setIsUpdateModalOpen,
}) => {
  const [updateContact, setUpdateContact] = useReducer(
    (state: Contact, data: Partial<Contact>) => _.merge({}, state, data),
    _.cloneDeep(contact)
  )
  const onChangeUpdate = (e: React.ChangeEvent<HTMLInputElement>) => {
    setUpdateContact(_.set({}, e.target.name, e.target.value))
  }

  const router = useRouter()
  const { db, currentUser } = useContext(FirebaseContext)

  const handleUpdate = async () => {
    const ref = doc(
      db,
      'users',
      currentUser?.uid as string,
      'contacts',
      contact.id
    ).withConverter<Contact>(new IdRemover<Contact>())
    await updateDoc(ref, updateContact)
    router.reload()
  }

  return (
    <Dialog fullScreen open={isUpdateModalOpen}>
      <AppBar sx={{ position: 'relative' }}>
        <Toolbar>
          <IconButton
            edge='start'
            color='inherit'
            onClick={() => setIsUpdateModalOpen(false)}>
            <CloseIcon />
          </IconButton>
          <Typography sx={{ ml: 2, flex: 1 }} variant='h6' component='div'>
            連絡先を編集中
          </Typography>
          <Button color='inherit' onClick={handleUpdate}>
            更新する
          </Button>
        </Toolbar>
      </AppBar>
      <Container>
        <Typography sx={{ my: 2, flex: 1 }} variant='h5' component='div'>
          基本情報
        </Typography>
        <Grid container rowSpacing={1} justifyContent='start'>
          <TextField
            value={updateContact.firstName}
            name='firstName'
            label='名前'
            onChange={onChangeUpdate}
            fullWidth
            margin='normal'
          />
          <TextField
            value={updateContact.lastName}
            name='lastName'
            label='苗字'
            placeholder='マムシ'
            onChange={onChangeUpdate}
            fullWidth
            margin='normal'
          />
          <TextField
            value={updateContact.phoneNumber}
            name='phoneNumber'
            label='電話番号'
            placeholder='1234567890'
            onChange={onChangeUpdate}
            fullWidth
            margin='normal'
          />
          <TextField
            value={updateContact.email}
            name='email'
            label='メールアドレス'
            placeholder='mamushi@mamushi.com'
            onChange={onChangeUpdate}
            fullWidth
            margin='normal'
          />
          <TextField
            value={updateContact.company}
            name='company'
            label='会社名'
            placeholder='マムシ株式会社'
            onChange={onChangeUpdate}
            fullWidth
            margin='normal'
          />
        </Grid>
        <Typography sx={{ my: 2, flex: 1 }} variant='h5' component='div'>
          住所
        </Typography>
        <Grid container rowSpacing={1} sx={{ mb: 5 }} justifyContent='start'>
          <TextField
            name='address.zip'
            value={updateContact.address?.zip}
            label='郵便番号'
            placeholder='2010017'
            onChange={onChangeUpdate}
            fullWidth
            margin='normal'
          />
          <TextField
            name='address.prefecture'
            value={updateContact.address?.prefecture}
            label='都道府県'
            placeholder='東京都'
            onChange={onChangeUpdate}
            fullWidth
            margin='normal'
          />
          <TextField
            name='address.city'
            value={updateContact.address?.city}
            label='市町村'
            placeholder='マムシ区マムシ町'
            onChange={onChangeUpdate}
            fullWidth
            margin='normal'
          />
          <TextField
            name='address.building'
            value={updateContact.address?.building}
            label='ビル・建物名'
            placeholder='マムシビル'
            onChange={onChangeUpdate}
            fullWidth
            margin='normal'
          />
          <TextField
            name='address.street'
            value={updateContact.address?.street}
            label='町・番地'
            fullWidth
            onChange={onChangeUpdate}
            margin='normal'
            placeholder='ねこマム番地'
          />
          <Button onClick={handleUpdate} sx={{ my: 3 }} variant='contained'>
            更新する
          </Button>
        </Grid>
      </Container>
    </Dialog>
  )
}

1
0
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0