承前
小説を簡単に同人誌の形にできる印刷できるWebサイトを作りたいと思っている。
今回はその前段階として、印刷する際のプレビュー画面を作成していたときに躓いた話。
事象
直前まで動いていたReactコンポーネントが急にエラーを吐き始めた。
Uncaught TypeError: Cannot read property 'chapterNumber' of undefined
at RenderUi (index.js:70616)
at renderWithHooks (index.js:48209)
at mountIndeterminateComponent (index.js:50888)
at beginWork (index.js:52002)
at HTMLUnknownElement.callCallback (index.js:33594)
at Object.invokeGuardedCallbackDev (index.js:33643)
at invokeGuardedCallback (index.js:33698)
at beginWork$1 (index.js:56609)
at performUnitOfWork (index.js:55560)
at workLoopSync (index.js:55536)
chapterNumber
はChapterContent
のメンバ。
ChapterContent
はNovelWithContents
のメンバ。
調査
ChapterContent
のインスタンスが空だった。
なんならNovelWithContents
のインスタンスも空だった。
('、3_ヽ)_
やりたかったことと原因
やりたかったこと
NovelWithContents
がなぜ空なのか
axiosで取得したJSONモデルデータが詰まっている予定だったが、ログを出してみたら問題が分かった。
-
componentDidMount()
はComponent描画後に呼ばれるため、Component描画時にココで取得する予定の値を参照してしまうとエラーになる。
その後、もう一つの問題も分かった。
- axiosは非同期処理をするので、Constructorなどで事前に値を取得しようとしてもデータ取得処理が間に合うかどうか分からない(間に合わない)
componentDidMount() {
console.log(this.state.novelId+"sssssssssssssssssssssssss");
axios
.get(/*API CALL*/)
.then(response => {
let data: NovelWithContents = response.data;
console.log(response.data);
this.setState({ contentsJson: data });
})
.catch(() => {
console.log("通信に失敗しました。")
});
}
結論
前処理の部分で、データが無いクラスのプロパティを参照してエラーを吐いていた。
対処
失敗した方法
constructor
のstate
初期化後にaxios
以下を移してもうまく行かなかった。
render
に移しても同様。
うまく行った方法
色々やった。
- 前処理後の値をStateにして初期値を与えた。
- 初回のRender時、即ちaxiosが間に合ってない時に空でエラーを吐かなくなった。
- 初期化処理をaxiosの
finally()
処理に集めた。- これによりデータ取得と初期化処理の順番の整合性を取りながら、非同期処理という体は崩さずに済んだ。
- 上記を可能にするために全体的にリファクタリング
componentDidMount() {
axios
.get(/*API CALL*/)
.then(response => {
let data: NovelWithContents = response.data;
console.log(response.data);
this.setState({ contentsJson: data });
})
.catch(() => {
console.log("通信に失敗しました。")
}).finally(()=>{
let hoge="";
//前処理。
});
}
結局3時間くらいハマってしまった。
反省
- Reactへの理解度が浅い中で始めたProjectだったので、設計はあとでキチンとやりなおす。
今回実現した機能
小説のページング
小説印刷支援サイト
やや遅れ気味。7月リリースを目指したい。