React-Redux Update Form (PUT request) issue - reactjs

I am trying to update a form but something is not working as it should. After I click Update, the updated information is logged in the console, but it seems that the Redux side of the state management is not working. I am not getting any errors in the console, but neither my action UPDATE_POST is visible in Redux Dev Tools on Chrome.
Here is the code:
The UpdateForm component:
import { useState , useEffect} from "react";
import { useHistory, useParams } from 'react-router-dom';
import jsonPlaceholder from "../apis/jsonPlaceholder";
import {updatePost} from '../actions'
import { useDispatch } from 'react-redux';
const UpdateForm = () => {
const dispatch = useDispatch()
const history = useHistory();
const { id } = useParams();
const [post, setPost] = useState({});
const [title, setTitle] = useState(post.title);
const [body, setBody] = useState(post.body);
const [author, setAuthor] = useState(post.author);
const fetchPost = async () => {
const response = await jsonPlaceholder.get(`/posts/${id}`)
console.log(response.data)
setPost(response.data)
setTitle(response.data.title)
setBody(response.data.body)
setAuthor(response.data.author)
return response.data
}
useEffect(() => {
fetchPost();
}, [])
const handleUpdate = async (e) => {
e.preventDefault();
const post = { title, body, author }
dispatch(updatePost(post))
console.log('post', post)//updated post is logged in console
history.push('/')
}
console.log("title", title)
return (
<div className="create">
<h2>Update Blog</h2>
<form>
<label>Blog title:</label>
<input
type="text"
required
defaultValue={title}
onChange={(e) => setTitle(e.target.value)}
/>
<label>Blog body:</label>
<textarea
required
defaultValue={body}
onChange={(e) => setBody(e.target.value)}
></textarea>
<label>Author:</label>
<input
type="text"
required
defaultValue={author}
onChange={(e) => setAuthor(e.target.value)}
/>
<button onClick={handleUpdate}>Update</button>
</form>
</div>
);
}
export default UpdateForm;
The action:
export const updatePost = (post) => async dispatch => {
const res = await jsonPlaceholder.put(`posts/update/${post._id}`);
dispatch({
type: UPDATE_POST,
payload: res.data
})
}
And the reducer:
import { ADD_POST, DELETE_POST, UPDATE_POST } from '../actions/types';
const postReducer = (state = [], action) => {
switch (action.type) {
case ADD_POST:
return state.concat([action.data]);
case UPDATE_POST:
return {
...state,
post: action.data
}
case DELETE_POST:
return state.filter((post)=>post.id !== action.id);
default:
return state
}
}
export default postReducer;
Here is the node.js/express server side of the request:
router.put('/update/:id', async (req, res) => {
try {
let post = await Post.findOneAndUpdate(req.params.id, {
title: req.body.title,
body: req.body.body,
author: req.author.body
})
console.log('server', post)
return res.json(post)
} catch (error) {
console.error(error.message);
res.status(500).send('Server Error')
}
})
I am now getting server error (500), and if I remove the line author: req.author.body, I am not getting the error. The code on the front still does not work.

As I see you are directly calling your actions instead of dispatching it
import useDispatch and use it like this
import { useDispatch } from "react-redux";
UpdateForm.js
const UpdateForm = () => {
....
const dispatch = useDispatch();
.....
const handleUpdate = async (e) => {
e.preventDefault();
const post = { title, body, author }
dispatch(updatePost(post)) // dispatch like this
console.log('post', post)//updated post is logged in console
history.push('/')
}
console.log("title", title)
return (
<div className="create">
.......
</div>
);
}
export default UpdateForm;
reducer
instead of action.payload, you're accessing action.data
case UPDATE_POST:
return {
...state,
post: action.payload
}

You need to dispatch the updatePost action, not call it directly. You're missing useDispatch call.
Here's a link to React Redux documentation covering it:
https://react-redux.js.org/api/hooks#usedispatch
Example:
import React from 'react'
import { useDispatch } from 'react-redux'
export const CounterComponent = ({ value }) => {
const dispatch = useDispatch()
return (
<div>
<span>{value}</span>
<button onClick={() => dispatch({ type: 'increment-counter' })}>
Increment counter
</button>
</div>
)
}
[UPDATE]
Just noticed that your updatePost action is an higher order function so once you add the call to useDispatch you'll need to change the call to updatePost from
updatePost(post)
to
updatePost(post)(dispatch)
To be honest I would probably go with a book action creator and move the API call to the component itself. If you're interested in async actions I would suggest looking into react-thunk, it is fairly easy to begin with.
[UPDATE 2]
There seem to be a typo in the express code.
req.author.body
should be
req.body.author
[UPDATE 3]
The post object in the updatePost does not contain the _id field (check your handleUpdate function) thus you're getting the url: "posts/update/undefined".

