React add class active on selected tab - reactjs

I have the following:
var Tab = React.createClass({
getInitialState: function(){
return {
selected:''
}
},
activateTab: function(e) {
e.preventDefault();
$('.navigation--active').removeClass('navigation--active');
this.setState({selected : true});
},
render: function() {
var isActive = this.state.selected === true ? 'navigation--active': '';
return (
<li onClick={this.activateTab} className={isActive}>
<p>
{this.props.content}
</p>
</li>
);
}
});
var Tabs = React.createClass({
render: function() {
var tabs = [],
total = this.props.data.points.total,
handleClick = this.handleClick;
total.forEach(function(el, i){
tabs.push(
<Tab content = {el.name}
key = {i}/>
);
});
return (
<ul className="navigation">
{tabs}
</ul>
);
}
});
however it only works when you click once on every tab, if you click the second time on the same tab the class doesn't get added anymore

In this case, would be better move state management to parent component Tabs, and pass to child only props which you need to detect class name or set new state in parent
var Tab = React.createClass({
render: function() {
return <li
className={ this.props.isActive ? 'navigation--active': '' }
onClick={ this.props.onActiveTab }
>
<p>{ this.props.content }</p>
</li>
}
});
var Tabs = React.createClass({
getInitialState: function() {
return { selectedTabId: 1 }
},
isActive: function (id) {
return this.state.selectedTabId === id;
},
setActiveTab: function (selectedTabId) {
this.setState({ selectedTabId });
},
render: function() {
var total = this.props.data.points.total,
tabs = total.map(function (el, i) {
return <Tab
key={ i }
content={ el.name }
isActive={ this.isActive(el.id) }
onActiveTab={ this.setActiveTab.bind(this, el.id) }
/>
}, this);
return <ul className="navigation">
{ tabs }
</ul>
}
});
const data = {
points: {
total: [
{ id: 1, name: 'tab-1', text: 'text' },
{ id: 2, name: 'tab-2', text: 'text-2' },
{ id: 3, name: 'tab-3', text: 'text-2' }
]
}
}
ReactDOM.render(
<Tabs data={ data } />,
document.getElementById('container')
);
.navigation {}
.navigation--active {
color: red;
}
<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="container"></div>

Necro poster here.
Cool answer up above!
In any way, here is the 2018 upgrade answer with recompose and styled-components. You can even make a HOC out of it for a joyful reusability!
https://codesandbox.io/s/1826454zl7
import React from "react";
import ReactDOM from "react-dom";
import { compose, withState, withHandlers } from "recompose";
import styled from "styled-components";
const enhancer = compose(
withState("selectedTabId", "setSelectedTabId", 1),
withHandlers({
isActive: props => id => {
return props.selectedTabId === id;
},
setActiveTab: props => id => {
props.setSelectedTabId(id);
}
})
);
const Tabs = enhancer(props => {
return (
<ul>
{props.data.map((el, i) => {
return (
<Tab
key={i}
content={el.name}
isActive={props.isActive(el.id)}
onActiveTab={() => props.setActiveTab(el.id)}
/>
);
})}
</ul>
);
});
const Tab = props => {
return (
<StyledLi isActive={props.isActive} onClick={props.onActiveTab}>
<p>{props.content}</p>
</StyledLi>
);
};
const StyledLi = styled.li`
font-weight: ${({ isActive }) => (isActive ? 600 : 100)};
cursor: pointer;
font-family: Helvetica;
transition: 200ms all linear;
`;
const data = [
{ id: 1, name: "tab-1", text: "text" },
{ id: 2, name: "tab-2", text: "text-2" },
{ id: 3, name: "tab-3", text: "text-2" }
];
const ExampleApp = () => <Tabs data={data} />;
ReactDOM.render(<ExampleApp />, document.getElementById("app"));
Basic idea is that you need to get selected index, map over the item on every click, compare selected index with all other indexes and return true to props of a needed component if the match is found.

Related

change text of a specific button when clicked in React

