Using increment to skip an iteration in React - reactjs

I am using an increment (count) for not to click the period (.) second time. So once the period is clicked then second time it skips. I used the example from Incrementing state value by one using React, but the count is not incrementing.
const InitVal = ({ strValue, handleClick }) => (
<div>
{strValue.map((item) => (
<button onClick={() => handleClick(item.key)}>{item.key}</button>
))}
</div>
);
class App extends React.Component {
constructor(props) {
super(props);
this.state = {strValue: [{ key: '7' },{ key: '8' },{ key: '9' },{ key: '4' },{ key: '5' },{ key: '6' },{ key: '1' },{ key: '2' },{ key: '3' },{ key: '0' },{key: '.'}],value: '0',count: 0,};
this.handleClick = this.handleClick.bind(this);
}
handleClick(key) {
const { value } = this.state;
const { count } = this.state;
const digNprd = /[0-9.]/
if (value.charAt(0) === "0") {
this.setState({ value: `${key}` })
} else if (digNprd.test(key)) {
this.setState((u) => {
if (key === '.') {
if (u.count < 1) {
count: u.count + 1
} else {
key = ''
}
}
return { value: `${value}${key}` }
})
}
}
render() {
return (
<div><br /><InitVal strValue={this.state.strValue} handleClick={this.handleClick} /> <br /> <div>value: {this.state.value}</div><br />
<div>count: {this.state.count}</div>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.0/umd/react-dom.production.min.js"></script>
<div id='root'></div>

Based on the code available in OP i am updating a working snippet for you as i am not sure why the updated solution is not working for you. With the help of this you can compare and find out where the issue lies.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.0/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.24.0/babel.js"></script>
<script type="text/babel">
const InitVal = ({ strValue, handleClick }) => (
<div>
{strValue.map((item) => (
<button onClick={() => handleClick(item.key)}>
{item.key}
</button>
))}
</div>
);
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
strValue: [
{ key: "7" },
{ key: "8" },
{ key: "9" },
{ key: "4" },
{ key: "5" },
{ key: "6" },
{ key: "1" },
{ key: "2" },
{ key: "3" },
{ key: "0" },
{ key: "." },
],
value: "0",
count: 0,
};
this.handleClick = this.handleClick.bind(this);
}
handleClick(key) {
const { count, value } = this.state;
const digNprd = /[0-9.]/;
if (value.charAt(0) === "0") {
this.setState((u) => {
let count = u.count
if (key === '.') {
if (count < 1) {
count = count + 1
} else {
key = ''
}
}
return { value: `${key}`, count }
});
} else if (digNprd.test(key)) {
this.setState((u) => {
let count = u.count;
if (key === ".") {
if (u.count < 1) {
count= u.count + 1;
} else {
key = "";
}
}
return { value: `${value}${key}`, count };
});
}
}
render() {
return (
<div>
<br />
<InitVal
strValue={this.state.strValue}
handleClick={this.handleClick}
/>{" "}
<br />{" "}
<div>value: {this.state.value}</div>
<br />
<div>count: {this.state.count}</div>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
</script>
</body>
</html>
For explanation you can refer to joseph's answer and my comment on that answer.

You are returning the value, and the key but you never return the new count value. So the state is not updating that value
try this:
handleClick(key) {
const { value } = this.state;
let { count } = this.state;
const digNprd = /[0-9.]/
if (value.charAt(0) === "0") {
this.setState({ value: `${key}` })
} else if (digNprd.test(key)) {
this.setState((u) => {
if (key === '.') {
if (u.count < 1) {
count = u.count + 1;
} else {
key = "";
}
}
return { value: `${value}${key}`, count }
})
}
}

Related

React: Unable to change value by a function in setState

I am trying to copy a value in state objects first and second through a function in setState, but the values are being changed in the object value, but not in first or second.
const InitVal = ({ strValue, handleClick }) => (
<div>
{strValue.map((item) => (
<button onClick={() => handleClick(item.key)}>{item.key}</button>
))}
</div>
);
class App extends React.Component {
constructor(props) {
super(props);
this.state = {strValue: [{ key: '7' },{ key: '8' },{ key: '9' },{ key: '4' },{ key: '5' },{ key: '6' },{ key: '1' },{ key: '2' },{ key: '3' },{ key: '0' },{key: '+'},{key: '-'} ],value: '0',auxStr: '0',first: '',second: ''};
this.handleClick = this.handleClick.bind(this);
}
handleClick(key) {
const { value, auxStr, first, second } = this.state;
const digNprd = /[0-9]/;
if (digNprd.test(key)) {
this.setState({ value: `${value}${key}`, auxStr: `${auxStr}${key}`} );
} else if (key === '+') {
this.setState({ value: ` ${auxStr} on `, function(auxStr) { return { first: `${auxStr}`} } })
} else if (key === '-') {
this.setState({ value: ` ${auxStr} off `, function() { return { second: `${auxStr}`} } })
}
}
render() {
return (
<div><br /><InitVal strValue={this.state.strValue} handleClick={this.handleClick} /> <br /> {" "}
<br />{" "}
<div>value: {this.state.value}</div>
<br />
<div>AuxStr: {this.state.value}</div>
<br />
<div>First: {this.state.first}</div>
<br />
<div>Second: {this.state.second}</div>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
You shouldn't add anonymous functions to your JavaScript classes, instead declare your functions/methods in the body of the class.
Take a look at this How to use anonymous functions in ES6 class.
But if you wish to play around how to manage to set those first & second values with functions as you started in your codesandbox, you can use something like this with IIFE:
...(function () {
return { second: `${auxStr}` };
})()
And again, you simply can do:
first: `${auxStr}`, second: `${auxStr}`
Edit: codesandbox
Try using arrow function
Like this
handleClick = (key) => { }

React apply function to single item in array

The following code creates a simple movie rating app. Everything works except that when an up or down vote is clicked in one of the array items, the votes state for all items in the array update, rather than just for the item that was clicked. How do I code this so that the vote only applies to the item where it was clicked?
class Ratings extends React.Component {
constructor(props){
super(props);
this.state = {
votes: 0
};
this.add = this.add.bind(this);
this.subtract = this.subtract.bind(this);
this.reset = this.reset.bind(this);
}
add(event){
this.setState ({
votes: this.state.votes + 1
})
}
subtract(event){
this.setState ({
votes: this.state.votes - 1
})
}
reset(event){
this.setState ({
votes: 0
})
}
render () {
this.movies = this.props.list.map(x => {
return (
<div key={x.id} className="movierater">
<MoviePoster poster={x.img}/>
<h1 className="title">{x.name}</h1>
<div className="votewrapper">
<button onClick={this.add}><i className="votebutton fa fa-thumbs-o-up" aria-hidden="true"></i></button>
<Votes count={this.state.votes} />
<button onClick={this.subtract}><i className="votebutton fa fa-thumbs-o-down" aria-hidden="true"></i></button>
</div>
<button onClick={this.reset} className="reset">Reset</button>
</div>
)
});
return (
<div>
{this.movies}
</div>
);
}
}
function MoviePoster(props) {
return (
<img src={props.poster} alt="Movie Poster" className="poster"/>
);
}
function Votes(props) {
return (
<h2>Votes: {props.count}</h2>
);
}
var movieposters = [
{id: 1, img:"http://www.impawards.com/2017/posters/med_alien_covenant_ver4.jpg", name: "Alien Covenant"},
{id: 2, img:"http://www.impawards.com/2017/posters/med_atomic_blonde_ver4.jpg", name: "Atomic Blonde"},
{id: 3, img:"http://www.impawards.com/2017/posters/med_easy_living_ver3.jpg", name: "Easy Living"},
{id: 4, img:"http://www.impawards.com/2017/posters/med_once_upon_a_time_in_venice_ver3.jpg", name: "Once Upon a Time in Venice"},
{id: 5, img:"http://www.impawards.com/2017/posters/med_scorched_earth.jpg", name: "Scorched Earth"},
{id: 6, img:"http://www.impawards.com/2017/posters/med_underworld_blood_wars_ver9.jpg", name: "Underworld: Blood Wars"},
{id: 7, img:"http://www.impawards.com/2017/posters/med_void.jpg", name: "The Void"},
{id: 8, img:"http://www.impawards.com/2017/posters/med_war_for_the_planet_of_the_apes.jpg", name: "War for the Planet of the Apes"},
]
ReactDOM.render(
<Ratings list={movieposters} />,
document.getElementById('app')
);
You need a separate vote count for each movie entity.
This can be accomplished by providing an id to each movie and setting the vote for that specific movie by id.
I would also recommend to extract a new component for a Movie.
this component will get the movieId as a prop and the handlers, it will invoke the up or down handlers and provide to them the current movieId.
See a running example:
class Movie extends React.Component {
onSubtract = () => {
const { subtract, movieId } = this.props;
subtract(movieId);
};
onAdd = () => {
const { add, movieId } = this.props;
add(movieId);
};
onReset = () => {
const { reset, movieId } = this.props;
reset(movieId);
};
render() {
const { movie, votes = 0 } = this.props;
return (
<div className="movierater">
<MoviePoster poster={movie.img} />
<h1 className="title">{movie.name}</h1>
<div className="votewrapper">
<button onClick={this.onAdd}>
<i className="votebutton fa fa-thumbs-o-up" aria-hidden="true" />
</button>
<Votes count={votes} />
<button onClick={this.onSubtract}>
<i className="votebutton fa fa-thumbs-o-down" aria-hidden="true" />
</button>
</div>
<button onClick={this.onReset} className="reset">
Reset
</button>
</div>
);
}
}
class Ratings extends React.Component {
constructor(props) {
super(props);
this.state = {
allVotes: {}
};
}
subtract = movieId => {
const { allVotes } = this.state;
const currentVote = allVotes[movieId] || 0;
const nextState = {
...allVotes,
[movieId]: currentVote - 1
};
this.setState({allVotes: nextState});
};
add = movieId => {
const { allVotes } = this.state;
const currentVote = allVotes[movieId] || 0;
const nextState = {
...allVotes,
[movieId]: currentVote + 1
};
this.setState({ allVotes: nextState });
};
reset = movieId => {
const { allVotes } = this.state;
const nextState = {
...allVotes,
[movieId]: 0
};
this.setState({ allVotes: nextState });
};
render() {
const { allVotes } = this.state;
this.movies = this.props.list.map(x => {
const votes = allVotes[x.id];
return (
<Movie
movieId={x.id}
movie={x}
votes={votes}
reset={this.reset}
subtract={this.subtract}
add={this.add}
/>
);
});
return <div>{this.movies}</div>;
}
}
function MoviePoster(props) {
return <img src={props.poster} alt="Movie Poster" className="poster" />;
}
function Votes(props) {
return <h2>Votes: {props.count}</h2>;
}
var movieposters = [
{
id: 1,
img: "http://www.impawards.com/2017/posters/med_alien_covenant_ver4.jpg",
name: "Alien Covenant"
},
{
id: 2,
img: "http://www.impawards.com/2017/posters/med_atomic_blonde_ver4.jpg",
name: "Atomic Blonde"
},
{
id: 3,
img: "http://www.impawards.com/2017/posters/med_easy_living_ver3.jpg",
name: "Easy Living"
},
{
id: 4,
img:
"http://www.impawards.com/2017/posters/med_once_upon_a_time_in_venice_ver3.jpg",
name: "Once Upon a Time in Venice"
},
{
id: 5,
img: "http://www.impawards.com/2017/posters/med_scorched_earth.jpg",
name: "Scorched Earth"
},
{
id: 6,
img:
"http://www.impawards.com/2017/posters/med_underworld_blood_wars_ver9.jpg",
name: "Underworld: Blood Wars"
},
{
id: 7,
img: "http://www.impawards.com/2017/posters/med_void.jpg",
name: "The Void"
},
{
id: 8,
img:
"http://www.impawards.com/2017/posters/med_war_for_the_planet_of_the_apes.jpg",
name: "War for the Planet of the Apes"
}
];
ReactDOM.render(<Ratings list={movieposters} />, document.getElementById("root"));
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet"/>
<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>
You need to keep track of votes for each element, thus this.state.votes +/- 1 doesn't do the job, so:
Change
this.state = {
votes: 0
}
to
this.state = {
votes: {}
}
then change the functions:
add(id){
return function(event) {
this.setState ({ ...this.state.votes, [id]: parseInt(this.state.votes[id]) + 1 })
}
}
and the same for subtract. Then change your buttons to:
<button onClick={this.add(x.id)} ... (same for subtract)
and last change your Vote component:
<Votes count={this.state.votes[x.id] || 0} />
On reset just do:
reset(event){
this.setState ({ votes: {} })
}

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')
);

React add class active on selected tab

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.

Resources