Convert class component (with local state) to stateless functional component & container - reactjs

I am new to react-redux and currently working on a project where we have a mandate from our software architecture team to write all our components as stateless functional components (introduced in React 0.14) and whenever we need to pass them a piece of the redux state we should use containers.
My questions are:
Is this pattern applicable for every component and how? Even for class based components that have their own "state" (not redux state)? Is it possible for all class components to be rewritten as stateless functional components plus a container?
The reason I am asking is more specific actually:
The team has decided to use reactstrap-tabs to implement tab functionality inside our components. The tabs will live inside a parent component and inside each tab a different child component will be displayed. From the documentation it seems that the parent tabbed component should be implemented as a class based component (handling this.state). Is it possible to rewrite this as a stateless functional component and a container?
Any help would be really appreciated since I am stuck on this. Here is the sample code...
import React from 'react';
import { TabContent, TabPane, Nav, NavItem, NavLink, Card, Button, CardTitle, CardText, Row, Col } from 'reactstrap';
import classnames from 'classnames';
import MyFirstChildComponent from '/components/MyFirstChildComponent';
import MySecondChildContainer from '/containers/MySecondChildContainer';
export default class TabbedParent extends React.Component {
constructor(props) {
super(props);
this.toggle = this.toggle.bind(this);
this.state = {
activeTab: '1'
};
}
toggle(tab) {
if (this.state.activeTab !== tab) {
this.setState({
activeTab: tab
});
}
}
render() {
return (
<div>
<Nav tabs>
<NavItem>
<NavLink
className={classnames({ active: this.state.activeTab === '1' })}
onClick={() => { this.toggle('1'); }}
>
MyFirstTab
</NavLink>
</NavItem>
<NavItem>
<NavLink
className={classnames({ active: this.state.activeTab === '2' })}
onClick={() => { this.toggle('2'); }}
>
MySecondTab
</NavLink>
</NavItem>
</Nav>
<TabContent activeTab={this.state.activeTab}>
<TabPane tabId="1">
<Row>
<Col sm="12">
<MyFirstChildComponent />
</Col>
</Row>
</TabPane>
<TabPane tabId="2">
<Row>
<Col sm="6">
<MySecondChildContainer />
</Col>
</Row>
</TabPane>
</TabContent>
</div>
);
}
}

It sounds like your architecture team wants you to keep state centralized in a Redux store, and you are asking if you can use components which maintain their own state. There's obviously a conflict there, but you can achieve what you want with the tabs by keeping the tab state in the store, using containers to update the tabs when they're toggled, and dispatching actions then the tabs are selected.
If you can't make changes to the structure of the store for your UI, then I suppose you could only make design and component choices that would work with the data available to you in the store. I'm not sure how inflexible your architecture team is.
A simple implementation of your example might look like this:
import MySecondChildContainer from '/containers/MySecondChildContainer';
const TabbedParent = ({activeTab, clickHandler}) =>
<div>
<Nav tabs>
<NavItem>
<NavLink
className={classnames({ active: activeTab === '1' })}
onClick={() => clickHandler('1')}
>
MyFirstTab
</NavLink>
</NavItem>
<NavItem>
<NavLink
className={classnames({ active: activeTab === '2' })}
onClick={() => clickHandler('2')}
>
MySecondTab
</NavLink>
</NavItem>
</Nav>
<TabContent activeTab={activeTab}>
<TabPane tabId="1">
<Row>
<Col sm="12">
<MyFirstChildComponent />
</Col>
</Row>
</TabPane>
<TabPane tabId="2">
<Row>
<Col sm="6">
<MySecondChildContainer />
</Col>
</Row>
</TabPane>
</TabContent>
</div>
const mapStateToProps = (state) => ({
activeTab: state.activeTab,
});
const mapDispatchToProps = dispatch => ({
clickHandler(id) { dispatch(TabClicked(id)) },
});
export connect(mapStateToProps, mapDispatchToProps)(TabbedParent);

Related

React : Error: Too many re-renders. React limits the number of renders to prevent an infinite loop