Related

How to cancel axios call in react-redux action on change of search text?

I am using redux for doing api call and searching on basis of text. I am dispatching action onChange of text and want to cancel the alternate api calls.
Here is my code for Input -
import { useDispatch } from "react-redux";
import { searchData } from "./action";
import "./styles.css";
export default function App() {
const dispatch = useDispatch()
const handleChange = (e) => {
dispatch(searchData({ searchText : e.target.value }))
}
return (
<div className="App">
<input onChange={handleChange}/>
</div>
);
}
Code for action -
export const searchData = ({ searchText = "" }) => {
return async (dispatch) => {
dispatch(initiate());
const response = await axios.post(apiUrl, {
query: queryToCall(searchText)
});
dispatch(success(response));
};
};
I have tried this solution -
how to cancel previous axios with redux in react
Also tried to pass cancelToken as parameter in action but it doesn't seem to work.
How can I make this work?
If this is for something like an autocomplete input, the simple solution is to only keep the most recent response and ignore the results of all previous requests.
export const searchData = ({ searchText = "" }) => {
return async (dispatch, getState) => {
const startedAt = Date.now();
// Adjust this action to write this timestamp in the state,
// for example state.mostRecentSearchStartedAt (initialState 0)
dispatch(initiate(startedAt));
const response = await axios.post(apiUrl, {
query: queryToCall(searchText)
});
// Ignore response if it belongs to an outdated search request
// - a more recent search has been triggered in the meantime.
if (getState().mostRecentSearchStartedAt === startedAt) {
dispatch(success(response));
}
};
};

Passing the state from a Redux Slice to the Component is giving me undefined

I'm doing this in other components very similarly, except this one takes a parameter of title_id. I am getting my data to fetch when I check the Redux DevTool and I console logged the payload from the mediaSlice too, but the useSelector in my Component is bringing nothing in and not updating my state. Can you help me figure out what exactly is it that I am messing up on so my selector can send the payload to my Component's state?
I made comments/questions in my code below where I think my problem areas are.
Here is my mediaSlice.js
import { createSlice, createAsyncThunk } from "#reduxjs/toolkit";
import axios from "axios";
const KEY = process.env.REACT_APP_API_KEY
const BASE_URL = process.env.REACT_APP_BASE_URL
const SINGLE_MEDIA_API = `${BASE_URL}/titlestest`
const initialState = {
media:[],
status: 'idle',
error:null
}
export const fetchSingleMediaTitle = createAsyncThunk(
'medias/fetchSingleMediaTitle',
async (mediaId) => { // <-- would this be where I declare the param key I am using?
try {
const response = await axios.get(
SINGLE_MEDIA_API,
{
headers: {
'Content-Type': 'application/json',
'X-API-KEY': KEY,
},
params: {
titleId: mediaId,
}
}
)
return response.data.Item;
} catch (error) {
console.error('API call error:', error.message);
}
}
)
const mediaSlice = createSlice({
name: 'medias',
initialState,
reducers:{},
extraReducers(builder) {
builder
.addCase(fetchSingleMediaTitle.pending, (state, action) => {
state.status = 'loading'
})
.addCase(fetchSingleMediaTitle.fulfilled, (state, action) => {
state.status = 'succeeded'
const loadedMedia = action.payload
state.media = loadedMedia
console.log("loadedMedia: ", loadedMedia); // this is successfully printing the data object
})
.addCase(fetchSingleMediaTitle.rejected, (state, action) => {
state.status = 'failed'
state.error = action.error.message
})
}
})
// SELECTORS
export const selectSingleMedia = (state, mediaId) => state.media.media.find(item => item.title_id === mediaId); //this is where I am suspecting the problem is in
export const getMediaStatus = (state) => state.media.status;
export const getMediaError = (state) => state.media.error;
export default mediaSlice.reducer
And my Component. Reduced for brevity
import React, { useState, useEffect, useRef, Fragment } from 'react'
import { useSelector, useDispatch } from 'react-redux';
import { getMediaError, getMediaStatus, selectSingleMedia, fetchSingleMediaTitle } from '../../features/medias/mediaSlice'
import { useParams, useHistory } from "react-router-dom";
const SingleMediaTitle = () => {
//STATES
const [data, setData] = useState([]);
const {mediaId} = useParams();
const dispatch = useDispatch();
const media = useSelector((state, mediaId) => selectSingleMedia(state, mediaId)) //This is a suspect line too
const mediaStatus = useSelector(getMediaStatus)
const error = useSelector(getMediaError)
useEffect(() => {
if (mediaStatus === 'idle') {
dispatch(fetchSingleMediaTitle(mediaId))
}
setData(media);
console.log("media: ", media); // This is undefined
}, [mediaId, media, mediaStatus, dispatch])
let content;
if (mediaStatus === 'loading') {
content = <Box className="loading">
<Typography variant="subtitle2">Loading ..</Typography>
</Box>
} else if (mediaStatus === 'succeeded') {
content = <Box>
{media.content_source} //Though redux succeeded, this is not displaying
<Box/>
} else if (mediaStatus === 'failed') {
content = <Box className="loading">
<Typography variant="subtitle2">{error}</Typography>
</Box>
}
return (
<Box sx={{p:3, pt:9}}> {content} </Box>
)
}
export default SingleMediaTitle
I really appreciate your help. Being the only front end dev in house is tough lol
I can spot 2 things that can cause the problem:
1) Your selectors
Since useSelector will get the entire state, you have to dig into your slice first and then deeper into the media data. Since the name of slice is medias this should be the first one. According to this your selectors should be:
// SELECTORS
export const selectSingleMedia = (state, mediaId) => state.medias.media.find(item => item.title_id === mediaId);
export const getMediaStatus = (state) => state.medias.media.status;
export const getMediaError = (state) => state.medias.media.error;
2) The single media selector
const {mediaId} = useParams();
const media = useSelector((state, mediaId) => selectSingleMedia(state, mediaId))
Since mediaId exists in the scope of the selector you should not expect it as an argument of the selector and just pass it as parameter to selectSingleMedia like below:
const {mediaId} = useParams();
const media = useSelector((state) => selectSingleMedia(state, mediaId))

