Reputation: 259
I'm building a simple review app with react and redux toolkit.
Reviews are added via a form in AddReview.js
, and I'm wanting to display these reviews in Venue.js
.
When I submit a review in AddReview.js
, the new review is added to state, as indicated in redux dev tools:
However when I try to pull that state from the store in Venue.js
, I only get the initial state (the first two reviews), and not the state I've added via the submit form:
Can anyone suggest what's going wrong here?
Here's how I've set up my store:
store.js
import { configureStore } from "@reduxjs/toolkit";
import reviewReducer from '../features/venues/venueSlice'
export const store = configureStore({
reducer:{
reviews: reviewReducer
}
})
Here's the slice managing venues/reviews:
venueSlice.js
import { createSlice } from "@reduxjs/toolkit";
const initialState = [
{id:1, title: 'title 1',blurb: 'blurb 1'},
{id:2, title: 'title 2',blurb: 'blurb 2'}
]
const venueSlice = createSlice({
name: 'reviews',
initialState,
reducers: {
ADD_REVIEW: (state,action) => {
state.push(action.payload)
}
}
})
export const { ADD_REVIEW } = venueSlice.actions
export default venueSlice.reducer
And here's the Venue.js
component where I want to render reviews:
import { useParams } from "react-router-dom";
import { useSelector } from "react-redux";
const Venue = () => {
const { id } = useParams()
const reviews = useSelector((state) => state.reviews)
console.log(reviews)
return (
<div>
{reviews.map(item => (
<h1>{item.title}</h1>
))}
</div>
)
}
export default Venue;
Form component AddReview.js
import { useState } from "react"
import { useDispatch } from "react-redux"
import { ADD_REVIEW } from "./venueSlice"
import { nanoid } from "@reduxjs/toolkit"
const AddReview = () => {
const [ {title,blurb}, setFormDetails ] = useState({title:'', blurb: ''})
const dispatch = useDispatch()
const handleChange = (e) => {
const { name, value } = e.target
setFormDetails(prevState => ({
...prevState,
[name]: value
}))
}
const handleSubmit = (e) => {
console.log('it got here')
e.preventDefault()
if(title && blurb){
dispatch(ADD_REVIEW({
id: nanoid(),
title,
blurb
}))
// setFormDetails({title: '', blurb: ''})
}
}
return(
<div>
<form onSubmit={handleSubmit}>
<input
type = 'text'
name = 'title'
onChange={handleChange}
/>
<input
type = 'text'
name = 'blurb'
onChange={handleChange}
/>
<button type = "submit">Submit</button>
</form>
</div>
)
}
export default AddReview;
Upvotes: 1
Views: 1174
Reputation: 3386
Expanding on @electroid answer (the solution he provided should fix your issue and here is why):
Redux toolkit docs mention on Rules of Reducers :
They are not allowed to modify the existing state. Instead, they must make immutable updates, by copying the existing state and making changes to the copied values.
and on Reducers and Immutable Updates :
One of the primary rules of Redux is that our reducers are never allowed to mutate the original / current state values!
And as mdn docs specify the push method changes the current array (so it mutates your state). You can read more about mutating the state in the second link link (Reducers and Immutable Updates).
If you really want to keep the state.reviews and avoid state.reviews.reviews you could also do something like this:
ADD_REVIEW: (state,action) => {
state = [...state, action.payload];
}
But I wouldn't recommend something like this in a real app (it is avoided in all the examples you can find online). Some reason for this would be:
But I would definitely advise to use something else (not reviews.reviews). In your case I think something like state.venue.reviews
(so on store.js
...
export const store = configureStore({
reducer:{
venue: reviewReducer // reviewReducer should probably also be renamed to venueSlice or venueReducer
}
})
So an option to avoid state.venue.reviews
or state.reviews.reviews
would be to export a selector from the venueSlice.js
:
export const selectReviews = (state) => state.venue.reviews
and in your Venue.js
component you can just use
const reviews = useSelector(selectReviews)
Exporting a selector is actually suggested by the redux toolkit tutorials as well (this link is for typescript but the same applies to javascript). Although this is optional.
Upvotes: 0
Reputation: 661
I can notice that you pushing directly to the state, I can suggest to use variable in the state and then modify that variable. Also I suggest to use concat instead of push. Where push will return the array length, concat will return the new array. When your code in the reducer will looks like that:
import { createSlice } from "@reduxjs/toolkit";
const initialState = [
reviews: [{id:1, title: 'title 1',blurb: 'blurb 1'},
{id:2, title: 'title 2',blurb: 'blurb 2'}]
]
const venueSlice = createSlice({
name: 'reviews',
initialState,
reducers: {
ADD_REVIEW: (state,action) => {
state.reviews = state.reviews.concat(action.payload);
}
}
})
export const { ADD_REVIEW } = venueSlice.actions
export default venueSlice.reducer
And then your selector will looks like that:
const reviews = useSelector((state) => state.reviews.reviews)
Upvotes: 1
Reputation: 1176
Your code seems to be fine. I don't see any reason why it shouldn't work. I run your code on stackblitz react template and its working as expected.
Following is the link to the app: stackblitz react-redux app
Link to the code:
Project files react-redux
if you are still unable to solve the problem, do create the sandbox version of your app with the issue to help further investigate.
Thanks
Upvotes: 0