I'm having some problems with deleting the post in my app. So, after deleting the post, the state should update and the component should re-render, right? So, after deleting my post, component re-renders with the same data. If I refresh, then only the updated data is shown on the page. For example, if I have 3 posts in my app when I delete ONE post, the component re-renders, but still it shows 3 posts. I don't know why this is happening.
Here's my code.
UserFeed
import React, { Component } from "react"
import { getUserPosts, getCurrentUser } from "../actions/userActions"
import { connect } from "react-redux"
import Cards from "./Cards"
class UserFeed extends Component {
componentDidMount() {
const authToken = localStorage.getItem("authToken")
if (authToken) {
this.props.dispatch(getCurrentUser(authToken))
if (this.props && this.props.userId) {
this.props.dispatch(getUserPosts(this.props.userId))
} else {
return null
}
}
}
render() {
console.log("render called")
const { isFetchingUserPosts, userPosts } = this.props
console.log(isFetchingUserPosts, userPosts)
return isFetchingUserPosts ? (
<p>Fetching....</p>
) : (
<div>
{userPosts &&
userPosts.map(post => {
return <Cards key={post._id} post={post} />
})}
</div>
)
}
}
const mapStateToPros = state => {
return {
isFetchingUserPosts: state.userPosts.isFetchingUserPosts,
userPosts: state.userPosts.userPosts.userPosts,
userId: state.auth.user._id
}
}
export default connect(mapStateToPros)(UserFeed)
Cards
import React, { Component } from "react"
import { connect } from "react-redux"
import { deletePost } from "../actions/userActions"
class Cards extends Component {
handleDelete = (_id) => {
this.props.dispatch(deletePost(_id))
}
render() {
const { _id, title, description } = this.props.post
return (
<div className="card">
<div className="card-content">
<div className="media">
<div className="media-left">
<figure className="image is-48x48">
<img
src="https://bulma.io/images/placeholders/96x96.png"
alt="Placeholder image"
/>
</figure>
</div>
<div className="media-content" style={{border: "1px grey"}}>
<p className="title is-5">{title}</p>
<p className="content">{description}</p>
<button onClick={() => {this.handleDelete(_id)}} className="button is-success">Delete</button>
</div>
</div>
</div>
</div>
)
}
}
const mapStateToProps = state => {
return state
}
export default compose(withRouter, connect(mapStateToProps))(Cards)
deletePost action
export const deletePost = (id) => {
return async dispatch => {
dispatch({ type: "DELETING_POST_START" })
try {
const res = await axios.delete(`http://localhost:3000/api/v1/posts/${id}/delete`)
dispatch({
type: "DELETING_POST_SUCCESS",
data: res.data
})
} catch(error) {
dispatch({
type: "DELETING_POST_FAILURE",
data: { error: "Something went wrong" }
})
}
}
}
userPosts reducer
const initialState = {
isFetchingUserPosts: null,
isFetchedUserPosts: null,
userPosts: [],
fetchingUserPostsError: null,
isDeletingPost: false,
isDeletedPost: false,
deletingError: false,
}
const userPosts = (state = initialState, action) => {
switch (action.type) {
case "FETCHING_USER_POSTS_START":
return {
...state,
isFetchingUserPosts: true,
fetchingUserPostsError: null,
}
case "FETCHING_USER_POSTS_SUCCESS":
return {
...state,
isFetchingUserPosts: false,
isFetchedUserPosts: true,
userPosts: action.data,
fetchingUserPostsError: null,
}
case "FETCHING_USER_POSTS_ERROR":
return {
...state,
isFetchingUserPosts: false,
isFetchedUserPosts: false,
fetchingUserPostsError: action.data.error,
}
case "DELETING_POST_START":
return {
...state,
isDeletingPost: true,
deletingError: null,
}
case "DELETING_POST_SUCCESS":
const filteredPostList = state.postList.filter((post) => post._id !== action.data._id)
return {
...state,
isDeletingPost: false,
isDeletedPost: true,
userPosts: filteredPostList,
deletingError: null,
}
case "DELETING_POST_ERROR":
return {
...state,
isDeletingPost: false,
deletingError: action.data.error,
}
default:
return state
}
}
export default userPosts
Delete post action needs to pass on id to the reducer upon success.
Delete post action
export const deletePost = (id) => {
return async dispatch => {
dispatch({ type: "DELETING_POST_START" })
try {
const res = await axios.delete(`http://localhost:3000/api/v1/posts/${id}/delete`)
dispatch({
type: "DELETING_POST_SUCCESS",
data: res.data,
id
})
} catch(error) {
dispatch({
type: "DELETING_POST_FAILURE",
data: { error: "Something went wrong" }
})
}
}
}
Access action.id in user posts reducer
case "DELETING_POST_SUCCESS":
return {
...state,
isDeletingPost: false,
isDeletedPost: true,
userPosts: state.postList.filter(post => post._id !== action.id),
deletingError: null,
}
Related
The problem is that when I first Logged-in, The userId Variable on renderList() or the this.props.user will always be null. it will work when I refreshed it. I tried checking it on the first line of renderList function but it seems it will always be null after I logged in. I even tried to dispatch the fetchBlog actions before redirecting after logging in successfully.
import React from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import { Card, Button } from 'react-bootstrap';
import { fetchBlogs, deleteBlog } from '../../actions';
class FetchBlogs extends React.Component{
componentDidMount(){
this.props.fetchBlogs();
}
renderButtons(blog, userId){
if(blog._id = userId){
return (
<div>
<Button as={Link} to={`/blogs/edit/${blog._id}`} className="mx-2" variant="outline-warning" size="md">Edit</Button>
<Button onClick={() => {
this.props.deleteBlog(blog._id)
}}
className="mx-2"
variant="outline-danger"
size="md">
Delete
</Button>
</div>
);
}
return '';
}
renderList(){
const userId = this.props.user && this.props.user._id;
return this.props.blogs.map(blog => {
return (
<Card className="m-2" key={blog.title}>
<Card.Body>
<Card.Title as={Link} to={`/blogs/${blog._id}`}>{blog.title}</Card.Title>
<Card.Text>{blog.content}</Card.Text>
</Card.Body>
<div className="mb-2">
{this.renderButtons(blog, userId)}
</div>
</Card>
);
})
}
render(){
return (
<div>
<h2 className="m-2">My Blogs</h2>
{this.renderList()}
</div>
);
}
}
const stateToProps = state => {
return {
blogs: Object.values(state.blogs),
user: state.auth.user,
}
}
export default connect(stateToProps, { fetchBlogs, deleteBlog, })(FetchBlogs);
This is the code for my action login
export const login = formValues => async dispatch => {
const config = {
header: {
'Content-Type': 'application/json'
}
};
try{
const res = await portfolios.post('/auth/login', formValues, config)
dispatch({
type: 'LOGIN_SUCCESS',
payload: res.data
});
dispatch(fetchBlogs())
history.push('/blogs/all');
}catch(e){
dispatch({
type: 'LOGIN_FAILED',
});
history.push('/auth/login');
}
}
This is my Auth Reducer
const INITIAL_STATE = {
access: localStorage.getItem('access'),
isAuthenticated: null,
isLoading: false,
user: null,
}
const authReducer = (state = INITIAL_STATE, action) => {
switch(action.type){
case 'LOGIN_SUCCESS':
case 'REGISTER_SUCCESS':
localStorage.setItem("access", action.payload.token);
return {
...state,
access: action.payload.token,
user: action.payload.user,
isAuthenticated: true,
isLoading: false
};
case 'AUTH_ERROR':
case 'LOGIN_FAILED':
case 'LOGOUT_SUCCESS':
case 'REGISTER_FAIL':
localStorage.removeItem('access');
return {
...state,
user: null,
isAuthenticated: false,
isLoading: false,
}
default:
return state;
}
}
I don't know what am I missing. Ive checked and Searched some answers and I did check if the prop is null but still no changes
I know this question has been asked multiple times but I cannot seem to find an answer. I have a component named DynamicTable which renders JSON as a data table. It has been tested in multiple other pages and works correctly. Here I have put it into a React-Bootstrap tab container. The data pull works correctly but the page is not re-rendering when the fetch is complete.
Here is the code I am using
//RetailRequests.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import FormComponent from '../Elements/FormComponent';
import TabContainer from 'react-bootstrap/TabContainer';
import Tabs from 'react-bootstrap/Tabs';
import Tab from 'react-bootstrap/Tab';
import DynamicTable from '../Elements/DynamicTable';
const mapStateToProps = (state) => {
return {
RequestData: state.RetailRequests,
siteMap: state.siteMap
}
}
const mapDispatchToProps = (dispatch) => {
return {
Retail_Request_Fetch: () => { return dispatch(Retail_Request_Fetch()) },
Retail_Request_Insert: (data) => { return dispatch(Retail_Request_Insert(data)) },
Retail_Request_Delete: (id) => { return dispatch(Retail_Request_Delete(id)) },
Retail_Request_DeleteAll: () => { return dispatch(Retail_Request_DeleteAll()) }
}
}
class RetailRequests extends Component {
constructor(props) {
super(props);
var roles = props.siteMap.siteMapData.userRoles.toLowerCase();
this.state = {
showAdmin: roles.indexOf('admin') >= 0 || roles.indexOf('systems') >= 0
}
}
componentDidMount() {
this.props.Retail_Request_Fetch();
}
// ...
render() {
let rows = this.buildData();
let data = this.props.RequestData?this.props.RequestData.adminData:null;
return (
<div style={{ transform: 'translateY(10px)' }} >
<TabContainer>
<div className='col-md-10 offset-1' >
<Tabs defaultActiveKey='general' id='retail_reports_tab_container' >
<Tab eventKey='general' title='Enter New Request'>
<h1> Retail Requests</h1>
<FormComponent rows={rows} submit={this.submitFn} />
</Tab>
<Tab eventKey='admin' title='Admin' disabled={!this.state.showAdmin}>
<h1>Manager Data</h1>
<DynamicTable
data={data}
border="solid 1px black"
title={"Retail Requests Admin"}
/>
</Tab>
</Tabs>
</div>
</TabContainer>
</div>
);
}
}
export default connect(mapStateToProps, mapDispatchToProps)(RetailRequests);
//RetailRequestsCreator.js
export const Retail_Request_Fetch = () => (dispatch, getState) => {
var init = JSON.parse(JSON.stringify(fetchInit()));//copy to not modify the original
var myReq = new Request(`${process.env.REACT_APP_HOST}/Retail_Request`, init);
dispatch({
type: ActionTypes.REQUESTS_LOADING
})
return fetch(myReq)
.then((response) => {
if (response.ok) {
return response;
}
else {
var error = new Error("Error " + response.statusText);
error.response = response;
throw error;
}
}, (error) => {
var err = new Error(error.message);
throw err;
})
.then((response) => { return response.json() })
.then((RequestData) => {
if (RequestData !== "False") {
console.log(RequestData)
dispatch({
type: ActionTypes.REQUESTS_LOADED,
payload: RequestData
})
}
else CurrentPage_Update({ componentId: 'NotAllowed' });
})
.catch((err) => {
dispatch({
type: ActionTypes.REQUESTS_FAILED,
payload: "Error: " + err.message
})
});
}
//RetailRequestReducer.js
import * as ActionTypes from '../ActionTypes';
export const retailRequests = (state = {
isLoading: true,
errMess: null,
currentPage: []
}, action) => {
switch (action.type) {
case ActionTypes.REQUESTS_LOADED:
return { ...state, isLoading: false, errMess: null, adminData: action.payload };
case ActionTypes.REQUESTS_LOADING:
return { ...state, isLoading: true, errMess: null, adminData: {} };
case ActionTypes.REQUESTS_FAILED:
return { ...state, isLoading: false, errMess: action.payload, adminData: null };
default:
return state;
}
}
I am sure that there is something simple in this but the only error I am getting is that the data I am using, this.props.RequestData, is undefined although after the fetch I am getting proper state change in Redux.
It looks like you have problem in mapStateToProps
const mapStateToProps = (state) => {
return {
RequestData: state.retailRequests, // use lower case for retailRequests instead of RetailRequests
siteMap: state.siteMap
}
}
reducer
import { COMBINE_POST } from '../Actions/actionType'
const initialState = {
posts: null,
users: null,
comments: null,
post: null
}
export const rootReducer = (state = initialState, action) => {
switch (action.type) {
case 'getPosts':
return {
...state,
posts: action.data
}
case 'getUsers':
return {
...state,
users: action.data
}
case 'getComments':
return {
...state,
comments: action.data
}
case COMBINE_POST :
return {
...state,
post: action.payload
}
case 'removePost':
console.log('removepost', state.post.post)
return {
...state,
post: state.post.post.filter(item => item.id !==
action.payload)
}
default:
return state
}
}
action
import { COMBINE_POST } from './actionType'
export const fetchPosts = () => {
return dispatch => {
fetch(`https://jsonplaceholder.typicode.com/posts/`)
.then(res => res.json())
.then(res => dispatch({ type: 'getPosts', data: res }))
}
}
export const fetchUsers = () => {
return dispatch => {
fetch(`https://jsonplaceholder.typicode.com/users/`)
.then(res => res.json())
.then(res => dispatch({ type: 'getUsers', data: res }))
}
}
export const fetchComments = () => {
return dispatch => {
fetch(`https://jsonplaceholder.typicode.com/comments/`)
.then(res => res.json())
.then(res => dispatch({ type: 'getComments', data: res }))
}
}
export const removePost = id => {
return dispatch => {
fetch(`https://jsonplaceholder.typicode.com/posts/${id}`, {
method: 'DELETE',
})
dispatch({ type: 'removePost', payload: id })
}
}
export const combimePost = arr => ({ type: COMBINE_POST, payload: arr })
component render
import React, { useEffect } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { fetchPosts, fetchUsers, fetchComments, removePost } from '../../Redux/Actions/action'
import { combimePost } from '../../Redux/Actions/action'
import './newsList.scss'
export const NewsList = () => {
const dispatch = useDispatch()
const selector = useSelector(state => state.rootReducer)
useEffect(() => {
dispatch(
fetchPosts()
)
}, [])
useEffect(() => {
dispatch(
fetchUsers()
)
}, [])
useEffect(() => {
dispatch(
fetchComments()
)
}, [])
This is where I combine the post and user id
useEffect(() => {
const combinePost = selector.posts?.map(post => ({
...post,
user: selector.users?.find(user => post.userId === user.id),
commetn: selector?.comments?.find(comment => post.id === comment.postId)
}))
return dispatch(
combimePost(
{
post: combinePost,
}
)
)
}, [selector.posts, selector.users, selector.comments])
return <>
{selector?.post?.post?.map((res, i) => (
<div className="card text-center" key={i}>
<div className="card-header">
{res.user?.name}
</div>
<div className="card-body">
<h5 className="card-title">{res.title}</h5>
<p className="card-text">{res.comment?.body}</p>
<button
className="btn btn-outline-danger"
onClick={() => dispatch(removePost(res.id))}
>DELETE</button>
</div>
<div className="card-footer text-muted">
</div>
</div>
)
)}
</>
}
When a post is deleted, all posts disappear from the page, in the console it shows that the selected post has been deleted, but the page is not rendered, it becomes empty. what is the problem ?
When a post is deleted, all posts disappear from the page, in the console it shows that the selected post has been deleted, but the page is not rendered, it becomes empty. what is the problem ?
I'm trying to show a list of recipes from an API in my component when a form submitted. It doesn't show any result in the component and doesn't have any error!
May somebody help me , What's wrong with my code ?
here is my action.js
import { getDataConstants } from "../_constants";
import { getDataService } from "../_service";
export const getDataAction = {
fetchRecipes
}
function fetchRecipes(query) {
return dispatch => {
dispatch(loading());
getDataService.fetchRecipes(query).then(
response => {
dispatch(success(response));
},
error =>{
dispatch(failed(error));
}
)
}
function loading() { return { type: getDataConstants.FETCH_RECIPES_LOADING }; }
function success(data) { return { type: getDataConstants.FETCH_RECIPES_SUCCESS, data }; }
function failed(error) { return { type: getDataConstants.FETCH_RECIPES_FAILED, error }; }
}
code for reducer.js
import { getDataConstants } from "../_constants";
const initialState = {
loading: false,
items: [],
error: null
};
export function getDataReducer(state = initialState, action) {
switch (action.type) {
case getDataConstants.FETCH_RECIPES_LOADING:
return {
...state,
loading: true,
error: null,
items: []
};
case getDataConstants.FETCH_RECIPES_SUCCESS:
return {
...state,
loading: false,
items: action.payload
};
case getDataConstants.FETCH_RECIPES_FAILED:
return {
...state,
loading: false,
error: action.payload,
items: []
};
default:
return state;
}
}
export const getRecipes = state => state.items;
export const getRecipesloading = state => state.loading;
export const getRecipesError = state => state.error;
I fetch data in the service.js component
code for service.js
import {TIMEOUT_DELAY,HOST} from '../_constants';
import axios from 'axios';
export const getDataService = {
fetchRecipes
}
async function fetchRecipes(query) {
let timeout = null;
try{
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
timeout = setTimeout(()=>{source.cancel()},TIMEOUT_DELAY);
debugger
const response = await axios({
url: `${HOST}?apiKey=94be430aadf644f6a8c8c95abbcce4c1&query=${query}&_number=12`,
method: "get",
headers: { "content-type": "application/json" },
cancelToken: source.token
});
if (response.status === 200) {
if (timeout) clearTimeout(timeout);
return response.data;
} else {
if (timeout) clearTimeout(timeout);
return Promise.reject({isTimeout:false,error: response.data});
}
}catch (error) {
if (timeout) clearTimeout(timeout);
if (axios.isCancel(error)) {
return Promise.reject({isTimeout:true});
} else {
return Promise.reject({isTimeout:false,error});
}
}
}
code for Recipes component where API response data shown
const Recipes = props => {
const { dispatch, error, loading, items } = props;
const classes = useStyles();
const [query, setQuery] = useState("beef");
const submitHandler = async event => {
event.preventDefault();
dispatch(getDataAction.fetchRecipes(query));
};
const handleChange = event => {
setQuery(event.target.value);
};
return (
<>
<form onSubmit={submitHandler} className={classes.formWidth}>
<input
type="text"
value={query}
onChange={handleChange}
className={classes.input}
/>
</form>
{error && <div>Something went wrong ...</div>}
{loading ? (
<div>
<img src={Loading} alt="Loading" />
</div>
) : (
<ul className={classes.centeredDiv}>
{items &&
items.results.map(recipe => (
<li
className={classes.media}
image={`${imgUrl}${recipe.image}`}
title={recipe.title}
/>
))}
</ul>
)}
}
</>
);
}
const mapStateToProps = state => {
return {
loading: getRecipesloading(state),
items: getRecipes(state),
error: getRecipesError(state)
};
};
Sorry about the large amount of code dumped, its just all related and I believe the error lies somewhere.
Your reducer expects action.payload but instead you send action.data. It should be:
case getDataConstants.FETCH_RECIPES_SUCCESS:
return {
...state,
loading: false,
items: action.data
}
You should update your success and failed functions like so:
function success(data) {
return {
type: getDataConstants.FETCH_RECIPES_SUCCESS,
payload: data
};
}
function failed(error) {
return {
type: getDataConstants.FETCH_RECIPES_FAILED,
payload: error
};
}
To avoid such typo problems you can create default action creator:
function createAction(type, payload, meta) {
return {
type: type,
payload: payload,
meta: meta
};
}
// usage
function success(data) {
return createAction(getDataConstants.FETCH_RECIPES_SUCCESS, data)
}
I am trying to setup a loader. But the code is never going through the if blocks.
import React from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import LoadingPage from './LoadingPage';
import { usersFetchData, addFollower, addFollowing, removeFollower,
removeFollowing, resetUser} from '../actions/users';
class UserProfile extends React.Component{
constructor(props){
super(props);
this.state = {
isFollowed: false,
content: undefined
}
}
componentDidMount(){
this.props.fetchData(`http://localhost:5000/api/user/
${this.props.match.params.uid}`);
(Object.keys(this.props.user).length !== 0) &&
(this.props.user.followers.includes(this.props.currentUser.uid)) &&
this.setState({isFollowed: true});
}
openContentModal = (post) => {
this.setState({content:post});
console.log(this.state);
}
closeContentModal = () =>{
this.setState(() => ({ content: undefined }));
console.log(this.state);
}
render(){
if (this.props.hasErrored) {
return <p>Sorry! There was an error loading the items</p>;
}
if (this.props.isLoading) {
console.log('loading...');
return <LoadingPage />;
}
console.log(this.props.isLoading);
return(
<div className="userProfile">
<div>
{console.log(this.props.user)}
{ Object.keys(this.props.user).length !== 0 &&
<div className="user__details">
<div className="user__dp">
<div className="dp__container">
<img src={this.props.user.avatar} alt=
{this.props.user.name}/>
</div>
</div>
<div className="user__description">
<p className="user__name">
{this.props.user.name}</p>
<div className="user__button">
{(this.props.currentUser.uid ===
this.props.user.uid) ?
</div>
</div>
</div>
}
</div>
<div className="user__bio">
<p>{this.props.user.bio}</p>
</div>
<div>
{/* <h3>Posts</h3> */}
<div className="userContent">
{this.props.user.posts &&
this.props.user.posts.map((post) =>{
return(
<div className="userPost">
<Link to=
{`/p/${this.props.user.name}/${post._id}`}>
<img src={post.content}/></Link>
</div>
);
})
}
</div>
</div>
</div>
)
}
}
const mapStateToProps = (state) =>{
console.log(state.usersIsLoading); //getting undefined
return{
currentUser: state.auth,
user: state.users,
hasErrored: state.usersHasErrored,
isLoading: state.usersIsLoading
}
};
const mapDispatchToProps = (dispatch) => {
return {
fetchData: (url) => dispatch(usersFetchData(url))
};
};
export default connect(mapStateToProps, mapDispatchToProps)(UserProfile);
Action
export const userIsLoading = (bool) => ({
type: 'USER_IS_LOADING',
isLoading: bool
});
export const usersFetchDataSuccess = (users) => ({
type: 'USERS_FETCH_DATA_SUCCESS',
users
});
export const usersFetchData = (url) => {
return (dispatch) => {
dispatch(userIsLoading(true));
console.log('hi');
axios
.get(url)
.then(res => {
if(!res){
throw Error(res.statusText)
}
dispatch(userIsLoading(false));
console.log(res.data);
return res.data;
})
.then(users => {
console.log('users',users);
dispatch(usersFetchDataSuccess(users))
})
.catch(() => dispatch(userHasErrored(true)));
}
}
reducer
export const userIsLoading = (state = false, action) => {
switch (action.type) {
case 'USER_IS_LOADING':{
return action.isLoading;
}
default:
return state;
}
}
export const users = (state = {}, action) => {
switch (action.type) {
case 'USERS_FETCH_DATA_SUCCESS':{
return action.users;
}
i have consoled the state. then i am getting a valid boolean value from userIsLoading. But when i am consoling the state.userIsLoading, I am getting undefined. This is very peculiar.can anyone tell me where am i getting it wrong?