Disable button based onChange value in React - reactjs

How can i tell onChange to set the disable button to true on the update button, if this.state.title is less than 3 characters, i made an attempt but it wont let me enter a value. It breaks the code pretty much. I want the update to be passed to redux.
here is what i have, disclaimer some code has been removed to be more relevant.
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} from '../actions/';
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, removePost, createdAt, post_content, username, editForm, isEditing, editChange, myTitle, postUpdate} = this.props
return(
<div>
<Typography variant="h6" component="h3">
{/* if else teneray operator */}
{isEditing ? (
// need a way to disable update button if the value is less than 3 characters
<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>
</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
// how would i set disabled to false if the value from editField is less than 3 charaters.
<Button
disabled={this.state.title.length > 3 }
variant="outlined"
onClick={this.onUpdate(id, myTitle)}>
Update
</Button>
)}
<Button
style={{marginLeft: '0.7%'}}
variant="outlined"
color="primary"
type="submit"
onClick={removePost(id)}>
Remove
</Button>
</div>
)
}
}
const mapStateToProps = (state) => ({
disabled: state.post.disabled
})
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)),
// Pass id to the DeletePost functions.
});
export default connect(mapStateToProps, mapDispatchToProps)(PostItem);
editChange is being called from this onChange method
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, 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);
}
render(){
const {posts} = this.props;
return (
<div>
{posts.map((post, i) => (
<Paper key={post.id} style={Styles.myPaper}>
{/* {...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) => ({
// disabled: state.post.disabled,
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)),
// Pass id to the DeletePost functions.
DeletePost: (id) => dispatch(DeletePost(id))
});
export default connect(mapStateToProps, mapDispatchToProps)(PostList);
Action for this.props.DisableButton
export const DisableButton = () => {
return(dispatch) => {
dispatch({type:DISABLED});
}
}
Posts reducer for disable button, etc.
import { DISABLED} from '../actions/';
const initialState = {,
disabled: false,
isEditingId:null
}
export default (state = initialState, action) => {
switch (action.type) {
case DISABLED:
return({
...state,
disabled:true
})
default:
return state
}
}
editable component
import React from 'react';
import Paper from '#material-ui/core/Paper';
import Button from '#material-ui/core/Button';
import Typography from '#material-ui/core/Typography';
import TextField from '#material-ui/core/TextField';
const Editable = (props) => (
<div>
<TextField
id="outlined-name"
label="Title"
style={{width: 560}}
name="title"
value={props.editField}
onChange={props.editChange}
margin="normal"
required
variant="outlined"/>
</div>
)
export default Editable;

You don't need to call DisableButton method to disable its value.
Fix your onChange, perhaps roughly like this:
onChange = (e) => {
this.setState({ title: e.target.value });
}
yes, this is sufficient.
now you can use a logic to disable the Button component, like this:
<Button
disabled={this.state.title.length <= 3}
// rest of the attributes
/>
You don't need to use disabled props on redux. It's not necessary since you can just move the logic by using local state of title directly on Button component.
==== edit
you need to initialize this.state.title first
constructor(props) {
super(props);
this.state = {
title: '',
}
}
=== edit 2nd time after the questioner added some details
If this.state.title is on PostList (the parent component) component and your Button is accessing it through myTitle props since it's on PostItem component. You can do it like this:
<Button
disabled={this.props.myTitle.length <= 3}
// rest of the attributes
/>

Related

Cannot update during an existing state transition when fetching counts

I'm trying to fetch post likes, and pass it as a prop to redux.
So i can use it like
myLikes={this.props.myLikes} which renders Likes 6 or whatever the count number is
{this.getLikes(post.Likes)} // retrieves like counts
However i get this warning
Cannot update during an existing state transition (such as within
render). Render methods should be a pure function of props and
state.
My objective is to retrieve likes and map it back to its respected post.
What approach should i use, or am i somewhere in the ballpark ? Cause of now all im getting duplicate values like this
Duplicate values shown here
here is the code
PostList.js
getLikes = (count) => {
this.props.getCount(count)
}
render(){
const {posts} = this.props;
return (
<div>
{posts.map((post, i) => (
<Paper key={post.id} style={Styles.myPaper}>
{this.getLikes(post.Likes)}
{/* {...post} prevents us from writing all of the properties out */}
<PostItem
myLikes={this.props.myLikes}
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,
myLikes: state.post.likes
})
export default connect(mapStateToProps, mapDispatchToProps)(PostList);
Actions.js
export const getCount = (count) => {
return (dispatch) => {
dispatch({type: GET_LIKES_COUNT, count})
}
}
export const GetPosts = () => {
return (dispatch, getState) => {
return Axios.get('/api/posts/myPosts')
.then( (res) => {
const data = res.data
dispatch({type: GET_POSTS, data})
})
}
}
reducer
const initialState = {
post: [],
postError: null,
posts:[],
isEditing:false,
isEditingId:null,
likes:[],
someLike:[],
postId:null
}
export default (state = initialState, action) => {
switch (action.type) {
case GET_POSTS:
return {
...state,
posts: action.data, // maps posts fine
}
case GET_LIKES_COUNT:
console.log(action.count)
return({
...state,
likes: action.count.length
})
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,
}
async componentWillMount(){
await this.props.GetPosts();
const thesePosts = await this.props.myPosts
const myPosts2 = await thesePosts
this.setState({
posts: myPosts2,
loading:false
})
console.log(this.state.posts.Likes);
}
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));
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, postLike, getCount} 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,
myId: 0,
likes:0
}
}
componentWillMount(){
}
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, myLikes} = 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={'span'} variant={'body2'}>
{post_content}
<h5>by: {username} </h5>
{/* component span cancels out the cant be a decedent of error */}
<Typography component={'span'} variant={'body2'} color="textSecondary">{moment(createdAt).calendar()}</Typography>
{/* gets like counts */}
<Like like={id} likes={myLikes} />
</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,
// myLikes: 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.
UpdatePost: (creds) => dispatch(UpdatePost(creds)),
postLike: (id) => dispatch( postLike(id)),
// Pass id to the DeletePost functions.
});
export default connect(mapStateToProps, mapDispatchToProps)(PostItem);
Value of action.count
values: 6
values: 0
Your problem is that you're changing the state inside your render method.
{this.getLikes(post.Likes)}
This piece of code leads to an action inside your reducer which then updates the state. That is what redux is all about.
But since react does not allow to change the state while rendering you get your error message. It would eventually lead to an infinite loop when a new state comes up, render didn't finish yet and the new state triggers render again.
What you want to do is put this.state.likes at the place of this.getLikes(post.Likes) and call the latter inside componentDidMount. That way render can finish without bein interrupted. On the first call of render you have to make sure that this.state.likes has a valid value (e.g. null) and on the second render this.state.likes will have the value of the action you trigger with this.getLikes(post.Likes).
For illustration:
render (with this.state.likes = null)
componentDidMount gets called
redux action
this.state.likes changes
render (with this.state.likes = 6)
Read the docs for detailed information about the lifecycle methods.

