Instead of styling the Li component via inline style, I want to do it via styled-component. How can I check the state and then assign the color red if the current Li is the selected language?
const Li = styled.li`
border: 0;
//Set color to red if this component is selected
${this.state => this.state.selectedLanguage`
color: 'red';
`}
`;
class Popular extends Component {
constructor(props) {
super(props);
this.state = {
selectedLanguage: 'All'
}
this.updateLanguage = this.updateLanguage.bind(this);
}
updateLanguage(lang) {
this.setState(function() {
return {
selectedLanguage: lang
};
});
}
render() {
const languages = ['All', 'Javascript', 'Java', 'Go', 'Rust'];
return (
<ul className={`list-group d-flex flex-row justify-content-center ${this.props.className}`}>
{languages.map(function(lang){
return (
<Li
key={lang}
// style={lang === this.state.selectedLanguage ? {color: '#d00d1b'} : null}
onClick={this.updateLanguage.bind(null, lang)}
className={`list-group-item p-2 ${this.props.className}`}>
{lang}
</Li>
)
}, this)}
</ul>
);
}
}
export default Popular;
code is based from tyler mcginnis - react fundamentals
Passing down isSelected as props then calling the css helper if true
const selectedLanguageStyle = css`
color: '#d00d1b'
`
const Li = styled.li`
border: 0;
${(props) => props.isSelected && selectedLanguageStyle}
`;
class Popular extends Component {
constructor(props) {
super(props);
this.state = {
selectedLanguage: 'All'
}
this.updateLanguage = this.updateLanguage.bind(this);
}
updateLanguage(lang) {
this.setState(function() {
return {
selectedLanguage: lang
};
});
}
render() {
const languages = ['All', 'Javascript', 'Java', 'Go', 'Rust'];
return (
<ul className={`list-group d-flex flex-row justify-content-center ${this.props.className}`}>
{languages.map(function(lang){
return (
<Li
key={lang}
isSelected={lang === this.state.selectedLanguage}
onClick={this.updateLanguage.bind(null, lang)}
className={`list-group-item p-2 ${this.props.className}`}>
{lang}
</Li>
)
}, this)}
</ul>
);
}
}
export default Popular;
Related
I have built a toggle component that shows one out of two components based on whether the Purchase is true or not. Now I want to select from 3 components and I'm struggling with refactoring this so it's clean and works. What is the best way to do this if I'm adding Component3 both to the toggle selectors and the component import?
import Component1 from "./component1";
import Component2 from "./component2";
class App extends Component {
constructor(props) {
super(props);
// this.toggle = this.toggle.bind(this);
this.state = {
popoverOpen: false,
isPurchase: true,
};
this.switchState = this.switchState.bind(this);
}
switchState(flag) {
this.setState({ isPurchase: flag });
}
render() {
return (
<div className={styles.cardBody}>
<div className={styles.row}>
<div className={styles.col12}>
<div
className={`${
styles.positionRelative
} ${styles.formGroup}`}>
<div
style={{
fontWeight:
"bolder",
color: "#7192a6",
}}>
<span
className={
this.state
.isPurchase
? `${
styles.switchState
}`
: `${
styles.switchState
} ${
styles.unselected
}`
}
onClick={() => {
this.switchState(
true,
);
}}>
Component1{" "}
</span>
/
<span
className={
this.state
.isPurchase
? `${
styles.switchState
} ${
styles.unselected
}`
: `${
styles.switchState
}`
}
onClick={() => {
this.switchState(
false,
);
}}>
{" "}
Component2
</span>
</div>
</div>
</div>
</div>
<div className={styles.row}>
<div className={styles.col12}>
<Component1
isPurchase={
this.state.isPurchase
}
/>
<Component2
isPurchase={
this.state.isPurchase
}
/>
</div>
</div>
</div>
);
}
}
And in the component itself I'm checking this in the state
class Component1 extends Component {
constructor(props) {
super(props);
this.state = {
isPurshase: props.isPurchase,
popoverOpen: false,
};
}
And inside the return display/hide this way
<div style={{ display: this.props.isPurchase ? "" : "none" }}>
You can do something like this to be more clean.
class App extends Component {
state = {
condition: 'condition1',
};
handleSwitch = () => {
const {condition} = this.state;
let newCondition = '';
switch (condition) {
case 'condition1':
newCondition = 'condition2';
break;
case 'condition2':
newCondition = 'condition3';
break;
case 'condition3':
newCondition = 'condition1';
break;
default:
newCondition = 'condition1';
}
this.setState({
condition: newCondition,
});
};
renderSwitch = () => {
<button onClick={() => this.handleSwitch()}> CHANGE </button>;
};
renderComponent = () => {
const {condition} = this.state;
switch (condition) {
case 'condition1':
return (<ComponentOne/>);
case 'condition2':
return (<ComponentTwo/>);
case 'condition3':
return (
<div>
<ComponentOne/>
<ComponentOne/>
</div>
);
default:
return null;
}
}
render() {
return (
<div>
{this.renderSwitch()}
{this.renderComponent()}
</div>
);
}
}
I try to build a to-do-list in react.
I have 2 components so far:
The first one handles the input:
import React from 'react';
import ListItems from './ListItems.js';
class InputComponent extends React.Component {
constructor(){
super();
this.state = {
entries: []
}
this.getText = this.getText.bind(this);
}
getText() {
if(this._inputField.value !== '') {
let newItem = {
text: this._inputField.value,
index: Date.now()
}
this.setState((prevState) => {
return {
entries: prevState.entries.concat(newItem)
}
})
this._inputField.value = '';
this._inputField.focus();
}
}
render() {
return(
<div>
<input ref={ (r) => this._inputField = r } >
</input>
<button onClick={ () => this.getText() }>Go</button>
<div>
<ListItems
entries={this.state.entries}
/>
</div>
</div>
)
}
}
export default InputComponent;
The second one is about the actual entries in the list:
import React from 'react';
class ListItems extends React.Component {
constructor() {
super();
this.lineThrough = this.lineThrough.bind(this);
this.listTasks = this.listTasks.bind(this);
}
lineThrough(item) {
console.log(item);
//item.style = {
// textDecoration: 'line-through'
//}
}
listTasks(item) {
return(
<li key = { item.index }>
<div
ref = { (r) => this._itemText = r }
style = {{
width: 50 + '%',
display: 'inline-block',
backgroundColor: 'teal',
color: 'white',
padding: 10 + 'px',
margin: 5 + 'px',
borderRadius: 5 + 'px'
}}
>
{ item.text }
</div>
<button onClick={ () => this.lineThrough(this._itemText) }>Done!</button>
<button>Dismiss!</button>
</li>
)
}
render() {
let items = this.props.entries;
let listThem = items.map( this.listTasks );
return(
<ul style = {{
listStyle: 'none'
}}>
<div>
{ listThem }
</div>
</ul>
)
}
}
export default ListItems;
As you can see, i want to have two buttons for each entry, one for the text to be line-through, and one to delete the entry.
I am currently stuck at the point where i try to address a specific entry with the "Done!" button to line-through this entry's text.
I set a ref on the div containing the text i want to style and pass that ref to the onClick event handler.
Anyways, the ref seems to be overwritten each time i post a new entry...
Now, always the last of all entries is addressed. How can i properly address each one of the entries?
What would be the best practice to solve such a problem?
you could pass an additional prop with index/key of the todo into every item of your todo list. With passing event object to your handler lineThrough() you can now get the related todo id from the attributes of your event target.
Kind regards
class TodoList extends Component {
constructor(props) {
super(props);
this.state = {
todos: []
}
this.done = this.done.bind(this);
}
done(id) {
this.state.todos[id].done();
}
render() {
return (
this.state.todos.map(t => <Todo item={t} onDone={this.done} />)
);
}
}
const Todo = ({item, onDone}) => {
return (
<div>
<h1>{item.title}</h1>
<button onClick={() => onDone(item.id)}>done</button>
</div>
)
}
You need map listItems then every list item get its own ref
import React from 'react';
import ReactDOM from 'react-dom';
class ListItems extends React.Component {
constructor() {
super();
this.lineThrough = this.lineThrough.bind(this);
}
lineThrough(item) {
item.style.textDecoration = "line-through";
}
render() {
return(
<ul style = {{
listStyle: 'none'
}}>
<div>
<li key={this.props.item.index}>
<div
ref={(r) => this._itemText = r}
style={{
width: 50 + '%',
display: 'inline-block',
backgroundColor: 'teal',
color: 'white',
padding: 10 + 'px',
margin: 5 + 'px',
borderRadius: 5 + 'px'
}}
>
{this.props.item.text}
</div>
<button onClick={() => this.lineThrough(this._itemText)}>Done!</button>
<button>Dismiss!</button>
</li>
</div>
</ul>
)
}
}
class InputComponent extends React.Component {
constructor(){
super();
this.state = {
entries: []
}
this.getText = this.getText.bind(this);
}
getText() {
if(this._inputField.value !== '') {
let newItem = {
text: this._inputField.value,
index: Date.now()
}
this.setState((prevState) => {
return {
entries: prevState.entries.concat(newItem)
}
})
this._inputField.value = '';
this._inputField.focus();
}
}
render() {
return(
<div>
<input ref={ (r) => this._inputField = r } >
</input>
<button onClick={ () => this.getText() }>Go</button>
<div>
{this.state.entries.map((item, index) => {
return <ListItems key={index} item={item} />
})}
</div>
</div>
)
}
}
Every time I click an option of size and click add to cart I would like to add the data of the selected object to this array cart. This currently works kinda but only one object can be added and when you try to do it again the old data disappears and is replaced with the new object.
I would like to keep odd objects in the array and add new objects too. How do I go about doing this?
index.js
export class App extends Component {
constructor(props) {
super(props);
this.state = {
evenSelected: null
};
}
handleSelectL1 = i => {
this.setState({
evenSelected: i,
oldSelected: null
});
};
render() {
const product = [
{
name: " size one",
price: 1
},
{
name: "size two",
price: 2
},
,
{
name: "size three",
price: 3
}
];
const cart = [];
const addCart = function() {
cart.push(product[evenIndex]);
if (cart.length > 0) {
}
};
console.log("cart", cart);
const evenIndex = this.state.evenSelected;
const priceShown = product[evenIndex] && product[evenIndex].price;
return (
<div>
<Child
product={product}
handleSelectL1={this.handleSelectL1}
evenIndex={evenIndex}
/>
<h2>Price:{priceShown} </h2>
<button onClick={addCart}>Add to cart</button>
</div>
);
}
}
child.js
export class Child extends Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
const { product, evenIndex } = this.props;
return (
<div>
{product.map((p, i) => {
return (
<div
key={p.id}
className={evenIndex === i ? "selectedRBox" : "selectorRBox"}
onClick={() => this.props.handleSelectL1(i)}
>
<h1 className="selectorTextL">{p.name}</h1>
</div>
);
})}
</div>
);
}
}
Here is my code on sandbox: https://codesandbox.io/s/14vyy31nlj
I've just modified your code to make it work. Here is the complete code. You need cart to be part of the state, so it does not initialize in each render, and to make the component render again when you add an element.
Remove the function to make it a method of the class:
addToCart() {
const selectedProduct = products[this.state.evenSelected];
this.setState({
cart: [...this.state.cart, selectedProduct]
});
}
And call it on render:
render() {
console.log("cart", this.state.cart);
const evenIndex = this.state.evenSelected;
const priceShown = products[evenIndex] && products[evenIndex].price;
return (
<div>
<Child
product={products}
handleSelectL1={this.handleSelectL1}
evenIndex={evenIndex}
/>
<h2>Price:{priceShown} </h2>
<button onClick={this.addToCart.bind(this)}>Add to cart</button>
</div>
);
}
}
Check that I have binded on render, which can bring performance issues in some cases. You should check this
Update
As devserkan made me notice (Thanks!), when you use the previous state to define the new state (for example adding an element to an array), it is better to use the updater function instead of passing the new object to merge:
this.setState(prevState => ({
cart: [...prevState.cart, products[selectedProduct]],
}));
For more info check the official docs.
I don't quite understand what are you trying to but with a little change here it is. I've moved product out of the components like a static variable. Also, I've changed the addCart method, set the state there without mutating the original one and keeping the old objects.
const product = [
{
name: " size one",
price: 1
},
{
name: "size two",
price: 2
},
{
name: "size three",
price: 3
}
];
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
evenSelected: null,
cart: [],
};
}
handleSelectL1 = i => {
this.setState({
evenSelected: i,
oldSelected: null
});
};
addCart = () => {
const evenIndex = this.state.evenSelected;
this.setState( prevState => ({
cart: [ ...prevState.cart, product[evenIndex] ],
}))
};
render() {
console.log(this.state.cart);
const evenIndex = this.state.evenSelected;
const priceShown = product[evenIndex] && product[evenIndex].price;
return (
<div>
<Child
product={product}
handleSelectL1={this.handleSelectL1}
evenIndex={evenIndex}
/>
<h2>Price:{priceShown} </h2>
<button onClick={this.addCart}>Add to cart</button>
</div>
);
}
}
class Child extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
const { product, evenIndex } = this.props;
return (
<div>
{product.map((p, i) => {
return (
<div
key={p.id}
className={evenIndex === i ? "selectedRBox" : "selectorRBox"}
onClick={() => this.props.handleSelectL1(i)}
>
<h1 className="selectorTextL">{p.name}</h1>
</div>
);
})}
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
.selectorRBox {
width: 260px;
height: 29.5px;
border: 1px solid #727272;
margin-top: 18px;
}
.selectedRBox {
width: 254px;
height: 29.5px;
margin-top: 14px;
border: 4px solid pink;
}
.selectorTextL {
font-family: "Shree Devanagari 714";
color: #727272;
cursor: pointer;
font-size: 18px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
How can i add class to single li element using onClick? At the moment when i click on whatever li, all div's are getting item-active class when state is changed.
I do have index from mapped array, but i'm not sure where (i believe in handleClick()?) should i use it to make it working...
//import {cost} from '...';
export class CostFilter extends Component {
constructor(props) {
super(props);
this.state = {active: "item-not-active"};
this.handleClick = this.handleClick.bind(this);
}
handleClick(){
let isActive = this.state.active === "item-not-active" ? "item-active" : "item-not-active";
this.setState({active: isActive});
}
render() {
return (
<ul>
{cost.map((element, index) =>
<li onClick={this.handleClick} value={`cost-${element.cost}`} key={index}>
<div className={`hs icon-${element.cost} ${this.state.active}`}></div>
</li>
)}
</ul>
);
}
}
Try something like this:
export class CostFilter extends Component {
constructor(props) {
super(props);
this.state = {activeIndex: null};
}
handleClick(index) {
let activeIndex = this.state.activeIndex === index ? null : index;
this.setState({activeIndex});
}
render() {
return (
<ul>
{cost.map((element, index) =>
<li onClick={this.handleClick.bind(this, index)} value={`cost-${element.cost}`} key={index}>
<div className={`
hs
icon-${element.cost}
${this.state.activeIndex === index && 'item-active'}
`}></div>
</li>
)}
</ul>
);
}
}
You can store index of the clicked element in the state of your component and then check in your render function in map if the current index is equal to the key in the state. Something like this :
let data = ['Item 1', 'Item 2', "Item 3"];
class Test extends React.Component {
constructor(){
this.state = {
active: null
}
}
handleClick(i){
this.setState({active: i});
}
render(){
return <ul>{this.props.data.map((item, i) => <li key={i} className={this.state.active === i ? 'active' : ''} onClick={this.handleClick.bind(this, i)}>{item}</li>)}</ul>
}
}
React.render(<Test data={data}/>, document.getElementById('container'));
Here is a fiddle.
I hope this is what are you looking for. fiddle
const data = ['Hello', 'World']
class Example extends React.Component {
constructor(){
this.state = {
isActive: -1
}
}
click(key,e){
this.setState({
isActive: key
})
}
render(){
const costsList = this.props.costs.map((item, key) => {
return <li key={key}
className={this.state.isActive === key ? 'foo' : ''}
onClick={this.click.bind(this, key)}>
{item}
</li>
})
return <ul>
{costsList}
</ul>
}
}
React.render(<Example costs={data}/>, document.getElementById('container'));
Trying to simplify this with React Hooks ( check example on codepen )
const MyApp = (props)=>{
const [activeIndex, setActiveIndex] = React.useState(null)
let activeStyle = {backgroundColor:"red", width:"400px", height:"22px", margin: "2px"}
let normalStyle = {backgroundColor:"blue", width:"400px", height:"22px", margin: "2px"}
const handleClick = (i)=>{
setActiveIndex(i)
}
let output= ["item-1", "item-2", "item-3", "item-4", "item-5", "item-6", "item-7", "item-8", "item-9", "item-10"];
return(
<div>
{output.map((v, i)=>{
return (
<li key={i} style={activeIndex === i ? activeStyle: normalStyle}>
{i} : {v}
<button onClick={()=>handleClick(i)} name={i}>Change background color</button>
</li>)
})}
</div>
)
}
I have a Notification class to manage creating/removing Notifications from the UI. I can successfully show/hide notifications, but the notifications do not animate in/out.
import React from 'react'
import addons from 'react/addons'
const ReactCSSTransitionGroup = React.addons.CSSTransitionGroup
let id = 0
export default class Notification {
constructor (content, className) {
let div = document.createElement('div')
document.body.appendChild(div)
let onClose = () => React.unmountComponentAtNode(div)
React.render(
<NotificationElement className={ className } id={ id++ } onClose={ onClose }>
{ content }
</NotificationElement>,
div
)
}
}
class NotificationElement extends React.Component {
constructor(_) {
super(_)
}
render() {
const className = `Notification ${ this.props.className }`
return (
<ReactCSSTransitionGroup transitionName="slideIn">
<div className={ className } key={ this.props.id }>
{ this.props.children }
<a className="close-button" onClick={ this.props.onClose }>×</a>
</div>
</ReactCSSTransitionGroup>
)
}
}
ReactCSSTransitionGroup will only animate its children when they are added or removed from its props. In your case, the children prop of your ReactCSSTransitionGroup never changes.
Here's an example of what you could do instead:
let notificationsContainer = document.createElement('div');
document.body.appendChild(notificationsContainer);
let notifications = [];
function renderNotifications() {
React.render(<Notifications notifications={notifications} />, notificationsContainer);
}
class Notifications extends React.Component {
render() {
return (
<ReactCSSTransitionGroup transitionName="slideIn">
{this.props.notifications.map(notification =>
<NotificationElement
key={ notification.id }
className={ notification.className }
onClose={ notification.onClose }
children={ content }
/>
)}
</ReactCSSTransitionGroup>
);
}
}
let id = 0
export default class Notification {
constructor (content, className) {
let notification = {
id: id++, content, className,
};
notification.onClose = () => {
notifications.splice(notifications.indexOf(notification), 1);
renderNotifications();
};
notifications.push(notification);
renderNotifications();
}
}
class NotificationElement extends React.Component {
constructor(_) {
super(_)
}
render() {
const className = `Notification ${ this.props.className }`
return (
<div className={ className }>
{ this.props.children }
<a className="close-button" onClick={ this.props.onClose }>×</a>
</div>
)
}
}
Every time a notification is added or removed, its corresponding element is added or removed from the children prop of ReactCSSTransitionGroup, which can then animate it in or out properly.
You can add transitionAppear={true} to your <ReactCSSTranstionGroup /> to make it animate the initial render.
It is disabled by default: https://github.com/facebook/react/blob/master/src/addons/transitions/ReactCSSTransitionGroup.js#L38