Styled Components Selected Nav Item - reactjs

I am trying to learn Styled Component's rendering based off props. I am just trying to create a simple Nav bar. Yes, I do realize that you can set links active by using React Router based off the location. This is more of just a learning experience for me.
class Nav extends Component {
constructor (props) {
super(props)
this.state = {
isActive: 0
}
this.handleClick = this.handleClick.bind(this)
}
handleClick (n) {
console.log('Hello There')
this.setState = {
isActive: n
}
}
render () {
const NAV = styled.div`
display: flex;
flex-direction: row;
justify-content: space-between;
width: 100%;
background-color: black;
`
const SPAN = styled.span`
font-size: 3.5vmin;
cursor: pointer;
color: white;
${props =>
props.selected &&
css`
color: cornflowerblue;
`}
`
const TEXT = styled.p``
return (
<NAV links={this.props.links}>
{this.props.links.map((i, ii) => (
<SPAN
onClick={e => this.handleClick(ii)}
key={ii}
selected={this.state.isActive === ii ? true : ''}
>
<TEXT>{i}</TEXT>
</SPAN>
))}
</NAV>
)
}
}
I am trying to make the link active by changing it's text color. When I map over the links I provide the Nav, if the index of the array if equal to the active state, I make that link active.
Then, onClick, I update the state's isActive to the index that was selected. Needless to say, it's not working. I guess the map's index is only available at render and not at the onClick event. I am not sure what to pass the handleClick function, however.
The App is rendering on my local environment just fine. I made a Codepen with the example, but it's not rendering on there. I've never used Codepen for React, here is the link: https://codepen.io/anon/pen/daqGQQ
Also, I realize I can use the className prop to make a CSS class to give the selected link an active status, but, I'd rather learn the 'styled components' way.
Any help would be much appreciated. Thanks
EDIT
Reformat based on comment:
import React, { Component } from 'react'
import styled, { css } from 'styled-components'
const NAV = styled.div`
display: flex;
flex-direction: row;
justify-content: space-between;
width: 100%;
background-color: black;
`
const SPAN = styled.span`
font-size: 3.5vmin;
cursor: pointer;
color: white;
${props =>
props.selected &&
css`
color: cornflowerblue;
`}
`
const TEXT = styled.p``
class Nav extends Component {
constructor (props) {
super(props)
this.state = {
isActive: 0
}
this.handleClick = this.handleClick.bind(this)
}
handleClick (e) {
console.log('Hello There')
console.log(e.target.key)
this.setState = {
isActive: 1
}
}
render () {
return (
<NAV links={this.props.links}>
{this.props.links.map((i, ii) => (
<SPAN
id={ii}
onClick={e => this.handleClick(e)}
key={ii}
selected={this.state.isActive === ii ? true : ''}
>
<TEXT>{i}</TEXT>
</SPAN>
))}
</NAV>
)
}
}
export default Nav

I figured it out by using e.currentTarget.textContent and then setting the state isSelected: e.currentTarget.textContent onClick. Code:
import React, { Component } from 'react'
import styled, { css } from 'styled-components'
const NAV = styled.div`
display: flex;
flex-direction: row;
justify-content: space-between;
width: 100%;
background-color: black;
`
const SPAN = styled.span`
font-size: 3.5vmin;
cursor: pointer;
color: white;
${props =>
props.selected &&
css`
color: cornflowerblue;
`}
`
const TEXT = styled.p``
class Nav extends Component {
constructor (props) {
super(props)
this.state = {
isSelected: 'Home'
}
this.handleClick = this.handleClick.bind(this)
}
componentDidMount () {}
handleClick (e) {
this.setState({
isSelected: e.currentTarget.textContent
})
}
render () {
return (
<NAV links={this.props.links}>
{this.props.links.map((i, ii) => (
<SPAN
id={ii}
onClick={e => this.handleClick(e)}
key={ii}
selected={this.state.isSelected === i ? true : ''}
>
<TEXT>{i}</TEXT>
</SPAN>
))}
</NAV>
)
}
}
export default Nav

