Increase Like count if post id is equal to action.payload - javascript

I am working on a social network app and I want to toggle the like count on clicking(it should increment by 1 on first click and should go back to null to when pressed again) for a particular post. But now when i click on the like button, nothing happens and the screen gets vanish. I am unable to get what is wrong with my code.
Here are my files-> action creator
export const fetchPosts = () => async dispatch => {
const request = await axios.get(`${ROOT_URL}/post`, {
headers: { Authorization: `${token}` }
});
dispatch({
type: FETCH_POSTS,
payload: request
});
};
export const incrementLikesCount = id => {
return {
type: INCREMENT_LIKES_COUNT,
payload: id
};
};
index.js(reducer)
import auth from "./authReducer";
import user from "./userReducer";
import post from "./postReducer";
export default combineReducers({
auth,
user,
post,
form: formReducer
});
postreducer.js
import _ from "lodash";
import { FETCH_POSTS, INCREMENT_LIKES_COUNT } from "../actions/types";
const initialState = {
postDetail: "",
likesCount: null
};
const post = (state = initialState, action) => {
switch (action.type) {
case FETCH_POSTS:
return {
...state,
postDetail: _.mapKeys(action.payload.data.data, "_id")
};
case INCREMENT_LIKES_COUNT:
return _.values(state.postDetail)
.reverse()
.map(post => {
if (action.payload === post._id) {
if (state.likesCount === null) {
console.log("I got executed");
return { ...state, likesCount: state.likesCount + 1 };
} else {
return {
...state,
likesCount: null
};
}
} else {
return {
state
};
}
});
default:
return state;
}
};
export default post;
and my react Component
import _ from "lodash";
// import uuid from "uuid";
import { connect } from "react-redux";
import React, { Component } from "react";
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
import {
faHeart,
faCommentAlt,
faShareAlt
} from "#fortawesome/free-solid-svg-icons";
import { fetchPosts, incrementLikesCount } from "../../../actions/FeedPost";
import "./FeedPosts.css";
class FeedPosts extends Component {
componentDidMount() {
if (!this.props.fetchPosts) {
return <div>Loading...</div>;
}
this.props.fetchPosts();
}
renderPosts = () => {
return _.values(this.props.post)
.reverse()
.map(post => (
<div key={post._id} className="post-content">
<img
src={require("../../../img/blue.jpeg")}
alt="user"
className="user-image"
/>
<span>{post.postBy}</span>
<span>{post.userDesignation}</span>
<li>{post.postText}</li>
<div className="fontawesome-icons">
<div className="like-font">
<FontAwesomeIcon
icon={faHeart}
onClick={() => this.props.incrementLikesCount(post._id)}
/>
<span>{this.props.likesCount}</span>
</div>
<div className="comment-font">
<FontAwesomeIcon icon={faCommentAlt} />
</div>
<div className="share-font">
<FontAwesomeIcon icon={faShareAlt} />
</div>
</div>
</div>
));
};
render() {
return (
<div>
<ul className="posts">{this.renderPosts()}</ul>
</div>
);
}
}
const mapStateToProps = state => ({
post: state.post.postDetail,
likesCount: state.post.likesCount
});
export default connect(
mapStateToProps,
{ fetchPosts, incrementLikesCount }
)(FeedPosts);
So, Basically my question is how can I increase the like count just for a particular post, because I was able to toggle the like button but it was increasing the like count of all the posts.

The following should kind of work but it would be easier to have state.posts as array instead of converting from array to object and object to array every time.
To be sure it'll work you need to show the code where you set state.posts
case INCREMENT_LIKES_COUNT:
return {
...state,
likesCount:state.likesCount+1,
//not sure why posts need to be an object instead of it being an array
posts:Object.entries(state.posts).reduce(
(result,[key,value])=>{
if(value._id===action.payload){
//you probably didn't set the initial likes but the reducer
// where you set state.posts isn't in your question
result[key]= {...value,likes:value.likes+1};
}else{
result[key]=value;
}
return result;
},
{}
)
}
Although after seeing this again I realize the posts is an object where the id is the key so you can make it simpler:
case INCREMENT_LIKES_COUNT:
return {
...state,
likesCount:state.likesCount+1,
//not sure why posts need to be an object instead of it being an array
posts:{
...state.posts,
[action.payload]:{
...state.posts[action.payload],
likes:state.posts[action.payload].likes+1
}
}
}

