This is for an interactive bible.
In the following code I have buttons that are being conditionally rendered based upon the buttons I previously clicked.
The code calls an API to retrieve the data.
useEffect(() => {
dispatch(fetchBibleBooks());
}, [dispatch]);
useEffect(() => {
if(selectedBook && chapter){
dispatch(fetchBibleBookChapter( {selectedBook, chapter} ));
}
}, [dispatch, selectedBook, chapter]);
useEffect(() => {
setOTBooks(bibleArray.filter(book => book.testament === 'OT'));
}, [ clickedOT ]);
useEffect(() => {
setNTBooks(bibleArray.filter(book => book.testament === 'NT'));
}, [ clickedNT ]);
useEffect(() => {
setBookChapters(bibleArray.find(book => book.id === selectedBook)?.chapters || []);
}, [ selectedBook ]);
useEffect(() => {
bibleArray.filter(chapter => chapter);
const subsetBibleArray = bibleArray.map( text => text.cleanText );
setChapterText(subsetBibleArray);
});
return(
<Container>
<Row>
<Col style={{textAlign: 'center'}}>
<h1 style={{ color: 'darkblue'}}>Biblia Interactiva</h1>
<Button style={{margin: '2rem'}} color='warning' size='lg' onClick={() => setClickedOT(true)}>
Antiguo Testamento
</Button>{' '}
<Button style={{margin: '2rem'}} color='warning' size='lg' onClick={() => setClickedNT(true)}>
Nuevo Testamento
</Button>
</Col>
</Row>
<Row style={{marginTop:'4rem'}}>
<Col style={{textAlign:'left', marginLeft:'-18rem'}}>
{
clickedOT && otBooks.sort((b1, b2) => {
if (b1.order < b2.order) return -1;
if (b1.order > b2.order) return 1;
return 0;
}).map(book => (
<Button color='primary' key={book.order} style={{ margin: '4px' }} onClick={() => setSelectedBook(book.id)}>
{book.name}
</Button>
))
}
</Col>
<Col style={{textAlign:'left', marginRight:'-18rem'}}>
{
clickedNT && ntBooks.sort((b1, b2) => {
if (b1.order < b2.order) return -1;
if (b1.order > b2.order) return 1;
return 0;
}).map(book => (
<Button color='primary' key={book.order} style={{ margin: '4px' }} onClick={() => setSelectedBook(book.id)}>
{book.name}
</Button>
))}
</Col>
</Row>
<Row>
<Col style={{textAlign:'center', marginLeft:'', marginTop: '6rem'}}>
{
bookChapters.map(ch => (
<Button color='success' key={ch.id} style={{ margin: '4px' }} onClick={() => setChapter(ch.chapter)}>
{ch.chapter}
</Button>
)
)
}
</Col>
</Row>
<Row>
<Col style={{textAlign:'left', marginLeft:'', marginTop: '6rem'}}>
{
chapterText.map((text, idx) => (
<p key={idx}>
{text}
</p>
)
)
}
</Col>
</Row>
</Container>
)
}
The code work well the first go around but if I re-click one of the Bible book buttons, the previously rendered chapter buttons will disappear and so will the text, which is fine but it should re-render the chapter buttons to the new relevant book clicked.
The ‘selectedBook’ variable is updated but the state variable ‘chapter’ is still holding the value of the previous chapter and so the new state has the new book with the old chapter and fetches that chapter from the API and the relevant chapter buttons don’t get rendered.
2
Answers
I actually fixed it.
It seems that the issue was with how I was updating state.
I make two calls to the API. That's fine. The issue was that I was updating the same state, state.bible.bibleArray, from the two different API calls.
So the second time I made the call to the API, I was overwriting my previous state.bible.bibleArray. What I did was I added a second array, newBibleArray, in the initialState in my bibleSlice.js in redux so that I can update state.bible.newBibleArray, using the data from the first API call in state.bible.bibleArray and finally displaying the text.
Two things:
and then have all your buttons call this function
bookChapters
is computed/derived as a consequence of a book being selected (or not), but it’s not state itself.That’s it. It gets recomputed on every render and that’s fine (unless you have a performance issue). You don’t need state to to hold
bookChapters
.It’s unlikely that
chapterText
is state as well, it shouldn’t be state and the effect shouldn’t exist.See this You might not need an effect