Related

handling events on custom components

I have these files:
button.module.css
.base {
display: flex;
font-size: 16px;
font-family: "Helvetica Neue";
align-items: center;
justify-content: center;
padding: 6px 12px 6px 12px;
width: 50px;
border-radius: 6px;
}
.normal {
composes: base;
color:black;
background-color: #aeeeb7;
border: 1px solid #42924d;;
}
.danger {
composes: base;
color:black;
background-color: rgb(253, 128, 128);
border: 1px solid rgb(196, 108, 108);
}
button.js
import classes from './button.module.css';
const ButtonTypes = {
'normal': classes.normal,
'danger': classes.danger
};
const Button = (props) => {
const className = ButtonTypes[props.type] ? ButtonTypes[props.type] : classes.normal;
return <div role="button" className={className} ...props>{text}</div>;
}
export default Button;
app.js
import Button from './button';
const App = () => {
const handleClick = () => {
console.log('app');
}
const handleMouseOver = () => {
console.log('mouse-over');
}
...
return (
<div>
<Button type="normal" onClick=(handleClick) onMouseOver={handleMouseOver} ...>
</div>
);
}
export default Button;
I am changing the class of the button depending on the "type" property. (It could be other components not just a button)
How should I handle events on the custom component? Is the way I am passing them right now to that div inside fine or I should do it differently?
I want to create some custom components and I am trying to verify I am doing it the right way.
One suggestion here. Instead of spreading all props in button. You can destructure out the required props and then spread the rest props.
import classes from "./button.module.css";
const ButtonTypes = {
normal: classes.normal,
danger: classes.danger
};
const Button = ({ type, ...rest }) => {
const className = ButtonTypes[type] ? ButtonTypes[type] : classes.normal;
return (
<div role="button" className={className} {...rest}>
{text}
</div>
);
};
export default Button;

React hooks component reloading

