The Reactjs code below works excellently by posting and retrieving Users newStatus from a form input to and from an API.
Now I want to add two additional form form inputs (newTitle and newEvent) as per code below
<input className="input-status" name="newTitle"
value={this.state.newTitle}
onChange={e => this.handleNewStatusChange(e)}
placeholder="your Title"
/>
<input className="input-status" name="newEvent"
value={this.state.newEvent}
onChange={e => this.handleNewStatusChange(e)}
placeholder="Your Event Centre Name"
/>
I have also implemented newEvent and newTitle variables in code where possible. My issue is that if type in the first form inputs (eg newStatus) it gets replicated or inserted in all other remaining two forms inputs as can be seen in the screenshot below.
And I believe that's why only the newStatus get posted and retrieved.
I have commented out all newEvent and newTitle variables in code so readers can see where I am wrong.
Here is the code profile.jsx:
import React, { Component } from 'react';
import {
isSignInPending,
loadUserData,
Person,
getFile,
putFile,
lookupProfile
} from 'blockstack';
import Status from './Status.jsx';
const avatarFallbackImage = 'https://mysite/onename/avatar-placeholder.png';
const statusFileName = 'statuses.json'
export default class Profile extends Component {
constructor(props) {
super(props);
this.state = {
person: {
name() {
return 'Anonymous';
},
avatarUrl() {
return avatarFallbackImage;
},
},
username: "",
newStatus: "",
//newTitle: "",
// newEvent: "",
statuses: [],
statusIndex: 0,
isLoading: false
};
this.handleDelete = this.handleDelete.bind(this);
this.isLocal = this.isLocal.bind(this);
}
componentDidMount() {
this.fetchData()
}
handleNewStatusChange(event) {
this.setState({
newStatus: event.target.value,
//newTitle: event.target.value,
//newEvent: event.target.value
})
}
handleNewStatusSubmit(event) {
this.saveNewStatus(
this.state.newStatus,
//this.state.newTitle,
//this.state.newEvent
)
this.setState({
newStatus: "",
//newTitle: "",
//newEvent: "",
})
}
handleDelete(id) {
const statuses = this.state.statuses.filter((status) => status.id !== id)
const options = { encrypt: false }
putFile(statusFileName, JSON.stringify(statuses), options)
.then(() => {
this.setState({
statuses
})
})
}
saveNewStatus(statusText) {
let statuses = this.state.statuses
let status = {
id: this.state.statusIndex++,
text: statusText.trim(),
//textTitle: statusText.trim(),
//textEvent: statusText.trim(),
created_at: Date.now()
}
statuses.unshift(status)
const options = { encrypt: false }
putFile(statusFileName, JSON.stringify(statuses), options)
.then(() => {
this.setState({
statuses: statuses
})
})
}
fetchData() {
if (this.isLocal()) {
this.setState({ isLoading: true })
const options = { decrypt: false, zoneFileLookupURL: 'https://myapi/v1/names/' }
getFile(statusFileName, options)
.then((file) => {
var statuses = JSON.parse(file || '[]')
this.setState({
person: new Person(loadUserData().profile),
username: loadUserData().username,
statusIndex: statuses.length,
statuses: statuses,
})
})
.finally(() => {
this.setState({ isLoading: false })
})
} else {
const username = this.props.match.params.username
this.setState({ isLoading: true })
lookupProfile(username)
.then((profile) => {
this.setState({
person: new Person(profile),
username: username
})
})
.catch((error) => {
console.log('could not resolve profile')
})
const options = { username: username, decrypt: false, zoneFileLookupURL: 'https://myapi/v1/names/'}
getFile(statusFileName, options)
.then((file) => {
var statuses = JSON.parse(file || '[]')
this.setState({
statusIndex: statuses.length,
statuses: statuses
})
})
.catch((error) => {
console.log('could not fetch statuses')
})
.finally(() => {
this.setState({ isLoading: false })
})
}
}
isLocal() {
return this.props.match.params.username ? false : true
}
render() {
const { handleSignOut } = this.props;
const { person } = this.state;
const { username } = this.state;
return (
!isSignInPending() && person ?
<div className="container">
<div className="row">
<div className="col-md-offset-3 col-md-6">
<div className="col-md-12">
<div className="avatar-section">
<img
src={ person.avatarUrl() ? person.avatarUrl() : avatarFallbackImage }
className="img-rounded avatar"
id="avatar-image"
/>
<div className="username">
<h1>
<span id="heading-name">{ person.name() ? person.name()
: 'no name found' }</span>
</h1>
<span>{username}</span>
{this.isLocal() &&
<span>
|
<a onClick={ handleSignOut.bind(this) }>(Logout)</a>
</span>
}
</div>
</div>
</div>
{this.isLocal() &&
<div className="new-status">
<div className="col-md-12">
<textarea className="input-status" name="newStatus"
value={this.state.newStatus}
onChange={e => this.handleNewStatusChange(e)}
placeholder="What's on your mind?"
/>
<input className="input-status" name="newTitle"
value={this.state.newTitle}
onChange={e => this.handleNewStatusChange(e)}
placeholder="your Title"
/>
<input className="input-status" name="newEvent"
value={this.state.newEvent}
onChange={e => this.handleNewStatusChange(e)}
placeholder="Your Event Centre Name"
/>
</div>
<div className="col-md-12 text-right">
<button
className="btn btn-primary btn-lg"
onClick={e => this.handleNewStatusSubmit(e)}
>
Submit
</button>
</div>
</div>
}
<div className="col-md-12 statuses">
{this.state.isLoading && <span>Loading...</span>}
{
this.state.statuses.map((status) => (
<Status
key={status.id}
status={status}
handleDelete={this.handleDelete}
isLocal={this.isLocal}
/>
))
}
</div>
</div>
</div>
</div> : null
);
}
}
Here is just sample part for for status.jsx showing how I successfully return the status:
//some coding...
return (
<div className="status">
<div className="status-text">
{status.text}
/*
{status.textTitle} {status.textEvent}
*/
</div>
</div>
)
You can use the name attribute of the input field to your advantage.
And update your change handler function:
handleNewStatusChange(event) {
this.setState({
[event.target.name]: event.target.value,
})
}
more info on { [key]: value }
Related
How do I remove the transaction with a certain ID it includes data like name, company, position etc., as shown in the code and image below. It is saved in firebase database and I want a certain transaction completely removed. I cant figure out how to remove I tried `
deleteItem(key){
const filteredItems = this.state.items.filter(item =>
item.key!==key);
this.setState({
items:filteredItems
})
}
but its not working
import React, { Component } from 'react';
import fire from '../../config/Fire';
import Transaction from './Transaction/Transaction';
class Tracker extends Component {
state = {
transactions: [],
name: '',
company: '',
position: '',
link: '',
currentUID: fire.auth().currentUser.uid
}
handleChange = input => e => {
this.setState({
[input]: e.target.value !=="0" ? e.target.value : ""
});
}
// add transaction
addNewTransaction = () => {
const {name, company, position, link, currentUID} = this.state;
// validation
if(name && company && position && link){
const BackUpState = this.state.transactions;
BackUpState.push({
id: BackUpState.length + 1,
name: name,
company: company,
position: position,
link: link,
user_id: currentUID
});
fire.database().ref('Transactions/' + currentUID).push({
id: BackUpState.length,
name: name,
company: company,
position: position,
link: link,
user_id: currentUID
}).then((data) => {
//success callback
console.log('success callback');
this.setState({
transactions: BackUpState,
name: '',
company: '',
position: '',
link: ''
})
}).catch((error)=>{
//error callback
console.log('error ' , error)
});
}
}
componentWillMount(){
const {currentUID} = this.state;
const BackUpState = this.state.transactions;
fire.database().ref('Transactions/' + currentUID).once('value',
(snapshot) => {
// console.log(snapshot);
snapshot.forEach((childSnapshot) => {
BackUpState.push({
id: childSnapshot.val().id,
name: childSnapshot.val().name,
company: childSnapshot.val().company,
position: childSnapshot.val().position,
link: childSnapshot.val().link,
user_id: childSnapshot.val().user_id
});
// console.log(childSnapshot.val().name);
});
this.setState({
transactions: BackUpState,
});
});
}
render(){
var currentUser = fire.auth().currentUser;
return(
<div className="newTransactionBlock">
<div className="newTransaction">
<form>
<input
onChange={this.handleChange('name')}
value={this.state.name}
placeholder="Name"
type="text"
name="name"
/>
<div className="inputGroup">
<input
onChange={this.handleChange('company')}
value={this.state.company}
placeholder="Company"
type="text"
name="company"
/>
<input
onChange={this.handleChange('position')}
value={this.state.position}
placeholder="Position"
type="text"
name="position"
/>
<input
onChange={this.handleChange('link')}
value={this.state.link}
placeholder="Link"
type="url"
name="link"
/>
</div>
</form>
<button onClick={() => this.addNewTransaction()} className="addTransaction">+ Add Transaction</button>
</div>
</div>
<div className="latestTransactions">
<p>Latest Transactions</p>
<ul>
{
Object.keys(this.state.transactions).map((id) => (
<Transaction key={id}
name={this.state.transactions[id].name}
company={this.state.transactions[id].company}
position={this.state.transactions[id].position}
link={this.state.transactions[id].link}
/>
))
}
</ul>
</div>
</div>
);
}
}
export default Tracker;
Here is the transaction List imported
import React from 'react';
const Transaction = props => {
return (
<li>
<div>{props.name}</div>
<div>{props.company}</div>
<div>{props.position}</div>
<div>{props.link}</div>
<button>remove</button>
</li>
);
}
export default Transaction;
In this code I am unable to clear both the text fields even though I'm setting their states to blank after clicking on filter via this.Search function.
import React from 'react' import Axios from './Axios' import
'./index.css' export default class Practise extends React.Component {
constructor(props) {
super(props)
this.state = {
lists: [],
names: '',
emails: '',
arr: [],
}
this.Search = (names, emails) => {
const arr = this.state.lists.filter(item => {
if (item.name.includes(this.state.names) && item.email.includes(this.state.emails)) {
return true
} else {
return false
}
})
this.setState({names:''})
this.setState({emails:''})
console.log(arr)
}
}
componentDidMount() {
Axios.get('/comments').then(response => {
// console.log(response.data, 'data')
this.setState({ lists: response.data })
}).catch(response => {
console.log(response, 'Errored!!!')
this.setState({ errored: 'Error has Occured' })
})
}
render() {
const { lists, arr, names, emails } = this.state
return (
<>
<div >
<input type='text' onChange={(e) => this.setState({ names: e.target.value })} />
<input type='text' onChange={(p) => this.setState({ emails: p.target.value })} />
<button onClick={()=>this.Search()}>Filter</button>
</div>
<div>
{lists.length ? lists.map(item =>
<div className='divone' key={item.id}>
Id:- {item.id} <br />
Name:- {item.name} <br />
Email:- {item.email}</div>) : null}
</div>
</>
)
}
};
You have to add the value prop to both of the inputs.After that,
if you change the state to a blank string, the value of inputs will also get blank.Here is an example -
<input type='text' value={this.state.names} onChange={(e) => this.setState({ names: e.target.value })} />
<input type='text' value={this.state.emails} onChange={(p) => this.setState({ emails: p.target.value })} />
It should work.
I have passed my user ID into my 'OrderMessages' component but in my function says undefined. When my user submits a messages using the form in the handleFormSubmit function I need the UserID and the datetime of the message. I have managed to get the date and time but when trying to console log to get the UserID I keep getting an error. I have tried this.props.... and this.state but both say undefined, can you please help. In my constructor I have tested using const UserId = props.location.state.UserID; and in debug I can see this has correctly got the UserID so im not sure how to get it into my hadleFormSubmit function.
import React from "react";
import Moment from "moment";
import { Form, Button } from "react-bootstrap";
class OrderMessages extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [],
isLoading: false,
checkboxes: [],
selectedId: [],
formLableSelected: "",
formSelectedSubject: "",
formSelectedSubjectId: "",
formNewSubject: "",
formChainID: "",
formMessageBody: "",
userId: '',
};
const UserId = props.location.state.UserID;
}
componentDidMount() {
this.setState({ isLoading: true });
const proxyurl = "https://cors-anywhere.herokuapp.com/";
const url =
"myURL" +
this.props.location.state.orderNumber;
fetch(proxyurl + url)
.then((res) => res.json())
.then((data) => this.setState({ data: data, isLoading: false }));
}
handleClick = (id) => {
if (this.state.selectedId !== id) {
this.setState({ selectedId: id });
} else {
this.setState({ selectedId: null });
}
};
setformSubjectID(messageSubject) {
if (messageSubject.subject === this.state.formSelectedSubject) {
this.setState({ formSelectedSubjectId: messageSubject.messageSubjectId });
}
}
handleChangeSubject = (event) => {
this.setState({ formSelectedSubject: event.target.value });
this.state.data.message_Subjects.map((ms) => this.setformSubjectID(ms));
};
handleFormSubmit(e) {
e.preventDefault();
// get current time
let submit_time = Moment().format("ddd DD MMM YYYY HH:mm:ss");
console.log("messageDatetime", submit_time);
// get user id THIS IS WHAT DOESN’T WORK
console.log("messageSentFrom", this.state.userId);
console.log("messageSentFrom", this.props.location.state.UserID);
}
render() {
const { data, isLoading } = this.state;
if (isLoading) {
return <p>Loading ...</p>;
}
if (data.length === 0) {
return <p> no data found</p>;
}
console.log("mess: ", data);
return (
<div>
<div className="sendMessageContent">
<Form className="sendMessageForm" onSubmit={this.handleFormSubmit}>
<Form.Group className="formRadio">
<Form.Check
className="formRadiob"
type="radio"
label="New chat"
value="new"
name="neworexisitng"
id="New Message"
onChange={this.onFormMessageChanged}
defaultChecked
/>
<Form.Check
className="formRadiob"
type="radio"
label="Reply to exisiting chat"
value="reply"
name="neworexisitng"
id="exisiting Message"
onChange={this.onFormMessageChanged}
/>
</Form.Group>
{this.returnCorrectFormFields(data)}
<Form.Group>
<Form.Label>Message Body</Form.Label>
<Form.Control as="textarea" rows={3} />
</Form.Group>
<Button variant="primary" type="submit">
Send Message
</Button>
</Form>
</div>
</div>
);
}
returnCorrectFormFields(data) {
if (this.state.formLableSelected === "new") {
return this.newMessageSubject(data);
} else {
return this.choseMessageSubject(data);
}
}
choseMessageSubject(data) {
return (
<Form.Group>
<Form.Label>Select the message subject</Form.Label>
<Form.Control as="select" onChange={this.handleChangeSubject}>
<option value="0">Choose...</option>
{data.message_Subjects.map((ms) => (
<option value={ms.subject}>{ms.subject}</option>
))}
</Form.Control>
</Form.Group>
);
}
newMessageSubject(data) {
return (
<Form.Group>
<Form.Label>Enter Message Subject</Form.Label>
<Form.Control type="text" placeholder="Enter message subject" />
</Form.Group>
);
}
onFormMessageChanged = (event) => {
this.setState({
formLableSelected: event.target.value,
});
};
getAllMessageInChain(messageChain) {
return (
<div className="messageHistory">
<div className="messageHistoryHeader">
<div className="innerMS-history-body">Message</div>
<div className="innerMS">Date and Time</div>
<div className="innerMS">Message sent by</div>
</div>
{messageChain.map((ms) => (
<div className="messageHistoryBody">
<div className="innerMS-history-body">{ms.messageBody}</div>
<div className="innerMS">
{Moment(ms.dateTime).format("ddd DD MMM YYYY hh:mm:ss")}
</div>
<div className="innerMS">{ms.sentFromId}</div>
</div>
))}
</div>
);
}
getLatestMessageDateTime(messageChain) {
const lastmessage = messageChain.length - 1;
Moment.locale("en");
var dt = messageChain[lastmessage].dateTime;
return Moment(dt).format("ddd DD MMM YYYY hh:mm:ss");
}
}
export default OrderMessages;
The scope of this isn't the component in the function you're using.
Either change handleFormSubmit to this to bind this automatically.
handleFormSubmit = (e) => {
// .. your code
}
or bind this manually in the constructor
constructor() {
// ..other code
this.handleFormSubmit = this.handleFormSubmit.bind(this)
}
So, as the title suggests, I am trying to find if the user exists or not. Here's what I have done until now. I'm having trouble with handling validation in the handleSubmit function.
RegistrationForm.js
import React, { Component } from "react"
import { registerUser, checkValidUser } from "../actions/userActions"
import { connect } from "react-redux"
import validator from "validator"
import { Link } from "react-router-dom"
class RegistrationForm extends Component {
constructor(props) {
super(props)
this.state = {
username: "",
email: "",
password: "",
}
}
handleChange = (event) => {
const { name, value } = event.target
this.setState({
[name]: value,
})
}
handleSubmit = (event) => {
event.preventDefault()
const { username, email, password } = this.state
const registrationData = this.state
if (!username || !email || !password) {
return toastError("Credentials should not be empty")
}
if (username.length < 6) {
return toastError("Username should be greater than 6 characters.")
}
if (!validator.isEmail(email)) {
return toastError("Invalid email.")
}
if (password.length < 6) {
return toastError("Password must contain 6 characters.")
}
this.props.dispatch(checkValidUser(email)) // how do i properly handle validations here
this.props.dispatch(
registerUser(registrationData, () => this.props.history.push("/login"))
)
}
render() {
const isRegistrationInProgress = this.props.registration.isRegistrationInProgress
return (
<div>
<div className="field">
<p className="control has-icons-left has-icons-right">
<input
onChange={this.handleChange}
name="username"
value={this.state.username}
className="input"
type="text"
placeholder="Username"
/>
<span className="icon is-small is-left">
<i className="fas fa-user"></i>
</span>
</p>
</div>
<div className="field">
<p className="control has-icons-left has-icons-right">
<input
onChange={this.handleChange}
name="email"
value={this.state.email}
className="input"
type="email"
placeholder="Email"
/>
<span className="icon is-small is-left">
<i className="fas fa-envelope"></i>
</span>
</p>
</div>
<div className="field">
<p className="control has-icons-left">
<input
onChange={this.handleChange}
name="password"
value={this.state.password}
className="input"
type="password"
placeholder="Password"
/>
<span className="icon is-small is-left">
<i className="fas fa-lock"></i>
</span>
</p>
</div>
<div className="field">
<div className="control">
{isRegistrationInProgress ? (
<button className="button is-success is-loading">Sign Up</button>
) : (
<button onClick={this.handleSubmit} className="button is-success">
Sign up
</button>
)}
<Link to="/login">
<p className="has-text-danger">
Already have an account? Sign In
</p>
</Link>
</div>
</div>
</div>
)
}
}
const mapStateToProps = (state) => {
return state
}
export default connect(mapStateToProps)(RegistrationForm)
checkValidUser action creator
export const checkValidUser = (email) => {
return async dispatch => {
dispatch({ type: "CHECK_VALID_USER_STARTS" })
try {
const res = await axios.get(`${baseUrl}/users/checkValidUser/${email}`)
dispatch({
type: "CHECK_VALID_USER_SUCCESS",
data: { message: res.data.message }
})
} catch (err) {
dispatch({
type: "CHECK_VALID_USER_ERROR",
data: { error: "Something went wrong" },
})
}
}
}
route- router.get("/checkValidUser/:email", usersController.checkValidUser)
checkValidUser controller function
checkValidUser: async (req, res, next) => {
const { email } = req.params
try {
const user = await User.findOne({ email })
if (!user) {
return res.status(404).json({ error: "No user found" })
}
return res.status(200).json({ message: "User already exists" })
} catch (error) {
return next(error)
}
}
registration reducer
const initialState = {
isRegistrationInProgress: false,
isRegistered: false,
registrationError: null,
user: {},
message: "",
}
const registration = (state = initialState, action) => {
switch (action.type) {
case "REGISTRATION_STARTS":
return {
...state,
isRegistrationInProgress: true,
registrationError: null,
}
case "REGISTRATION_SUCCESS":
return {
...state,
isRegistrationInProgress: false,
registrationError: null,
isRegistered: true,
user: action.data,
}
case "REGISTRATION_ERROR":
return {
...state,
isRegistrationInProgress: false,
registrationError: action.data.error,
isRegistered: false,
user: {},
}
case "CHECK_VALID_USER_STARTS":
return {
...state,
isRegistrationInProgress: true,
}
case "CHECK_VALID_USER_SUCCESS":
return {
...state,
isRegistrationInProgress: false,
message: action.data.message,
}
case "CHECK_VALID_USER_ERROR":
return {
...state,
registrationError: action.data.error,
}
default:
return state
}
}
export default registration
Any help would be appreciated. Thanks.
Isn't checkValidUser the same type of validation like password.length < 6? I would just call it synchronously inside the validation. On the backend side I would change the status codes if possible. If the user already exists, it's the problematic case, so 419 (conflict) is a better fit than 200. 200 or 204 instead should be returned if the email wasn't used. With this the frontend check is quite easy:
export const checkValidUser = async (email) => {
try {
const res = await axios.get(`${baseUrl}/users/checkValidUser/${email}`)
return true
} catch (err) {
return false
}
}
and in handleSubmit
handleSubmit = async (event) => {
event.preventDefault()
const { username, email, password } = this.state
const registrationData = this.state
...
if(!(await checkValidUser(email))) {
return toastError("...")
}
this.props.dispatch(
registerUser(registrationData, () => this.props.history.push("/login"))
)
}
With this you don't need the CHECK_VALID_USER-states, but may want to add some kind of VALIDATION_IN_PROGRESS action to show some indicator on the page, that a background process is running, like a spinner. This can be achieved with a simple try/finally wrapper. So no matter in which case the validation will be exited the validation progress status will be reset.
handleSubmit = async (event) => {
event.preventDefault()
const { username, email, password } = this.state
const registrationData = this.state
try {
this.props.dispatch(setValidationProgress(true)
...
if(!(await checkValidUser(email))) {
return toastError("...")
}
} finally {
this.props.dispatch(setValidationProgress(false)
}
this.props.dispatch(
registerUser(registrationData, () => this.props.history.push("/login"))
)
}
Given a react form with multiple radio buttons and a text input that is visible only when the other option radio button is selected, I currently have the submit button disabled until a radio button is selected. However, if the other option is selected, the submit button will still work even if there is no text in the input field associated with it. How can I check the length of the input box if and only if the other option is selected?
class CancelSurvey extends React.Component {
constructor (props) {
super(props)
this.state = {
reasons: [],
reason: {},
otherReasonText: undefined,
submitting: false
}
this.processData = this.processData.bind(this)
this.handleSubmit = this.handleSubmit.bind(this)
this.setReason = this.setReason.bind(this)
this.setOtherReasonText = this.setOtherReasonText.bind(this)
this.otherReason = {
reason_id: 70,
client_reason: 'other'
}
}
componentDidMount () {
this.fetchSurvey()
}
/**
* Fetch reasons
*/
fetchSurvey (cb) {
superagent
.get('/api/user/survey')
.then(this.processData)
}
processData (data) {
this.setState({ reasons: data.body })
}
async handleSubmit (e) {
e.preventDefault()
await this.setState({ submitting: true })
const { reason, otherReasonText } = this.state
superagent
.post('/api/user/survey')
.send({
optionId: reason.reason_id,
optionText: reason.client_reason,
otherReasonText
})
.then(async (data) => {
await this.setState({ submitting: false })
if (data.body.success) {
this.props.setStep(OFFER)
}
})
}
setOtherReasonText (e) {
this.setState({ otherReasonText: e.target.value })
}
setReason (reason) {
this.setState({ reason })
}
/**
* render
*/
render (props) {
const content = this.props.config.contentStrings
const reasons = this.state.reasons.map((reason, i) => {
return (
<div
className='form-row'
key={i}>
<input type='radio'
id={reason.reason_id}
value={reason.client_reason}
name='reason'
checked={this.state.reason.reason_id === reason.reason_id}
onChange={() => this.setReason(reason)} />
<label htmlFor={reason.reason_id}>{reason.client_reason}</label>
</div>
)
})
return (
<div className='cancel-survey'>
<form className='cancel-survey-form'>
{ reasons }
<div className='form-row'>
<input
type='radio'
id='other-option'
name='reason'
onChange={() => this.setReason(this.otherReason)} />
<label htmlFor='other-option'>
Other reason
</label>
</div>
{ this.state.reason.reason_id === 70 &&
<div>
<input
className='valid'
type='text'
id='other-option'
name='other-text'
placeholder="placeholder"
onChange={this.setOtherReasonText} />
</div>
}
<div className='button-row'>
<button
disabled={!this.state.reason.client_reason}
className={btnClassList}
onClick={this.handleSubmit}>
<span>Submit</span>
</button>
</div>
</form>
</div>
)
}
}
export default CancelSurvey
disabled={
!this.state.reason.client_reason
||
(this.state.reason.client_reason === 'other' && !this.state.otherReasonText)
}
If you want to make sure otherReasonText is not just empty spaces, use otherReasonText: '' as initial state then check !this.state.otherReasonText.trim().
Do some conditional logic before like:
const reasonId = this.state.reason.reason_id;
const otherReasonText = this.state.otherReasonText;
const clientReason = this.state.reason.client_reason;
const shouldDisableButton = !clientReason || (reasonId === 70 && otherReasonText.length === 0)
...
<div className='button-row'>
<button
disabled={shouldDisableButton}
className={btnClassList}
onClick={this.handleSubmit}>
<span>Submit</span>
</button>
</div>