I'm creating, in componentDidMount, a lots of <div>'s.
constructor (props) {
super(props)
this.state = {
componentLoaded: false,
divs: []
}
}
componentDidMount () {
this.createDivs()
}
createDivs () {
// Actually, this divs are created dinamically and with infinite scroll
let divs = <div className='container'>
<div className='item' onClick={() => { /* Add class */ }}>...</div>
<div className='item' onClick={() => { /* Add class */ }}>...</div>
<div className='item' onClick={() => { /* Add class */ }}>...</div>
/* ... n divs ... */
</div>
let newDivs = this.state.divs
newDivs.push(divs)
this.setState({
componentLoaded: true,
divs: newDivs
})
}
render () {
return {this.state.componentLoaded ? this.state.divs : null }
/* In my return, if X event occurs, re-call this.createDivs() to add more divs */
}
What I'm trying to achieve, is to toggle a class into only one of the .item divs, and then if clicking another one, remove it from the before and add it to the one was clicked.
I've tried to add an attribute to the state, but it didn't add it. I also searched for some solutions, but I always find solutions which doesn't toggle, as they are "toggled individually" in separated components.
Hoping to find some help, maybe this thing is real simple, but for now, I cannot figure out how to make it.
PS: I'm adding the createDivs into the state because it's an infinite scroll that re-uses the function, so I just push them into the state and the scroll won't go to the top again when adding the previous ones + the new ones.
In problems like these it is always helpful to determine what goes into react's state. You want the state to be as lightweight as possible (so you store only the stuff which is necessary)
class Test extends React.Component {
state = {
selectedDiv: null,
};
handleClick = id => {
this.setState(prev => ({
// sets it to null if its already active else, sets it active
selectedDiv: prev.selectedDiv === id ? null : id,
}));
};
render() {
// Array to map over
const divs = [1, 2, 3, 4];
const { selectedDiv } = this.state;
return (
<div className="container">
{divs.map(div => {
return (
<div
key={div}
className={selectedDiv === div ? "item class_to_add" : "item"}
onClick={() => this.handleClick(div)}
>Item {div}</div>
);
})}
</div>
);
}
}
In the above examples we are only storing the unique Id of the div in the state and using that to determine if the selected div is active or not, if it is then we simply remove it from the state. The above solution does not require any complex lifecycle methods, my advice would be to keep the component as simple as possible.
PS. not part of the answer but I suggest you to look into the newer hooks API its more intuitive and most probably the future of react
First, note that you're breaking a React rule here:
this.state.divs.push(divs)
You must never directly modify state. The correct thing there is either:
this.setState({divs}); // Replaces any previous ones
or
this.setState(({divs: oldDivs}) => {divs: [...oldDivs, divs]}); // Adds to any previous ones
However, the "React way" to do this would probably be not to store those divs in state at all; instead, store the information related to them in state, and render them (in render) as needed, with the appropriate classes. The information about which one of them has the class would typically either be information on the items themselves, or some identifying information about the item (such as an id of some kind) held in your component's state.
Here's an example using items that have an id:
class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
// No items yet
items: null,
// No selected item yet
selectedId: null
};
}
componentDidMount() {
this.createDivs();
}
createDivs() {
// Simulate ajax or whatever
setTimeout(() => {
const items = [
{id: 42, label: "First item"},
{id: 12, label: "Second item"},
{id: 475, label: "third item"},
];
this.setState({items});
}, 800);
}
render () {
const {items, selectedId} = this.state;
if (!items) {
// Not loaded yet
return null;
}
return (
<div className='container'>
{items.map(({id, label}) => (
<div
key={id}
className={`item ${id === selectedId ? "selected" : ""}`}
onClick={() => this.setState({selectedId: id})}
>
{label}
</div>
))}
</div>
);
}
}
ReactDOM.render(<Example />, document.getElementById("root"));
.selected {
color: green;
}
.item {
cursor: pointer;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js"></script>
Related
Let's say i have a tabbed component. Each tab presents a person object. The user can switch the active tab, modify or remove each one and even change their order:
state={
activeTab:0,
tabs:[
{
name:'John',
age:34
},
{
name:'Bob',
age:31
},
]
}
Let's say i want to modify one of the fields, of a specific tab(person). I could have this function:
modifyTab = (index, prop, value) => {
const tabs = [...this.state.tabs];
const tab = tabs[index];
tab[prop] = value;
this.setState({ tabs })
}
The problem with this, is that it relies on the index of the given tab. But what if the tab index changes, if i provide, let's say, the ability of switching the tab order?(like the browsers do for their tabs).
Or what if i need to register some callback for an a-sync operation, that might be called when the relevant person sits in a different tab(maybe the tab was moved from 1 to 0, by the time the callback was called)?
Is there any way to just rely on object reference, regardless of id, index or any other "identifier", which makes the code much more complicated than it needs to be?
For those who are familiar with VueJS and Angular, i'm sure you know how easy it is to modify objects, being that you do not need to return a new state tree on each change.
If you are changing the order of the array, you cannot rely on the array index for the key prop when you are rendering. One common way around this is to add a unique property to every object in the array and use that instead, e.g. an id.
Passing in the entire object reference to modifyTab would be perfectly fine. You could figure out what object in the array that is with a simple indexOf.
Example
class App extends React.Component {
state = {
tabs: [
{
name: "John",
age: 34,
id: 1
},
{
name: "Bob",
age: 31,
id: 2
}
]
};
modifyTab = (tab, prop, value) => {
this.setState(prevState => {
const tabs = [...prevState.tabs];
const index = tabs.indexOf(tab);
tabs[index] = { ...tabs[index], [prop]: value };
return { tabs };
});
};
render() {
return (
<div>
{this.state.tabs.map(tab => (
<span
key={tab.id}
onClick={() => this.modifyTab(tab, "name", Math.random())}
style={{ margin: '0 10px' }}
>
{tab.name}
</span>
))}
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
I have three files: ShopsContainer.js ShopsComponent.js and ShopsItemComponent.js
ShopsContainer maintains an array of shop items in local state that gets passed down into ShopsComponent as props. ShopsComponent then maps through the items array that is being received as props and renders a ShopsItemComponent for each item in the array.
Within my ShopsContainer file, I have a method that removes a shop item from state using the following code:
removeShop = (shopAccount) => {
this.setState(prevState => ({
items: prevState.items.filter(shop => {
return shop.shopAccount !== shopAccount
})
}));
}
When this happens, the correct item is removed from the items array in state, however, whatever the last ShopItem is that is in the DOM at the time of the removeShop call will get removed no matter if it is the correct item that should be removed or not. In other words, when removeShop gets called and the items array in state gets updated correctly, the wrong ShopItemComponent gets removed from the DOM.
What I would like to happen (or what I think should happen) is when removeShop gets called, that shop gets removed from the items array in state and ShopsContainer re-renders causing ShopsComponent to re-render with the updated props being received. And lastly ShopsComponent would map through the newly updated items array in props displaying a `ShopItemComponent for the correct items. Perhaps the problem has to do with the props being updated?
My code is as follows:
ShopsContainer.js
class ShopsContainer extends Component {
constructor() {
this.state = {
items: null
}
this.getAll();
this.removeShop = this.removeShop.bind(this);
}
getAll = () => {
// API request that fetches items and updates state
}
removeShop = (shopAccount) => {
this.setState(prevState => ({
items: prevState.items.filter(shop => {
return shop.shopAccount !== shopAccount
})
}));
}
render() {
return (
<div>
{this.state.items ? <ShopComponent items={this.state.items} removeShop={this.removeShop} /> : <div><h1>Loading...</h1></div>}
</div>
);
}
}
ShopsComponent.js
class ShopsComponent extends Component {
constructor() {
this.handleRemove = this.handleRemove.bind(this);
}
handleRemove = (shopAccount) => {
this.props.removeShop(shopAccount);
}
render() {
return (
<React.Fragment>
<Header />
{this.props.items.map((shopItem, i) => {
return (<ShopItemComponent key={i} item={shopItem} removeShop={this.handleRemove} />);
})}
</React.Fragment>
);
}
}
Your code is working great, but you only has one mistake , your ShopComponent is assign index as a key for each ShopItemComponent and react is tracking those indexes to update the correct component, so you need to set key as a unique value between items, then I realize that shopAccount should be your id for each item.
The solution code is below.
class ShopsComponent extends Component {
handleRemove = (shopAccount) => {
this.props.removeShop(shopAccount);
}
render() {
return (
<React.Fragment>
<Header />
{this.props.items.map((shopItem) => <ShopItemComponent key={shopItem.shopAccount} item={shopItem} removeShop={this.handleRemove} />)}
</React.Fragment>
);
}
}
I hope you can find useful.
Note, when you are using a arrow function into your class, don't bind that method into the constructor, so remove it, because
handleRemove = (shopAccount) => {
this.props.removeShop(shopAccount);
}
is already binded.
reproduced here: https://jsfiddle.net/69z2wepo/204131/
A parent component has two 'notifications' that it renders with different 'decay' rates.
class Page extends React.Component {
constructor(props) {
super(props);
this.state = {
notifications: [
{ message: "I am the first component", code: 1, decay: 2000 },
{ message: "I am the second component", code: 2, decay: 5000 }
]
}
this.dismissNotification = this.dismissNotification.bind(this)
}
dismissNotification(code) {
this.setState({ notifications: this.state.notifications.filter(
n => n.code != code
)})
}
render() {
return (
<div>
{
this.state.notifications.map( (n, idx) => {
return (
<Notification
key={idx}
code={n.code}
decay={n.decay}
dismiss={this.dismissNotification}
>
{n.message}
</Notification>
)
})
}
</div>
)
}
}
The components set their own timeOut which will cause an animation and then send a message for them to be dismissed.
class Notification extends React.Component {
constructor(props) {
super(props)
this.state = {
style: { opacity: 1 }
}
this.makeRedFunction = this.makeRedFunction.bind(this)
}
componentDidMount = () => {
let timeout = parseInt(this.props.decay) || 2000
setTimeout(() => {
this.makeRedFunction();
setTimeout(() => {
this.dismiss();
}, 125)
}, timeout)
}
fadeOutFunction = () => {
let opacity = Math.floor(this.state.style.opacity * 10)
if (opacity > 0) {
opacity -= 1
setTimeout( () => { this.fadeOutFunction() }, 10)
}
let newState = Object.assign({}, this.state.style)
newState.opacity = opacity / 10
this.setState({ style: newState })
}
makeRedFunction = () => {
this.setState({ style: {color: 'red'} })
}
dismiss = () => {
this.props.dismiss(this.props.code)
}
render () {
return(
<div style={this.state.style}>{this.props.children}</div>
)
}
}
ReactDOM.render(
<Page/>,
document.getElementById('container')
);
Unforunately, the style seems to change for both notifications when the dismiss function has been called for only one of them.
In general there is strange behavior with the mounting lifecycle of the components with this approach.
tl;dr: Don't use array indexes as keys if elements in the list have state. Use something that is unique for each data point and does not depend on its position in the array. In your case that would be key={n.code}.
This is related to how React reconciles the component tree and is a good example for why using array index as keys doesn't always produce the expected outcome.
When you are mutating a list of elements, the key helps React to figure out which nodes it should reuse. In your case are going from
<Notification />
<Notification />
to
<Notification />
But how should React know whether to delete the first or second <Notification /> node? It does that by using keys. Assume we have
<Notification key="a">Foo</Notification>
<Notification key="b">Bar</Notification>
Now if it gets either
<Notification key="a">...</Notification>
or
<Notification key="b">...</Notification>
in the next render cycle it knows to remove the <Notification /> with key "b" (or "a").
However, your problem is that you base the key on the position of the data in the array. So on the first render you pass
<Notification key="0">First</Notification>
<Notification key="1">Second</Notification>
Then you are removing the first notification from the list, which changes the position of the second notification in the array, so React gets
<Notification key="0">Second</Notification>
which means
remove the element with key 1 and update the element with key 0 to show "Second"
But the element with key="0" already had its style changed to red text, so you see the text from the second notification in red.
Have a look at the documentation for more information.
So, React newbie here... I'll start off by saying I have a simple single page application which consists of a few simple pages.
Using react-router I have a 'top-down' set up for my components. To give you a basic idea of my SPA structure see below:
index -- layout(react routers) --
|--About Page
|--Home Page
|--Contact Page
I am rendering a component called "GlobalHero" from my Home Page component.
Here is the GlobalHero.jsx component.
import React from "react";
var classNames = require('classnames');
import s from '../../../index.scss';
class GlobalHero extends React.Component {
constructor() {
super();
//sets initial state
this.state = {
fadeIn: "",
titleSelected: "",
subTitleSelected: ""
};
}
// <<========= ON COMPONENT RENDER =========
componentDidMount = () => {
console.log("GlobalHero");
console.log(this.props);
this.handleClass("fadeIn");
}
// =========>>
// <<========= CONTROLS THE STATE =========
handleClass = (param) => {
if (param === "fadeIn" && this.state.fadeIn != "true") {
this.setState({fadeIn: "true"});
}
if (param === "titleSelected" && this.state.titleSelected != "true") {
this.setState({titleSelected: "true"});
}
if (param === "subTitleSelected" && this.state.subTitleSelected != "true") {
this.setState({subTitleSelected: "true"});
}
}
// =========>>
render() {
const heroImg = require(`../../../images/hero${this.props.page}.jpg`);
//REMOVES CLASS IN REALTIME BASED ON STATE'S VALUE =========
var containerClasses = classNames({
[s['text-center']]: true,
[s["hidden"]]: this.state.fadeIn != "true",
[s["fadeIn"]]: this.state.fadeIn === "true"
});
var titleClasses = classNames({
[s['blue']]: this.state.titleSelected === "true"
});
var subTitleClasses = classNames({
[s['subTitle']]: true,
[s['text-center']]: true,
[s['blue']]: this.state.subTitleSelected === "true"
});
// =========>>
return (
<div className={s["container-fluid"]}>
<div className={s["row"]}>
<div className={s["col-lg-16"]}>
<div className={containerClasses}>
<img src={heroImg} className={s["hero__img"]}></img>
<h1 onClick={() => this.handleClass("titleSelected")} className={titleClasses}>{this.props.page}!</h1>
<p className={subTitleClasses} onClick={() => this.handleClass("subTitleSelected")}>{this.props.name}, {this.props.age}, {this.props.city}</p>
</div>
</div>
</div>
</div>
);
}
}
export default GlobalHero;
I noticed there is a lot of complexity there for assigning a few simple class names to the component's elements.
I was wondering if there is a better practice for doing this? Maybe
using an external js page to manage my classnames?
Any input or adivce is appreciated... Thankyou in adnvance.
Your title mentions BEM but it looks like you are using CSS Modules, which is inspired by similar ideas but not the same thing.
Anyway, this is quite subjective but I have a few thoughts that are too much to fit in a comment:
Assuming you are using css modules through Webpack's css-loader, you can use camelCase to make your style properties more JS friendly:
loader: "css-loader?modules&camelCase"
Now for .text-center css class name you can simply use s.textCenter instead of s["test-center"].
You could componentize this better: first, you are kind of doing a lot for a single component, but you could break it down into a few smaller components that each have a single responsibility (for example container, title, subtitle). Second, your handleClass() method is doing a lot, when you could just have simple handlers that call setState() without knowing anything about class names. In other words, the component should have props and state, only the render() function deals with how to translate that into class names to render. You also really don't need to check the state's current value before setting it. Just set it to what it should be and let React optimize rendering performance for you.
You have boolean state flags that you store using strings "true" and "false"... this makes it noisy to handle, just store as booleans.
You have a lot of [s["class-name"]]: true which is not necessary; if you always want a class name to be rendered just pass it as an argument to classNames:
classNames(s.subTitle, { [s.blue]: this.state.subTitleSelected })
There's no reason to call a handler on componentDidMount, just initialize the state how you want it.
It looks like you're using bootstrap CSS but not the React Bootstrap components. I would highly recommend using React Bootstrap.
Putting that together I'd have something like:
class GlobalHero extends React.Component {
state = {
fadeIn: true,
titleSelected: false,
subTitleSelected: false
};
handleTitleClick = () => {
this.setState({titleSelected: true});
};
handleSubTitleClick = () => {
this.setState({subTitleSelected: true});
};
render() {
return (
<Grid fluid>
<Row>
<Col lg={16}>
<HeroContainer fadeIn={this.state.fadeIn}>
<HeroImage page={this.props.page} />
<HeroTitle selected={this.state.titleSelected}
onClick={this.handleTitleClick}
page={this.props.page} />
<HeroSubTitle selected={this.state.subTitleSelected}
onClick={this.handleSubTitleClick}
name={this.props.name}
age={this.props.age}
city={this.props.city} />
</HeroContainer>
</Col>
</Row>
</Grid>
);
}
}
const HeroContainer = ({fadeIn, children}) => {
return (
<div className={classNames(s.textCenter, fadeIn ? s.fadeIn : s.hidden)}>
{children}
</div>
);
};
const HeroImage = ({page}) => {
const heroImg = require(`../../../images/hero${page}.jpg`);
return (
<img src={heroImg} className={s.heroImg} />
);
};
const HeroTitle = ({onClick, selected, page}) => (
<h1 onClick={onClick} className={selected ? s.blue : null}>{page}!</h1>
);
const HeroSubTitle = ({onClick, selected, name, age, city}) => (
<p className={classNames(s.subTitle, s.textCenter, { [s.blue]: selected })} onClick={onClick}>
{name}, {age}, {city}
</p>
);
Breaking it into smaller components like this is not completely necessary, but notice how from the perspective of GlobalHero it does nothing with styles, it just sets props and state, and the little parts have no state, they just render the correct styles based on props.
PS maybe this should move to Code Reviews?
I have a basic component that looks as follows.
class List extends React.Component {
constructor() {
super(...arguments);
this.state = {
selected: null,
entities: new Map([
[0, { 'name': 'kot'} ],
[1, { 'name': 'blini'} ]
])
};
}
render() {
return (<div>
<ul>{this.renderItems()}</ul>
</div>)
}
renderItems() {
return Array.from(this.state.entities.entries()).map(s => {
const [ id, entry ] = s;
return <li
key={id}
onClick={() => this.setState(state => ({ selected: id }))}
style={{
color: id === this.state.selected ? 'red' : 'black'
}}
>{entry.name}</li>
})
}
}
This works in order to allow me to click on any element and select it. A selected element will appear red. codepen for easy editing.
However, I want behavior that will unset any currently selected item if a click event was found that was not one of these <li> elements.
How can this be done in React?
In your List component, You can add
componentDidMount() {
window.addEventListener("click", (e) => {
let withinListItems = ReactDOM.findDOMNode(this).contains(e.target);
if ( ! withinListItems ) {
this.setState({ selected: null });
}
});
}
And in your renderItems, change onClick to
onClick={ (e) => {
// e.stopPropagation();
this.setState({ selected: id });
}
}
You can checkout this codepen http://codepen.io/anon/pen/LRkzWd
Edit:
What #kubajz said is true, and hence i have updated the answer.
Random User's answer is correct, it may have one flaw - it relies on stopPropagation and there is possibility that some piece of code may no longer work as expected - imagine collecting user's behaviour on page and sending metrics somewhere - stopPropagation will prevent bubbling, thus click is not recorded. Alternative approach is to check what was clicked in event.target: http://codepen.io/jaroslav-kubicek/pen/ORXxkL
Also there is nice utility component for listening on document level: react-event-listener