I'm building a typescript react app that has a child component called Accordion that when is clicked it is opened. When is opened it renders a table with some data. This accordion is made depending on a group that can be changed with a selector. My problem is that I want that when I change this group by my Accordion component closes if it's opened. I tried to pass a prop to close the Accordion but nothing occurs and I'm starting to be frustrated. How can I reload this component in order for the state to be closed? That's my code:
This is my Accordion component:
import React, { useState, useRef, Fragment, ReactChildren, ReactNode } from "react";
import Chevron from "./Chevron"
interface accordionPropsType {
title: string
children: ReactNode
}
const Accordion = (props: accordionPropsType) => {
const [setActive, setActiveState] = useState("");
const [setHeight, setHeightState] = useState("0px");
const [setRotate, setRotateState] = useState("accordion__icon");
const content = useRef(null);
const toggleAccordion = () => {
setActiveState(setActive === "" ? "active" : "");
setHeightState(setActive === "active" ? "0px" : `${content.current.scrollHeight}px`);
setRotateState(setActive === "active" ? "accordion__icon" : "accordion__icon rotate");
}
return(
<Fragment>
<div className="accordion__section">
<button className={`accordion ${setActive}`} onClick={toggleAccordion}>
<p className="accordion__title">{props.title}</p>
<Chevron className={`${setRotate}`} width={10} color={"#777"} />
</button>
<div
ref={content}
style={{ maxHeight: `${setHeight}` }}
className="accordion__content"
>
{props.children}
</div>
</div>
<style jsx>
{`
/* Style the accordion section */
.accordion__section {
display: flex;
flex-direction: column;
margin: 10px;
}
/* Style the buttons that are used to open and close the accordion panel */
.accordion {
background-color: #eee;
color: #444;
cursor: pointer;
padding: 18px;
display: flex;
align-items: center;
border: none;
outline: none;
transition: background-color 0.6s ease;
}
/* Add a background color to the button if it is clicked on (add the .active class with JS), and when you move the mouse over it (hover) */
.accordion:hover,
.active {
background-color: #ccc;
}
/* Style the accordion content title */
.accordion__title {
font-family: "Open Sans", sans-serif;
font-weight: 600;
font-size: 14px;
text-align: left;
}
/* Style the accordion content panel. Note: hidden by default */
.accordion__content {
background-color: white;
overflow: auto;
transition: max-height 0.6s ease;
margin: 5px;
}
/* Style the accordion content text */
.accordion__text {
font-family: "Open Sans", sans-serif;
font-weight: 400;
font-size: 14px;
padding: 18px;
}
`}
</style>
</Fragment>
);
}
export default Accordion;
And this is the component that calls this child component:
import React, { useState, Fragment, useEffect, FormEvent } from "react"
import Select from "../components/Select"
import Accordion from "../components/Accordion"
import Table from "../components/Table"
interface findingsType {
body: object
header: Array<headersType>
}
interface headersType {
className: string
rows: Array<rowType>
}
interface rowType {
className: string
rowspan: number
colspan: number
text: string
}
const CloudFindingsList = (props) => {
const [groupBy, setGroupBy] = useState<Array<string>>([]);
const [tableData, setTableData] = useState<findingsType>(null);
const headerRows = [] as Array<rowType>
const headers = [{
className: "thead_custom" as string,
rows: headerRows
}] as Array<headersType>
console.log('eee')
const getGroupBy = (event) => {
let distinctGroupsBy = []
let allFindings = {}
props.findings.map(finding => {
let value = finding[event.target.value]
distinctGroupsBy.includes(value) ? '' : distinctGroupsBy.push(value)
})
distinctGroupsBy.map(order => {
allFindings[order] = []
})
props.findings.map(finding => {
let value = finding[event.target.value]
distinctGroupsBy.map(order => {
value == order ? allFindings[order].push(finding) : ''
})
});
setGroupBy(distinctGroupsBy)
console.log(groupBy)
Object.keys(allFindings[distinctGroupsBy[0]][0]).map(value => {
headerRows.push({
className: "" as string,
rowspan: 0 as number,
colspan: 0 as number,
text: value as string
})
})
setTableData({
header: headers,
body: allFindings
} as findingsType)
}
const listFindings =
groupBy.map((group, index) => {
return(
<Accordion title={group} key={index}>
<Table jsonData={tableData.body[group]} header={tableData.header}/>
</Accordion>
)
})
return(
<Fragment>
<Select
id='1'
options={[{"val": "severity", "text": "severity"}, {"val": "account", "text": "account"}, {"val": "service", "text": "service"}]}
placeholder='Group by'
handleChange={getGroupBy as () => {}}
/>
{listFindings}
</Fragment>
);
}
export default CloudFindingsList
You don't have to understand all the code I just want that when I change the selected item in the selector the Accordion is closed again. Does anyone see the solution?
Thanks!
You could try to use a useEffect hook passing the props of the Accordion as change parameter. Every time that prop change you execute the code that changes the value.
useEffect(() => {
// YOUR CODE GOES HERE
}, [props])

Styled-components: Styles are not applied when trying to style already styled component

