I am trying to get some data back from my database and map over it to display in the client (using react/redux-toolkit).
The current structure of the data is a series of arrays filled with objects:
[
{
id: 2,
prize_id: 1,
book_id: 2,
author_id: 2,
}
]
[
{
id: 1,
prize_id: 1,
book_id: 1,
author_id: 1,
}
]
The front end is only displaying one of the arrays at a time despite needing to display both. I think the problem is in how my redux is set up. Becuase when I console.log
the action.payload
I get back just one of the arrays, usually the second one.
Here is how my redux slices and actions look:
slice:
const booksSlice = createSlice({
name: 'books',
initialState: {
booksByYear: [], //possibly the source of the problem
},
reducers: {},
extraReducers: (builder) => {
builder.addCase(actions.fetchBooksByYear.fulfilled, (state, action) => {
console.log(action.payload)
state.booksByYear = action.payload
})
},
})
action:
export const fetchBooksByYear = createAsyncThunk(
'books/get books by prize and year year',
async ({ prizeId, prizeYear }) => {
const data = await api.getBooksByPrizeAndYear(prizeId, prizeYear.year)
return data
}
Here is how I am fetching the data from my component:
useEffect(() => {
dispatch(fetch.fetchPrizeYears(prizeId))
}, [dispatch])
const booksByYear = prizeYears.map((year, id) => {
console.log(year)
return <PrizeLists key={id} prizeYear={year} prizeId={prizeId} />
})
export default function PrizeLists(props) {
const dispatch = useDispatch()
const listOfBooks = useSelector((state) => state.books.booksByYear)
useEffect(() => {
dispatch(fetch.fetchBooksByYear(props))
}, [dispatch])
Previously it was working when the call was being made without redux
CodePudding user response:
So the booksByYear
is expected to be an array of arrays, is that correct? For example:
booksByYear: [
[
{
id: 2,
prize_id: 1,
book_id: 2,
author_id: 2,
}
],
[
{
id: 1,
prize_id: 1,
book_id: 1,
author_id: 1,
}
]
]
The slice setup seems fine, I think the problem might be api.getBooksByPrizeAndYear
.
Because the action.payload
in the callback of builder.addCase
is returned by the corresponding createAsyncThunk
, which is fetchBooksByYear
in your case.
So if action.payload
is not something you're expecting, there's a high chance that the API is not responding the correct dataset in the first place.
I'm not sure about the use case in you app, if the API will only return one array at a time, you probably want to merge action.payload
with state.booksByYear
instead of replacing it.
Oh, now I know why you said initialState.booksByYear
might be the problem! Yes, it is a problem because from you code it seems that you want to "group" those books by { prizeYear, prizeId }
, and display the books in each group on UI. Due to the fact that there's only one array at the moment, the last fulfilled action will always overwrite the previous fulfilled action because of how we handle API response (state.booksByYear = action.payload
).
In this case I think it makes more sense to leave those books in the component by using useState
. But if you really want to store those books in redux, you could try making initialState.booksByYear
into a Map-like object, and find the corresponding array by { prizeYear, prizeId }
from <PrizeLists />
.
For example:
// Slice
// You may want to implement the hash function that suits your case!
// This example may lead to a lot of collisions!
export const hashGroupKey = ({ prizeYear, prizeId }) => `${prizeYear}_${prizeId}`
const booksSlice = createSlice({
name: 'books',
initialState: {
// We can't use Map here because it's non serializable.
// key: hashGroupKey(...), value: Book[]
booksMap: {}
},
extraReducers: (builder) => {
builder.addCase(actions.fetchBooksByYear.fulfilled, (state, action) => {
const key = hashGroupKey(action.meta.arg)
state.booksMap[key] = action.payload
})
},
})
// PrizeLists
import { hashGroupKey } from '../somewhere/book.slice';
const listOfBooks = useSelector((state) => {
const key = hashGroupKey(props)
return state.books.booksMap[key] ?? []
})