State does not update and error : TypeError: dispatch is not a function shows up

I am currently working on a personal project. I use an API to fetch news articles : I fetch latest news when the APP is rendering, not a single issue, the store gets all the datas needed and so on.
But when I start want to search for a topic, nothing happens and I have this error in the browser console : "TypeError: dispatch is not a function"
Here's my action file where the error occurs
import { API_URL_BASE, API_KEY, FETCH_API } from "./constants";
import { ApiReducerInterface } from "./apiReducerInterface";
import axios from "axios";
export const fetchMainTopics = () => async (dispatch: any) => {
try{
const res = await axios.get(`${API_URL_BASE}top-headlines?country=fr&apiKey=${API_KEY}`);
await dispatch(fetchAPI(res.data.articles));
}
catch(error) {
console.log(error);
}
}
export const searchTopics = (search: string) => async (dispatch: any) => {
try{
const res = await axios.get(`${API_URL_BASE}everything?q=${search}&apiKey=${API_KEY}`);
await dispatch(fetchAPI(res.data.articles));
}
catch(error) {
console.log(error);
}
}
export const fetchAPI = (response: ApiReducerInterface[]) => ({
type: FETCH_API,
articles: response,
});
Here's my button to fetch the topic :
import { useState } from 'react';
import { searchTopics } from '../../store/apiReducer/action';
const Search: React.FC = () => {
const [search, setSearch] = useState('');
return(
<div>
<input onChange={(e) => setSearch(e.target.value)} placeholder="Search for a topic ..."/>
<button onClick={searchTopics(search)}>Submit</button>
</div>
);
}
export default Search;
If anyone has an idea to help me out there please ? :D
Thanks for considering my request and happy coding :)

How mock result of redux thunk action for component (just function mock instead of request)

I have component that use thunk action.
And inside the component, I have an asynchronous execution that, after processing, returns the username to the markup, how to lock the result of such an execution
import React, { useEffect } from 'react'
import { useDispatch, useSelector } from "react-redux";
import setTestData from "../redux/asyncServices/testService";
function TestPage() {
const dispatch = useDispatch()
const user = useSelector((state) => state.testReducer.user)
const loading = useSelector((state) => state.testReducer.loading)
useEffect(() => {
dispatch(setTestData())
}, [])
return (
<div className='users_wrapper'>
<div className='container'>
<div className='users_content'>
<div className='title'>
<h1>Test</h1>
</div>
{
!loading ? <h1>{user.name}</h1> : null
}
</div>
</div>
</div>
)
}
export default TestPage
Async action
import { createAsyncThunk } from '#reduxjs/toolkit'
import db from '../../indexedDB/db'
import '../../indexedDB/db.timesheetHooks'
const setTestData = createAsyncThunk(
'setTestData',
async () => {
const user = await db.loggedUser.orderBy('id').last()
return {user}
},
)
And code where I try to mock function result, but something went wrong.I understand how mock simple function in jest, but how mock in this case
jest.mock('../redux/asyncServices/testService', () => {
return {
setTestData: () => ({type: "setTestData/fulfilled", payload: {
user: {name: 'Loaded user name'}
}}),
};
});
describe('Timesheet Menu page tests', () => {
beforeEach(async () => {
matchMedia = new MatchMediaMock()
})
afterEach(() => {
matchMedia.clear()
})
test('Component renders correctly', async () => {
const testFn = require('../pages/TestPage');
jest.spyOn(testFn, 'setTestData').mockReturnValue('c');
await waitFor(() => {
renderWithRedux(<TestPage/>, {initialState})
})
expect(screen.getByText('Test')).toBeInTheDocument()
expect(screen.getByText('Loaded user name')).toBeInTheDocument()
})
})