I want to change the text of a specific button when I click on that button in React. But the issue is when I click the button the title will change for all buttons!
class Results extends Component {
constructor() {
super();
this.state = {
title: "Add to watchlist"
}
}
changeTitle = () => {
this.setState({ title: "Added" });
};
render() {
return (
<div className='results'>
{
this.props.movies.map((movie, index) => {
return (
<div className='card wrapper' key={index}>
<button className='watchListButton' onClick={this.changeTitle}>{this.state.title}</button>
</div>
)
})
}
</div>
)
}
}
You would need to come up with a mechanism to track added/removed titles per movie. For that, you would have to set your state properly. Example:
this.state = {
movies: [
{id: 1, title: 'Casino', added: false},
{id: 2, title: 'Goodfellas', added: false}
]
This way you can track what's added and what's not by passing the movie id to the function that marks movies as Added/Removed. I have put together this basic Sandbox for you to get you going in the right direction:
https://codesandbox.io/s/keen-moon-9dct9?file=/src/App.js
And here is the code for future reference:
import React, { Component } from "react";
import "./styles.css";
class App extends Component {
constructor() {
super();
this.state = {
movies: [
{ id: 1, title: "Casino", added: false },
{ id: 2, title: "Goodfellas", added: false }
]
};
}
changeTitle = (id) => {
this.setState(
this.state.movies.map((item) => {
if (item.id === id) item.added = !item.added;
return item;
})
);
};
render() {
const { movies } = this.state;
return (
<div className="results">
{movies.map((movie, index) => {
return (
<div className="card wrapper" key={index}>
{movie.title}
<button
className="watchListButton"
onClick={() => this.changeTitle(movie.id)}
>
{movie.added ? "Remove" : "Add"}
</button>
</div>
);
})}
</div>
);
}
}
export default App;

How to add a class to an image by click?

How to add a class to an image if the flag is done: true? As I have not tried, the class is added to all images, and not to those with true...
import React, { useState } from "react";
import "./styles.css";
export default function App() {
const [avatarArr, setAvatarArr] = useState({
avatar: [
{
id: 1,
url: "https://starwars-visualguide.com/assets/img/characters/1.jpg"
},
{
id: 2,
url: "https://starwars-visualguide.com/assets/img/characters/2.jpg"
},
{
id: 3,
url: "https://starwars-visualguide.com/assets/img/characters/3.jpg"
}
]
});
const [classUser, setClassUser] = useState(null);
const [selectUser, setSelectUser] = useState(false);
const onAddClass = id => {
if (avatarArr.avatar.find(items => items.id === id)) {
const index = avatarArr.avatar.findIndex(items => items.id === id);
setAvatarArr([
...avatarArr.slice(0, index),
...avatarArr.slice(index + 1)
]);
} else {
setAvatarArr([...avatarArr, { done: true }]);
setSelectUser(avatarArr.avatar.map(items => items.done));
if (selectUser) {
setClassUser("active__user");
}
}
};
const blockCreate = () => {
return avatarArr.avatar.map(items => {
return (
<div key={items.id}>
<img
src={items.url}
alt="avatar"
width="150px"
onClick={() => onAddClass(items.done, items.id)}
className={selectUser ? classUser : null}
/>
</div>
);
});
};
return (
<div className="App">
<div>{blockCreate()}</div>
</div>
);
}
<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>
I'm trying to set true on click, to tell the user that the avatar that was clicked on is selected, and add some kind of styling class.
And if you click a second time, then true - becomes false, in short - the choice
Are you looking like this
export default function App() {
const [avatarArr, setAvatarArr] = useState({
avatar: [
{
id: 1,
url: "https://starwars-visualguide.com/assets/img/characters/1.jpg"
},
{
id: 2,
url: "https://starwars-visualguide.com/assets/img/characters/2.jpg"
},
{
id: 3,
url: "https://starwars-visualguide.com/assets/img/characters/3.jpg"
}
]
});
const [selectUser, setSelectUser] = useState(false);
const onAddClass = item => {
setSelectUser(item);
};
const blockCreate = () => {
return avatarArr.avatar.map(items => {
return (
<div key={items.id}>
<img
src={items.url}
alt="avatar"
width="150px"
onClick={() => onAddClass(items)}
className={selectUser.id === items.id ? "myClass" : ""}
/>
</div>
);
});
};
return (
<div className="App">
<div>{blockCreate()}</div>
</div>
);
}
Live working demo https://codesandbox.io/s/vigilant-almeida-169zj

react-photo-gallery don't change photos via state

The photo object is not changing in the Gallery component via state.
I created a "folder gallery", a multi gallery component to render a new one if you select it by click.
See the demonstration:
https://codesandbox.io/s/50wr02n8q4
github issue:
https://github.com/neptunian/react-photo-gallery/issues/118
Envs:
react-photo-gallery: 6.2.2
react: 16.6.3
npm: 6.4.1
See the code below:
const folderPhotos = [
{
title: "Gallery one",
photos: [
{
src: "https://source.unsplash.com/2ShvY8Lf6l0/800x599"
},
{
src: "https://source.unsplash.com/Dm-qxdynoEc/800x799"
},
{
src: "https://source.unsplash.com/qDkso9nvCg0/600x799"
}
]
},
{
title: "Gallery two",
photos: [
{
src: "https://source.unsplash.com/iecJiKe_RNg/600x799"
},
{
src: "https://source.unsplash.com/epcsn8Ed8kY/600x799"
},
{
src: "https://source.unsplash.com/NQSWvyVRIJk/800x599"
}
]
}
];
The photoProps dimensions object:
const photoProps = {
width: 1,
height: 1
};
The internal component here:
<Gallery
columnsLength={6}
photosObj={folderPhotos}
photoProps={photoProps}
withLightbox={true}
/>
Now the Gallery component:
import _ from "lodash";
import React, { Component, Fragment } from "react";
import Gallery from "react-photo-gallery";
import Lightbox from "react-images";
export class PhotoGallery extends Component {
constructor(props) {
super(props);
// Bindables
this.showGallery = this.showGallery.bind(this);
this.openLightbox = this.openLightbox.bind(this);
this.closeLightbox = this.closeLightbox.bind(this);
this.goToPrevious = this.goToPrevious.bind(this);
this.goToNext = this.goToNext.bind(this);
}
state = {
photosObj: [],
currentImage: 0,
lightboxIsOpen: false
};
async showGallery(itemObj, photoProps) {
let photosObj = [];
if (!_.isEmpty(itemObj)) {
photosObj = await Object.keys(itemObj)
.map(i => itemObj[i])
.map(item => ({
...item,
width: photoProps.width,
height: photoProps.height
}));
this.setState({
photosObj
});
console.log("-- props: ", this.state.photosObj);
}
}
openLightbox(event, obj) {
this.setState({
currentImage: obj.index,
lightboxIsOpen: true
});
}
closeLightbox() {
this.setState({
currentImage: 0,
lightboxIsOpen: false
});
}
goToPrevious() {
this.setState({
currentImage: this.state.currentImage - 1
});
}
goToNext() {
this.setState({
currentImage: this.state.currentImage + 1
});
}
render() {
const { columnsLength, photosObj, photoProps, withLightbox } = this.props;
return (
<div className="section-body">
<div className="content-col-info">
<ul className="list-mentors w-list-unstyled">
{photosObj.map((itemObj, i) => (
<li key={i}>
<span
className="mentors-item w-inline-block"
onClick={() => this.showGallery(itemObj.photos, photoProps)}
>
<div>{itemObj.title}</div>
</span>
</li>
))}
</ul>
</div>
<div className="content-col-info">
{!_.isEmpty(this.state.photosObj) && (
<Gallery
columns={columnsLength}
photos={this.state.photosObj}
onClick={withLightbox ? this.openLightbox : null}
/>
)}
{!_.isEmpty(this.state.photosObj) && withLightbox && (
<Lightbox
images={this.state.photosObj}
onClose={this.closeLightbox}
onClickPrev={this.goToPrevious}
onClickNext={this.goToNext}
currentImage={this.state.currentImage}
isOpen={this.state.lightboxIsOpen}
/>
)}
</div>
</div>
);
}
}
export default PhotoGallery;
EDIT - I updated the Gallery props
I fix the Gallery component props of the example too.
loadGallery(columnsLength) {
import("./photo-gallery").then(Gallery => {
console.log("-- load gallery: ", this.state.photosObj);
return (
<Gallery.default
columnsLength={columnsLength}
photosObj={this.state.photosObj}
withLightbox={true}
/>
);
});
}
And to call this:
{!_.isEmpty(this.state.photosObj) && this.loadGallery(columnsLength)}
References:
Intro to Dynamic import() in Create React App
React images
Since photos option is required in Gallery.js (Library)
Gallery.propTypes = {
photos: _propTypes2.default.arrayOf(_Photo.photoPropType).isRequired,
direction: _propTypes2.default.string,
onClick: _propTypes2.default.func,
columns: _propTypes2.default.number,
margin: _propTypes2.default.number,
ImageComponent: _propTypes2.default.func
};
Pass "photos={this.state.photosObj}" in <Gallery /> of Gallery.js (your js file) as follows:
Code:
<Gallery
columnsLength={columnsLength}
photosObj={this.state.photosObj}
photos={this.state.photosObj}
withLightbox={true}
/>
I am not sure why you pass other options.

Cannot Find what is causing this : Warning: setState(...): Can only update a mounted or mounting component

So after looking over many different questions asking the about this warning, I have found that there is not one single reason why this would be occurring making it difficult to infer a solution on my own code.
So here is my code incase anyone has another pair of eyes they can put on it and spot maybe why i would be getting this error.
This error occurs when not on first arrival of the component but after leaving and returning to it again.
I have a smart container and a dumb component set up so here is the container:
import React, { PropTypes } from 'react';
import { connect } from 'react-redux';
import { listOrders, listUseCases } from '../../actions/order';
import { storeWithExpiration } from '../../utils/common.js';
import OrdersIndex from './OrdersIndex';
export class OrdersIndexContainer extends React.Component {
static propTypes = {
account: PropTypes.object.isRequired,
orders: PropTypes.object.isRequired,
platformMap: PropTypes.object.isRequired,
progress: PropTypes.number,
listOrdersAction: PropTypes.func.isRequired,
listUseCasesAction: PropTypes.func.isRequired,
};
render() {
const { orders, platformMap, progress } = this.props;
return (
<div>
<OrdersIndex
orders={ orders }
platformMap={ platformMap }
progress={ progress }
/>
</div>
);
}
renderOrderIndex = () => {
}
componentWillMount = () => {
const { account, listOrdersAction, listUseCasesAction } = this.props;
const token = storeWithExpiration.get('token');
listOrdersAction(token);
listUseCasesAction(account.id, token);
}
}
function mapStateToProps(state) {
const { account, orderList, progress } = state;
const orders = orderList.get('orders');
const platformMap = orderList.get('platformMap');
return { account, platformMap, orders, progress };
}
export default connect(mapStateToProps, {
listOrdersAction: listOrders,
listUseCasesAction: listUseCases,
})(OrdersIndexContainer);
And here is the dumb component:
import React, { PropTypes } from 'react';
import { Link } from 'react-router';
import ReactDataGrid from 'react-data-grid';
import { Toolbar } from 'react-data-grid/addons';
import { Data } from 'react-data-grid/addons';
import moment from 'moment';
import { SIMPLE_DATE_FORMAT } from '../../config/app_config';
// import OrderWrapFormatter from './OrderWrapFormatter';
const TABLE_COLUMNS = [
{ key: 'cNumber', name: 'Customer #', width: 125 },
{ key: 'name', name: 'Name', width: 150 },
{ key: 'orderNumber', name: 'Order #', width: 90 },
{ key: 'platform', name: 'Platform' },
{ key: 'useCase', name: 'Use Case'/* , formatter: OrderWrapFormatter */ },
{ key: 'list', name: 'List' },
{ key: 'sku', name: 'SKU' },
{ key: 'startDate', name: 'Start Date' },
{ key: 'endDate', name: 'End Date' },
];
export default class OrdersIndex extends React.Component {
static propTypes = {
orders: PropTypes.object.isRequired,
platformMap: PropTypes.object.isRequired,
progress: PropTypes.number,
};
state = {
rows: [],
originalRows: [],
columns: TABLE_COLUMNS,
sortColumn: null,
sortDirection: null,
filters: {},
}
renderRows = (orders) => {
const _rows = [];
orders.map((o) => {
_rows.push({
key: o.order.id,
id: o.order.id,
cNumber: o.order.providerCustomerNumber,
name: o.order.company.name,
orderNumber: o.order.providerOrderNumber,
platform: o.platformUseCases[0].platform.description,
useCase: this.renderMulti(o.platformUseCases, 'useCase', 'description'),
list: this.renderMulti(o.listSKUs, 'dataSet', 'name'),
sku: this.renderMulti(o.listSKUs, 'fieldSet', 'name'),
startDate: moment(o.order.startDate).format(SIMPLE_DATE_FORMAT),
endDate: moment(o.order.endDate).format(SIMPLE_DATE_FORMAT),
});
return _rows;
});
return this.setState({
rows: _rows,
originalRows: _rows.slice(),
});
}
getRows = () => {
return Data.Selectors.getRows(this.state);
}
rowGetter = (rowIdx) => {
const rows = this.getRows();
return rows[rowIdx];
}
getSize = () => {
return this.getRows().length;
}
renderMulti = (multi, itemName, subItemName) => {
const objectArray = multi.map((object) => {
return object[itemName][subItemName];
});
return objectArray.join('\n');
}
handleGridSort = (sortColumn, sortDirection) => {
const { originalRows, rows } = this.state;
const comparer = (a, b) => {
if (sortDirection === 'ASC') {
return (a[sortColumn] > b[sortColumn]) ? 1 : -1;
}
else if (sortDirection === 'DESC') {
return (a[sortColumn] < b[sortColumn]) ? 1 : -1;
}
};
const newRows = sortDirection === 'NONE' ? originalRows.slice() : rows.sort(comparer);
this.setState({
rows: newRows,
});
}
handleRowUpdated = (e) => {
// merge updated row with current row and rerender by setting state
const { rows } = this.state;
Object.assign(rows[e.rowIdx], e.updated);
this.setState({
...rows,
});
}
handleFilterChange = (filter) => {
const { filters } = this.state;
const newFilters = Object.assign({}, filters);
if (filter.filterTerm) {
newFilters[filter.column.key] = filter;
}
else {
delete newFilters[filter.column.key];
}
this.setState({
filters: newFilters,
});
}
onClearFilters = () => {
// all filters removed
this.setState({
filters: {},
});
}
// Creates appropriate warnings to prevent entering
// the order form if the account is missing information
renderNotice = (message, buttonMessage, route) => {
return (
<div className="alert alert-warning">
<strong>Notice:</strong>
<p>{ message }</p>
<p>
<Link
to={ route }
className="btn btn-warning"
>
<i className='fa fa-plus'></i>
{ buttonMessage }
</Link>
</p>
</div>
);
}
render() {
const { platformMap, progress } = this.props;
const platformMessage = 'Your account is not associated with any platform use cases.' +
'You must select at least one use case before creating new orders.';
const platformButton = 'Add Use Cases';
const platformRoute = '/products';
return (
<div className="container">
<div className="row">
<div className="col-sm-12 col-md-8">
<h1>Orders</h1>
</div>
<div className="col-sm-12 col-md-4">
<span className="pull-right">
<Link
to="/orders/create/1"
className="btn btn-primary"
disabled
>
<i className='fa fa-plus'></i>Create New Order
</Link>
</span>
</div>
</div>
{ platformMap.size === 0 && progress === 0 ?
this.renderNotice(platformMessage, platformButton, platformRoute) : null }
<div className="row">
{ progress === 0 ?
<div className="col-md-12">
{ this.renderTable() }
</div> : null }
</div>
</div>
);
}
renderTable = () => {
const { orders } = this.props;
const { columns } = this.state;
return (
<div>
{ orders.size === 0 || orders === undefined ?
<p>Your account has no orders</p> :
<ReactDataGrid
onGridSort={ this.handleGridSort }
rowKey="key"
id="key"
columns={ columns }
rowGetter={ this.rowGetter }
rowsCount={ this.getSize() }
onRowUpdated={ this.handleRowUpdated }
toolbar={ <Toolbar enableFilter /> }
onAddFilter={ this.handleFilterChange }
onClearFilters={ this.onClearFilters }
minHeight={ 500 }
filterRowsButtonText="Search By Field"
/>
}
</div>
);
}
componentWillMount = () => {
const { orders } = this.props;
const columnArray =
TABLE_COLUMNS.map((c) => {
const copy = Object.assign({}, c);
copy.filterable = true;
copy.locked = true;
if (copy.key !== 'useCase') {
copy.sortable = true;
}
return copy;
});
this.setState({
columns: columnArray,
});
this.renderRows(orders);
}
componentWillReceiveProps = (nextProps) => {
const { orders } = nextProps;
if (orders.size > 0) {
this.renderRows(orders);
}
}
}
I understand this might be a lot but I cannot for the life of me determine what could be the cause. Thanks to anyone who takes a look.

Render element grouped by property object reactjs

I have json Array Like this:
{
area:1,
label: "element1"
},
{
area:3,
label: "element3"
},
{
area:1,
label: "element2"
},
{
area:2,
label: "element2_1"
}
I would render an element like:
Area 1 -
element1
element2
Area 2-
element2_1
Area 3 -
element3
I have done this for now, but now I don't know how to group for property "area"
this.state.areas.map(function(el, key){
return (
<div>
<span><strong>{el.label}</strong></span>
</div>
);
)}
If you make your labels into an Array, you can group and render them like this.
var Hello = React.createClass({
componentWillMount: function() {
this.state = {
area: [
{
area: 1,
label: ['element 1', 'element 2']
},
{
area: 2,
label: ['element 2_1']
},
{
area: 3,
label: ['element 3']
}
]
};
},
render: function() {
return (
<div>
{ this.state.area.map(function(el, key) {
return (
<div key={el.area}>
<span>
<strong>
{ el.label.map(function(label) {
return (
<span key={label}>{label}</span>
)
}) }
</strong>
</span>
</div>
);
}) }
</div>
)
}
});
ReactDOM.render(
<Hello /> ,
document.getElementById('container')
);
<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="container"></div>
I guess this can help you https://jsfiddle.net/69z2wepo/69759/
const myState = [{
area:1,
label: "element1"
},
{
area:3,
label: "element3"
},
{
area:1,
label: "element2"
},
{
area:2,
label: "element2_1"
}];
const ShowArea = (props) => <div><strong>Area {props.area}</strong><span>{props.children}</span></div>
const ShowLabel = (props) =>{
return(<span style={{paddingLeft:'10px'}} key={props.label}><strong>{props.label}</strong></span>)
};
var Hello = React.createClass({
render: function() {
const CompareByArea = (a,b) => a.area > b.area;
const OrderList = myState.sort(CompareByArea);
let newState = {};
OrderList.forEach(element=>{
if(!newState[element.area])
newState[element.area] = [];
newState[element.area].push({label: element.label});
});
const keys = Object.keys(newState);
const Elements = keys.map((key) => <ShowArea key={key} area={key} >
{newState[key].map(ShowLabel)}
</ShowArea>);
return (<div>{Elements}</div>)
}
});
ReactDOM.render(
<Hello/>,document.getElementById('container')
);

Resources