Reactのstate hookに値を変更した配列を渡してもre-renderされなかった

タイトル通りです。
JSのArrayは参照渡しということがわかっていなかったのと、ReactのuseStateの理解が浅かったことが原因で若干時間をとってしまいました。

# 起こったこと

(コンポーネントの設計が色々とアレなのは許してください...)

一覧表示画面を実装していました。

```jsx
//色々略

const App = () => {
	//略

	const [blogs, setBlogs] = useState([])
	
	//略

	return (
		<div>
			{/*略*/}

			{user && <BlogList blogs={blogs} setBlogs={setBlogs} />}
		</div>
	)
}

export default App;
```

↑のBlogListというのが一覧です。中身はこうなっています

```jsx
const BlogList = ({ blogs, setBlogs}) => {
	return (
		<ul className="blog_ul">
			{blogs.map(blog =>
				<Blog key={blog.id} blog={blog} blogList={blogs} setBlogs={setBlogs} />
			)}
		</ul>
	)
}

```

Appコンポーネントからblogsステートとblogsを更新するためのsetBlogsをpropsとして受け取り、さらにリスト要素のBlogコンポーネントに渡しています。こういうバケツリレーをやっていいのかやるべきでないのか正直自信ないですが、とりあえずこうなってます。
Blogコンポーネントの中身は以下の通りです。

```jsx
const Blog = ({blog, blogList, setBlogs}) => {
	const [showDetail, setShowDetail] = useState(false)

	const toggleDetail = () => {
		setShowDetail(!showDetail)
	}

	const incrementLike = async blog => {
		const currentBlogList = blogList
		const targetBlogId = blog.id
		const targetBlogIndex = currentBlogList.findIndex(blog => blog.id === targetBlogId)

		blog.likes = blog.likes + 1
		const updatedBlog = await blogService.like(blog)

		currentBlogList[targetBlogIndex] = updatedBlog
		setBlogs(currentBlogList)
	}

	const blogBrief = () => (
		<>
			{blog.title} {blog.author}
			<button onClick={toggleDetail}>view</button>
		</>
	)

	const blogDetail = () => (
		<>
			{blog.title} <button onClick={toggleDetail}>hide</button><br />
			{blog.url}<br />
			likes {blog.likes} <button onClick={() => incrementLike(blog)}>like</button><br />
			{blog.author}<br />
		</>
	)

	return (
		<li className="blog_style">
			{showDetail ? blogDetail() : blogBrief()}
		</li>
	)
}
```

色々書いてありますが、今回問題が起こったのは incrementLike の関数でした。
この関数はblogのlikeの値をインクリメントする関数で、画面上だと "like" のボタンをクリックすることで発火します。
サーバーから更新されたblogオブジェクトが返ってきたらblogList内の該当する要素と置き換えて、setBlogs関数にその配列を渡します。そうすることでblogsが更新されて画面がre-renderされ、画面上のlikeの数が変わるというわけです。

likeボタンを押すと確かにlikeの値はしっかり変更されるのですが、画面上ではlikeの数はそのままでした。
つまり、re-renderされていませんでした。

# 一旦デバッグしてみる

とりあえずconsole.logしまくります。

```jsx
const incrementLike = async blog => {
	const currentBlogList = blogList
	console.log('current blogs:',currentBlogList)
	const targetBlogId = blog.id
	console.log('target blog id:',targetBlogId)
	const targetBlogIndex = currentBlogList.findIndex(blog => blog.id === targetBlogId)
	console.log('targ blog index:', targetBlogIndex)

	blog.likes = blog.likes + 1
	const updatedBlog = await blogService.like(blog)
	console.log('updated blog:',updatedBlog)

	currentBlogList[targetBlogIndex] = updatedBlog
	setBlogs(currentBlogList)
}
```

で、この要素を更新してみます
![スクリーンショット 2021-03-24 23.31.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329016/db5c28d8-08ce-7c7d-c79b-e621217c41d4.png)
現在likesの値は6なので、一度likeをクリックすれば7になるはずです。

↓

![スクリーンショット 2021-03-24 23.54.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329016/13e04efa-46d0-480b-88b4-3b39e15494d3.png)


ちゃんと7になってます。
ただ、なんか元の配列内の要素も同じく更新されてしまっています。参照渡しになってるっぽい?
意図していた挙動ではなかったので一旦Arrayでググってみる。

https://www.w3schools.com/js/js_arrays.asp
>Arrays are a special type of objects. The typeof operator in JavaScript returns "object" for arrays.

ArrayはObjectらしいです。

Objectということは参照渡しです。つまり、BlogコンポーネントのincrementLikeでcurrentBlogListを更新するということは、もとを辿ってゆくとAppコンポーネントのblogsを更新しているのと同じということです。
ただ、同じ値であったとしてもblogsを更新するsetBlogsに値を渡しているのだからre-renderされるのでは? という考えが払拭できなかったので、とりあえず公式ドキュメントを読み直してみました。

# 同じ値で更新を行った場合re-renderされない

https://ja.reactjs.org/docs/hooks-reference.html

>現在値と同じ値で更新を行った場合、React は子のレンダーや副作用の実行を回避して処理を終了します（React は Object.is による比較アルゴリズムを使用します）。

らしいです。
つまり

```jsx
const incrementLike = async blog => {
	const currentBlogList = blogList
	console.log('current blogs:',currentBlogList)
	const targetBlogId = blog.id
	console.log('target blog id:',targetBlogId)
	const targetBlogIndex = currentBlogList.findIndex(blog => blog.id === targetBlogId)
	console.log('targ blog index:', targetBlogIndex)

	blog.likes = blog.likes + 1
	const updatedBlog = await blogService.like(blog)
	console.log('updated blog:',updatedBlog)

	currentBlogList[targetBlogIndex] = updatedBlog
	console.log('are blogList and currentBloglist the same object?:', Object.is(blogList, currentBlogList))
	setBlogs(currentBlogList)
}
```

↓
![スクリーンショット 2021-03-25 0.29.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329016/5e6d5337-e8c1-dde5-56d4-634da15015be.png)

こういうことなので画面はそのままだったということのようです。

# 修正

```jsx
const incrementLike = async blog => {
	const currentBlogList = [...blogList]
	const targetBlogId = blog.id
	const targetBlogIndex = currentBlogList.findIndex(blog => blog.id === targetBlogId)

	blog.likes = blog.likes + 1
	const updatedBlog = await blogService.like(blog)

	currentBlogList[targetBlogIndex] = updatedBlog
	setBlogs(currentBlogList)
}
```