Need clarification for react + react-redux hooks + middleware thunk+ fetching API

I am new to React and Redux. Learning now about hooks and got really confused.
Doing a tutorial app (the teacher is using classes) which should fetch some API data from jsonplaceholder (async) and afterwards use it with redux. For now, I fail to display the fetched data on my screen.
Also at the very bottom are two of my additional questions.
My code (that is not working):
ERROR:
TypeError: posts.map is not a function
PostList.js
import React, { useEffect, useState } from "react";
import { fetchPosts } from "../actions";
import { useSelector } from "react-redux";
const PostList = () => {
const [ posts, getPosts ] = useState("");
// posts = useSelector((state) => state.posts);
// const dispatch = useDispatch();
useEffect(() => {
setPosts(fetchPosts());
}, []);
return (
<div className="ui relaxed divided list">
<ul>{posts.map((post) => <li key={post.id}>{post.title}</li>)}</ul>
</div>
);
};
export default PostList;
action/index.js
import jsonPlaceholder from "../apis/jsonPlaceholder";
export const fetchPosts = () => async (dispatch) => {
const response = await jsonPlaceholder.get("/posts");
dispatch({ type: "FETCH_POSTS", payload: response.data });
};
apis/jsonPlaceholder.js
import jsonPlaceholder from "../apis/jsonPlaceholder";
export const fetchPosts = () => async (dispatch) => {
const response = await jsonPlaceholder.get("/posts");
dispatch({ type: "FETCH_POSTS", payload: response.data });
};
reducers/postsReducer.js
export default (state = [], action) => {
switch (action.type) {
case "FETCH_POSTS":
return action.payload;
default:
return state;
}
};
I got it to work (to show the posts on my screen with the following) with this:
components/PostList.js
import React, { useEffect, useState } from "react";
import { fetchPosts } from "../actions";
import axios from "axios";
const PostList = () => {
const [ posts, setPosts ] = useState([]);
useEffect(() => {
axios
.get("https://jsonplaceholder.typicode.com/posts")
.then((response) => {
console.log(response);
setPosts(response.data);
})
.catch((err) => {
console.log(err);
});
}, []);
return (
<div className="ui relaxed divided list">
<ul>{posts.map((post) => <li key={post.id}>{post.title}</li>)}</ul>
</div>
);
};
export default PostList;
1) But I do not use any async nor await in useEffect. Is this correct?
2) Should I use a middleware (like thunk) when I use useEffect?
3) What is with redux hooks like useSelector and useDispatch, where should I use them or should I be using either react hooks or either redux hooks?
Working code (only changed the PostList.js file):
import React, { useEffect } from "react";
import { fetchPosts } from "../actions";
import { useSelector, useDispatch } from "react-redux";
const PostList = () => {
// const [ posts, setPosts ] = useState([]);
const posts = useSelector((state) => state.posts);
const dispatch = useDispatch();
useEffect(
() => {
dispatch(fetchPosts());
},
[ dispatch ]
);
return (
<div className="ui relaxed divided list">
<ul>{posts.map((post) => <li key={post.id}>{post.title}</li>)}</ul>
</div>
);
};
export default PostList;
you are using .then for waiting for the call to end, as much as async tells the code to wait
you need to use redux-thunk if you want to run this action as redux action (because the usage of async behavior, .then either async), there is no relation between useEffect which is react effect to redux-thunk that belongs to redux part of your project
you need useDispatch to dispatch function from UI
const dispatch = useDispatch()
useEffect(() => {
dispatch(fetchPosts());
}, []);
and useSelector for subscribing a redux prop to the component (as mapStateToProps)
const posts = useSelector((state) => state.posts);
if you use them both, const [ posts, getPosts ] = useState([]); unneeded

Resources