Im currently trying to import my Navbar into a new project. I created a Navbar using the state hook.
export default function Heading() {
const [isOpen, setIsOpen] = useState(false);
return (
<div>
<Navbar color="light" fixed="top" light expand="md">
<NavbarBrand href="/">reactstrap</NavbarBrand>
<NavbarToggler onclick={setIsOpen(!isOpen)} />
<Collapse isOpen={isOpen} navbar>
<Nav className="me-auto" navbar>
<NavItem>
<NavLink href="/components/">Components</NavLink>
</NavItem>
<NavItem>
<NavLink href="https://github.com/reactstrap/reactstrap">
GitHub
</NavLink>
</NavItem>
<UncontrolledDropdown inNavbar nav>
<DropdownToggle caret nav>
Options
</DropdownToggle>
<DropdownMenu end>
<DropdownItem>Option 1</DropdownItem>
<DropdownItem>Option 2</DropdownItem>
<DropdownItem divider />
<DropdownItem>Reset</DropdownItem>
</DropdownMenu>
</UncontrolledDropdown>
</Nav>
<NavbarText>Simple Text</NavbarText>
</Collapse>
</Navbar>
</div>
);
}
I am importing this in my App.tsx like this
import Heading from "./modules/Heading";
export default function App() {
return (
<div className="App">
<Heading/>
</div>
);
}
But im getting this error
Uncaught Error: Too many re-renders. React limits the number of renders to prevent an infinite loop.
Does anyone know how to fix this?
The issue is highly likely with the onclick in your NavbarToggler component.
Usually in React, onclick event handler usually takes in a FUNCTION, not a FUNCTION CALL. Therefore, you should change to
<NavbarToggler onClick={() => setIsOpen(currentState => !currentState)} />
This small change creates an arrow function that will do the same as before. However, the difference is that this is only a function definition, meaning it is not called yet (and is just existing)
It's missing an arrow function in NavbarToggler onClick Event . I guess will solve the problem

React Bootstrap Cards and CardDeck Components - responsive layout

