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.
Related
I am new to react and self-taught, struggling with state and react-select
I have a dropdown with react-select. Depending on what value the user selects I want to display the relevant data onto the screen. The data is coming from the authContext(useContext). This is what I have written so far. But its not working. Can someone please guide me in the right direction:
import React, { useContext, useState } from 'react'
import styles from './FullRecord.module.css'
import {AuthContext} from '../../shared/context/auth-context'
import Select from 'react-select'
import { makeStyles } from '#material-ui/core/styles';
import Card from '#material-ui/core/Card';
import CardContent from '#material-ui/core/CardContent';
import Typography from '#material-ui/core/Typography';
const useStyles = makeStyles({
custom: {
backgroundColor: "#558d6b",
fontWeight: "bold"
},
customFont: {
fontWeight: "bold",
fontSize: "20px"
},
customFont1: {
fontWeight: "light"
}
});
const FullRecord = (props) => {
const auth = useContext(AuthContext)
const classes = useStyles();
const [selectedValue, setSelectedValue] = useState('');
const [tableValue, setTableValue] = useState(false)
let data
const options = auth.tournaments.map((tournament) => {
return {
value: tournament.TournamentName,
label: tournament.TournamentName,
}
})
const handleChange = (selectedValue) => {
setSelectedValue(selectedValue);
setTableValue(true)
const {value} = selectedValue
let tname
if(value === 'GSM Edition 1'){
const noOfMatches = auth.profile.MemberMatches.filter((match) => match.TournamentName === 'GSM Edition 1')
if(tableValue){
return (
<div>
<li className={styles['member-item']}>
<Card className={classes.custom} variant="outlined">
<CardContent>
<Typography className={classes.customFont} gutterBottom>
Number Of Matches Played
</Typography>
<Typography className={classes.customFont}>
{noOfMatches}
</Typography>
</CardContent>
</Card>
</li>
</div>
)
}
}
}
return (
<React.Fragment>
<div className={styles['fullrecord__maindiv']}>
<Select
onChange={handleChange}
options={options}
/>
</div>
</React.Fragment>
)
}
export default FullRecord
I would say that your first problem stems from using a function argument name that is the same as your state variable. Especially a problem as the function is also an arrow function.
const handleChange = (newValue) => {
setSelectedValue(newValue);
setTableValue(true);
}
You're also trying to return JSX from your event handler, where it seems like what you really want is the core content to change when your selection changes. Ultimately, that logic goes in to your FullRecord return statement, and will automatically update as state is updated.
const FullRecord = (props) => {
// ...bunch of stuff, then
return (
<React.Fragment> {/* really don't need here, as you only have one root element */}
<div className={styles['fullrecord__maindiv']}>
<Select
onChange={handleChange}
options={options}
/>
{selectedValue ? (
{/* output something here */}
) : null}
</div>
</React.Fragment>
)
}
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.
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)
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
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
/>