diff --git a/src/features/photos/create/index.js b/src/features/photos/create/index.js index d988b0a..9690898 100644 --- a/src/features/photos/create/index.js +++ b/src/features/photos/create/index.js @@ -1,13 +1,12 @@ -import { useState } from 'react'; +import { useState } from "react"; +import { addPhoto } from "../photos.slice"; -// Task 2: Import the `useDispatch()` method from the appropriate package -// Task 3: Import the `addPhoto()` action creator from the photos slice - -import './create.css'; +import "./create.css"; +import { useDispatch } from "react-redux"; export default function CreatePhoto() { - const [formData, setFormData] = useState({ imageUrl: '', caption: '' }); - // Task 4: Store a reference to the Redux store's dispatch method in a variable called `dispatch` + const [formData, setFormData] = useState({ imageUrl: "", caption: "" }); + const dispatch = useDispatch(); function handleChange({ target: { name, value } }) { setFormData({ @@ -18,8 +17,8 @@ export default function CreatePhoto() { function handleSubmit(event) { event.preventDefault(); - // Task 5: Dispatch the `addPhoto()` action creator, passing in the form data - setFormData({ imageUrl: '', caption: '' }); + dispatch(addPhoto(formData)); + setFormData({ imageUrl: "", caption: "" }); } return ( diff --git a/src/features/photos/list/index.js b/src/features/photos/list/index.js index 3ed9ea5..136f73e 100644 --- a/src/features/photos/list/index.js +++ b/src/features/photos/list/index.js @@ -1,18 +1,16 @@ import { useSelector, useDispatch } from 'react-redux'; import { - // Task 7: Import the `removePhoto()` action creator from the photos slice - selectAllPhotos, - // Task 13: Import the `selectFilteredPhotos()` selector from the photos slice + removePhoto, + selectFilteredPhotos } from '../photos.slice'; import './list.css'; export default function PhotosList() { - // Task 14: Call `useSelector()` below with `selectFilteredPhotos` instead of `selectAllPhotos` - const photos = useSelector(selectAllPhotos); - // Task 8: Store a reference to the Redux store's dispatch method in a variable called `dispatch` + const photos = useSelector(selectFilteredPhotos); + const dispatch = useDispatch() function handleDeleteButtonClick(id) { - // Task 9: Dispatch the `removePhoto()` action creator, passing in the id + dispatch(removePhoto(id)) } const photosListItems = photos.map(({ id, caption, imageUrl }) => ( diff --git a/src/features/photos/photos.slice.js b/src/features/photos/photos.slice.js index 50985e8..e8b2bb0 100644 --- a/src/features/photos/photos.slice.js +++ b/src/features/photos/photos.slice.js @@ -1,22 +1,28 @@ -import { createSlice } from '@reduxjs/toolkit'; -import { selectSearchTerm } from '../search/search.slice'; -import photos from './photos.data.js'; +import { createSlice } from "@reduxjs/toolkit"; +import { selectSearchTerm } from "../search/search.slice"; +import photos from "./photos.data.js"; const initialState = { photos, }; const options = { - name: 'photos', + name: "photos", initialState, reducers: { - // Task 1: Create an `addPhoto()` case reducer that adds a photo to state.photos. - // Task 1 Hint: You can use state.photos.unshift() - // `unshift()` documentation: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/unshift - - // Task 6: Create an `removePhoto()` case reducer that removes a photo from state.photos - // Task 6 Hint: You can use state.photos.splice() - // `splice()` documentation: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice + addPhoto: (state, action) => { + state.photos.push({ + id: state.photos.length + 1, + caption: action.payload.caption, + imageUrl: action.payload.imageUrl, + }); + }, + removePhoto: (state, action) => { + state.photos.splice( + state.photos.findIndex((photo) => photo.id === action.payload), + 1 + ); + }, }, }; @@ -28,5 +34,10 @@ export default photosSlice.reducer; export const selectAllPhotos = (state) => state.photos.photos; export const selectFilteredPhotos = (state) => { - // Task 12: Complete `selectFilteredPhotos()` selector to return a filtered list of photos whose captions match the user's search term + const allPhotos = selectAllPhotos(state); + const searchTerm = selectSearchTerm(state); + + return allPhotos.filter((photo) => + photo.caption.toLowerCase().includes(searchTerm.toLowerCase()) + ); }; diff --git a/src/features/search/search-bar/index.js b/src/features/search/search-bar/index.js index 0169c64..c577913 100644 --- a/src/features/search/search-bar/index.js +++ b/src/features/search/search-bar/index.js @@ -4,10 +4,10 @@ import './search-bar.css'; export default function SearchBar() { const searchTerm = useSelector(selectSearchTerm); - // Task 10: Store a reference to the Redux store's dispatch method in a variable called `dispatch` + const dispatch = useDispatch() function handleChange({ target: { value } }) { - // Task 11: Dispatch the `setSearchTerm()` action creator, passing in the value of the search input + dispatch(setSearchTerm(value)) } return ( diff --git a/src/features/suggestion/index.js b/src/features/suggestion/index.js index e41c18c..5d5181d 100644 --- a/src/features/suggestion/index.js +++ b/src/features/suggestion/index.js @@ -1,23 +1,22 @@ -import { useEffect } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; +import { useEffect } from "react"; +import { useDispatch, useSelector } from "react-redux"; import { fetchSuggestion, selectError, selectLoading, - // Task 18: Import the `selectSuggestion()` selector from the suggestion slice -} from './suggestion.slice'; -import './suggestion.css'; + selectSuggestion, +} from "./suggestion.slice"; +import "./suggestion.css"; export default function Suggestion() { - // Task 19: Call useSelector() with the selectSuggestion() selector - // The component needs to access the `imageUrl` and `caption` properties of the suggestion object. const loading = useSelector(selectLoading); const error = useSelector(selectError); + const { imageUrl, caption } = useSelector(selectSuggestion); const dispatch = useDispatch(); useEffect(() => { async function loadSuggestion() { - // Task 20: Dispatch the fetchSuggestion() action creator + await dispatch(fetchSuggestion()); } loadSuggestion(); }, [dispatch]); @@ -28,11 +27,10 @@ export default function Suggestion() { } else if (error) { render =
{imageUrl}
*/} +{imageUrl}
> ); } diff --git a/src/features/suggestion/suggestion.slice.js b/src/features/suggestion/suggestion.slice.js index 6db58f1..11a2a53 100644 --- a/src/features/suggestion/suggestion.slice.js +++ b/src/features/suggestion/suggestion.slice.js @@ -1,20 +1,41 @@ -import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'; +import { createAsyncThunk, createSlice } from "@reduxjs/toolkit"; -export const fetchSuggestion = - createAsyncThunk(/* Task 15: Complete the `createAsyncThunk()` function to load a suggestion from this URL: http://localhost:3004/api/suggestion */); +export const fetchSuggestion = createAsyncThunk( + "suggestion/fetchSuggestion", + async (arg, thunkAPI) => { + const response = await fetch("http://localhost:3004/api/suggestion"); + const { data } = await response.json(); + return data; + } +); const initialState = { - suggestion: '', + suggestion: "", loading: false, error: true, }; const options = { - name: 'suggestion', + name: "suggestion", initialState, reducers: {}, extraReducers: { - /* Task 16: Inside `extraReducers`, add reducers to handle all three promise lifecycle states - pending, fulfilled, and rejected - for the `fetchSuggestion()` call */ + [fetchSuggestion.pending]: (state) => { + state.loading = true; + state.error = false; + }, + [fetchSuggestion.fulfilled]: ( + state, + { payload: { imageUrl, caption } } + ) => { + state.suggestion = { imageUrl, caption }; + state.loading = false; + state.error = false; + }, + [fetchSuggestion.error]: (state) => { + state.error = true; + state.loading = false; + }, }, }; @@ -22,7 +43,6 @@ const suggestionSlice = createSlice(options); export default suggestionSlice.reducer; -// Task 17: Create a selector, called `selectSuggestion`, for the `suggestion` state variable and export it from the file - export const selectLoading = (state) => state.suggestion.loading; export const selectError = (state) => state.suggestion.error; +export const selectSuggestion = (state) => state.suggestion.suggestion;