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
Related
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;
I would like to give Border-bottom as a function. What should I do?
This is the code that Border-bottom should appear.👇
import React from "react";
import { Header } from "../BaseLabel";
import { Link, withRouter } from "react-router-dom";
const Header = ({ location: { pathname } }) => {
const getStyle = (path) => {
return {
color: pathname === path ? "#191919" : "#B6B6B6",
borderBottom: pathname === path ? "#191919" : null,
};
};
return (
<>
<ShapMenu>
<ShapLinks to="/covid19" style={getStyle("/covid19")}> //Link
<Header title="코로나19" current={pathname === "/covid19"} />
</ShapLinks>
</ShapMenu>
</>
);
}
This is Header Styled-components👇
const ShapMenu = styled.div`
display: flex;
box-sizing: content-box;
overflow-x: scroll;
overflow-y: hidden;
white-space: nowrap;
scroll-behavior: smooth;
scrollbar-color: inherit;
cursor: pointer;
`;
const ShapLinks = styled(Link)``;
This is a reusable component code. This code is not only used on this screen because it is a reuse code.👇
import PropTypes from "prop-types";
import styled from "styled-components";
import React from "react";
export const Header = ({ title, children }) => {
return (
<>
<Title>{title}</Title>
<Items>{children}</Items>
</>
);
};
Header.propTypes = {
title: PropTypes.node,
children: PropTypes.object,
};
const Items = styled.div``;
const Title = styled.div`
margin-right: 14px;
font-size: 20px;
`;
This is the style property that I want to give to the title.👇
border-bottom: 2px solid
${(props) => (props.current ? "#191919" : "transparent")};
transition: border-bottom 0.5s ease-in-out;
The CSS styled rules appear to be correct. You should pass the current prop from Header to Title.
const Header = ({ current, title, children }) => { // <-- destructure current
return (
<>
<Title current={current}>{title}</Title> // <-- pass current prop
<Items>{children}</Items>
</>
);
};
Header.propTypes = {
children: PropTypes.object,
current: PropTypes.bool,
title: PropTypes.node
};
const Title = styled.div`
margin-right: 14px;
font-size: 20px;
border-bottom: 2px solid
${(props) => (props.current ? "#191919" : "transparent")}; // <-- use current prop
transition: border-bottom 0.5s ease-in-out;
`;
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])
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
I'm trying to get a small example of using react-dnd with styled-components and it works! However, it seems to have completely broken PropTypes.
Which seems pretty strange to me. I have several working components with each and this worked before introducing react-dnd, so I'm baffled to say the least.
As mentioned, the code works as expected in browser and console.logging the props and their types shows that they are indeed present and of valid type.
Here's my code:
import React from 'react';
import PropTypes from 'react';
import styled from 'styled-components';
import { DragSource } from 'react-dnd';
class StyleTest extends React.Component {
constructor(props) {
super(props);
console.log(Object.keys(props).map(p => (typeof props[p])));
// prints ["string", "string", "function"]
}
render() {
const { color, title, connectDragSource } = this.props;
return (
<Wrapper color={color}>
<h1>HUZZAAAAH {title}</h1>
<Button ref={instance => connectDragSource(instance)}>Make boring</Button>
<CoolerButton>Make cool</CoolerButton>
</Wrapper>
);
}
}
StyleTest.propTypes = {
color: PropTypes.string,
title: PropTypes.string,
connectDragSource: PropTypes.func
};
export default DragSource(
'button_drag',
{
beginDrag: () => {
return {id: 42};
}
},
(connect) => ({
connectDragSource: connect.dragSource()
})
)(StyleTest);
let Wrapper = styled.div`
height: 200px;
background-color: peachpuff;
h1 {
color: ${props => props.color};
}
`;
let Button = styled.div`
width: 200px;
margin: 5px;
padding: 5px 10px;
text-align: center;
border: 2px solid grey;
`;
let CoolerButton = styled(Button)`
border-color: orange;
border-radius: 10px;
`;