I'm having an issue getting the Parent component componentWillRecieveProps to work on the child component.
If i put all the logic in one component, everything will work fine. However, i want the post items to be in a separate component.
The only prop that is being updated is
myLikes={post.Likes.length}
Posts.js(Parent)
import React, { Component } from 'react';
import PostList from './PostList';
import {connect} from 'react-redux';
import { withRouter, Redirect} from 'react-router-dom';
import {GetPosts} from '../actions/';
const Styles = {
myPaper:{
margin: '20px 0px',
padding:'20px'
}
,
wrapper:{
padding:'0px 60px'
}
}
class Posts extends Component {
state = {
posts: [],
loading: true,
isEditing: false,
// likes:[]
}
componentWillMount(){
this.props.GetPosts();
// this.setState({
// loading:false
// })
}
componentWillReceiveProps(nextProps, prevState) {
let hasNewLike = true ;
if(prevState.posts !== this.state.posts && this.state.posts>0) {
for(let index=0; index < nextProps.myPosts.length; index++) {
if(nextProps.myPosts[index].Likes.length !==
prevState.posts[index].Likes.length) {
hasNewLike = true;
}
}
}
if(hasNewLike) {
this.setState({posts: nextProps.myPosts, loading:false}) // here we are updating the posts state if redux state has updated value of likes
}
console.log(nextProps.myPosts);
}
render() {
const {loading} = this.state;
const { myPosts} = this.props
console.log(this.state.posts);
if (!this.props.isAuthenticated) {
return (<Redirect to='/signIn' />);
}
if(loading){
return "loading..."
}
return (
<div className="App" style={Styles.wrapper}>
<h1> Posts </h1>
<PostList posts={this.state.posts}/>
</div>
);
}
}
const mapStateToProps = (state) => ({
isAuthenticated: state.user.isAuthenticated,
myPosts: state.post.posts,
})
const mapDispatchToProps = (dispatch, state) => ({
GetPosts: () => dispatch( GetPosts())
});
export default withRouter(connect(mapStateToProps,mapDispatchToProps)(Posts));
PostList.js (Child)
import React, { Component } from 'react';
import Paper from '#material-ui/core/Paper';
import Button from '#material-ui/core/Button';
import Typography from '#material-ui/core/Typography';
import moment from 'moment';
import {connect} from 'react-redux';
import {DeletePost, postLike, UpdatePost,EditChange, getCount, DisableButton} from '../actions/';
import PostItem from './PostItem';
import _ from 'lodash';
const Styles = {
myPaper: {
margin: '20px 0px',
padding: '20px'
}
}
class PostList extends Component{
constructor(props){
super(props);
this.state ={
title: '',
}
}
// Return a new function. Otherwise the DeletePost action will be dispatch each
// time the Component rerenders.
removePost = (id) => () => {
this.props.DeletePost(id);
}
onChange = (e) => {
e.preventDefault();
this.setState({
title: e.target.value
})
}
formEditing = (id) => ()=> {;
this.props.EditChange(id);
}
render(){
const {posts} = this.props;
console.log(this.props.posts)
// console.log(this.props.ourLikes);
return (
<div>
{posts.map(post => (
<Paper key={post.id} style={Styles.myPaper}>
<PostItem
myLikes={post.Likes.length} // right here
myTitle={this.state.title}
editChange={this.onChange}
editForm={this.formEditing}
isEditing={this.props.isEditingId === post.id}
removePost={this.removePost}
{...post}
/>
</Paper>
))}
</div>
);
}
}
const mapStateToProps = (state) => ({
isEditingId: state.post.isEditingId,
// ourLikes: state.post.likes // reducer likes
})
const mapDispatchToProps = (dispatch) => ({
// pass creds which can be called anything, but i just call it credentials but it should be called something more
// specific.
EditChange: (id) => dispatch(EditChange(id)),
UpdatePost: (creds) => dispatch(UpdatePost(creds)),
postLike: (id) => dispatch( postLike(id)),
// Pass id to the DeletePost functions.
DeletePost: (id) => dispatch(DeletePost(id))
});
export default connect(mapStateToProps, mapDispatchToProps)(PostList);
Alternatively if everything was in one component, it will work
Posts.js(all in one)
import React, {Component} from 'react';
import PostList from './PostList';
import Paper from '#material-ui/core/Paper';
import {connect} from 'react-redux';
import {withRouter, Redirect} from 'react-router-dom';
import {
DeletePost,
postLike,
UpdatePost,
EditChange,
getCount,
DisableButton
} from '../actions/';
import PostItem from './PostItem';
import {GetPosts} from '../actions/';
const Styles = {
myPaper: {
margin: '20px 0px',
padding: '20px'
},
wrapper: {
padding: '0px 60px'
}
}
class Posts extends Component {
constructor(props){
super(props);
this.state = {
posts: [],
title: '',
loading: true,
isEditing: false,
}
}
componentWillMount() {
this.props.GetPosts();
}
removePost = (id) => () => {
this.props.DeletePost(id);
}
onChange = (e) => {
e.preventDefault();
this.setState({title: e.target.value})
}
formEditing = (id) => () => {
this.props.EditChange(id);
}
componentWillReceiveProps(nextProps, prevState) {
let hasNewLike = true;
if (prevState.posts && prevState.posts.length) {
for (let index = 0; index < nextProps.myPosts.length; index++) {
if (nextProps.myPosts[index].Likes.length !== prevState.posts[index].Likes.length) {
hasNewLike = true;
}
}
}
if (hasNewLike) {
this.setState({posts: nextProps.myPosts, loading: false}); // here we are updating the posts state if redux state has updated value of likes
}
}
render() {
const {loading} = this.state;
const {myPosts} = this.props
console.log(this.state.posts);
if (!this.props.isAuthenticated) {
return (<Redirect to='/signIn'/>);
}
if (loading) {
return "loading..."
}
return (
<div className="App" style={Styles.wrapper}>
<h1>Posts</h1>
{/* <PostList posts={this.state.posts}/> */}
<div>
{this.state.posts.map(post => (
<Paper key={post.id} style={Styles.myPaper}>
<PostItem myLikes={post.Likes.length} // right here
myTitle={this.state.title} editChange={this.onChange} editForm={this.formEditing} isEditing={this.props.isEditingId === post.id} removePost={this.removePost} {...post}/>
</Paper>
))}
</div>
</div>
);
}
}
const mapStateToProps = (state) => ({
isAuthenticated: state.user.isAuthenticated,
myPosts: state.post.posts, isEditingId:
state.post.isEditingId
})
const mapDispatchToProps = (dispatch, state) => ({
GetPosts: () => dispatch(GetPosts()),
// specific.
EditChange: (id) => dispatch(EditChange(id)),
UpdatePost: (creds) => dispatch(UpdatePost(creds)),
postLike: (id) => dispatch(postLike(id)),
// Pass id to the DeletePost functions.
DeletePost: (id) => dispatch(DeletePost(id))
});
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Posts));
I took #jank advice, and added the componentWillReceiveProps within the child component.
I was also missing withRouter in PostList, which without it the componentWillReciveProps will not work.
// without withRouter componentWillReceiveProps will not work like its supposed too.
export default withRouter(connect(mapStateToProps,mapDispatchToProps)(PostList));
Updated code
PostList.js
import React, { Component } from 'react';
import Paper from '#material-ui/core/Paper';
import Button from '#material-ui/core/Button';
import Typography from '#material-ui/core/Typography';
import moment from 'moment';
import {connect} from 'react-redux';
import { withRouter, Redirect} from 'react-router-dom';
import {DeletePost, postLike, UpdatePost,EditChange, GetPosts, getCount, DisableButton} from '../actions/';
import PostItem from './PostItem';
import _ from 'lodash';
const Styles = {
myPaper: {
margin: '20px 0px',
padding: '20px'
}
}
class PostList extends Component{
constructor(props){
super(props);
this.state ={
title: '',
posts:[],
loading:true
}
}
componentWillMount() {
this.props.GetPosts();
}
componentWillReceiveProps(nextProps, prevState) {
let hasNewLike = true;
if (prevState.posts && prevState.posts.length) {
for (let index = 0; index < nextProps.myPosts.length; index++) {
if (nextProps.myPosts[index].Likes.length !== prevState.posts[index].Likes.length) {
hasNewLike = true;
}
}
}
if (hasNewLike) {
this.setState({posts: nextProps.myPosts, loading: false}); // here we are updating the posts state if redux state has updated value of likes
}
}
// Return a new function. Otherwise the DeletePost action will be dispatch each
// time the Component rerenders.
removePost = (id) => () => {
this.props.DeletePost(id);
}
onChange = (e) => {
e.preventDefault();
this.setState({
title: e.target.value
})
}
formEditing = (id) => ()=> {;
this.props.EditChange(id);
}
render(){
const { posts, loading} = this.state;
// console.log(this.props.posts)
// console.log(this.props.ourLikes);
if(loading){
return "loading..."
}
return (
<div>
{this.state.posts.map(post => (
<Paper key={post.id} style={Styles.myPaper}>
<PostItem
myLikes={post.Likes.length} // right here
myTitle={this.state.title}
editChange={this.onChange}
editForm={this.formEditing}
isEditing={this.props.isEditingId === post.id}
removePost={this.removePost}
{...post}
/>
</Paper>
))}
</div>
);
}
}
const mapStateToProps = (state) => ({
isEditingId: state.post.isEditingId,
myPosts: state.post.posts,
})
const mapDispatchToProps = (dispatch) => ({
// pass creds which can be called anything, but i just call it credentials but it should be called something more
// specific.
GetPosts: () => dispatch(GetPosts()),
EditChange: (id) => dispatch(EditChange(id)),
UpdatePost: (creds) => dispatch(UpdatePost(creds)),
postLike: (id) => dispatch( postLike(id)),
// Pass id to the DeletePost functions.
DeletePost: (id) => dispatch(DeletePost(id))
});
// without withRouter componentWillReceiveProps will not work like its supposed too.
export default withRouter(connect(mapStateToProps,mapDispatchToProps)(PostList));
Posts.js
import React, { Component } from 'react';
import PostList from './PostList';
import {connect} from 'react-redux';
import { withRouter, Redirect} from 'react-router-dom';
import {GetPosts} from '../actions/';
const Styles = {
myPaper:{
margin: '20px 0px',
padding:'20px'
}
,
wrapper:{
padding:'0px 60px'
}
}
class Posts extends Component {
state = {
}
render() {
if (!this.props.isAuthenticated) {
return (<Redirect to='/signIn' />);
}
return (
<div className="App" style={Styles.wrapper}>
<h1> Posts </h1>
<PostList />
</div>
);
}
}
const mapStateToProps = (state) => ({
isAuthenticated: state.user.isAuthenticated,
})
export default connect(mapStateToProps)(Posts);
Related
I wanna display my cart item inside my CartList component but for some reason, it doesn't work out...Is there anything wrong with the way i destructure the cart variable able or anything? please help me! Thank you so much!
context.js:
class ProductProvider extends React.Component {
state = {
products: storeProducts,
detailProduct: detailProduct,
cart: storeProducts,
modalOpen: false,
modalProduct: detailProduct
};
getItem = (id) => {
const product = this.state.products.find((item) => item.id === id);
return product;
};
addToCart = (id) => {
let tempProducts = [...this.state.products];
const index = tempProducts.indexOf(this.getItem(id));
const product = tempProducts[index];
product.inCart = true;
product.count = 1;
const price = product.price;
product.total = price;
this.setState(() => {
return (
{ products: tempProducts, cart: [...this.state.cart, product] },
() => console.log(this.state)
);
});
};
openModal = (id) => {
const product = this.getItem(id);
this.setState(() => {
return { modalProduct: product, openModal: true };
});
};
closeModal = (id) => {
this.setState(() => {
return { modalOpen: false };
});
};
render() {
return (
<ProductContext.Provider
value={{
...this.state,
addToCart: this.addToCart,
openModal: this.openModal,
closeModal: this.closeModal
}}
>
{this.props.children}
</ProductContext.Provider>
);
}
}
Cart.js:
import React from "react";
import CartColumns from "./CartColumns";
import CartList from "./CartList";
const ProductContext = React.createContext();
export default class Cart extends React.Component {
render() {
return (
<div>
your cart
<ProductContext.Consumer>
{(value) => {
if (value && value.length > 0) {
return (
<div>
<CartColumns />
<CartList value={value}/>
</div>
);
}
}}
</ProductContext.Consumer>
</div>
);
}
}
CartItem.js:
import React from "react";
function CartItem() {
return <div>this is a cart item</div>;
}
export default CartItem;
CartList.js:
import React from "react";
import CartItem from "./CartItem"
export default class CartList extends React.Component {
{value =>{
return <div>
{cart.map((item) => {
return (
<CartItem key={item.id} value={value} item={item}/>
)
}}
</div>;
}}
}
export default CartList;
Sandbox link: https://codesandbox.io/s/cart-code-addict-buz0u?file=/src/cart/CartList.js
first of all fixed your cartList component
import React from "react";
import CartItem from "./CartItem"
export default function CartList (props) {
const {cart} = props
return (
<div>
{cart.map((item) => (
<CartItem key={item.id} item={item} />
))}
</div>
)
}
I am using react +redux+typescript+firebase for a simple todo application. How to move all actions from components to store if i use react hooks(without other third party libraries, if it is possible).
Components/Todo.tsx:
import React from 'react';
import {connect} from 'react-redux';
import Switch from '#material-ui/core/Switch';
import IconButton from '#material-ui/core/IconButton';
import DeleteIcon from '#material-ui/icons/Delete';
import "./Todo.scss";
import {todosRef} from "../../firebase/firebase";
const Todo = (props: any) => {
const classes = [''];
const { todo } = props;
const updateTodo = () => {
todosRef.child(todo.id).set({...todo,done:!todo.done})
}
if (todo.done) {
classes.push('_completed');
}
return (
<div className="Todo">
<Switch
edge="end" checked={todo.done} onChange={updateTodo}
inputProps={{ "aria-labelledby": "switch-list-label-bluetooth" }}
/>
<p className={classes.join(' ')}>{todo.task}</p>
<IconButton aria-label="delete" onClick={e => todosRef.child(todo.id).remove()}>
<DeleteIcon fontSize="large" />
</IconButton>
</div>
);
}
export default connect()(Todo);
components/TodoForm.tsx
import React, { useState } from "react";
import TextField from '#material-ui/core/TextField';
import {todosRef} from "../../firebase/firebase";
const TodoForm:React.FC = () => {
const [title, setTitle] = useState<string>("");
const createTodo = (e: React.FormEvent<EventTarget>) => {
e.preventDefault();
const item = {
task: title,
done: false,
};
todosRef.push(item);
setTitle("");
};
return (
<form onSubmit={createTodo}>
<TextField
style={{ width: "100%" }}
id="outlined-basic"
value={title}
onChange={(e:React.ChangeEvent<HTMLInputElement>) => setTitle(e.target.value)}
label="Write task"
variant="outlined"
/>
</form>
);
}
export default TodoForm;
components/TodoList.tsx:
import React, {useState, useEffect} from "react";
import Todo from "../Todo/Todo";
import Divider from '#material-ui/core/Divider';
import {todosRef} from "../../firebase/firebase";
const TodoList:React.FC = () => {
const [todos,setTodos] = useState<any>([]);
useEffect(() => {
todosRef.on('value', (snapshot) => {
let items = snapshot.val();
let newState = [];
for (let item in items) {
newState.push({
id: item,
task: items[item].task,
done: items[item].done
});
}
setTodos(newState)
});
},[])
return (
<>
{todos.map((todo: any, i: number) => (
<React.Fragment key={todo.id}>
<Todo todo={todo} />
{i<todos.length -1 && <Divider />}
</React.Fragment>
))}
</>
);
}
export default TodoList;
actions/index.ts:
export const fetchTodos = (todo:any) => {
return {
type: 'FETCH_TODOS',
payload: todo,
}
}
export const addToDo = (setTodos:any) => {
return {
type: 'ADD_TODO',
payload: setTodos,
}
}
export const updateTodo = (todo:any) => {
return {
type: 'UPDATE_TODO',
payload: todo,
}
}
export const removeTodo = (todo:any) => {
return {
type: 'REMOVE_TODO',
payload: todo,
}
}
I started describing the actions, but I don't know exactly what to write in the context of the store.
const initialState = {
todos: [],
setTodos: [],
}
export default (state = initialState, action:any) => {
switch (action.type) {
case FETCH_TODOS:
return {
...state,
todo:action.payload
};
case REMOVE_TODO:
...
default:
return state;
...
I will also be grateful if you can indicate with a couple of examples (instead of any) which types I should use, since I'm still new to typescript
I'm trying to pass the updated like count to the child component PostList.js.
it is called like
myLikes={post.Likes.length} // right here
The console.log(nextProps.myPosts)
makes a new likes object array, with the updated Likes count. How would i reflex this update to the UI ?
myLikes={post.Likes.length} gets Likes count, but does not get the updated nextProps.
Posts.js
import React, { Component } from 'react';
import PostList from './PostList';
import {connect} from 'react-redux';
import { withRouter, Redirect} from 'react-router-dom';
import {GetPosts} from '../actions/';
const Styles = {
myPaper:{
margin: '20px 0px',
padding:'20px'
}
,
wrapper:{
padding:'0px 60px'
}
}
class Posts extends Component {
state = {
posts: [],
loading: true,
isEditing: false,
// likes:[]
}
componentWillMount(){
this.props.GetPosts();
this.setState({
loading:false
})
}
componentWillReceiveProps(nextProps, prevState) {
let hasNewLike = true ;
if(prevState.posts && prevState.posts.length) {
for(let index=0; index < nextProps.myPosts.length; index++) {
if(nextProps.myPosts[index].Likes.length !==
prevState.posts[index].Likes.length) {
hasNewLike = true;
}
}
}
if(hasNewLike) {
this.setState({posts: nextProps.myPosts}); // here we are updating the posts state if redux state has updated value of likes
}
console.log(nextProps.myPosts)
// console.log(nextProps.myPosts[1].Likes.length) // shows a like count
}
render() {
const {loading} = this.state;
const { myPosts} = this.props
if (!this.props.isAuthenticated) {
return (<Redirect to='/signIn' />);
}
if(loading){
return "loading..."
}
return (
<div className="App" style={Styles.wrapper}>
<h1> Posts </h1>
<PostList posts={this.state.posts}/>
</div>
);
}
}
const mapStateToProps = (state) => ({
isAuthenticated: state.user.isAuthenticated,
myPosts: state.post.posts,
})
const mapDispatchToProps = (dispatch, state) => ({
GetPosts: () => dispatch( GetPosts())
});
export default withRouter(connect(mapStateToProps,mapDispatchToProps)(Posts));
PostList.js
import React, { Component } from 'react';
import Paper from '#material-ui/core/Paper';
import Button from '#material-ui/core/Button';
import Typography from '#material-ui/core/Typography';
import moment from 'moment';
import {connect} from 'react-redux';
import {DeletePost, postLike, UpdatePost,EditChange, getCount, DisableButton} from '../actions/';
import PostItem from './PostItem';
import _ from 'lodash';
const Styles = {
myPaper: {
margin: '20px 0px',
padding: '20px'
}
}
class PostList extends Component{
constructor(props){
super(props);
this.state ={
title: '',
}
}
// Return a new function. Otherwise the DeletePost action will be dispatch each
// time the Component rerenders.
removePost = (id) => () => {
this.props.DeletePost(id);
}
onChange = (e) => {
e.preventDefault();
this.setState({
title: e.target.value
})
}
formEditing = (id) => ()=> {;
this.props.EditChange(id);
}
render(){
const {posts} = this.props;
// console.log(this.props.ourLikes);
return (
<div>
{posts.map(post => (
<Paper key={post.id} style={Styles.myPaper}>
<PostItem
myLikes={post.Likes.length} // right here
myTitle={this.state.title}
editChange={this.onChange}
editForm={this.formEditing}
isEditing={this.props.isEditingId === post.id}
removePost={this.removePost}
{...post}
/>
</Paper>
))}
</div>
);
}
}
const mapStateToProps = (state) => ({
isEditingId: state.post.isEditingId,
})
const mapDispatchToProps = (dispatch) => ({
// pass creds which can be called anything, but i just call it credentials but it should be called something more
// specific.
EditChange: (id) => dispatch(EditChange(id)),
UpdatePost: (creds) => dispatch(UpdatePost(creds)),
postLike: (id) => dispatch( postLike(id)),
// Pass id to the DeletePost functions.
DeletePost: (id) => dispatch(DeletePost(id))
});
export default connect(mapStateToProps, mapDispatchToProps)(PostList);
Navbar.js
import React from 'react';
import {BrowserRouter as Router, Route, Link, Switch} from "react-router-dom";
import signUp from '../auth/signUp';
import signIn from '../auth/signIn';
import Post from '../Post';
import Forgot from '../account/Forgot';
import Home from '../Home';
import Posts from '../Posts';
import Users from '../account/Users';
import AppBar from '#material-ui/core/AppBar';
import Toolbar from '#material-ui/core/Toolbar';
import Typography from '#material-ui/core/Typography';
import Button from '#material-ui/core/Button';
import {withStyles} from '#material-ui/core';
import Dashboard from '../account/dashBoard';
import {connect} from 'react-redux';
import {createBrowserHistory} from 'history';
import PropTypes from 'prop-types';
import {compose} from 'redux';
import Axios from '../../Axios';
import updatePassword from '../account/updatePassword';
import ResetPassword from '../account/ResetPassword';
import ourStyles from '../../styles/ourStyles';
export const history = createBrowserHistory({forceRefresh: true});
const logout = (e) => {
e.preventDefault()
Axios.get(process.env.REACT_APP_LOGOUT, {withCredentials: true})
.then(res => {
// console.log(res);
if (res.status === 200) {
localStorage.removeItem('auth')
localStorage.removeItem('myAuth')
history.push('/')
}
})
.catch(err => {
// // their will be an inevitable error, so you would need this for it to work
localStorage.removeItem('auth')
localStorage.removeItem('myAuth')
history.push('/')
})
}
const Navbar = ({classes, isAuthenticated}) => (
<Router history={history}>
<div className={classes.navRoot}>
<AppBar position="static" className={classes.navbar}>
<Toolbar>
<Typography variant="h6" color="inherit">
Express Seqeuelize App
</Typography>
<Typography classcolor="inherit" className={classes.rightt}>
{!isAuthenticated && (
<Button>
<Link to="/" className={classes.rightToolbar}>
Home
</Link>
</Button>
)}
{isAuthenticated && (
<Button>
<Link className={classes.rightToolbar} to="/posts">
Posts
</Link>
</Button>
)}
{!isAuthenticated && (
<Button>
<Link to="/signUp" className={classes.rightToolbar}>
Sign Up
</Link>
</Button>
)}
{!isAuthenticated && (
<Button>
<Link to="/signIn" className={classes.rightToolbar}>
Sign In
</Link>
</Button>
)}
{isAuthenticated && (
<Button>
<Link className={classes.rightToolbar} to="/Post">
New Post
</Link>
</Button>
)}
{isAuthenticated && (
<Button>
<Link to="/dashboard" className={classes.rightToolbar}>
Dashboard
</Link>
</Button>
)}
{isAuthenticated && (
<Button onClick={logout}>
LogOut
</Button>
)}
</Typography>
</Toolbar>
</AppBar>
<Switch>
<Route exact path="/signUp" component={signUp}/>
<Route exact path="/" component={Home}/>
<Route exact path="/signIn" component={signIn}/>
<Route exact path="/Post" component={Post}/>
<Route exact path="/Posts" component={Posts}/>
<Route path="/Forgot" component={Forgot}/>
<Route path="/users" component={Users}/>
<Route exact path="/logout"/>
<Route exact path="/dashboard" component={Dashboard}/>
<Route path="/test"/>
<Route path="/reset/:token" component={ResetPassword}/>
<Route exact path="/updatePassword/:username" component={updatePassword}/>
</Switch>
</div>
</Router>
);
const mapStateToProps = (state) => ({
token: state.user.getToken, githubAuth: state.user.githubAuth,
// owl: state.user.owl,
isAuthenticated: state.user.isAuthenticated
})
const mapDispatchToProps = (dispatch) => ({
// logIn: (user) => dispatch(logIn(user))
});
Navbar.propTypes = {
isAuthenticatd: PropTypes.string
}
// export default withStyles(styles)(Navbar);
export default compose(connect(mapStateToProps, mapDispatchToProps), withStyles(ourStyles))(Navbar);
Reducer
const initialState = {
post: [],
postError: null,
posts:[],
isEditing:false,
isEditingId:null,
likes:[],
someLike:[],
postId:null
}
export default (state = initialState, action) => {
switch (action.type) {
case ADD_LIKE:
const newState = {...state}; // here I am trying to shallow copy the existing state;
const existingLikesOfPost = newState.posts.find(post => post.id == action.id).Likes;
newState.posts.find(post => post.id == action.id).Likes = [...existingLikesOfPost, action.newLikeObject];
console.log(newState)
return newState
console.log(newState)
{
"post": [],
"postError": null,
"posts": [
{
"id": 5,
"title": "React estiossssnsdd",
"post_content": "ssss",
"username": "owlman",
"createdAt": "2019-04-26T09:38:10.324Z",
"updatedAt": "2019-04-27T20:53:16.898Z",
"userId": 1,
"Likes": [
{
"id": 236,
"like": true,
"createdAt": "2019-04-27T20:57:44.395Z",
"updatedAt": "2019-04-27T20:57:44.395Z",
"userId": 1,
"postId": 5
},
{
"id": 220,
"like": true,
"createdAt": "2019-04-27T15:57:29.753Z",
"updatedAt": "2019-04-27T15:57:29.753Z",
"userId": 1,
"postId": 5
},
"isEditing": false,
"isEditingId": null,
"likes": [
117,
39
],
"someLike": [],
"postId": null
}
Post.js
componentWillReceiveProps(nextProps, prevState) {
let hasNewLike = true ;
if(prevState.posts && prevState.posts.length) {
for(let index=0; index < nextProps.myPosts.length; index++) {
if(nextProps.myPosts[index].Likes.length !==
prevState.posts[index].Likes.length) {
hasNewLike = true;
}
}
}
if(hasNewLike) {
this.setState({posts: nextProps.myPosts}); // here we are updating the posts state
if redux state has updated value of likes
}
console.log(nextProps.myPosts)
return true // return your change --- this can be a reason of not re-rendering
// console.log(nextProps.myPosts[1].Likes.length) // shows a like count
}
So it appears, that moving the PostList Component logic all to Posts.js seems to do the desired action. However it would be nice to keep the logic separated, but this logic will be good for now.
So componentWillReceiveProps needs to be on the component in which you want to make the update on. In this case i wanted to make the update on
post.Likes.length
which was on the PostList.js component. However, the componentWillReceiveProps is on the posts.js component. So i moved the logic on the Posts.js component.
Posts.js
import React, {Component} from 'react';
import PostList from './PostList';
import Paper from '#material-ui/core/Paper';
import {connect} from 'react-redux';
import {withRouter, Redirect} from 'react-router-dom';
import {
DeletePost,
postLike,
UpdatePost,
EditChange,
getCount,
DisableButton
} from '../actions/';
import PostItem from './PostItem';
import {GetPosts} from '../actions/';
const Styles = {
myPaper: {
margin: '20px 0px',
padding: '20px'
},
wrapper: {
padding: '0px 60px'
}
}
class Posts extends Component {
state = {
posts: [],
title: '',
loading: true,
isEditing: false,
}
componentWillMount() {
this.props.GetPosts();
}
removePost = (id) => () => {
this.props.DeletePost(id);
}
onChange = (e) => {
e.preventDefault();
this.setState({title: e.target.value})
}
formEditing = (id) => () => {
this.props.EditChange(id);
}
componentWillReceiveProps(nextProps, prevState) {
let hasNewLike = true;
if (prevState.posts && prevState.posts.length) {
for (let index = 0; index < nextProps.myPosts.length; index++) {
if (nextProps.myPosts[index].Likes.length !== prevState.posts[index].Likes.length) {
hasNewLike = true;
}
}
}
if (hasNewLike) {
this.setState({posts: nextProps.myPosts, loading: false}); // here we are updating the posts state if redux state has updated value of likes
}
}
render() {
const {loading} = this.state;
const {myPosts} = this.props
console.log(this.state.posts);
if (!this.props.isAuthenticated) {
return (<Redirect to='/signIn'/>);
}
if (loading) {
return "loading..."
}
return (
<div className="App" style={Styles.wrapper}>
<h1>Posts</h1>
{/* <PostList posts={this.state.posts}/> */}
<div>
{this.state.posts.map(post => (
<Paper key={post.id} style={Styles.myPaper}>
<PostItem myLikes={post.Likes.length} // right here
myTitle={this.state.title} editChange={this.onChange} editForm={this.formEditing} isEditing={this.props.isEditingId === post.id} removePost={this.removePost} {...post}/>
</Paper>
))}
</div>
</div>
);
}
}
const mapStateToProps = (state) => ({
isAuthenticated: state.user.isAuthenticated,
myPosts: state.post.posts, isEditingId:
state.post.isEditingId
})
const mapDispatchToProps = (dispatch, state) => ({
GetPosts: () => dispatch(GetPosts()),
// specific.
EditChange: (id) => dispatch(EditChange(id)),
UpdatePost: (creds) => dispatch(UpdatePost(creds)),
postLike: (id) => dispatch(postLike(id)),
// Pass id to the DeletePost functions.
DeletePost: (id) => dispatch(DeletePost(id))
});
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Posts));
How to update the state of the props when a user likes a post?
The props would need to automatically update when a user clicks like.
Currently, a user can like a post, and only on page refresh I am able to see the updated number of likes, which shows on
{this.props.likeCount}
What Component lifecycle would be best for seeing the updated props without refreshing the page? this application is using redux.
Like.js
import React, { Component } from 'react';
import ReactDOM from 'react-dom'
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome';
import { faCoffee, faAdjust } from '#fortawesome/free-solid-svg-icons';
import {connect} from 'react-redux';
import { getLikeCount} from '../actions/';
class Like extends Component{
constructor(props){
super(props);
this.state = {
likes: null
}
}
getLikes = (id) => {
// console.log(id);
this.props.getLikeCount(id)
console.log(this.props.likeCount)
}
render(){
return(
<div style={{float:'right', fontSize: '1.5em', color:'tomato'}} >
<i style={{ marginRight: '140px'}} className="fa fa-heart-o">
<span style={{ marginLeft: '6px'}}>
<a href="#" onClick={this.props.like}>Like </a>
{this.getLikes(this.props.postId)}
</span>
{/* gets the like counts */}
{this.props.likeCount}
</i>
</div>
)
}
}
const mapStateToProps = (state) => ({
isEditingId: state.post.isEditingId,
likeCount:state.post.likes
})
const mapDispatchToProps = (dispatch) => ({
// pass creds which can be called anything, but I just call it credentials but it should be called something more
// specific.
getLikeCount: (id) => dispatch(getLikeCount(id)),
// Pass id to the DeletePost functions.
});
export default connect(mapStateToProps, mapDispatchToProps)(Like);
Actions.js
export const getLikeCount = (id) => {
return (dispatch, getState) => {
return Axios.get(`/api/posts/likes/count/${id}`)
.then( (res) => {
const data = res.data
console.log(data);
dispatch({type: GET_LIKES_COUNT, data})
})
}
}
Reducer
import { GET_LIKES_COUNT} from '../actions/';
const initialState = {
post: [],
postError: null,
posts:[],
isEditing:false,
isEditingId:null,
likes:[],
postId:null
}
export default (state = initialState, action) => {
switch (action.type) {
case GET_LIKES_COUNT:
// console.log(action.data)
return({
...state,
likes:action.data
})
default:
return state
}
}
edit(im getting a wierd infinite post loop)
wierd error
Update the code to the following code.
GET_LIKES_COUNT handles the api action, of getting the number of likes for a post.
Without it, it will be always set to 0 likes on render.
ADD_LIKE action gives it the functionality of updating the state without refreshing the page.(i know that their is more specific term they call this in react, maybe its re-rendering) Update the state without re-rendering the component as well as the most important part which is making the api call to the backend to allow the user to like a post. We set likes to 0 to make it possible to upvote the state and it to have it updated without refresh.
Thanks for the assistance #novonimo.
Reducer
import { GET_LIKES_COUNT, ADD_LIKE} from '../actions/';
const initialState = {
post: [],
postError: null,
posts:[],
isEditing:false,
isEditingId:null,
likes:0,
postId:null
}
export default (state = initialState, action) => {
switch (action.type) {
// get number of likes from api
case GET_LIKES_COUNT:
// console.log(action.data)
return({
...state,
likes:action.data
})
case ADD_LIKE:
return({
...state,
likes: state.likes + 1
})
default:
return state
}
}
Actions
export const postLike = (id) => {
return (dispatch) => {
// console.log(userId);
return Axios.post('/api/posts/like', {
postId: id
}).then( (like) => {
dispatch({type: ADD_LIKE})
// console.log('you have liked this', like)
}).catch( (err)=> {
console.log('there seem to be an error', err);
})
}
}
export const getLikeCount = (id) => {
return (dispatch, getState) => {
return Axios.get(`/api/posts/likes/count/${id}`)
.then( (res) => {
const data = res.data
console.log(data);
dispatch({type: GET_LIKES_COUNT, data})
})
}
}
PostItem.js
import React, { Component } from 'react';
import Paper from '#material-ui/core/Paper';
import Button from '#material-ui/core/Button';
import Typography from '#material-ui/core/Typography';
import moment from 'moment';
import Editable from './Editable';
import {connect} from 'react-redux';
import {UpdatePost, getLikeCount, postLike} from '../actions/';
import Like from './Like';
import Axios from '../Axios';
const Styles = {
myPaper: {
margin: '20px 0px',
padding: '20px'
},
button:{
marginRight:'30px'
}
}
class PostItem extends Component{
constructor(props){
super(props);
this.state = {
disabled: false,
}
}
onUpdate = (id, title) => () => {
// we need the id so expres knows what post to update, and the title being that only editing the title.
if(this.props.myTitle !== null){
const creds = {
id, title
}
this.props.UpdatePost(creds);
}
}
clickLike = (id) => () => {
this.props.postLike(id);
}
render(){
const {title, id, userId, removePost, createdAt, post_content, username, editForm, isEditing, editChange, myTitle, postUpdate, likes} = this.props
return(
<div>
<Typography variant="h6" component="h3">
{/* if else teneray operator */}
{isEditing ? (
<Editable editField={myTitle ? myTitle : title} editChange={editChange}/>
): (
<div>
{title}
</div>
)}
</Typography>
<Typography component="p">
{post_content}
<h5>
by: {username}</h5>
<Typography color="textSecondary">{moment(createdAt).calendar()}</Typography>
<Like like={this.clickLike(id)} postId={id}/>
</Typography>
{!isEditing ? (
<Button variant="outlined" type="submit" onClick={editForm(id)}>
Edit
</Button>
):(
// pass id, and myTitle which as we remember myTitle is the new value when updating the title
<div>
<Button
disabled={myTitle.length <= 3}
variant="outlined"
onClick={this.onUpdate(id, myTitle)}>
Update
</Button>
<Button
variant="outlined"
style={{marginLeft: '0.7%'}}
onClick={editForm(null)}>
Close
</Button>
</div>
)}
{!isEditing && (
<Button
style={{marginLeft: '0.7%'}}
variant="outlined"
color="primary"
type="submit"
onClick={removePost(id)}>
Remove
</Button>
)}
</div>
)
}
}
const mapStateToProps = (state) => ({
isEditingId: state.post.isEditingId,
})
const mapDispatchToProps = (dispatch) => ({
// pass creds which can be called anything, but i just call it credentials but it should be called something more
// specific.
UpdatePost: (creds) => dispatch(UpdatePost(creds)),
getLikeCount: (id) => dispatch(getLikeCount(id)),
postLike: (id) => dispatch( postLike(id))
// Pass id to the DeletePost functions.
});
export default connect(null, mapDispatchToProps)(PostItem);
Like.js
import React, { Component } from 'react';
import ReactDOM from 'react-dom'
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome';
import { faCoffee, faAdjust } from '#fortawesome/free-solid-svg-icons';
import {connect} from 'react-redux';
import { getLikeCount} from '../actions/';
class Like extends Component{
constructor(props){
super(props);
this.state = {
likes: null
}
}
getLikes = (id) => {
// console.log(id);
this.props.getLikeCount(id)
console.log(this.props.likeCount)
}
render(){
return(
<div style={{float:'right', fontSize: '1.5em', color:'tomato'}} >
<i style={{ marginRight: '140px'}} className="fa fa-heart-o">
<span style={{ marginLeft: '6px'}}>
<a href="#" onClick={this.props.like}>Like </a>
{this.getLikes(this.props.postId)}
</span>
{/* gets the like counts */}
{this.props.likeCount}
</i>
</div>
)
}
}
const mapStateToProps = (state) => ({
isEditingId: state.post.isEditingId,
likeCount:state.post.likes
})
const mapDispatchToProps = (dispatch) => ({
getLikeCount: (id) => dispatch(getLikeCount(id)),
// Pass id to the DeletePost functions.
});
export default connect(mapStateToProps, mapDispatchToProps)(Like);
React philosophy is based on remove Refresh pages on changes.
so forget refresh in all react app.
in the component you can change code like this:
handleAddUpVote = ()=> this.props.dispatch(addUpVote())
return(
<div onClick={this.handleAddUpVote}> sth </div>
)
and in action:
const ADD_UP_VOTE = "ADD_UP_VOTE";
const addUpVote = ({type: ADD_UP_VOTE});
export {ADD_UP_VOTE, addUpVote}
and finally, change your reducer:
initialState={
voteCounter: 0
}
const Reducer = (state=initialState, action) => {
switch(action.type){
case(ADD_UP_VOTE):
return{
...state,
voteCounter: state.voteCounter + 1
};
}
}
I'm trying to tell react/redux to upvote a likeCount when ADD_LIKE is called from the reducer.
However its upvoting the number for all post items
How can i do something like.
if(action.id === post.id) then do likes: state.likes + 1
That way it can only upvote for that one post only.
case ADD_LIKE:
// console.log(action.id)
return({
...state,
likes: state.likes + 1
})
Actions.js
export const postLike = (id) => {
return (dispatch) => {
// console.log(userId);
return Axios.post('/api/posts/like', {
postId: id
}).then( (like) => {
dispatch({type: ADD_LIKE, id})
// console.log('you have liked this', like)
}).catch( (err)=> {
console.log('there seem to be an error', err);
})
Full Reducer
import { ADD_LIKE, GET_LIKES_COUNT} from '../actions/';
const initialState = {
post: [],
postError: null,
posts:[],
isEditing:false,
isEditingId:null,
likes:0,
postId:null
}
export default (state = initialState, action) => {
switch (action.type) {
case GET_LIKES_COUNT:
// console.log(action.data)
return({
...state,
likes:action.data
})
case ADD_LIKE:
// console.log(action.id)
return({
...state,
likes: state.likes + 1
})
default:
return state
}
}
Like Component
import React, { Component } from 'react';
import ReactDOM from 'react-dom'
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome';
import { faCoffee, faAdjust } from '#fortawesome/free-solid-svg-icons';
import {connect} from 'react-redux';
import { getLikeCount, postLike} from '../actions/';
class Like extends Component{
constructor(props){
super(props);
this.state = {
likes: null,
heart: false
}
}
// dont remove () after (id) or else infinite onClick
clickLike = (id) => () => {
this.props.postLike(id);
// toggles between css class
this.setState({
heart: !this.state.heart
})
}
render(){
return(
<div style={{float:'right', fontSize: '1.5em', color:'tomato'}} >
<i style={{ marginRight: '140px'}} className={this.state.heart ? 'fa fa-heart':'fa fa-heart-o' }>
<span style={{ marginLeft: '6px'}}>
<a href="#" onClick={this.clickLike(this.props.like)}>Like </a>
</span>
{/* gets the like counts */}
{this.props.likeCount}
</i>
</div>
)
}
}
const mapStateToProps = (state) => ({
isEditingId: state.post.isEditingId,
likeCount:state.post.likes
})
const mapDispatchToProps = (dispatch) => ({
getLikeCount: (id) => dispatch(getLikeCount(id)),
postLike: (id) => dispatch( postLike(id))
// Pass id to the DeletePost functions.
});
export default connect(mapStateToProps, mapDispatchToProps)(Like);
<Like like={id}/> post id is id and being passed in as a prop
Postitem.js
import React, { Component } from 'react';
import Paper from '#material-ui/core/Paper';
import Button from '#material-ui/core/Button';
import Typography from '#material-ui/core/Typography';
import moment from 'moment';
import Editable from './Editable';
import {connect} from 'react-redux';
import {UpdatePost, getLikeCount, postLike} from '../actions/';
import Like from './Like';
import Axios from '../Axios';
const Styles = {
myPaper: {
margin: '20px 0px',
padding: '20px'
},
button:{
marginRight:'30px'
}
}
class PostItem extends Component{
constructor(props){
super(props);
this.state = {
disabled: false,
}
}
onUpdate = (id, title) => () => {
// we need the id so expres knows what post to update, and the title being that only editing the title.
if(this.props.myTitle !== null){
const creds = {
id, title
}
this.props.UpdatePost(creds);
}
}
render(){
const {title, id, userId, removePost, createdAt, post_content, username, editForm, isEditing, editChange, myTitle, postUpdate, likes, clickLike} = this.props
return(
<div>
<Typography variant="h6" component="h3">
{/* if else teneray operator */}
{isEditing ? (
<Editable editField={myTitle ? myTitle : title} editChange={editChange}/>
): (
<div>
{title}
</div>
)}
</Typography>
<Typography component="p">
{post_content}
<h5>
by: {username}</h5>
<Typography color="textSecondary">{moment(createdAt).calendar()}</Typography>
<Like like={id}/>
</Typography>
{!isEditing ? (
<Button variant="outlined" type="submit" onClick={editForm(id)}>
Edit
</Button>
):(
// pass id, and myTitle which as we remember myTitle is the new value when updating the title
<div>
<Button
disabled={myTitle.length <= 3}
variant="outlined"
onClick={this.onUpdate(id, myTitle)}>
Update
</Button>
<Button
variant="outlined"
style={{marginLeft: '0.7%'}}
onClick={editForm(null)}>
Close
</Button>
</div>
)}
{!isEditing && (
<Button
style={{marginLeft: '0.7%'}}
variant="outlined"
color="primary"
type="submit"
onClick={removePost(id)}>
Remove
</Button>
)}
</div>
)
}
}
const mapStateToProps = (state) => ({
isEditingId: state.post.isEditingId,
})
const mapDispatchToProps = (dispatch) => ({
// pass creds which can be called anything, but i just call it credentials but it should be called something more
// specific.
UpdatePost: (creds) => dispatch(UpdatePost(creds)),
getLikeCount: (id) => dispatch(getLikeCount(id)),
postLike: (id) => dispatch( postLike(id))
// Pass id to the DeletePost functions.
});
export default connect(null, mapDispatchToProps)(PostItem);
PostList.js
import React, { Component } from 'react';
import Paper from '#material-ui/core/Paper';
import Button from '#material-ui/core/Button';
import Typography from '#material-ui/core/Typography';
import moment from 'moment';
import {connect} from 'react-redux';
import {DeletePost, getLikeCount, postLike, UpdatePost,EditChange, DisableButton} from '../actions/';
import PostItem from './PostItem';
const Styles = {
myPaper: {
margin: '20px 0px',
padding: '20px'
}
}
class PostList extends Component{
constructor(props){
super(props);
this.state ={
title: '',
}
}
// Return a new function. Otherwise the DeletePost action will be dispatch each
// time the Component rerenders.
removePost = (id) => () => {
this.props.DeletePost(id);
}
onChange = (e) => {
e.preventDefault();
this.setState({
title: e.target.value
})
}
formEditing = (id) => ()=> {;
this.props.EditChange(id);
}
getLikes = (id) => {
// console.log(id);
this.props.getLikeCount(id)
console.log(this.props.likeCount)
}
render(){
const {posts} = this.props;
return (
<div>
{posts.map((post, i) => (
<Paper key={post.id} style={Styles.myPaper}>
{this.getLikes(post.id)}
{/* {...post} prevents us from writing all of the properties out */}
<PostItem
myTitle={this.state.title}
editChange={this.onChange}
editForm={this.formEditing}
isEditing={this.props.isEditingId === post.id}
removePost={this.removePost}
{...post}
/>
</Paper>
))}
</div>
)
}
}
const mapStateToProps = (state) => ({
isEditingId: state.post.isEditingId,
})
const mapDispatchToProps = (dispatch) => ({
// pass creds which can be called anything, but i just call it credentials but it should be called something more
// specific.
EditChange: (id) => dispatch(EditChange(id)),
UpdatePost: (creds) => dispatch(UpdatePost(creds)),
getLikeCount: (id) => dispatch(getLikeCount(id)),
postLike: (id) => dispatch( postLike(id)),
// Pass id to the DeletePost functions.
DeletePost: (id) => dispatch(DeletePost(id))
});
export default connect(mapStateToProps, mapDispatchToProps)(PostList);
Edit
Actions.js
export const getLikeCount = (id) => {
return (dispatch, getState) => {
return Axios.get(`/api/posts/likes/count/${id}`)
.then( (res) => {
const data = res.data
console.log(data); // logs data and i can see an array
dispatch({type: GET_LIKES_COUNT, data})
})
}
}
{this.props.likeCount} logs
ive tried doing this
case ADD_LIKE:
return {
...state,
posts: state.posts.map(post => {
if (post.id === action.id) {
return {
...post,
likes: post.likes + 1
}
} else return post
})
};
but it does not update the like count for some reason.
I'm not sure how your code works, but I assume Like component is like a love icon for each post? But from your code, seems like all the Like components are sharing the same redux state post.likes
const mapStateToProps = (state) => ({
isEditingId: state.post.isEditingId,
likeCount:state.post.likes
})
That is why all the Like share the same likes count. I will suggest to restructure your redux state, such as putting the likes count into the post object, for example:
Reducer
import { ADD_LIKE, GET_LIKES_COUNT} from '../actions/';
const initialState = {
post: [],
postError: null,
posts:[], // put likes count in post item, for eg { id: number, likes: number }
isEditing:false,
isEditingId:null,
postId:null
}
export default (state = initialState, action) => {
switch (action.type) {
case ADD_LIKE:
return({
...state,
posts: state.posts.map(post => (post.id === action.id ? { ...post, likes: post.likes + 1 } : post))
})
default:
return state
}
}
Then instead of use Redux in Like component, just passed the data from PostItem will be more easy to maintain:
PostItem
render() {
return (
....
<Like likeCount={post.likes} onClick={() => this.onClick(post.id)} />
....
)
}
try this,
did you mean post.id instead of postId in your state. because you can not compare if(action.id === post.id) then do likes: state.likes + 1 like this because post is array.
if you wrongly used post.id instead of postId you can try below solution
export default (state = initialState, action) => {
switch (action.type) {
case GET_LIKES_COUNT:
// console.log(action.data)
return({
...state,
likes:action.data
})
case ADD_LIKE:
// console.log(action.id)
return({
...state,
likes: action.id === state.postId? state.likes + 1 : state.likes
})
default:
return state
}
}