How to get count from nested array in react

I'm trying to fetch like counts from the nested array(Likes) within the posts object array.
I'm trying to do
<Like like={id} likes={Likes.data.count} />
but getting this error.
TypeError: Cannot read property 'count' of undefined
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,
myId: 0,
likes:0
}
}
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);
}
}
getLikes = (id) => {
// console.log(id);
this.props.getLikeCount(id)
const myLike = this.props.likeCount
const like = this.props.likeCount
}
render(){
const {title, id, userId, removePost, createdAt, post_content, username, editForm, isEditing, editChange, myTitle, postUpdate, Likes, clickLike} = this.props
return(
<div>
{/* {this.getLikes(id)} */}
<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} likes={Likes.data.count} />
</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,
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.
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}>
{/* {...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);
backend
router.get('/myPosts', async (req, res) =>{
await models.Post.findAll({
include:[{
model:models.Likes
}],
order:[
['createdAt', 'DESC'],
], limit: 6 })
.then( (posts) =>{
res.json(posts);
})
});
Likes is an Array so you can get the length by doing Likes.length. Or if you want the total of true like, you can do Likes.reduce((count, ({ like })) => like ? count + 1 : count), 0)

How would i see updated props without refreshing

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
};
}
}

Not able to enter value on input field onChange prop

I'm unable to input a value on the input field when selecting edit, i think it could be a onChange issue.
I look at something similar here, but the code seems to be outdated, and im using controlled components and not refs.
Editable.js this component renders an input field when edit is clicked
import React from 'react';
import Paper from '#material-ui/core/Paper';
import Button from '#material-ui/core/Button';
import Typography from '#material-ui/core/Typography';
import TextField from '#material-ui/core/TextField';
const Editable = (props) => (
<div>
<TextField
id="outlined-name"
label="Title"
style={{width: 560}}
name="title"
value={props.editField}
onChange={props.onChange}
margin="normal"
variant="outlined"/>
</div>
)
export default Editable;
PostList.js renders a list of the post items
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} from '../actions/';
import Editable from './Editable';
const Styles = {
myPaper: {
margin: '20px 0px',
padding: '20px'
}
}
class PostList extends Component{
constructor(props){
super(props);
this.state ={
}
}
// 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();
// maybe their is issue with it calling title from name in the editable
// component
this.setState({
[e.target.title]: e.target.value
})
}
render(){
const {posts, editForm, isEditing} = this.props;
return (
<div>
{posts.map((post, i) => (
<Paper key={i} style={Styles.myPaper}>
<Typography variant="h6" component="h3">
{/* if else teneray operator */}
{isEditing ? (
<Editable editField={post.title} onChange={this.onChange}/>
): (
<div>
{post.title}
</div>
)}
</Typography>
<Typography component="p">
{post.post_content}
<h5>
by: {post.username}</h5>
<Typography color="textSecondary">{moment(post.createdAt).calendar()}</Typography>
</Typography>
{!isEditing ? (
<Button variant="outlined" type="submit" onClick={editForm}>
Edit
</Button>
):(
<Button variant="outlined" type="submit" onClick={editForm}>
Update
</Button>
)}
<Button
variant="outlined"
color="primary"
type="submit"
onClick={this.removePost(post.id)}>
Remove
</Button>
</Paper>
))}
</div>
)
}
}
const mapDispatchToProps = (dispatch) => ({
// Pass id to the DeletePost functions.
DeletePost: (id) => dispatch(DeletePost(id))
});
export default connect(null, 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 = {
posts: [],
loading: true,
isEditing: false,
}
async componentWillMount(){
await this.props.GetPosts();
this.setState({ loading: false })
const reduxPosts = this.props.myPosts;
const ourPosts = reduxPosts
console.log(reduxPosts); // shows posts line 35
}
formEditing = () => {
if(this.state.isEditing){
this.setState({
isEditing: false
});
}
else{
this.setState({
isEditing:true
})
}
}
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 isEditing={this.state.isEditing} editForm={this.formEditing} posts={myPosts}/>
</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));
You're setting the value of the input with the property this.props.posts[index].title but you're handling the change through the PostLists state.
You should either delegate the onChange function to the component that's passing the list to your PostList component or store and update the list through the PostLists state.
you need to pass the value being set in change function
onChange = (e) => {
e.preventDefault();
// maybe their is issue with it calling title from name in the editable
// component
this.setState({
[e.target.title]: e.target.value
})
}
youre setting the state of your edit field. You have to reference that value again when you reference your Editable.
<Editable editField={this.state.[here should be whatever e.target.title was for editable change event] } onChange={this.onChange}/>
in your editable component your setting the value to the prop editField.
<TextField
id="outlined-name"
label="Title"
style={{width: 560}}
name="title"
**value={props.editField}**
onChange={props.onChange}
margin="normal"
variant="outlined"/>
hope that helps