I'm trying to style my component which was styled already. But styles in the new rules are not applied in output CSS.
Can I style component that I already styled?
Thanks you in advance for your help.
EDIT: Add rest of LanugageChooser definition
// COMPONENT THAT I'M TRYING TO STYLE
const LanguageChooser = () => {
const Container = styled.div`
display: flex;
align-items: center;
height: 36px;
& > div:not(:last-child) {
margin-right: 5px;
}
`;
return (
<Container>
<Flag { ...languages.pl }/>
<Flag { ...languages.en }/>
</Container>
)
}
const Flag = ({ flag, language }) => {
const { i18n } = useTranslation();
const Button = styled.div`
cursor: pointer;
font-size: 24px;
transition: .2s all;
&:hover {
font-size: 36px;
}
`;
return (
<Button onClick={ () => i18n.changeLanguage(language) }>{ flag }</Button>
)
}
// TRYING ADD MARGIN LEFT, BUT THERE IS NO RESULT.
// ANY OTHER CSS PROPERTY ARE NOT APPLYING
const Navbar = ({ color }) => {
...
const StyledLanguageChooser = styled(LanguageChooser)`
margin-left: auto;
`;
const Nav = styled.nav`
display: flex;
align-content:center;
background: ${ color };
padding: 2px 3px;
`;
return (
<Nav className="navbar">
<StyledNavLink to="/home">...</StyledNavLink>
<StyledNavLink to="/maps">...</StyledNavLink>
<StyledNavLink to="/charts">...</StyledNavLink>
<StyledLanguageChooser/>
</Nav>
)
}
First, move the styled component outside of function scope or your style will reset on every render and you will get heavy performance issues.
Secondly, in order to apply styles, you need to pass className property.
See Styling normal React components
If you use the styled(MyComponent) notation and MyComponent does not render the passed-in className prop, then no styles will be applied.
const Container = styled.div`
display: flex;
align-items: center;
height: 36px;
& > div:not(:last-child) {
margin-right: 5px;
}
`;
const LanguageChooser = ({ className }) => {
return (
<Container className={className}>
<Flag {...languages.pl} />
<Flag {...languages.en} />
</Container>
);
};
const Button = styled.div`
cursor: pointer;
font-size: 24px;
transition: 0.2s all;
&:hover {
font-size: 36px;
}
`;
const Flag = ({ flag, language }) => {
const { i18n } = useTranslation();
return <Button onClick={() => i18n.changeLanguage(language)}>{flag}</Button>;
};

Why is my navbar "inexistant" for my other components?

I just created a react app. First thing first, I wanted to make a navbar for the left side that will be accessible on every page. So far so good, it's working well, my issue arrises when I started to create my first page: it keeps clipping under my navbar, and nothing I do gets it out of under the bar, this is driving me insane. Here's the current state of the code...
App.js
class App extends Component {
render() {
return(
<Router>
<SideNavBar />
<Switch>
<Route exact path={"/"} component={HomePage} />
</Switch>
</Router>
);
}
}
Navbar
class SideNavBar extends Component {
constructor(props) {
super(props);
this.state = {
currentPath: props.location.pathname,
};
}
onClick (path) {
this.setState({ currentPath: path });
}
render() {
const { currentPath } = this.state;
const navItems =
[
{
path: "/",
css: "fas fa-home"
}, {
path: "/user",
css: "fas fa-user"
},
];
return(
<StyledSideNavBar>
{
navItems.map((item, index) => {
return (
<NavItem
item={item}
active={item.path === currentPath}
onClick={this.onClick.bind(this)}
key={index}
/>
);
})
}
</StyledSideNavBar>
);
}
}
Styled Navbar
const StyledSideNavBar = styled.div`
display: flex;
flex-direction: column;
justify-content: flex-start;
position: fixed;
height: 100vh;
width: 5rem;
top: 0;
left: 0;
padding-top: 1.5rem;
background-color: #EEEEEE;
`;
Navitem
class NavItem extends Component {
render() {
const { item, active, onClick } = this.props;
return(
<StyledNavItem active={active}>
<Link to={item.path} className={item.icon} onClick={() => onClick(item.path)} />
</StyledNavItem>
);
}
}
Styled Navitem
const StyledNavItem = styled.div`
display: flex;
flex-direction: row;
justify-content: center;
margin-bottom: 1.5rem;
a {
font-size: 2.7em;
color: ${(props) => props.active ? "#8394F5" : "black"};
:hover {
opacity: 0.7;
text-decoration: none;
}
}
`;
HomePage
class HomePage extends Component {
render() {
return (
<StyledHomePage>
{"Hi {user}!hhhhhhhhhhhhhhhhhhhhhh"}
</StyledHomePage>
);
}
}
Styled HomePage
const StyledHomePage = styled.div`
display: "flex",
margin: "5rem 5rem 0 5rem"
`;
The problem arises when you give postion: fixed to your NavBar, instead you should create a fluid design and remove fixed position. Let me know if you need more help in it.

react all list items get re-rendered