Related

Eliminate this error message 'Warning: Each child in a list should have a unique "key" prop.'

I really need help. I spent a lot of time but I can't solve this problem..
When I add a new post and then redirect to the post list page,
this error message appears "Warning: Each child in a list should have a unique "key" prop.".
Also empty item added on the list page. but when I refresh the page, the empty item is gone.
Please check the code and give me advice for this problem.
When I enter the list page, fetchPosts() loads empty array.
After I add new post then redirect to the list page, the error appears.
Also the empty item is added on the list.
QnA.js
import React, { Component } from 'react';
import { Link } from 'react-router-dom';
import { connect } from 'react-redux';
import { fetchPosts } from '../actions';
import './QnA.scss';
class QnA extends Component {
componentDidMount() {
this.props.fetchPosts();
console.log(this.props.qnas);
}
renderList() {
return this.props.qnas.map(qna => {
return (
<div className="item" key={qna._id}>
<div className="content">
<div className="postTitle">
<Link to={`/qna/${qna._id}`}>
<span><b>{qna.title}</b></span>
</Link>
</div>
<div className="postInfo">
<span>{qna.author}</span>
<span>{qna.createdAt}</span>
</div>
</div>
<hr />
</div>
);
})
}
render() {
return (
<div className="QnA">
<h1>QnA</h1>
<div className="list">
{this.renderList()}
</div>
<Link to="/qnanew">
<button className="ui brown button">New Post</button>
</Link>
</div>
);
}
}
const mapStateToProps = (state) => {
return {
qnas: Object.values(state.qnas)
};
}
export default connect(mapStateToProps, { fetchPosts })(QnA);
action - index.js
//Create a Post
export const createPost = (formProps) => async (dispatch, getState) => {
const author = getState().auth.nickName;
const response = await baseURL.post('/qnanew', { ...formProps, author });
dispatch({ type: CREATE_QNA, payload: response.data });
};
// List
export const fetchPosts = () => async dispatch => {
const response = await baseURL.get('/qna');
dispatch({ type: FETCH_QNAS, payload: response.data });
};
qnaReducer.js
import _ from 'lodash';
import { FETCH_QNA, FETCH_QNAS, CREATE_QNA, EDIT_QNA, DELETE_QNA } from '../actions/types';
const qnaReducer = function(state = {}, action) {
switch (action.type) {
// One Item
case FETCH_QNA:
return { ...state, [action.payload._id]: action.payload };
// List
case FETCH_QNAS:
return { ...state, ..._.mapKeys(action.payload, '_id') };
case CREATE_QNA:
return { ...state, [action.payload._id]: action.payload };
case EDIT_QNA:
return { ...state, [action.payload._id]: action.payload };
case DELETE_QNA:
return _.omit(state, action.payload);
default:
return state;
}
}
export default qnaReducer;
In React you have to use 'key' in each child on the list. example :
const data_users = [{name: "james", age: "19"},{name: "brandon", age: "20"}
renderList(){
return data_users.map((data, index) => {
return <div key={index}>{data.name}</div>
} // ^ you need this key, simply put the index value just in case if you dont have unique identifier
}
The problem in your code problably because your qna.__id doesnt have uniqe value.
Hope this help you to solve your problem.

How to set loader for a promise.all action in React Redux?

So when I check Redux dev-tools i see that I've received my data and they are a part of the state, but when I try to use conditional rendering it wont render the page and gives error TypeError: Cannot read property 'Global Quote' of undefined !
If I just use this.props.data.TSLA it works fine and the page renders...
When I use this.props.data.TSLA["Global Quote"]["01. symbol"] page won't render! (the keys are strings in the JSON so I need to use square brackets).
I am also using Redux-Thunk !
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { START_FETCH_DATA } from './redux/dataReducer';
class Fetcher extends Component {
componentDidMount() {
this.props.START_FETCH_DATA()
}
render() {
const { data, dataLoading } = this.props;
return (
<li className="tesla-container">
{ this.props.dataLoading ?
(<div className="ticker"> Loading! </div>)
:
(<div className="ticker">{
this.props.data.TSLA["Global Quote"]["01. symbol"] }</div>) }
</li>
)
}
const mapStateToProps = (state) => {
return {
data: state.data,
dataLoading: state.dataLoading
}
}
const mapDispatchToProps = (dispatch) => {
return {
START_FETCH_DATA: bindActionCreators(START_FETCH_DATA, dispatch)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Fetcher)
and here is the reducer + actions...
export const dataReducer = (state = {dataLoading: true}, action) => {
switch(action.type) {
case "START_FETCH_DATA":
return {...state, dataLoading: true}
case "FINISH_FETCH_DATA":
return {...state, dataLoading: false, data: action.payload}
default:
return state;
}};
export const START_FETCH_DATA = () => {
return (dispatch) => {
Promise.all(
[
fetch(`https://www.alphavantage.co/query?function=GLOBAL_QUOTE&symbol=TSLA&apikey=LOL`).then(data => data.json()),
fetch(`https://www.alphavantage.co/query?function=GLOBAL_QUOTE&symbol=AMZN&apikey=LOL`).then(data => data.json())
]
)
.then(([TSLA, AMZN]) => {
dispatch({ type: "FINISH_FETCH_DATA", payload: {TSLA, AMZN} })
})
}};
DEVTOOLS SCREENSHOT
https://imgur.com/a/2Tcrdpe
For starters, you should use the data provided by redux in render():
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { START_FETCH_DATA, dataReducer } from './redux/dataReducer';
class Fetcher extends Component {
componentDidMount() {
this.props.START_FETCH_DATA()
}
render() {
return (
<li className="tesla-container">
{ this.props.dataLoading ?
(<div className="ticker"> Loading! </div>)
:
(<div className="ticker">{
this.props.data.TSLA["Global Quote"]["01. symbol"] }</div>) }
</li>
)
}
const mapStateToProps = (state) => {
return {
data: state.data,
dataLoading: state.dataLoading
}
}
const mapDispatchToProps = (dispatch) => {
return {
START_FETCH_DATA: bindActionCreators(START_FETCH_DATA, dispatch)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Fetcher)
I fixed my issue.
Fixed code below:
`const mapStateToProps = (state) => {
return {
dataLoading: state.dataReducer.dataLoading,
data: state.dataReducer.data
}
}`
instead of
`const mapStateToProps = (state) => {
return {
dataLoading: state.dataLoading,
data: state.data
}
}`

Cannot change redux boolean state

I feel little confused, the problem is defineAvailableTouch action and state update connected to it.
Here is my code:
Actions/index.js
import {
ANIMATE_HELLO,
HANDLE_SCROLL,
IS_TOUCH_DEVICE,
SET_ABOUT_TOP,
SET_CONTACT_TOP,
SET_PORTFOLIO_TOP
} from "../Constants/ActionTypes";
export const animateHello = hello => ({
type: ANIMATE_HELLO,
payload: hello
});
export const handleScroll = scrollDelta => ({
type: HANDLE_SCROLL,
payload: scrollDelta
});
export const defineTouchAvailable = isTouchDevice => ({
type: IS_TOUCH_DEVICE,
payload: isTouchDevice
});
export const setAboutTop = aboutTop => ({
type: SET_ABOUT_TOP,
payload: aboutTop
});
export const setContactTop = contactTop => ({
type: SET_CONTACT_TOP,
payload: contactTop
});
export const setPortfolioTop = portfolioTop => ({
type: SET_PORTFOLIO_TOP,
payload: portfolioTop
});
Reducers/index.js
import {
IS_TOUCH_DEVICE,
} from "../Constants/ActionTypes";
import { initialState } from "../Constants/InitialState/InitialState";
export const rootReducer = (state = initialState, action) => {
switch(action.type) {
case ANIMATE_HELLO:
return {
...state,
hello: action.payload
};
case HANDLE_SCROLL:
return {
...state,
scrollState: action.payload
};
case IS_TOUCH_DEVICE:
console.log(action.payload); //!!!!!! THIS PRINTS EXPECTED VALUE !!!!!!!!!!
return {
...state,
isTouchDevice: action.payload
};
case SET_ABOUT_TOP:
return {
...state,
aboutTop: action.payload
};
case SET_CONTACT_TOP:
return {
...state,
contactTop: action.payload
};
case SET_PORTFOLIO_TOP:
return {
...state,
portfolioTop: action.payload
};
default:
return state
}
};
InitialState.js
export const initialState = {
scrollState: 0,
hello: 'H',
aboutTop: 0,
portfolioTop: 0,
contactTop: 0,
isTouchDevice: true
};
App.js
import React, { Component } from 'react';
import { connect } from "react-redux";
import About from "./Containers/About";
import Contact from "./Containers/Contact";
import Page from "./Containers/Page";
import Projects from "./Containers/Projects";
import {
defineTouchAvailable,
handleScroll
} from "./Actions";
window.onbeforeunload = () => {
handleScroll(0);
document.documentElement.scrollTop = 0;
};
const mapStateToProps = state => {
return {
isTouchDevice: state.isTouchDevice
}
};
const dispatchStateToProps = dispatch => {
return {
defineTouchAvailable: isTouchDevice =>
dispatch(defineTouchAvailable(isTouchDevice)),
handleScroll: scrollState => dispatch(handleScroll(scrollState))
}
};
class App extends Component {
componentDidMount() {
try {
document.createEvent('touchevent');
this.props.defineTouchAvailable(true);
} catch(e) {
this.props.defineTouchAvailable(false);
}
console.log(this.props.isTouchDevice); //!!!!!!!!!!!!!!! THIS ALWAYS PRINTS VALUE FROM initialState !!!!!!!!!!!!!!
if(this.props.isTouchDevice) {
document.documentElement.scroll(0, 1);
}
document.addEventListener('scroll', () => {
if (document.documentElement.scrollTop === 0) {
this.props.handleScroll(0);
}
});
}
render() {
return (
<div>
<Page/>
<Projects/>
<About/>
<Contact/>
</div>
);
}
}
export default connect(mapStateToProps, dispatchStateToProps)(App);
I really can't figure out whats wrong here.
As I commented
reducer console.log prints correct value that is expected to be assigned to my state (isTouchDevice field), but
after assigning it in dispatch action nothing changes - it is always value from initialState.
Can someone please explain it to me? Do I change my redux state uncorrectly? Then why other actions work as they're expected to?
The updated value of isTouchDevice will be available in componentDidUpdate, render or componentWillReceiveProps, not in componentDidMount.
componentDidMount will only be called one time when your component is mounted.
Note: componentWillReceiveProps is deprecated, better to not use it.

Having trouble connecting redux to react and dispatching actions

I'm new to redux so I might be missing something here...
redux/reducers/schools.js:
export const SET_SELECTED = 'schools/SET_SELECTED';
const initialState = {
selected: {},
schools: []
};
export default function schools(state = initialState, action) {
switch (action.type) {
case SET_SELECTED:
return {
...state,
selected: action.payload
};
default:
return state;
}
}
export function setSelected(school) {
return {
type: SET_SELECTED,
payload: school
};
}
containers/Search.js:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { setSelected } from '../redux/reducers/schools';
import SchoolCard from '../components/SchoolCard';
class Search extends Component {
setSelectedSchool(school) {
this.props.dispatch(setSelected(school)); // Error here
}
renderShools(schools) {
return schools.map(school => {
return (
<div className="column is-8 is-offset-2" key={school.emis}>
<SchoolCard school={school} setSelected={this.setSelectedSchool} />
</div>
);
});
}
render() {
return (
<div className="container">
<div className="columns">{this.renderShools(this.props.schools)}</div>
</div>
);
}
}
export default connect(state => ({
schools: state.schools.schools
}))(Search);
When setSelectedSchool() in Search.js runs I get the following error:
Uncaught TypeError: Cannot read property 'dispatch' of undefined...
What am I doing wrong / what am I missing?
Code below represents an example how to map to props and then call dispatch :
import { addItem } from './actions/items';
class App extends Component {
render() {
return (
<div className="App">
// some more tags
</div>
);
}
};
const mapStateToProps = (state) => {
return {
items: state.items
};
};
const mapDispatchToProps = dispatch => {
return {
addItem: () => {
dispatch(addItem())
}
};
};
export default connect(mapStateToProps, mapDispatchToProps)(App);
Incase you do not do mapDispatchToProps then this.props.dispatch will be undefined.
export default connect(state => ({
schools: state.schools.schools
}),
(dispatch)=>{return {setSelected:(data)=>(dispatch)({type:"SET_SELECTED",payload:data})}}
)(Search);
You can give 2 objects to connect
1. mapStateToProps {
schools: state.schools.schools
}
2(dispatch)=>{return {setSelected:(data)=>(dispatch)({type:"SET_SELECTED",payload:data})}
You required dispatch (2) also in order to dispatch function
So change last line to(where you use connect) to
export default connect(state => ({
schools: state.schools.schools
}),
(dispatch)=>{return {setSelected:(data)=>(dispatch)({type:"SET_SELECTED",payload:data})}}
)(Search);
and the function call to
setSelectedSchool(school) {
this.props.setSelected(school); // Error here
}
You need to refract your code a bit
containers/Search.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import * as schoolActions from '../actions/schoolActions;
import SchoolCard from '../components/SchoolCard';
class Search extends Component {
setSelectedSchool=(school)=> {
this.props.setSelectedschool(school)
}
renderShools(schools) {
return schools.map(school => {
return (
<div className="column is-8 is-offset-2" key={school.emis}>
<SchoolCard school={school} setSelected={this.setSelectedSchool} />
</div>
);
});
}
render() {
return (
<div className="container">
<div className="columns">{this.renderShools(this.props.schools)}</div>
</div>
);
}
}
export default connect(
state => ({
schools: state.schoolReducer.schools,
}),
{ ...schoolActions }
)(Search);
Create actions/schoolActions.js
export function setSelectedschool(school){
return (dispatch)=>{
dispatch({'SET_SELECTED',school})
}
}
create reducers/schoolReducer.js
const initialState={
school:[] //assuming it's an array
}
const schoolReducer =(state=initialState,action)=>{
switch(action.type){
case 'SET_SELECTED':{
return {
...state,
school:action.school
}
}
}
}

Redux dispatches an API call failure even though the network tab in devtools shows the API call received a status of 200

I am new to redux and I am having a hard time understanding how to connect the payload of my API call to my state.
Right now my action.js file looks like this:
import ApiService from '../../services/ApiService';
import { reset } from 'redux-form';
//actions
export const getStock = () => {
return {
type: 'GET_STOCK'
}
}
export const getStockPending = () => {
return {
type: 'GET_STOCK_PENDING'
}
}
export const getStockFulfilled = (stock) => {
return {
type: 'GET_STOCK_FULFILLED',
payload: stock
}
}
export const getStockRejected = () => {
return {
type: 'GET_STOCK_REJECTED'
}
}
// async function calls
export function fetchStocksWithRedux() {
const action_type = "GET_STOCK";
const stock = 'AAPL';
return (dispatch) => {
dispatch({type: `${action_type}_PENDING`});
return ApiService.get(`/search?query=${stock}`)
.then(([response, json]) =>{
if(response.status === 200){
dispatch(getStockFulfilled(json))
}
else{
dispatch(getStockRejected())
}
})
}
}
and my reducer.js file looks like this:
const initialState = {
inProgress: false,
stock: {},
stocks: ['NKE', 'AMZN', 'AAPL'],
error: {}
}
export default (state = initialState, action) => {
switch(action.type) {
case 'GET_STOCK_PENDING':
return {
...state,
inProgress: true,
error: false
}
case 'GET_STOCK_FULFILLED':
return {
...state,
stock: action.payload,
inProgress: false
}
case 'GET_STOCK_REJECTED':
return {
...state,
inProgress: false,
error: action.error
}
default:
return state;
}
}
When I go to call my method fetchStocksWithRedux in my component, the network tab in my dev tools shows a 200 status and the response I'm expecting, but the reducer dispatches the 'GET_STOCK_REJECTED' action, but the error hash is empty. What do you think is going wrong?
Here is my component, for reference:
import React, { Component } from 'react';
import { fetchStocksWithRedux } from '../../redux/modules/Stock/actions';
import { connect } from 'react-redux';
class Dashboard extends Component {
componentDidMount() {
this.props.fetchStocksWithRedux()
}
render() {
return (
<div className="uk-position-center">
</div>
)
}
}
export default connect(
state => ({
stocks: state.stocks,
stock: state.stock
})
, { fetchStocksWithRedux }
)(Dashboard);
Thanks. Any advice or guidance would be greatly appreciated!

Categories

Resources