How to manage state in a signup flow (ReactJS) - reactjs

So, I'm building a signup and payment flow using React/NextJS, AWS Cognito, and Stripe. I have created a class component to manage state and the overall flow with child components handling the individual steps. The problem I am having is managing the state so that each component can access the state and make changes to it as well as use functions that are in the parent component and not within the child components. Keep note that the child components are functional and not classes. Here is the flow step by step:
Parent Component
import React, { Component } from "react";
import Layout from "../../components/layouts/mainlayout/mainlayout";
//import { handleSubmit } from "../../components/auth/awshelper";
import { Auth } from "aws-amplify";
import FlowStep1 from "../../components/auth/clubflow/step1";
import FlowStep2 from "../../components/auth/clubflow/step2";
import FlowStep3 from "../../components/auth/clubflow/step3";
//import { Router } from "next/router";
const meta = {
title: "Join the club!",
description: "Sign up and enjoy the best travel experiences ever."
};
class ClubFlow extends Component {
state = {
firstname: "",
lastname: "",
username: "",
phonenumber: "",
password: "",
confirmpassword: "",
step: 1,
go: false,
plan: null,
member: false,
errors: {
cognito: null,
blankfield: false,
passwordmatch: false
}
};
/*Unnecessary functions:
getStripeFunc = func => {
return func;
};
*/
clearErrorState = () => {
this.setState({
errors: {
cognito: null,
blankfield: false,
passwordmatch: false
}
});
};
choosePlan = e => {
setState({ plan: e.target.value });
};
onInputChange = event => {
//const [firstname, setFirstname] = useState("");
this.setState({
[event.target.id]: event.target.value
});
document.getElementById(event.target.id).classList.remove("is-danger");
};
handleSubmit = async event => {
event.preventDefault();
// Form validation
this.clearErrorState();
const error = null; /*Validate(event, this.state)*/
if (error) {
console.log("an validation erreor was fired");
this.setState({
errors: { ...this.state.errors, ...error }
});
}
// AWS Cognito integration here
const { firstname, lastname, phonenumber, username, password } = this.state;
try {
const signUpResponse = await Auth.signUp({
username,
password,
attributes: {
name: firstname,
family_name: lastname,
phone_number: phonenumber
}
});
if (signUpResponse) {
this.setState({ step: 2, go: true });
}
} catch (error) {
let err = null;
!error.message ? (err = { message: error }) : (err = error);
this.setState({
errors: { ...this.state.errors, cognito: error }
});
}
};
//This function sets which plan the user selects. Eventually this value will be passed down to the checkout form as a prop.
goNext = async () => {
try {
if (this.state.step === 1) {
this.handleSubmit(event);
if (
this.state.step <
3 /*&&
this
.signUpResponse /*Remove the previous comment in order for the next button to work correctly.*/
) {
this.setState(state => ({
step: state.step + 1
}));
}
} else if (this.state.step === 2) {
this.setState(state => ({
step: state.step + 1
}));
} else {
this.getStripeFunc();
}
} catch (error) {
console.log("You have a problem");
}
};
goBack = () => {
if (this.state.step > 1) {
this.setState(state => ({
step: state.step - 1
}));
}
};
render() {
let stage;
if (this.state.step === 1) {
stage = (
<FlowStep1 state={this.state} onInputChange={this.onInputChange} />
);
} else if (this.state.step === 2) {
stage = <FlowStep2 state={this.state} choosePlan={this.choosePlan} />;
} else {
stage = <FlowStep3 state={this.state} />;
}
//console.log(this.state.plan);
return (
<Layout class={"ovh"} meta={meta}>
A user is able to signup and creates an account by entering their name, email, phone, and password.
import FormErrors from "../FormErrors";
import { Auth } from "aws-amplify";
import React, { Component } from "react";
/*import {
backspacerUP,
backspacerDOWN
} from "../../utilities/phonenumbervalidator";
const onKeyUp = backspacerUP;
const onKeyDown = backspacerDOWN;
Might need to use a plugin: https://www.npmjs.com/package/react-phone-input-auto-format*/
class FlowStep1 extends Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
return (
<section className="section auth">
<div className="container">
<h1>Register</h1>
<FormErrors /*formerrors={state.errors}*/ />
A user is able to choose a plan which needs to pass the plan that the user chooses onto step 3:
import React, { useState } from "react";
const FlowStep2 = (props) => {
const plan
const [plan, setPlan] = useState(0);
return (
<div className="mb-4">
<h2>this is what is in state:{plan}</h2>
<input
type="radio"
name="plan"
value="1"
readOnly
//checked={this.state.plan === 1}
onChange={() => setPlan(1)}
/>
<input
type="radio"
name="plan"
value="2"
readOnly
onChange={() => setPlan(2)}
className="ml-3"
/>
<input
type="radio"
name="plan"
value="3"
//checked={true}
onChange={() => setPlan(3)}
className="ml-3"
/>
</div>
);
};
export default FlowStep2;
A user is able to enter their credit card info and submit it. Once the charge is okay'd by stripe and charged based on the plan the user choosed in step two they will get a confirmation email and will be navigated to the welcome page. The selected plan will need to be eventually passed on to an express server that will handle the charge and pass it on to stripe for processing charging based on what plan they selected.
import ClubPayWrap from "../../payments/clubpaywrap";
const FlowStep3 = props => {
return (
<div>
<h2>Payment</h2>
<ClubPayWrap flowstate={props.state} />
</div>
);
};
export default FlowStep3;
Please, let me know if I need to give more detail.

For managing states in react based application there are different approaches. You need to store the logged in user data in a global store in which you can access in different routes and components.
The most used store manager to react is Redux.In the latest version, it provided some hooks. The redux hooks made it so easy to access to your store.
The next one is mobX. I personally haven't used it yet and I can not help you with that!
The other one is Context Api which is provided by react itself. React added some hooks to make it easy to use.
There is another technology provided for managing states in React called Rxjs which is a little bit different and maybe is not good for your case.
In the end I think number one is the perfect solution for you and then number 3.

Related

FormValidation with Button State

In my React app, I have a component call <ComingSoonForm/> Inside that, I have a TextInputField. If the Email valid, the Notify-Button is able. If the Email is invalid, the Notify-Button is disabled.
Here is my Component File:
import React, { Component } from "react";
import { TextInputField, toaster, Button, } from "evergreen-ui";
import Box from 'ui-box';
import { validateEmail } from "../FormValidation/FormValidator";
class ComingSoonForm extends Component {
constructor(props) {
super(props);
this.state = {
emailErr: {
status: true,
value: ""
},
email: "",
isDisabled: true,
};
this.handleSubmit = this.handleSubmit.bind(this);
this.checkFormStatus = this.checkFormStatus.bind(this);
}
handleEmailInput = e => {
const email = e.target.value;
this.setState({ email: email});
console.log(this.state.email);
this.checkFormStatus();
};
handleSubmit() {
if (this.checkFormStatus()) {
alert("Form is OK")
}
}
checkFormStatus() {
// form validation middleware
const { email } = this.state;
const emailErr = validateEmail(email);
if (!emailErr.status) {
this.setState({isDisabled:false})
return true;
} else {
this.setState({
emailErr,
});
return false;
}
}
render() {
return (
<div>
<Box className="welcomePageWelcomeInnerLoginButton">
<TextInputField
marginTop={15}
width={200}
onChange={this.handleEmailInput}
value={this.state.email}
type="email"
placeholder="Your email-address"
inputHeight={40}
/>
</Box>
<Button height="40" appearance="primary" marginBottom={5} className="welcomePageWelcomeInnerLoginButtonWidth" disabled={this.state.isDisabled} onClick={this.handleSubmit}>Notify Me</Button>
</div>
);
}
}
export default ComingSoonForm;
But this case doesn't work correctly. So when the command console.log(this.state.email) in the handleEmailInput Function run, I get the following data in the console:
I type one letter (t) and I get:
//ComingSoonForm.js:25
I type a second letter (t) and I get:
t //ComingSoonForm.js:25
t //FormValidator.js:10
Why do I have to enter two letters in order for one to appear in the console?
setState is asynchronous, you can pass a callback method as a second parameter like this:
handleEmailInput = e => {
const email = e.target.value;
this.setState({ email: email }, () => console.log(this.state.email));
this.checkFormStatus();
};

How to check form is valid or not in react + material?

Is there any way to know that form is valid or not in react + material ui .I am using react material in my demo .I have three field in my form all are required . I want to check on submit button that form is valid or not
Here is my code
https://codesandbox.io/s/w7w68vpjj7
I don't want to use any plugin
submitButtonHandler = () => {
console.log("error");
console.log(this.state.form);
};
render() {
const { classes } = this.props,
{ form } = this.state;
return (
<div className={classes.searchUser__block}>
<SearchForm
handleInput={this.handleInputFieldChange}
submitClick={this.submitButtonHandler}
form={form}
/>
</div>
);
}
You would have to manually do that verification if you don't want to use any library. Material-ui does not have any validation built in as per their documentation. BUT it does give you some tools for that like errorMessage to text fields for example. You just have to play with it
Example:
class PhoneField extends Component
constructor(props) {
super(props)
this.state = { errorText: '', value: props.value }
}
onChange(event) {
if (event.target.value.match(phoneRegex)) {
this.setState({ errorText: '' })
} else {
this.setState({ errorText: 'Invalid format: ###-###-####' })
}
}
render() {
return (
<TextField hintText="Phone"
floatingLabelText="Phone"
name="phone"
errorText= {this.state.errorText}
onChange={this.onChange.bind(this)}
/>
)
}
}
a bit outdated example i had laying around
Form validation can be pretty complex, so I'm pretty sure you'll end up using a library. As for now, to answer your question, we need to think about form submission flow. Here is a simple example:
"Pre-submit"
Set isSubmitting to true
Proceed to "Validation"
"Validation"
Run all field-level validations using validationRules
Are there any errors?
Yes: Abort submission. Set errors, set isSubmitting to false
No: Proceed to "Submission"
"Submission"
Proceed with running your submission handler (i.e.onSubmit or handleSubmit)
Set isSubmitting to false
And some minimal implementation would be something like:
// ...imports
import validateForm from "../helpers/validateForm";
import styles from "./styles";
import validationRules from "./validationRules";
const propTypes = {
onSubmit: PropTypes.func.isRequired,
onSubmitError: PropTypes.func.isRequired,
initialValues: PropTypes.shape({
searchValue: PropTypes.string,
circle: PropTypes.string,
searchCriteria: PropTypes.string
})
};
const defaultProps = {
initialValues: {}
};
class SearchForm extends Component {
constructor(props) {
super(props);
this.validateForm = validateForm.bind(this);
this.state = {
isSubmitting: false,
values: {
searchValue: props.initialValues.searchValue || "",
circle: props.initialValues.circle || "",
searchCriteria: props.initialValues.searchCriteria || ""
},
...this.initialErrorState
};
}
get hasErrors() {
return !!(
this.state.searchValueError ||
this.state.circleError ||
this.state.searchCriteriaError
);
}
get initialErrorState() {
return {
searchValueError: null,
circleError: null,
searchCriteriaError: null
};
}
handleBeforeSubmit = () => {
this.validate(this.onValidationSuccess);
};
validate = (onSuccess = () => {}) => {
this.clearErrors();
this.validateForm(validationRules)
.then(onSuccess)
.catch(this.onValidationError);
};
onValidationSuccess = () => {
this.setState({ isSubmitting: true });
this.props
.onSubmit(this.state.values)
.catch(this.props.onSubmitError)
.finally(() => this.setState({ isSubmitting: false }));
};
onValidationError = errors => {
this.setState({ ...errors });
};
clearErrors = () => {
this.setState({ ...this.initialErrorState });
};
updateFormValue = fieldName => event => {
this.setState(
{
values: { ...this.state.values, [fieldName]: event.target.value }
},
() => this.validate()
);
};
render() {
// ...
}
}
SearchForm.propTypes = propTypes;
SearchForm.defaultProps = defaultProps;
export default withStyles(styles)(SearchForm);
As you can see, if submission flow will grow larger (for example touching inputs, passing errors, etc), the of amount of complexity inside of a component will significantly grow as well. That is why it's more preferable to use a well-maintained library of choice. Formik is my personal preference at the moment.
Feel free to check out updated codesandbox. Hope it helps.
Hi Joy I've made desirable form validation if required fields are empty.
Here is the updated codesandbox: https://codesandbox.io/s/50kpk7ovz4

Correct approach for using flux and component lifecycle

I'm migrating the code from what I see on here on CodePen.
Within IssueBox, I am planning to implement a form which an enduser will update setting a state from 'unverified' to 'verified'.
App (ill rename this component) will be my parent and IssueBox would be the child.
So I got through flux => Action -> dispatcher -> udpate db -> update view.
Now that I have the new state and the view should be updated, do I use componentWillRecieveProps() and then setState there, so that in IssueBox I can continue using this.props thus in turn updating it.
import React, { Component } from "react";
import IssueBox from "./issuebox.js";
import "./App.css";
class App extends Component {
constructor(props) {
super(props);
this.state = {
isLoaded: false,
email: [],
counter: 0,
title: "Test run"
};
}
componentDidMount() {
fetch(
"https://s3-us-west-2.amazonaws.com/s.cdpn.io/311743/dummy-emails.json"
)
.then(res => res.json())
.then(result => {
const emails = result.data;
console.log("resutl state: ", emails);
let id = 0;
for (const email of emails) {
email.id = id++;
email.verified = 'False'
}
this.setState({
isLoaded: true,
emails: emails
});
});
}
render() {
//console.log(this.state.email);
return (
<div className="App">
<div>
<IssueBox emails={this.state.email} />
</div>
</div>
);
}
}
//issuebox.js
import React, { Component } from "react";
class IssueBox extends Component {
constructor(args) {
super(args);
const emails = this.props.emails;
console.log("inner props: ", emails);
let id = 0;
for (const email of emails) {
email.id = id++;
}
this.state = {
selectedEmailId: 0,
currentSection: "inbox",
emails
};
}
//...copy and pase from codepen
setSidebarSection(section) {
let selectedEmailId = this.state.selectedEmailId;
if (section !== this.state.currentSection) {
selectedEmailId = "";
}
this.setState({
currentSection: section,
selectedEmailId
});
}
componentWillReceiveProps(newProps) {
// Assign unique IDs to the emails
this.setState({ emails: newProps.data });
}
render() {
const currentEmail = this.state.emails.find(
x => x.id === this.state.selectedEmailId
);
return (
<div>
<Sidebar
emails={this.props.emails}
setSidebarSection={section => {
this.setSidebarSection(section);
}}
/>
)}
///.....copy and pase from codepen
The error is being caused by this line in componentWillReceiveProps():
this.setState({ emails: newProps.data });
The emails are coming in on a property called emails so that line should be:
this.setState({ emails: newProps.emails });
That being said, componentWillReceiveProps() gets called more frequently than you might expect. I recommend that you add the id's to the emails within componentDidMount() of App so they come into IssueBox ready to use. This means that App is keeping the emails in its state and simply passing them to IssueBox as props, so you can remove emails from the state in IssueBox and just use the emails that come in through the props everywhere within IssueBox (similar to how the other components use emails coming in on their props and don't keep them in their own local state).

React: this.setState() working in some functions, not in others within the same component

I am trying to develop a simple little survey component with its own local state. I've already successfully used this.setState() in my handleChange function to update which radio button is selected. I can see that it's working in console logs and on the live page. However, I am now trying to use the state to display a modal and highlight incomplete questions if the user tries to submit the survey without completing it. The functions I have to do this execute, but the state does not update. I am having trouble seeing any difference between where I updated state successfully and where I didn't.
Here's my container where I'm controlling state and defining the functions. I don't think any other code is relevant, but let me know if I need to include something else:
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { Redirect } from 'react-router'
import { history } from '../history'
import { connect } from 'react-redux'
import _ from 'lodash'
import { postPhq, sumPhq } from '../store'
import { Phq } from '.'
import { UnfinishedModal } from '.'
/**
* CONTAINER
*/
class PhqContainer extends Component {
constructor(props) {
super(props)
this.state = {
phq: {
q1: false, q2: false, q3: false, q4: false, q5: false,
q6: false, q7: false, q8: false, q9: false, q10: false
},
hilitRows: [],
modalOpen: false
}
this.handleChange = this.handleChange.bind(this)
this.handleSubmit = this.handleSubmit.bind(this)
this.makeButtons = this.makeButtons.bind(this)
this.toggleModal = this.toggleModal.bind(this)
}
//handles clicks on radio buttons
handleChange(question, event) {
var newPhq = this.state.phq
newPhq[question] = event.target.value
this.setState({phq: newPhq})
console.log("radio button", this.state)
}
// on submit, first check that PHQ-9 is entirely completed
// then score the PHQ-9 and dispatch the result to the store
// as well as posting the results to the database and
// dispatching them to the store
// if the PHQ-9 is not complete, open a modal to alert the user
handleSubmit(event) {
event.preventDefault()
if (this.checkCompletion()) {
this.props.sumPhq(this.sum())
this.props.postPhq(this.state.phq)
this.props.history.push('/results')
} else {
console.log(this.unfinishedRows())
this.toggleModal()
this.setState({hilitRows: this.unfinishedRows()})
}
}
// returns true if user has completed the PHQ-9 with no missing values
checkCompletion() {
return !this.unfinishedRows().length || this.oneToNine().every(val => val === 0)
}
unfinishedRows() {
return Object.keys(_.pickBy(this.state.phq, (val) => val === false))
}
oneToNine() {
var qs1to9 = Object.assign({}, this.state.phq)
delete qs1to9.q10
return Object.values(qs1to9)
}
toggleModal() {
console.log("I'm about to toggle the modal", this.state)
this.setState({modalOpen: !this.state.modalOpen})
console.log("I toggled the modal", this.state)
}
// scores the PHQ-9
sum() {
return this.oneToNine
.map(val => parseInt(val))
.reduce((total, num) => total + num)
}
makeButtons = (num, labels) => {
var question = 'q' + (num + 1).toString()
return (
<div style={rowStyle}>
{labels.map((label, i) => {
return (
<td style={{border: 'none'}}>
<div className="col radio center-text" >
<input type="radio"
value={i}
onChange={(event) => {this.handleChange(question, event)}}
checked={this.state.phq[question] && parseInt(this.state.phq[question]) == i} />
<label>{label}</label>
</div>
</td>
)
})}
</div>
)
}
render() {
return (
<div>
<Phq {...this.state} {...this.props}
handleChange={this.handleChange}
handleSubmit={this.handleSubmit}
makeButtons={this.makeButtons} />
<UnfinishedModal show={this.state.modalOpen} toggleModal={this.toggleModal} />
</div>
)
}
}
const mapDispatch = (dispatch) => {
return {
postPhq: (qs) => {
dispatch(postPhq(qs))
},
sumPhq: (total) => {
dispatch(sumPhq(total))
}
}
}
export default connect(() => { return {} }, mapDispatch)(PhqContainer)
const rowStyle = {
paddingRight: 15,
borderWidth: 0,
margin: 0,
border: 'none',
borderTop: 0
}
This line this.setState({phq: newPhq}) updates the state. This line this.setState({hilitRows: this.unfinishedRows()}) and this line this.setState({modalOpen: !this.state.modalOpen}) do not.
I had the same issue happen in my project like setState is not working in some functions but its perfect for other functions in the same component. First of all you should understand, As mentioned in the React documentation, the setState being fired synchronously. So you can check like below value being updated.
this.setState({mykey: myValue}, () => {
console.log(this.state.mykey);
});
In my case, I was tried to update the value of an event onChange with an input, in every keyUp it's trying to update, Its causes the problem for me. So I have tried to used a debounce(delay) for the function so its working fine for me. Hope this hint will help you or someone else.
this.myFunction = debounce(this.myFunction,750);
<Input onChange={this.myFunction} />

React - Splitting large comment component into multiple components

I have a large Comment component which works great but is fairly lengthy. I have recently added a report button to the UI which toggles a reported state which should then change the output of the comment (removing everything and displaying a reported message). Trying to write the if statement in the return method of the component made me realise that I should be splitting this component up (not to mention that I have found myself copy/pasting a lot of code between this Comment component and the very similar Reply component).
The comment has 3 main 'views' - the default view, the reported view and the 'my comment' view.
Whenever I have tried to split up components in the past I have found myself getting bogged down in the passing of multiple props to each component. I'm not sure whether I'm doing it wrong or whether it is just something I need to get used to. Any tips on the best way of splitting this component up would be appreciated.
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { replyToCommentService, deleteCommentService, reportCommentService } from '../../../services/CommentService';
import { likeService, removeLikeService } from '../../../services/LikeService';
import Reply from './Reply';
import Avatar from '../Avatars/Avatar';
import IconWithText from '../Icons/IconWithText';
import CommentForm from './CommentForm';
import Dropdown from '../Dropdowns/Dropdown';
import DropdownSection from '../Dropdowns/DropdownSection';
export default class Comment extends Component {
constructor(props) {
super(props);
this.state = {
replies: this.props.replies,
showReply: false,
reply: '',
replyBtnDisabled: true,
liked: this.props.liked,
numberOfLikes: this.props.likes.length,
moreActionsActive: false,
reported: this.props.reported,
};
}
handleInput = (reply) => {
this.setState({ reply }, () => {
this.fieldComplete();
});
}
fieldComplete = () => {
if (this.state.reply.length) {
this.setState({ replyBtnDisabled: false });
} else {
this.setState({ replyBtnDisabled: true });
}
}
toggleReply = () => {
this.setState({ showReply: !this.state.showReply }, () => {
if (this.state.showReply === true) {
this.replyInput.focus();
}
});
}
postReply = () => {
const data = { comment_id: this.props.id, comment_content: this.state.reply };
replyToCommentService(data, this.postReplySuccess, this.error);
}
postReplySuccess = (res) => {
this.setState({ replies: this.state.replies.concat(res.data) });
this.toggleReply();
this.handleInput('');
}
error = (res) => {
console.log(res);
}
toggleLike = (e) => {
e.preventDefault();
const data = { model_id: this.props.id, model_type: 'comment' };
if (this.state.liked) {
removeLikeService(this.props.id, 'comment', this.removeLikeSuccess, this.error);
} else {
likeService(data, this.likeSuccess, this.error);
}
}
likeSuccess = () => {
this.toggleLikeState();
this.setState({ numberOfLikes: this.state.numberOfLikes += 1 });
}
removeLikeSuccess = () => {
this.toggleLikeState();
this.setState({ numberOfLikes: this.state.numberOfLikes -= 1 });
}
toggleLikeState = () => {
this.setState({ liked: !this.state.liked });
}
moreActionsClick = () => {
this.setState({ moreActionsActive: !this.state.moreActionsActive });
}
deleteReply = (replyId) => {
this.setState({ deletedReplyId: replyId });
deleteCommentService(replyId, this.deleteReplySuccess, this.error);
}
deleteReplySuccess = () => {
this.setState(prevState => ({ replies: prevState.replies.filter(reply => reply.id !== this.state.deletedReplyId) }));
}
ifEnterPressed = (e) => {
if (e.key === 'Enter') {
e.preventDefault();
this.postReply();
}
}
reportComment = () => {
const data = { model_id: this.props.id, model_type: 'comment' };
reportCommentService(data, this.reportCommentSuccess, this.error);
}
reportCommentSuccess = (res) => {
console.log(res);
}
render() {
let repliesList;
if (this.state.replies.length) {
repliesList = (this.state.replies.map((reply) => {
const { id, owner_id, content, owner_image_url, owner_full_name, ago, likes, liked } = reply;
return (
<Reply
key={id}
id={id}
authorId={owner_id}
title={content}
image={owner_image_url}
authorName={owner_full_name}
timeSinceComment={ago}
likes={likes}
liked={liked}
newComment={this.newCommentId}
deleteReply={this.deleteReply}
/>
);
}));
}
const commentClass = classNames('comment-container', {
'my-comment': this.props.myComment,
'comment-reported': this.state.reported,
});
let likeBtnText;
const numberOfLikes = this.state.numberOfLikes;
if (numberOfLikes > 0) {
likeBtnText = `${numberOfLikes} Likes`;
if (numberOfLikes === 1) {
likeBtnText = `${numberOfLikes} Like`;
}
} else {
likeBtnText = 'Like';
}
const likeBtnClass = classNames('like-btn', 'faux-btn', 'grey-link', 'h5', {
liked: this.state.liked,
});
let likeIconFill;
if (this.state.liked) {
likeIconFill = 'green';
} else {
likeIconFill = 'grey';
}
return (
<li className={commentClass}>
<div className="comment">
<Avatar image={this.props.image} />
<div className="body">
<div className="header">
{this.props.authorName}
<span className="h5 text-grey">{this.props.timeSinceComment}</span>
<Dropdown
size="S"
position="right"
onClick={this.moreActionsClick}
active={this.state.moreActionsActive}
handleClickOutside={this.moreActionsClick}
disableOnClickOutside={!this.state.moreActionsActive}
>
<DropdownSection>
{this.props.myComment &&
<button className="faux-btn dropdown-link" onClick={() => this.props.deleteComment(this.props.id)}>Delete comment</button>
}
</DropdownSection>
<DropdownSection>
<button className="faux-btn dropdown-link" onClick={() => this.reportComment(this.props.id)}>Report as inappropriate</button>
</DropdownSection>
</Dropdown>
</div>
<div className="comment-text"><p>{this.props.title}</p></div>
<div className="actions">
<button onClick={this.toggleLike} className={likeBtnClass}>
<IconWithText text={likeBtnText} iconName="thumb-up" iconSize="S" iconFill={likeIconFill} />
</button>
<button onClick={this.toggleReply} className="reply-btn faux-btn grey-link h5">
<IconWithText text="Reply" iconName="reply" iconSize="S" iconFill="grey" />
</button>
</div>
</div>
</div>
{this.state.replies.length > 0 &&
<div className="replies-container">
<ul className="replies-list faux-list no-margin-list">
{repliesList}
</ul>
</div>
}
{this.state.showReply &&
<div className="reply-to-comment-form">
<CommentForm
commentContent={this.handleInput}
postComment={(e) => { e.preventDefault(); this.postReply(); }}
formDisabled={this.state.replyBtnDisabled}
placeholder="Write a reply... press enter to submit"
btnText="Reply"
inputRef={(input) => { this.replyInput = input; }}
handleKeyPress={this.ifEnterPressed}
/>
</div>
}
</li>
);
}
}
Comment.propTypes = {
id: PropTypes.number,
authorId: PropTypes.number,
title: PropTypes.string,
image: PropTypes.string,
authorName: PropTypes.string,
timeSinceComment: PropTypes.string,
likes: PropTypes.array,
liked: PropTypes.bool,
replies: PropTypes.array,
myComment: PropTypes.bool,
deleteComment: PropTypes.func,
newCommentId: PropTypes.number,
reported: PropTypes.bool,
};
Well the general problem is where your state lives.
Currently you have your state in the component (and/or services), that makes splitting up the component somewhat tricky and not feeling so "natural".
The reason is that each natural sub component (for example a replies list, or a reply itself) requires pieces of that state and perhaps also needs to modify that state, that's then being done by the "property passing" and it can be tedious. Passing pieces of the state down to sub components and/or passing event callbacks down such as "upDateState" "showThis" "showThat".
This is sometimes what you want, you can then create a stateless components that only renders ui, a list of answers for example. If this is what you want then yes, you just have to get used to passing in props from parents.
The other answer to continue growing you application is modeling it by its state and the only way to do that (properly) is to abstract the application state away from the component. Creating a state that does not live inside of a component, a state that every component can access.
You might have guess what my suggestion is by now, have a look at Redux (or similar state management lib.) and you can easily cut out pieces (components) and attach them to the Redux global state and action. Once you get used to "never" keep application state in your components you wont go back. :)
PS!
This is perhaps not an answer but its to long for a comment. Just wanted to share my thoughts.

Resources