163 lines
5.6 KiB
JavaScript
163 lines
5.6 KiB
JavaScript
|
|
import {
|
|
createSlice,
|
|
createAsyncThunk,
|
|
createSelector,
|
|
createEntityAdapter
|
|
} from "@reduxjs/toolkit";
|
|
import { sub } from 'date-fns';
|
|
import axios from "axios";
|
|
|
|
const POSTS_URL = 'https://jsonplaceholder.typicode.com/posts';
|
|
|
|
const postsAdapter = createEntityAdapter({
|
|
sortComparer: (a, b) => b.date.localeCompare(a.date)
|
|
})
|
|
|
|
const initialState = postsAdapter.getInitialState({
|
|
status: 'idle', //'idle' | 'loading' | 'succeeded' | 'failed'
|
|
error: null,
|
|
count: 0
|
|
})
|
|
|
|
export const fetchPosts = createAsyncThunk('posts/fetchPosts', async () => {
|
|
const response = await axios.get(POSTS_URL)
|
|
return response.data
|
|
})
|
|
|
|
export const addNewPost = createAsyncThunk('posts/addNewPost', async (initialPost) => {
|
|
const response = await axios.post(POSTS_URL, initialPost)
|
|
return response.data
|
|
})
|
|
|
|
export const updatePost = createAsyncThunk('posts/updatePost', async (initialPost) => {
|
|
const { id } = initialPost;
|
|
// try-catch block only for development/testing with fake API
|
|
// otherwise, remove try-catch and add updatePost.rejected case
|
|
try {
|
|
const response = await axios.put(`${POSTS_URL}/${id}`, initialPost)
|
|
return response.data
|
|
} catch (err) {
|
|
//return err.message;
|
|
return initialPost; // only for testing Redux!
|
|
}
|
|
})
|
|
|
|
export const deletePost = createAsyncThunk('posts/deletePost', async (initialPost) => {
|
|
const { id } = initialPost;
|
|
|
|
const response = await axios.delete(`${POSTS_URL}/${id}`)
|
|
if (response?.status === 200) return initialPost;
|
|
return `${response?.status}: ${response?.statusText}`;
|
|
})
|
|
|
|
const postsSlice = createSlice({
|
|
name: 'posts',
|
|
initialState,
|
|
reducers: {
|
|
reactionAdded(state, action) {
|
|
const { postId, reaction } = action.payload
|
|
const existingPost = state.entities[postId]
|
|
if (existingPost) {
|
|
existingPost.reactions[reaction]++
|
|
}
|
|
},
|
|
increaseCount(state, action) {
|
|
state.count = state.count + 1
|
|
}
|
|
},
|
|
extraReducers(builder) {
|
|
builder
|
|
.addCase(fetchPosts.pending, (state, action) => {
|
|
state.status = 'loading'
|
|
})
|
|
.addCase(fetchPosts.fulfilled, (state, action) => {
|
|
state.status = 'succeeded'
|
|
// Adding date and reactions
|
|
let min = 1;
|
|
const loadedPosts = action.payload.map(post => {
|
|
post.date = sub(new Date(), { minutes: min++ }).toISOString();
|
|
post.reactions = {
|
|
thumbsUp: 0,
|
|
wow: 0,
|
|
heart: 0,
|
|
rocket: 0,
|
|
coffee: 0
|
|
}
|
|
return post;
|
|
});
|
|
|
|
// Add any fetched posts to the array
|
|
postsAdapter.upsertMany(state, loadedPosts)
|
|
})
|
|
.addCase(fetchPosts.rejected, (state, action) => {
|
|
state.status = 'failed'
|
|
state.error = action.error.message
|
|
})
|
|
.addCase(addNewPost.fulfilled, (state, action) => {
|
|
// Fix for API post IDs:
|
|
// Creating sortedPosts & assigning the id
|
|
// would be not be needed if the fake API
|
|
// returned accurate new post IDs
|
|
const sortedPosts = state.posts.sort((a, b) => {
|
|
if (a.id > b.id) return 1
|
|
if (a.id < b.id) return -1
|
|
return 0
|
|
})
|
|
action.payload.id = sortedPosts[sortedPosts.length - 1].id + 1;
|
|
// End fix for fake API post IDs
|
|
|
|
action.payload.userId = Number(action.payload.userId)
|
|
action.payload.date = new Date().toISOString();
|
|
action.payload.reactions = {
|
|
thumbsUp: 0,
|
|
wow: 0,
|
|
heart: 0,
|
|
rocket: 0,
|
|
coffee: 0
|
|
}
|
|
console.log(action.payload)
|
|
postsAdapter.addOne(state, action.payload)
|
|
})
|
|
.addCase(updatePost.fulfilled, (state, action) => {
|
|
if (!action.payload?.id) {
|
|
console.log('Update could not complete')
|
|
console.log(action.payload)
|
|
return;
|
|
}
|
|
action.payload.date = new Date().toISOString();
|
|
postsAdapter.upsertOne(state, action.payload)
|
|
})
|
|
.addCase(deletePost.fulfilled, (state, action) => {
|
|
if (!action.payload?.id) {
|
|
console.log('Delete could not complete')
|
|
console.log(action.payload)
|
|
return;
|
|
}
|
|
const { id } = action.payload;
|
|
postsAdapter.removeOne(state, id)
|
|
})
|
|
}
|
|
})
|
|
|
|
//getSelectors creates these selectors and we rename them with aliases using destructuring
|
|
export const {
|
|
selectAll: selectAllPosts,
|
|
selectById: selectPostById,
|
|
selectIds: selectPostIds
|
|
// Pass in a selector that returns the posts slice of state
|
|
} = postsAdapter.getSelectors(state => state.posts)
|
|
|
|
|
|
export const getPostsStatus = (state) => state.posts.status;
|
|
export const getPostsError = (state) => state.posts.error;
|
|
export const getCount = (state) => state.posts.count;
|
|
|
|
export const selectPostsByUser = createSelector(
|
|
[selectAllPosts, (state, userId) => userId],
|
|
(posts, userId) => posts.filter(post => post.userId === userId)
|
|
)
|
|
|
|
export const { increaseCount, reactionAdded } = postsSlice.actions
|
|
|
|
export default postsSlice.reducer |