using if-else statements in React JSX - javascript

I'm trying to display react component based on what the pair name is, but I am not sure how to get that to happen. This is the code I have so far, any suggestions?
class App extends React.Component {
state = {
bases: ['EUR', 'AUD', 'CAD', 'JPY', 'NZD'],
selectedBase: 'USD',
};
displayGraph = () => {
if(document.getElementById('pairName').innerText === 'USD/EUR'){
<Graph defaultBase={"EUR"} />
} else if (document.getElementById('pairName').innerText === 'USD/CAD'){
<Graph defaultBase={"CAD"} />
} else if(document.getElementById('pairName').innerText === 'USD/AUD'){
<Graph defaultBase={"NZD"}/>
} else if(document.getElementById('pairName').innerText === 'USD/NZD'){
<Graph defaultBase={"AUD"}/>
} else if (document.getElementById('pairName').innerText === 'USD/JPY') {
<Graph defaultBase={"JPY"}/>
}
}
render(){
return (
<div id="three">
<h2 id="pairName">USD/EUR</h2>
{/* GOAL: show graphs when correct pairs are selected */}
{this.displayGraph}
</div>
}
}
export default App;

In your code I think you are missing return before the <Graph... />.
However another way you could do this could also be like this:
class App extends React.Component {
state = {
bases: ['EUR', 'AUD', 'CAD', 'JPY', 'NZD'],
selectedBase: 'USD',
};
displayGraph = () => {
switch(document.getElementById('pairName').innerText) {
case 'USD/EUR':
return <Graph defaultBase={"EUR"} />;
case 'USD/CAD':
return <Graph defaultBase={"CAD"} />;
case 'USD/AUD':
return <Graph defaultBase={"NZD"}/>;
case 'USD/NZD':
return <Graph defaultBase={"AUD"}/>;
case 'USD/JPY':
return <Graph defaultBase={"JPY"}/>;
}
render(){
return (
<div id="three">
<h2 id="pairName">USD/EUR</h2>
{this.displayGraph}
</div>
}
}
export default App;
P.s. As a side note, I would look at converting the React class to a function and using the useState hook as it is the newer way of writing react code. You can read more about it here.

Related

How to get back previous state in ReactJS Class Component

I'm implementing a project where
I have a array of 44 object data
When I type a it returns 37 data immediately by onChange()
After type ad it return 20
The Problem is when I return back to a by backspace. It stay on 20.
How can I get back 37 data again.
Code of Root.jsx
import React, { Component } from 'react'
import icons from './services/icons'
import IconCard from './components/IconCard'
import Header from './components/Header'
import Search from './components/Search'
const icon = new icons()
class Root extends Component {
state = {
data: icon.getIcon(),
}
getBadge = (e) => {
console.log(e)
const searched = this.state.data.filter(
item => {
if (e === '') {
return item
} else if (item.title.toLowerCase().includes(e.toLowerCase())) {
console.log(item)
return item
}
}
)
this.setState({ data:searched })
}
render() {
const data = this.state.data
return (
<>
<>
<Header />
<Search getBadge={this.getBadge} />
</>
<div className='container'>
<IconCard data={data} />
</div>
</>
)
}
}
export default Root
state data be like
state={
data:data
}
data
{
"title": "Academia",
"hex": "41454A"
},
{
"title": "Academia",
"hex": "41454A"
}
Code of Search.jsx
import React, { Component } from 'react';
class Search extends Component {
handleChange = (e) => {
this.props.getBadge(e.target.value)
}
render() {
// console.log(this.state.search)
return (
<div className='container pb-3'>
<div className="row">
<div className="col-md-3 align-self-center ">
<input type="text" className="form-control" placeholder="Search by brand..." onChange={this.handleChange} />
</div>
</div>
</div>
)
}
}
export default Search;
I understood your problem. You are mutating the original data whenever the search text is changing. Actually, you should not do that.
Instead,
import React, { Component } from 'react'
import icons from './services/icons'
import IconCard from './components/IconCard'
import Header from './components/Header'
import Search from './components/Search'
const icon = new icons()
class Root extends Component {
state = {
data: icon.getIcon(),
searchText: '',
}
getBadge = (search) => {
console.log(search)
return this.state.data.filter(
item => {
if (item.title.toLowerCase().includes(search.toLowerCase())) {
console.log(item)
return true;
}
return false;
}
)
}
render() {
const data = this.state.data
return (
<>
<>
<Header />
<Search
value={this.state.searchText}
onChange={(value) => this.setState({searchText: value})} />
</>
<div className='container'>
<IconCard data={this.getBatchData(this.state.searchText)} />
</div>
</>
)
}
}
export default Root
Set searchText state in the component
Change the props of the <Search /> component
Update the state when the search updates
Update the getBatchData() as per above code.
Everytime you update the search text, the data will remains same, but the filter will return the results according to search text
In your function getBadge :
const searched = this.state.data.filter(...)
this.setState({ data:searched })
You are replacing the state with the object you found. So if the data object had 44 elements, after a search it will only have the filtered elements. All the other elements are gone.
You should consider filtering from a constant object instead of state.data

How to dynamically render a nested component in React?

I want to render a child element based on the state in its parent. I tried to do the following (simplified version of the code):
class DeviceInfo extends Component {
constructor(props) {
super(props);
this.state = {
currentTab: "General",
};
this.tabsMap = {
General:
<React.Fragment>
<GeneralCard
id={this.props.id}
/>
</React.Fragment>
}
navToggle(tab) {
this.setState({ currentTab: tab });
}
this.tabsMap = {
General:
<React.Fragment>
<GeneralCard
id={this.props.id}
/>
</React.Fragment>
};
render() {
return (
<React.Fragment>
<div className="container">
<Nav className="nav-tabs ">
<NavItem>
<NavLink
className={this.state.currentTab === "General" ? "active" : ""}
onClick={() => {
this.navToggle("General");
}}
>
General
</NavLink>
</div>
{ this.tabsMap[this.state.currentTab] }
</React.Fragment>
);
}
}
But it did not work properly. Only when I put the contents of the tabsMap straight in the render function body it works (i.e. as a react element rather then accessing it through the object). What am I missing here?
Instead of making tabsMap an attribute which is only set when the component is constructed, make a method that returns the object, and call it from render:
getTabsMap() {
return {
General:
<React.Fragment>
<GeneralCard
id={this.props.id}
/>
</React.Fragment>
}
};
render() {
...
{ this.getTabsMap()[this.state.currentTab] }
...
}
You defining instance property with this.tabsMap (should be syntax error):
export default class App extends React.Component {
tabsMap = { General: <div>Hello</div> };
// Syntax error
// this.tabsMap = { General: <div>World</div> };
render() {
// depends on props
const tabsMapObj = {
General: <div>Hello with some props {this.props.someProp}</div>
};
return (
<FlexBox>
{this.tabsMap['General']}
{tabsMapObj['General']}
</FlexBox>
);
}
}
Edit after providing code:
Fix the bug in the constructor (Note, don't use constructor, it's error-prone, use class variables).
Moreover, remember that constructor runs once before the component mount if you want your component to be synchronized when properties are changed, move it to render function (or make a function like proposed).
class DeviceInfo extends Component {
constructor(props) {
...
// this.props.id not defined in this point
this.tabsMap = {
General:
<React.Fragment>
<GeneralCard
id={props.id}
/>
</React.Fragment>
}
render() {
// make a function to change the id
this.tabsMap = {
General:
<React.Fragment>
<GeneralCard
id={this.props.id}
/>
</React.Fragment>
};
return (
<>
{ this.tabsMap[this.state.currentTab] }
</>
);
}
}
I think it's a this binding issue. Not sure if your tabsMap constant should have this in front of it.
Alternative answer... you can inline the expression directly in the render as
{ this.state.currentTab === 'General' && <GeneralCard id={this.props.id} /> }

state changes without calling setState

So I'm trying to build a Notifications component in React. The component's state holds an array of notifications which are objects. One of their keys is 'seen'. The purpose of seen, as you can probably guess, is mainly visual. Everytime a user clicks on a notification, I run a function that's supposed to set the notification as seen in the database (for consistency) and in the local state (for UI).
The database part works great, but for some reason the state change doesn't work. When I put some console.logs, weirdly enough I see that the 'seen' property changes to 1 before I even call this.setState. I've been at it for hours now and I can't figure out what's happening.
And now, some code:
import React, {Component} from 'react';
import {connect} from 'react-redux';
import classes from './Notifications.module.css';
import * as actions from '../../../store/actions';
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome';
import moment from 'moment';
import {Redirect} from 'react-router-dom';
class Notifications extends Component {
constructor(props) {
super(props);
// Set an interval to update notifications every 4 minutes.
this.update = setInterval(() => {
this.props.fetchNotifications(this.props.username)
}, 240000)
}
state = {
opened: false,
count: 0,
notifications: []
}
componentDidUpdate(prevProps, prevState) {
if (!prevProps.username && this.props.username) this.props.fetchNotifications(this.props.username);
if (!prevProps.notifications && this.props.notifications) {
this.setState({notifications: this.props.notifications, count: this.props.notifications.filter(not => !not.seen).length});
}
if (this.props.notifications) {
if (JSON.stringify(this.state.notifications) !== JSON.stringify(prevState.notifications)) {
this.setState({count: this.state.notifications.filter(not => !not.seen).length})
}
}
}
componentWillUnmount() {
// Clear the update interval
clearInterval(this.update);
}
redirect(model, model_id) {
switch (model) {
case 'sealant_customer':
return <Redirect to={`/profile/sealant-customer/${model_id}`} />;
case 'unapproved_business':
return <Redirect to={`/profile/unapproved-business/${model_id}`} />;
case 'business':
return <Redirect to={`/profile/business/${model_id}`} />;
case 'missed_call':
return <Redirect to={`/data/missed-calls`} />;
default: return null;
}
}
render() {
let content = (
<React.Fragment>
<div className={classes.svgWrapper}>
<p className={classes.counter} style={{opacity: this.state.count === 0 ? '0' : '1'}}>{this.state.count}</p>
<FontAwesomeIcon icon='bell' onClick={() => this.setState(prevState => ({opened: !prevState.opened}))} />
</div>
{this.state.opened && <div className={classes.notificationsWrapper}>
<ul className={classes.notificationsList}>
{this.state.notifications.length !== 0 ? Array.from(this.state.notifications).map(notifi => {
let icon;
switch (notifi.model) {
case 'sealant_customer':
case 'person':
icon = 'user';
break;
case 'business':
case 'unapproved_business':
icon = 'warehouse';
break;
default: icon = 'user';
}
let classArray = [classes.notification];
if (!notifi.seen) classArray.push(classes.unseen);
return (
<li key={notifi.id} className={classArray.join(' ')} onClick={ () => {
// If already seen, simply redirect on click.
if (notifi.seen) return this.redirect(notifi.model, notifi.model_id);
let newNotifications = [...this.state.notifications];
// If not seen, mark as seen in State & in Database.
let index = newNotifications.findIndex(not => notifi.id === not.id);
newNotifications[index].seen = 1;
this.setState({ notifications: newNotifications});
this.props.markSeen(notifi.id, this.props.username);
// Redirect.
return this.redirect(notifi.model, notifi.model_id);
}}>
<div className={classes.iconWrapper}>
<FontAwesomeIcon icon={icon} />
</div>
<div className={classes.textWrapper}>
<p className={classes.msg}>
{notifi.message}
</p>
<label className={classes.ago}>
{moment(notifi.date).fromNow()}
</label>
</div>
</li>
)
}) : <li className={classes.notification} style={{cursor: 'default'}}><p style={{whiteSpace: 'nowrap'}}>No notifications to show...</p></li>}
</ul>
</div>}
</React.Fragment>
)
return (
<div className={classes.wrapper}>
{content}
</div>
)
}
}
const mapStateToProps = state => {
return {
username: state.auth.username,
notifications: state.data.notifications
};
};
const mapDispatchToProps = dispatch => {
return {
fetchNotifications: username => dispatch(actions.fetchNotifications(username)),
markSeen: (id, username) => dispatch(actions.markSeen(id, username))
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Notifications);
Any help would be appreciated.
For future readers - the SOLUTION:
The problem was that when I called the line let newNotifications = [...this.state.notifications]; I really did create a copy of notifications, but a copy which holds the original notification objects inside.
Once I changed the line newNotifications[index].seen = 1 to newNotifications[index] = {...newNotifications[index], seen: 1} everything worked like a charm.

Matching strings to components React

I'm making a Calendar which consists of an header and calendar. The header is for picking the type of calendar; weekly or monthly.
I had to make a dummy component called CalendarPicker just so I can use a switch. Inline switch is what I think needed but jsx doesn't accept it.
Is there a better way to do this? Or another way to match strings to components?
<CalendarController
render={({ type, onTypeClick }) => (
<>
<header>
<p>header of agenda</p>
<button onClick={onTypeClick("weekly")}>weekly</button>
<button onClick={onTypeClick("monthly")}>monthly</button>
</header>
<CalendarPicker
type={type}
render={type => {
switch (this.props.type) {
case "monthly":
return <MonthlyCalendar />;
case "weekly":
return <WeeklyCalendar />;
default:
return <MonthlyCalendar />;
}
}}
/>
</>
)}
/>
This is what I did to achieve this. In my case there were around 50 components and according to the name in the string I had to render that component.
I created a file ComponentSelector.js which imports all the components.
ComponentSelector.js
export default const objComponents = {
MonthlyCalendar : {
LoadComponent: function () {
return require('../../Modules/MonthlyCalendar ').default;
}
},
WeeklyCalendar : {
LoadComponent: function () {
return require('../../Modules/WeeklyCalendar ').default;
}
}
}
Import it into your CalendarController component
import objComponents from './ComponentSelector.js';
render(){
var CalComp=objComponents[this.props.type].LoadComponent();
return(<div>
<header>
<p>header of agenda</p>
<button onClick={onTypeClick("weekly")}>weekly</button>
<button onClick={onTypeClick("monthly")}>monthly</button>
</header>
<CalComp type={type}/>
</div>)
}

Prop not being passed to Child

This is bizarre. My console.log produces a company:
but for some reason in my child, when I try pulling it from props, it's null
CompanyDetailContainer
class CompanyDetailContainer extends Component {
async componentDidMount() {
const { fetchCompany } = this.props,
{ companyId } = this.props.match.params;
await fetchCompany(companyId);
}
render(){
const { company } = this.props;
console.log(company) // this outputs a company
return (
<CompanyDetail className="ft-company-detail" company={company} />
);
}
}
const mapStateToProps = state => ({
company: state.company.company
});
const mapDispatchToProps = {
fetchCompany: fetchCompany
};
export default connect(mapStateToProps, mapDispatchToProps)(CompanyDetailContainer);
CompanyDetail
export default class CompanyDetail extends Component {
render(){
const callToAction = 'test';
const { company } = this.props;
console.log(company) // this is null! why??? I've never had this problem before
const title = `${company.name} Details`;
return (
<Main>
<MainLayout title={title}>
<div>
<div id='ft-company-detail'>
<div className="panel vertical-space">
<CompanyHeader className="ft-company-header" company={company} />
<div className="ft-call-to-action-interview">{callToAction}</div>
<CompanyProfile className="ft-company-profile" company={company} />
<RelatedLinks className="ft-company-resources" company={company} />
</div>
</div>
</div>
</MainLayout>
</Main>
);
}
}
///// UPDATE ////
this worked:
return (
company && <CompanyDetail className="ft-company-detail" company={company} />
);
But then why does this combo work fine? it's setup pretty much the same way. This is the first route hit on my app, renders this container:
HomepageContainer
class HomePageContainer extends Component {
async componentDidMount() {
await this.props.fetchFeaturedCompanies();
await this.props.fetchCompanies();
await this.props.fetchCountries();
}
render(){
return (<HomePage
className='ft-homepage'
companies={this.props.companies}
countries={this.props.countries}
featuredCompanies={this.props.featuredCompanies}
/>);
}
}
const mapStateToProps = state => ({
countries: state.country.countries,
companies: state.company.companies,
featuredCompanies: state.company.featuredCompanies
});
const mapDispatchToProps = {
fetchCountries: fetchCountries,
fetchCompanies: fetchCompanies,
fetchFeaturedCompanies: fetchFeaturedCompanies
};
export default connect(mapStateToProps, mapDispatchToProps)(HomePageContainer);
HomePage
export default class HomePage extends Component {
render(){
return (
<Main>
<MainLayout title='Test'>
<div className="homepage panel vertical-space margin-bottom-300">
<FeaturedCompanies companies={this.props.featuredCompanies} />
<div>
<div className="column-group">
<div className="all-100 width-100 align-center fw-300 extralarge">
test
</div>
</div>
</div>
<CompanyList className="ft-company-list" companies={this.props.companies} countries={this.props.countries} />
</div>
</MainLayout>
</Main>
);
}
}
To the fella who commented on my theme, the first image above is from Chrome tools dark theme. Here is my actual theme in WebStorm which I think is even better :P:
componentDidMount is called after the render and your async call is in the componentDidMount, so for the first render the parent and the child both get null, and since you use company.name in child without a conditional check it errors out. Provide a conditional check in the child and it will work fine
const { company } = this.props;
console.log(company)
const title = company ? `${company.name} Details`: null;

Categories