I’m building a small blog app to help with learning Redux Toolkit. I’m having an issue where a selector defined in the postsSlice and imported into the SinglePostView is returning an undefined value when attempting to retrieve a post by ID using useParams(). The other selectors work in the same component, but I’m struggling to figure out why this one selector will only return an undefined value. Any ideas on what’s going wrong here or how to fix it? Thanks in advance for any constructive input!
App.jsx
import { Routes, Route } from 'react-router-dom';
import Layout from './components/Layout';
import AddPostView from "./features/posts/AddPostView";
import PostsView from "./features/posts/postsView";
import SinglePostView from './features/posts/SinglePostView';
const App = () => {
return (
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<PostsView />} />
<Route path="post">
<Route index element={<AddPostView />} />
<Route path=":postId" element={<SinglePostView />} />
</Route>
</Route>
</Routes>
);
}
export default App;
store.js
import { configureStore } from '@reduxjs/toolkit';
import postsReducer from '../features/posts/postsSlice';
import usersReducer from '../features/users/usersSlice';
export const store = configureStore({
reducer: {
posts: postsReducer,
users: usersReducer
}
});
postsSlice.js
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
import { sub } from 'date-fns';
import axios from 'axios';
const POSTS_URL = 'https://jsonplaceholder.typicode.com/posts';
const initialState = {
posts: [],
status: 'idle',
error: null
};
export const fetchPosts = createAsyncThunk('posts/fetchPosts', () => (
axios.get(POSTS_URL)
.then((response) => response.data)
));
export const addPost = createAsyncThunk('posts/addPost', (newPost) => (
axios.post(POSTS_URL, newPost)
.then((response) => response.data)
));
const postsSlice = createSlice({
name: 'posts',
initialState,
reducers: {
reactionAdded(state, action) {
const {postId, reaction} = action.payload;
const post = state.posts.find(post => post.id === postId);
if (post) {
post.reactions[reaction]++;
}
},
reactionRemoved(state, action) {
const {postId, reaction} = action.payload;
const post = state.posts.find(post => post.id === postId);
if (post && post.reactions[reaction] > 0) {
post.reactions[reaction]--;
}
}
},
extraReducers(builder) {
builder
.addCase(fetchPosts.pending, (state) => {
state.status = 'pending';
})
.addCase(fetchPosts.fulfilled, (state, action) => {
state.status = 'fulfilled';
const posts = action.payload.map(post => {
post.createdAt = new Date().toISOString();
post.reactions = {
thumbsUp: 0,
wow: 0,
heart: 0,
rocket: 0,
coffee: 0
}
return post;
});
state.posts = state.posts.concat(posts);
})
.addCase(fetchPosts.rejected, (state, action) => {
state.status = 'rejected';
state.error = action.error.message;
})
.addCase(addPost.fulfilled, (state, action) => {
action.payload.id = state.posts.length + 1;
action.payload.userId = Number(action.payload.userId);
action.payload.createdAt = new Date().toISOString();
action.payload.reactions = {
thumbsUp: 0,
wow: 0,
heart: 0,
rocket: 0,
coffee: 0
}
state.posts.push(action.payload);
});
}
});
export const selectAllPosts = (state) => state.posts.posts;
export const selectPostById = (state, postId) =>
state.posts.posts.find((post) => post.id === postId);
export const getPostsStatus = (state) => state.posts.status;
export const getPostsError = (state) => state.posts.error;
export const { postAdded, reactionAdded, reactionRemoved } = postsSlice.actions;
export default postsSlice.reducer;
SinglePostView.jsx
import { useSelector } from 'react-redux';
import { useParams } from 'react-router-dom';
import { selectPostById } from './postsSlice';
import PostAuthorView from './PostAuthorView';
import CreatedAt from './CreatedAtView';
import ReactionsView from './ReactionsView';
const SinglePostView = () => {
const { postId } = useParams();
// Returns undefined
const post = useSelector((state) => selectPostById(state, Number(postId)))
if (!post) {
return (
<section>
<h2>This post doesn't exist!</h2>
</section>
);
}
return (
<article>
<h2>{post.title}</h2>
<p>{post.body}</p>
<p className="postCredit">
<PostAuthorView userId={post.userId} />
<CreatedAt timestamp={post.createdAt} />
</p>
<ReactionsView post={post} />
</article>
)
}
export default SinglePostView;
I have tried changing the strict equality comparison in the selector definition to a loose comparison, I’ve imported and used the other selectors into the same component (they all work as expected, save for selectPostById), and I’ve tried inspecting the component in React and Redux dev tools.
Nick Barker is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.