I have my state structured like this. It's an object with multiple fields inside of it. For certain purposes, I cannot modify the structure of the state. Here's my component which renders the entire List
import React, { Component } from 'react'
import styled from 'styled-components';
import FoodListItem from '../Food-List-Item'
const Wrapper = styled.div`
width: 300px;
max-width: 300px;
`
const Button = styled.button`
width: 300px;
text-align: center;
padding: 1em;
border: 1px solid #eee;
background-color: #ffffff;
text-transform: uppercase;
font-size: 1em;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.45s ease-in-out, border 0.45s ease-in-out;
:hover {
background-color: #eee;
border: 1px solid black;
}
`
class FoodList extends Component {
state = {
data: {
e5d9d9f5: {
label: 'ice cream',
isDelicious: true,
isHealthy: false,
},
a9ba692b: {
label: 'pizza',
isDelicious: true,
isHealthy: false,
},
ze128a47: {
label: 'spinach',
isDelicious: false,
isHealthy: true,
},
},
}
renderListItems = () => {
const { data } = this.state
return Object.keys(data).map((key) => {
return <FoodListItem
key={key}
{...data[key]}
id={key}
handleDecliousChange={this.handleDecliousChange}
handleHealthyChange={this.handleHealthyChange}
/>
})
}
handleDecliousChange = (id) => {
this.setState(state => ({
data: {
...state.data,
[id]: {
...state.data[id],
isDelicious: !state.data[id].isDelicious
}
}
}))
}
handleHealthyChange = (id) => {
this.setState(state => ({
data: {
...state.data,
[id]: {
...state.data[id],
isHealthy: !state.data[id].isHealthy
}
}
}))
}
handleShowAppState = () => {
console.log(this.state.data)
}
render() {
return (
<Wrapper>
{this.renderListItems()}
<Button type="button" onClick={this.handleShowAppState}>Show App State</Button>
</Wrapper>
)
}
}
export default FoodList;
Here's the component which renders a single list item
import React from 'react'
import styled from 'styled-components'
const Title = styled.p`
text-transform: uppercase;
font-weight: bold;
`
const Item = styled.div`
padding: 1em 1em 1em 0em;
padding-left: ${props => props.isDelicious ? '30px': '0px'}
margin-bottom: 2em;
background-color: ${props => props.isHealthy ? 'green' : 'gray'};
transition: background-color 0.45s ease-in-out, padding-left 0.45s ease-in-out;
color: #ffffff;
`
class FoodListItem extends React.PureComponent {
deliciousFn = () => {
this.props.handleDecliousChange(this.props.id)
}
healthyFn = (id) => {
this.props.handleHealthyChange(this.props.id)
}
render() {
console.log('render called', this.props.label);
const {
id,
label, isDelicious, isHealthy,
handleDecliousChange, handleHealthyChange
} = this.props
return (
<Item isHealthy={isHealthy} isDelicious={isDelicious}>
<Title>{label}</Title>
<div>
<input type="checkbox" checked={isDelicious} onChange={this.deliciousFn} />
<label><code>isDelicious</code></label>
</div>
<div>
<input type="checkbox" checked={isHealthy} onChange={this.healthyFn} />
<label><code>isHealthy</code></label>
</div>
</Item>
)
}
}
export default FoodListItem
Whenever I click on a single list item, it re-renders all of them. Is there a way to avoid this? Ideally, only the row which was clicked on should re-render.
You should implement shouldComponentUpdate to handle component’s output in FoodListItem:
Use shouldComponentUpdate() to let React know if a component’s output
is not affected by the current change in state or props. The default
behavior is to re-render on every state change, and in the vast
majority of cases you should rely on the default behavior.
class FoodListItem extends React.Component {
//...
shouldComponentUpdate(nextProps) {
return (
this.props.isDelicious !== nextProps.isDelicious ||
this.props.isHealthy !== nextProps.isHealthy
)
}
//...
}
Reference: https://reactjs.org/docs/react-component.html#shouldcomponentupdate
Or consider using the PureComponent: https://reactjs.org/docs/react-api.html#reactpurecomponent

Resources