I’ve found a weird behavior in my react code. I’m fairly new to react and can’t figure it out.
In the past few days, I’ve created a nice dashboard and want to add a data page with CRUD transactions. I want to change the text inside the search button, when the searchForm state is true, but it does only work after the component updated, not on first rendering. I’ve initiated the searchForm state with false and on searchBtnClick the state gets set to true. But the text inside the button doesn’t change.
import React, { Component, Fragment } from 'react'; import SideBar from '../../components/navBar/SideBar'; import SearchForm from '../../components/forms/SearchForm'; import TransactionTable from '../../components/tables/TransactionTable'; import './data.css'; import { getTransaction } from '../../actions/Transactions'; export default class Data extends Component { constructor(props) { super(props); this.state = { year: 0, month: '', transactions: [], searchForm: false, addForm: false, editForm: false, error: false, errorMessage: '', }; this.navBtnClick = this.navBtnClick.bind(this); this.addBtnClick = this.addBtnClick.bind(this); this.searchBtnClick = this.searchBtnClick.bind(this); this.editBtnClick = this.editBtnClick.bind(this); this.deleteBtnClick = this.deleteBtnClick.bind(this); this.updateTable = this.updateTable.bind(this); this.setError = this.setError.bind(this); this.months = [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December', ]; } componentDidMount() { const currentDate = new Date(); var currentYear = currentDate.getYear() + 1900; this.setState({ year: currentYear }); var currentMonth = this.months[currentDate.getMonth()].toLowerCase(); this.setState({ month: currentMonth }); getTransaction({ year: currentYear, month: currentMonth }).then((res) => { if (res.error) { this.setError(true, res.error); } else { this.setError(false); this.setState({ transactions: res }); } }); } navBtnClick() { this.props.updateNavBarState(); } addBtnClick(e) { this.setState({ addForm: !this.state.addForm }); } searchBtnClick() { this.setState({ searchForm: !this.state.searchForm }); } editBtnClick(e) { this.setState({ editForm: !this.state.editForm }); } deleteBtnClick(e) {} updateTable(transactions) { // If there isn't an error, close the form if (this.state.error === false) { this.setState({ transactions: transactions }); this.setState({ addForm: false }); this.setState({ searchForm: false }); this.setState({ editForm: false }); } } setError(state, message = '') { this.setState({ error: state }); this.setState({ errorMessage: message }); } render() { return ( <Fragment> <SideBar sideBarState={this.props.sideBarState} /> <div className="page"> <div className="grid head"> <span id="sidebarCollapseBtn"> <i className="fas fa-align-left" onClick={this.navBtnClick}></i> </span> <h1 className="capitalize">data</h1> </div> <div className="content"> <div className="card" id="dataCard"> <div className="actions" id="actions"> <div className="flex"> // This if statement {this.state.searchForm === true ? ( <button className="search btn" id="searchBtn" onClick={this.searchBtnClick} > close </button> ) : ( <button className="search btn" id="searchBtn" onClick={this.searchBtnClick} > <i className="fas fa-search mr-025"></i>search </button> )} <button className="add btn" id="addBtn" onClick={this.addBtnClick} > <i className="fas fa-plus mr-025"></i>add </button> </div> {this.state.searchForm ? ( <SearchForm year={this.state.year} month={this.state.month} updateTable={this.updateTable} setError={this.setError} /> ) : ( <Fragment /> )} </div> <div className="output"> {this.state.transactions.length > 1 ? ( <TransactionTable transactions={this.state.transactions} /> ) : ( <Fragment /> )} </div> </div> </div> </div> </Fragment> ); } }
Thanks in advance, Marc
Advertisement
Answer
A couple suggestions I would make about this code:
- A somewhat personal preference, using arrow notation to define class methods so you don’t have to
.bind(this)
for every one.
// this is the same as constructor(props) { this.someFunc = this.someFunc.bind(this) } someFunc() {} // writing just this someFunc = () => {}
The code inside your
if (this.state.error) {}
is almost identical to the entire component with some minor changes. I would suggest being more targeted / specific with your if statements and just change the smallest piece needed. (see large code blow below)In a couple places you are using a ternary operator to return something OR a
<Fragment />
. Again, this might just be personal preference, but you could instead just use&&
to simplify the code some.
// this is the same as {this.state.searchForm ? ( <MyComponent /> ) : ( <Fragment /> )} // is the same as writing {this.state.searchForm && <MyComponent />} // or {this.state.searchForm && ( <MyComponent foo="something" bar="baz" onClick={this.onClick} /> )}
Here is your full code sample with the simplifications from above applied.
RE: your actual question though, about why the text isn’t swapping out inside your search button… your click handler looks correct and should be changing the state properly… Maybe having the if statement like I suggested, be more targeted to only changing the actual text inside the button instead of the whole button will help.
import React, { Component, Fragment } from "react"; import SideBar from "../../components/navBar/SideBar"; import SearchForm from "../../components/forms/SearchForm"; import TransactionTable from "../../components/tables/TransactionTable"; import "./data.css"; import { getTransaction } from "../../actions/Transactions"; export default class Data extends Component { constructor(props) { super(props); this.state = { year: 0, month: "", transactions: [], searchForm: false, addForm: false, editForm: false, error: false, errorMessage: "", }; this.months = [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", ]; } componentDidMount() { const currentDate = new Date(); var currentYear = currentDate.getYear() + 1900; this.setState({ year: currentYear }); var currentMonth = this.months[currentDate.getMonth()].toLowerCase(); this.setState({ month: currentMonth }); getTransaction({ year: currentYear, month: currentMonth }).then((res) => { if (res.error) { this.setError(true, res.error); } else { this.setError(false); this.setState({ transactions: res }); } }); } navBtnClick = () => { this.props.updateNavBarState(); }; addBtnClick = (e) => { this.setState({ addForm: !this.state.addForm }); }; searchBtnClick = () => { this.setState({ searchForm: !this.state.searchForm }); }; editBtnClick = (e) => { this.setState({ editForm: !this.state.editForm }); }; deleteBtnClick = (e) => {}; updateTable = (transactions) => { // If there isn't an error, close the form if (this.state.error === false) { this.setState({ transactions: transactions }); this.setState({ addForm: false }); this.setState({ searchForm: false }); this.setState({ editForm: false }); } }; setError = (state, message = "") => { this.setState({ error: state }); this.setState({ errorMessage: message }); }; render() { return ( <Fragment> <SideBar sideBarState={this.props.sideBarState} /> <div className="page"> <div className="grid head"> <span id="sidebarCollapseBtn"> <i className="fas fa-align-left" onClick={this.navBtnClick}></i> </span> <h1 className="capitalize">data</h1> </div> <div className="content"> <div className="card" id="dataCard"> <div className="actions" id="actions"> <div className="flex"> <button className="search btn" id="searchBtn" onClick={this.searchBtnClick} > {this.state.searchForm ? ( "close" ) : ( <Fragment> <i className="fas fa-search mr-025"></i>search </Fragment> )} </button> <button className="add btn" id="addBtn" onClick={this.addBtnClick} > <i className="fas fa-plus mr-025"></i>add </button> </div> {this.state.searchForm && ( <SearchForm year={this.state.year} month={this.state.month} updateTable={this.updateTable} setError={this.setError} /> )} </div> <div className="output"> {this.state.error && <h2>{this.state.errorMessage}</h2>} {this.state.transactions.length > 1 && ( <TransactionTable transactions={this.state.transactions} /> )} </div> </div> </div> </div> </Fragment> ); } }