I'm using React Bootstrap and am pulling data from an API using Axios. I'm trying to display the data from that API in bootstrap Card / CarDeck components. I have most of it working using the code below but I'm having problems with the layout.
I'd like to be able to have the card deck be responsive and show a flexible number of cards per row depending on the screen size. At the moment if there are lots of items in the response each card is ridiculously thin and can't be viewed. What is the correct way to do this?
import React, { useState, useEffect } from "react";
import CardDeck from "react-bootstrap/CardDeck";
import Card from "react-bootstrap/Card";
import axios from "axios";
import Container from "react-bootstrap/Container";
const CardColumns = () => {
const [cardInfo, setData] = useState([]);
console.log(cardInfo);
useEffect(() => {
axios
.get(
"https://webhooks.mongodb-realm.com/api/client/v2.0/app/cards-fvyrn/service/Cards/incoming_webhook/getAllCards"
)
.then((response) => {
setData(response.data);
console.log(response.data);
})
.catch(function (error) {
console.log(error);
});
}, []);
const renderCard = (card, index) => {
return (
<Card style={{ width: "18rem" }} key={index} className="box">
<Card.Img variant="top" src="holder.js/100px180" src={card.image} />
<Card.Body>
<Card.Title>
{index} - {card.manufacturer}
</Card.Title>
<Card.Text>{card.player}</Card.Text>
</Card.Body>
</Card>
);
};
return <CardDeck>{cardInfo.map(renderCard)}</CardDeck>;
};
export default CardColumns;
In the end this is the solution I went with the below. Key I think were:
Wrapping the cardDeck in <Container> and then wrapping the cards with <Col className="container-fluid mt-4">:
`
const renderCard = (card, index) => {
return (`
<Col className="container-fluid mt-4">
{/* <Card key={index} className="box"> */}
<Card style={{ width: "18rem" }} key={index} className="box">
<Card.Header>
{card.brand} - {card.series}
</Card.Header>
<Card.Img variant="top" src={card.front_image} fluid />
<Card.Body>
<Card.Title>
{card.player} (#
{card.card_number.$numberDouble}) {card.variation}
</Card.Title>
{/* <Card.Text className="d-flex">{card.player}</Card.Text> */}
{/* <Card.Text>{card.player}</Card.Text> */}
</Card.Body>
<ListGroup className="list-group-flush">
<ListGroupItem>
Print Run - {card.print_run.$numberDouble}
</ListGroupItem>
<ListGroupItem>Career Stage - {card.career_stage} </ListGroupItem>
<ListGroupItem>For Trade - {card.forTrade}</ListGroupItem>
</ListGroup>
<Card.Footer className="text-muted">{card.team}</Card.Footer>
</Card>
</Col>
//{/* </Col> */}
);
};
return (
<Container>
<Button variant="primary">Filter By Brand</Button>{" "}
<Button variant="primary">Filter By Player</Button>{" "}
<Button variant="primary">Filter By Team</Button>
<CardDeck>{cardInfo.map(renderCard)}</CardDeck>
</Container>
);`
Here are my suggestions for this issue:
Each card should have a min-width to ensure that they do not shrink below a certain amount. So instead of width: "18rem" try min-width: "18rem". If your CSS is properly set up it should cause other cards to overflow to the next row.
You can make use of media-queries or grid layout as mentioned to determine how many cards you want to show for various screen types based on their varying widths say see this link media-query-breakpoints react-bootstrap-grid
Also you can try using CSS flexbox layout, I have an article on this CSS Flex Box
Try to use a col-'n' to each card instead of a fix width.
...
import { Card, Col } from 'react-bootstrap';
...
<Col md="n"> // n must be your desired width like
<Card key={index} className="box">
...
</Col>
You can see more at: Bootstrap Card Sizing

Toggling Images for Active Links with React.js

I am currently trying to learn React.js for a school project and am having trouble with toggling images in combination with active links (I have searched high and low for a tutorial with no avail). My site is going to look similar to the old time Kingdom Hearts 1 menu theme; the Navbar looks like the following image.
As shown in the above image, the home page is hard-coded to be "active." What I need help with is making each link appear in this "active" state when selected/active. When a user clicks on one of the NavLinks, the image should go from grey to black/orange. When a link is not selected (or inactive), the image should go from black/orange back to grey. Active/selected links should also have black text while inactive links have grey. So far, all I have found out is how to toggle a singular class but not multiple. Overall, I really don't know how to go about this at all. How do I toggle one NavLink active and change its image while also changing all other NavLinks to inactive, changing their images as well? Any help would be much appreciated. I have the following code for my Navbar so far:
export class NavMenu extends Component {
static displayName = NavMenu.name;
constructor(props) {
super(props);
this.toggleNavbar = this.toggleNavbar.bind(this);
this.state = {
collapsed: true
};
}
toggleNavbar() {
this.setState({
collapsed: !this.state.collapsed
});
}
render() {
return (
<Navbar
fixed="bottom"
className="navbar-expand-sm navbar-toggleable-sm ng-white border-top mb-3"
light
>
<NavbarToggler onClick={this.toggleNavbar} className="mr-2" />
<Collapse
className="d-sm-inline-flex flex-sm-row-reverse"
isOpen={!this.state.collapsed}
navbar
>
<ul className="navbar-nav mr-auto">
<NavItem>
<NavLink
tag={Link}
id="navHome"
className="text-dark active"
to="/"
>
<img
src="/Images/NLUnactiveImg.png"
alt="Unactive Link Image"
height="45"
width="45"
hidden
/>
<img
src="/Images/NLActiveImg.png"
alt="Active Link Image"
height="45"
width="45"
/>
Home
</NavLink>
</NavItem>
<NavItem>
<NavLink
tag={Link}
d="navResume"
className="text-dark inactive"
to="/resume"
>
<img
src="/Images/NLUnactiveImg.png"
alt="Unactive Link Image"
height="45"
width="45"
/>
<img
src="/Images/NLActiveImg.png"
alt="Active Link Image"
height="45"
width="45"
hidden
/>
Resume
</NavLink>
</NavItem>
<NavItem>
<NavLink
tag={Link}
d="navContact"
className="text-dark inactive"
to="/contact"
>
<img
src="/Images/NLUnactiveImg.png"
alt="Unactive Link Image"
height="45"
width="45"
/>
<img
src="/Images/NLActiveImg.png"
alt="Active Link Image"
height="45"
width="45"
hidden
/>
Contact
</NavLink>
</NavItem>
<NavItem>
<NavLink
tag={Link}
d="navFetch"
className="text-dark inactive"
to="/fetch-data"
>
<img
src="/Images/NLUnactiveImg.png"
alt="Unactive Link Image"
height="45"
width="45"
/>
<img
src="/Images/NLActiveImg.png"
alt="Active Link Image"
height="45"
width="45"
hidden
/>
Fetch data
</NavLink>
</NavItem>
</ul>
</Collapse>
<div>
{' '}
<PlaySound child={this.state.child} />{' '}
</div>
</Navbar>
);
}
}
First of all remove you css (images) into a seperate css file. Make a class and put your css into that. Something like this
<NavLink tag={Link} d="navResume" className="text-dark inactive resumeImage" to="/resume">
Resume
</NavLink>;
in your css file
.resumeImage {
//declare your image styles here
}
Repeat above for all your Navlinks.
Now keep those styles which you only want to apply to the active link in a seperate class and apply that class to all the Navlinks like below
<NavLink
tag={Link}
d="navResume"
className="text-dark inactive resumeImage"
activeClassName="activestyleclass"
to="/resume"
>
Resume
</NavLink>;
You can check the current URL and change the image based on the url since active class should be depend on the url too.
You can use react hook useLocation to get current URL.
import { useLocation } from 'react-router-dom';
const Component= () => {
const [pathname, setPathname] = useState('/');
const location = useLocation();
useEffect(() => {
setPathname(location.pathname);
}, [location]);
return (
<nav className="navbar">
<NavLink to="/" className="navbar-item">
{pathname === '/' ? (
<img src="/images/navbar/home_active.png" alt="Home" />
) : (
<img src="/images/navbar/home.png" alt="Home" />
)}
</NavLink>
.....

How to show a loading spinner until component loads in React

My question is not as simple as the title. I'm trying to show a loading component for 1 second and then replace that component with the actual data.
Here is my code:
const TopNavigationAuth = () => (
<Container>
<Menu.Item
name="landing"
content="Landing"
as={Link}
to={routes.LANDING}
/>
<Menu.Item name="home" content="Home" as={Link} to={routes.HOME} />
<Menu.Menu position="right">
<Menu.Item
name="account"
content="Account"
as={Link}
to={routes.ACCOUNT}
/>
<UserSection />
<Menu.Item
name="signout"
content={
<Button
content="Sign Out"
onClick={authFunctions.doSignOut}
primary
/>
}
/>
</Menu.Menu>
</Container>
);
So here I have the <UserSection /> component which essentially just holds the user's picture and name (for now). I would like to load that component after 1 or 2 seconds but until then I would like to show a spinner instead.
I'm using semantic ui react for my app and they have a handy spinner which looks like this:
const LoaderExampleInlineCentered = () => <Loader active inline='centered' />
Can I please have some guidance with this?
You can conditionally render one of the two components, Loader Or UserSection.
this.state.profileExist === true ? <UserSection /> : <Loader />
Then initialize profileExist as a False in componentDid mount, then use setTimeout to set it to true
componentDidMount() {
this.setState({profileExist: false})
setTimeout(() => {
this.setState({profileExist: true})
}, 1000);
}

React implementation of Material Design with presentational components

Is it possible to use Material UI with stateless components, or is state a requirement?
I intended to implement Popovers, and from what I gathered from the official code example is that it's state-depenedent.
It depends on the Material UI component.
Some are ideal, and are recommended, as stateless components. In fact, many of the examples in the Material UI documentation use stateless components. For example, a <Badge /> component:
const BadgeExampleSimple = () => (
<div>
<Badge
badgeContent={4}
primary={true}
>
<NotificationsIcon />
</Badge>
<Badge
badgeContent={10}
secondary={true}
badgeStyle={{top: 12, right: 12}}
>
<IconButton tooltip="Notifications">
<NotificationsIcon />
</IconButton>
</Badge>
</div>
);
Or an <Icon />:
const HomeIcon = (props) => (
<SvgIcon {...props}>
<path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z" />
</SvgIcon>
);
Other components require them to be stateful to manage state like open.
And this is the case for the <Popover /> component unfortunately.
export default class PopoverExampleSimple extends React.Component {
constructor(props) {
super(props);
this.state = {
open: false,
};
}
...
}
So, to answer your questions:
Yes, it's possible to use a stateless component if the component does not require state
No, it's not a requirement to use a stateful component, unless the component requires state

Resources