React updates state to all items that are mapped

React should be able to edit one item within the mapped array, however when selecting edit. It edits all of the items within the array. How would i be able to fix this so that i can edit for that specific post.id
For example
I'm not really sure how to tell react to edit a specific item, that is equivalent to the id.
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} from '../actions/';
import PostItem from './PostItem';
const Styles = {
myPaper: {
margin: '20px 0px',
padding: '20px'
}
}
class PostList extends Component{
constructor(props){
super(props);
this.state ={
isEditing:false
}
}
// 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({
[e.target.title]: e.target.value
})
}
formEditing = (id) => ()=> {
if(this.state.isEditing){
this.setState({
isEditing: false
});
}
else{
this.setState({
isEditing:true
})
}
}
render(){
const {posts, editForm, isEditing, editChange} = this.props;
return (
<div>
{posts.map((post, i) => (
<Paper key={post.id} style={Styles.myPaper}>
<PostItem editForm={this.formEditing} isEditing={this.state.isEditing} removePost={this.removePost} {...post} />
</Paper>
))}
</div>
)
}
}
const mapDispatchToProps = (dispatch) => ({
// Pass id to the DeletePost functions.
DeletePost: (id) => dispatch(DeletePost(id))
});
export default connect(null, mapDispatchToProps)(PostList);
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';
const Styles = {
myPaper: {
margin: '20px 0px',
padding: '20px'
}
}
const PostItem = ({ title, id, removePost, createdAt, post_content, username, editForm, isEditing, editChange}) => {
return(
<div>
<Typography variant="h6" component="h3">
{/* if else teneray operator */}
{/* when edit is clicked all items reveal an input field when it should just be the item that has */}
{isEditing ? (
<Editable editField={title} onChange={editChange}/>
): (
<div>
{title}
</div>
)}
</Typography>
<Typography component="p">
{post_content}
<h5>
by: {username}</h5>
<Typography color="textSecondary">{moment(createdAt).calendar()}</Typography>
</Typography>
{!isEditing ? (
<Button variant="outlined" type="submit" onClick={editForm(id)}>
Edit
</Button>
):(
<Button variant="outlined" type="submit" onClick={editForm(id)}>
Update
</Button>
)}
<Button
variant="outlined"
color="primary"
type="submit"
onClick={removePost(id)}>
Remove
</Button>
</div>
)
}
export default PostItem;
In PostList.js:
First setting your initial state to { isEditingId: null }.
You're not editing any post ids now :)
Change formEditing to something like this:
formEditing = (id) => ()=> {
this.setState({
isEditingId: id
});
}
This will always store a post's id to the isEditingId property (yes, I had to change isEditing to isEditingId to make it make sense)
When you're creating your PostItem mapping, instead of using isEditing={this.state.isEditing}, now check if post.id is equal to the post id stored in isEditingId with this: isEditing={this.state.isEditingId === post.id}.
This will make sure isEditing is still passed to PostItem.js as a boolean, indicating whether that post is being edited or not.
In PostItem.js:
You're already passing the post's id in editForm back to PostList when you click the "Edit" button, which is great.
Now change your onClick handler of your "Update" button to onClick={editForm(null)}.
With this change, you should now have isEditing equal to null in PostList.js, so you should see no Editable elements.
Hope this works for ya :) It was a bit hard to test since you didn't include your Editable file, but hopefully the explanation above helps at least get you on the right track.

Resources