I've got a component UsersList which I'd like to reuse with two different reducers - one for listing regular users (state.users.x) and one for listing administrators (state.adminusers.x). The display is the same in both cases, but the state is in different places and different api actions apply (different endpoints with different business rules).
How can I write my component so it can use either reducer?
Write the UsersList component as normal, but do not connect it to redux.
For example:
import React, { Component } from 'react';
import { Table } from 'react-bootstrap';
import UserInviteRow from 'jsx/components/Lib/Users/UserInviteRow';
export class UsersList extends Component {
render() {
const { inviteUserToOrg } = this.props;
return (
<Table bordered hover>
<thead>
<tr>
<th className="width-200">First Name</th>
<th className="width-250">Last Name</th>
<th>Email</th>
<th className="width-150">Last Login</th>
<th className="width-100"> </th>
</tr>
</thead>
<tbody>
<UserInviteRow invitefxn={ inviteUserToOrg }/>
{ this.renderRows() }
</tbody>
</Table>
);
}
renderRows() {
const { usersList } = this.props;
if( ! usersList.length ) {
return (
<tr>
<td colSpan="5">
<em>No users exist for this non-profit</em>
</td>
</tr>
);
}
return usersList.map( (user) => {
return (
<tr key={user.key}>
<td>{user.firstName}</td>
<td>{user.lastName}</td>
<td>{user.correspondenceEmailAddress}</td>
<td>{ (user.lastSeen) ? formatTime(user.lastSeen) : '' }</td>
<td className="text-center">
{ this.renderRemoveButton( user ) }
</td>
</tr>
);
});
}
renderRemoveButton(user) {
const { currentUser } = this.props;
if( currentUser.key === user.key ) {
// users cannot remove themselves
return null;
}
return (
<a className="text-danger" onClick={ () => { this.removeUser(user) } }>
<em className="fa fa-times" />
</a>
);
}
removeUser( user ) {
this.props.removeUserFromOrg(user.key);
}
}
export default UsersList;
Make sure both your reducers implement the action functions you use, in this case inviteUserToOrg and removeUserFromOrg.
Create new container components connected to each reducer
For example:
import { connect } from 'react-redux';
import {
inviteUserToOrg,
removeUserFromOrg
} as actions from 'jsx/redux/modules/nonadminUsers';
import UsersList from 'jsx/components/Lib/Users/UsersList';
var NonadminUserList = connect(
state => {
return {
usersList: state.users.usersList,
};
},
actions
)(UsersList);
export default NonadminUserList;
and
import { connect } from 'react-redux';
import {
inviteUserToOrg,
removeUserFromOrg
} as actions from 'jsx/redux/modules/adminUsers';
import UsersList from 'jsx/components/Lib/Users/UsersList';
var AdminUserList = connect(
state => {
return {
usersList: state.adminusers.usersList,
};
},
actions
)(UsersList);
export default AdminUserList;
Now changes to your presentation component, UsersList, will affect both container components and each container component can reference it's own reducer state and actions.
Related
Langage used : JS with REACT REDUX
The context : I have a component who render a list of quotes following the user filter and categories choice.
In my filter component, i store the select value (buttonsData), and here i re render a certains component depending on select value.
import React from 'react';
import { Table } from 'react-bootstrap';
import { useSelector } from 'react-redux';
//here each component following the user choice
import { AllForms } from './categories/AllForms';
import { AtoZ } from './sorted/AtoZ';
import { ZtoA } from './sorted/ZtoA';
import { Ascend } from './sorted/Ascend';
import CurrentOffers from './categories/CurrentOffers';
import ValidateOffers from './categories/ValidateOffers';
export const OfferList = () => {
const buttonsData = useSelector((state) => state.buttonReducer);
return (
<Table hover responsive="md" className="folder__table">
<thead className="folder__content">
<tr className="folder__titles">
<th className="folder__title"> </th>
<th className="folder__title">Order REF</th>
<th
className="folder__title"
>
Entité
</th>
<th className="folder__title">Customer</th>
<th className="folder__title">Status</th>
<th className="folder__title">Date</th>
<th className="folder__title "> </th>
</tr>
</thead>
{buttonsData.activeComponent === 'AllForms' && <AllForms />}
{buttonsData.activeComponent === 'Ascend' && <Ascend />}
{buttonsData.activeComponent === 'validate' && <ValidateOffers />}
</Table>
);
};
I have used createSelector to filter and sort my datas (working fine).
import { useSelector } from 'react-redux';
export const SelectOffersValidate = () => {
//here i select ALL my forms, get with axios
const formsDatas = useSelector((state) => state.offersReducer);
const sortedForms = [...formsDatas].filter(
(oneOffer) => oneOffer.status == 'validate'
);
console.log(sortedForms);
return sortedForms;
};
export const SelectOffersAscend = () => {
const formsDatas = useSelector((state) => state.offersReducer);
const sortedForms = [...formsDatas].sort((a, b) =>
b.createdAt.localeCompare(a.createdAt)
);
return sortedForms;
};
Here a component filtered ( i have one component for AllForms, one for Validate and one for ascend, exaclty the same but with own select function)
import React, { useState } from 'react';
import { FiEdit3 } from 'react-icons/fi';
import {
SelectOffersAscend,
} from '../../../selector/projects.selector.js';
import { isEmpty } from '../../../middlewares/verification.js';
import Moment from 'react-moment';
export const Ascend = () => {
const formsAscend = SelectOffersAscend();
return (
<>
<tbody>
{!isEmpty(formsAscend[0]) &&
formsAscend?.map((oneForm) => {
return (
<tr key={oneForm.id}>
<td>
<input
type="checkbox"
/>
</td>
<td>{oneForm.ref} </td>
<td> {oneForm.entity}</td>
<td>{oneForm.customer} </td>
<td>{oneForm.status} </td>
<td>
<Moment format="DD/MM/YYYY" date={oneForm.createdAt} />
</td>
<td>
<FiEdit3 />
</td>
</tr>
);
})}
</tbody>
</>
);
};
My first problem :
I have made a component for EACH filter, but it's repetitive, is there a better way to do ?
The second problem :
"AllForms" and "ValidateOffers" are categories and "Ascend" is a filter.
For the moment i filter only with AllForms but i would like to filtered based on categories choosen.
I've tried to create an action to store the actual categories, so i've tried to dispatch on my createSelector validate function but it's looping so i don't think is the best way to do
SOLUTION : thanks to Chris whol helped me :)
So i have delete all my filtered component to just have one and create a custom hook
import React, { useMemo } from 'react';
import { Table } from 'react-bootstrap';
import { useSelector } from 'react-redux';
import { OfferRows } from './OfferRows';
export const useFilteredOffers = () => {
const buttonsData = useSelector((state) => state.buttonReducer);
const offersData = useSelector((state) => state.offersReducer);
return useMemo(() => {
switch (buttonsData.activeComponent) {
case 'Ascend': // fix casing
return offersData?.sort((a, b) =>
b.createdAt.localeCompare(a.createdAt)
);
case 'validate':
return offersData?.filter((oneOffer) => oneOffer.status === 'validate');
case 'not validate':
return offersData?.filter(
(oneOffer) => oneOffer.status === 'not validate'
);
case 'AtoZ':
return offersData?.sort((a, b) => a.customer.localeCompare(b.customer));
case 'ZtoA':
return offersData?.sort((a, b) => b.customer.localeCompare(a.customer));
default:
return offersData;
}
}, [buttonsData.activeComponent, offersData]);
};
export const OfferList = () => {
const filteredOffers = useFilteredOffers();
return (
<Table hover responsive="md" className="folder__table">
<thead className="folder__content">
<tr className="folder__titles">
<th className="folder__title"> </th>
<th className="folder__title">Order REF</th>
<th className="folder__title">Entité</th>
<th className="folder__title">Customer</th>
<th className="folder__title">Status</th>
<th className="folder__title">Date</th>
<th className="folder__title "> </th>
</tr>
</thead>
<OfferRows offers={filteredOffers} />
</Table>
);
};
Here the rows
import React from 'react';
import { FiEdit3 } from 'react-icons/fi';
import Moment from 'react-moment';
import { isEmpty } from '../../middlewares/verification.js';
export const OfferRows = ({ offers }) => {
return (
<>
<tbody>
{!isEmpty(offers[0]) &&
offers?.map((oneForm) => {
return (
<tr key={oneForm.id}>
<td>
<input type="checkbox" />
</td>
<td>{oneForm.ref} </td>
<td> {oneForm.entity}</td>
<td>{oneForm.customer} </td>
<td>{oneForm.status} </td>
<td>
<Moment format="DD/MM/YYYY" date={oneForm.createdAt} />
</td>
<td>
<FiEdit3 />
</td>
</tr>
);
})}
</tbody>
</>
);
};
I would create a single component for the rendering of the offer rows. The data can be filtered using a single hook that also selects the active filter. You can also pass this down as an argument.
Custom hooks MUST start with the use keyword. See the Rules of Hooks documentation for more information.
const useFilteredOffers = () => {
const activeFilter = useSelector((state) => state.buttonReducer);
const offers = useSelector((state) => state.offersReducer);
return useMemo(() => {
switch (activeFilter) {
case 'Ascend': // fix casing
return offers?.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
case 'validate':
return offers?.filter(oneOffer => oneOffer.status == 'validate');
default:
return offers;
}
}, [activeFilter, offers]);
}
export const OfferList = () => {
const filteredOffers = useFilteredOffers();
return (
<Table hover responsive="md" className="folder__table">
<thead className="folder__content">
<tr className="folder__titles">
<th className="folder__title"> </th>
<th className="folder__title">Order REF</th>
<th
className="folder__title"
>
Entité
</th>
<th className="folder__title">Customer</th>
<th className="folder__title">Status</th>
<th className="folder__title">Date</th>
<th className="folder__title "> </th>
</tr>
</thead>
<OfferRows offers={filteredOffers} />
</Table>
);
};
For completeness, here is the OfferRows component.
PS: You won't need to use the isEmpty validator because Array#map won't have any effect when the Array is empty.
export const OfferRows = (offers) => {
return (
<>
<tbody>
{offers?.map((oneForm) => {
return (
<tr key={oneForm.id}>
<td>
<input
type="checkbox"
/>
</td>
<td>{oneForm.ref} </td>
<td> {oneForm.entity}</td>
<td>{oneForm.customer} </td>
<td>{oneForm.status} </td>
<td>
<Moment format="DD/MM/YYYY" date={oneForm.createdAt} />
</td>
<td>
<FiEdit3 />
</td>
</tr>
);
})}
</tbody>
</>
);
};
I'm working on a web-application using the MERN stack that displays a table of clients with their name, email, and phone number. I haven't implemented Redux quite yet, but I'm using 'uuid' to supplement data in the table until I can get the redux store set up. So far I have displaying the the list and adding a client to the list working fine, but I am having trouble with the pesky delete button.
This is the current ClientTable component
import React, { Component } from "react";
import { Table, Container, Button } from "reactstrap";
import { connect } from "react-redux";
import {
getClients,
addClient,
editClient,
deleteClient,
} from "../actions/clientActions";
import PropTypes from "prop-types";
const renderClient = (clients, index, id) => {
return (
<tr key={index}>
<td>
<Button
className="remove-btn"
color="danger"
size="sm"
onClick={() => {
this.setState((state) => ({
clients: state.clients.filter((client) => client.id !== id),
}));
}}
>
×
</Button>
</td>
<td>{clients.name}</td>
<td>{clients.email}</td>
<td>{clients.number}</td>
</tr>
);
};
class ClientTable extends Component {
componentDidMount() {
this.props.getClients();
}
onDeleteClick = (id) => {
this.props.deleteClient(id);
};
render() {
const { clients } = this.props.client;
// const { clients } = this.state;
return (
<Container id="listContainer">
<Table
id="listTable"
className="table-striped table-bordered table-hover"
dark
>
<tr class="listRow">
<thead id="tableHeader">
<tr>
<th id="listActions">Actions</th>
<th id="listName">Name</th>
<th id="listEmail">Email</th>
<th id="listNumber">Number</th>
</tr>
</thead>
<tbody class="listRow">{clients.map(renderClient)}</tbody>
</tr>
</Table>
</Container>
);
}
}
ClientTable.propTypes = {
getClients: PropTypes.func.isRequired,
client: PropTypes.object.isRequired,
};
const mapStateToProps = (state) => ({
client: state.client,
});
export default connect(mapStateToProps, {
getClients,
deleteClient,
addClient,
})(ClientTable);
This is the bit of code that is causing me issues
<Button
className="remove-btn"
color="danger"
size="sm"
onClick={() => {
this.setState((state) => ({
clients: state.clients.filter((client) => client.id !== id),
}));
}}
>
×
</Button>
When I click the "delete" button I keep getting TypeError: Cannot read property 'setState' of unedefined
I know the error is because of 'this' isn't bound to anything, but I'm uncertain how to bind it within an onClick event if that is even possible or what even to bind it to. I am just lost as to how to approach this problem. (I'm still quite new to React).
If anyone has any ideas it would be greatly appreciated!
move renderClient function to ClientTable, and use it as a method of this class.
class ClientTable extends Component {
componentDidMount() {
this.props.getClients();
}
renderClient = (clients, index) => {
return (
<tr key={index}>
<td>
<Button
className="remove-btn"
color="danger"
size="sm"
onClick={() => this.onDeleteClient(clients.id)}
>
×
</Button>
</td>
<td>{clients.name}</td>
<td>{clients.email}</td>
<td>{clients.number}</td>
</tr>
);
};
onDeleteClick = (id) => {
this.props.deleteClient(id);
};
render() {
const { clients } = this.props.client;
// const { clients } = this.state;
return (
<Container id="listContainer">
<Table
id="listTable"
className="table-striped table-bordered table-hover"
dark
>
<tr class="listRow">
<thead id="tableHeader">
<tr>
<th id="listActions">Actions</th>
<th id="listName">Name</th>
<th id="listEmail">Email</th>
<th id="listNumber">Number</th>
</tr>
</thead>
<tbody class="listRow">{clients.map(this.renderClient)}</tbody>
</tr>
</Table>
</Container>
);
}
}
I follow a React/Redux tutorial and from what I saw on a few articles on internet I realized that inline functions are bad for performance in React.
From what I understood functions are reference type and if you use an inline function, for every re-render, this function will take a different spot in memory.
In the tutorial example I have this deleteExperience() method, that the instructor used inline.
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import Moment from 'react-moment';
import { Link, withRouter } from 'react-router-dom';
import { deleteExperience } from '../../actions/profileActions';
const Experience = ({ experience, deleteExperience }) => {
const experiences = experience.map(exp => (
<tr key={exp._id}>
<td>{exp.company}</td>
<td className="hide-sm">{exp.title}</td>
<td>
<Moment format="YYYY/MM/DD">{exp.from}</Moment> -
{exp.to === null ? (
' Now '
) : (
<Moment format="YYYY/MM/DD">{exp.to}</Moment>
)}
</td>
<td>
<button className="btn btn-danger" onClick={() => deleteExperience(exp._id)}>
Delete
</button>
</td>
</tr>
));
return (
<Fragment>
<h2 className="my-2">Experience Credentials</h2>
<table className="table">
<thead>
<tr>
<th>Company</th>
<th className="hide-sm">Title</th>
<th className="hide-sm">Years</th>
<th />
</tr>
</thead>
<tbody>{experiences}</tbody>
</table>
</Fragment>
);
};
Experience.propTypes = {
experience: PropTypes.array.isRequired,
deleteExperience: PropTypes.func
};
export default connect(
null,
{deleteExperience}
)(withRouter(Experience));
So the instructor said that he used inline function
onClick={() => deleteExperience(exp._id)}
and not just called directly the function
onClick={deleteExperience(exp._id)}
to not be execute immediately.
So, please someone tell me, if the theory about bad practice to use inline function is true, how to handle this situation? I tried many ways, without any success.
The performance issue isn't from using arrow functions, but rather from creating fresh ones on every render. In your case, you can use useCallback() to memoize them. (You'll need to extract a component to render each exp object to avoid breaking the rules of hooks.) Something like this should work:
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import Moment from 'react-moment';
import { Link, withRouter } from 'react-router-dom';
import { deleteExperience } from '../../actions/profileActions';
const Exp = ({ exp, deleteExperience }) => {
const del = useCallback(() => deleteExperience(exp._id), [deleteExperience, exp._id]);
return (
<tr>
<td>{exp.company}</td>
<td className="hide-sm">{exp.title}</td>
<td>
<Moment format="YYYY/MM/DD">{exp.from}</Moment> -
{exp.to === null ? (
' Now '
) : (
<Moment format="YYYY/MM/DD">{exp.to}</Moment>
)}
</td>
<td>
<button className="btn btn-danger" onClick={del}>
Delete
</button>
</td>
</tr>
);
};
const Experience = ({ experience, deleteExperience }) => {
const experiences = experience.map(exp => (
<Exp key={exp._id} exp={exp} deleteExperience={deleteExperience} />
));
return (
<Fragment>
<h2 className="my-2">Experience Credentials</h2>
<table className="table">
<thead>
<tr>
<th>Company</th>
<th className="hide-sm">Title</th>
<th className="hide-sm">Years</th>
<th />
</tr>
</thead>
<tbody>{experiences}</tbody>
</table>
</Fragment>
);
};
Experience.propTypes = {
experience: PropTypes.array.isRequired,
deleteExperience: PropTypes.func
};
export default connect(
null,
{deleteExperience}
)(withRouter(Experience));
I have a component. It displays a list of records. You can click the delete icon, and as soon as you go to a different page and return to the list, the record is no longer there. How do I remove the record from the list without going to a different page?
I've tried using componentWillUpdate() and componentDidUpdate() and placing my getTerritoryGeographies(this.props.params.id) in those functions, but those functions keep calling the data and do not stop. I'm restricted to API limits.
import React, { Component, PropTypes} from 'react';
import { Link } from 'react-router';
import { connect } from 'react-redux';
import { reduxForm } from 'redux-form';
import { getTerritoryGeographies, deleteTerritoryGeography } from '../actions/index';
import TerritoryTabs from './territory-tabs';
class TerritoryGeographyList extends Component {
componentWillMount() {
//console.log('this is the child props (TerritoryGeographyList)');
console.log(this.props);
this.props.getTerritoryGeographies(this.props.params.id);
}
componentDidMount() {
console.log('componentDidMount');
}
componentWillUpdate() {
console.log('componentWillUpdate');
this.props.getTerritoryGeographies(this.props.params.id);
}
componentDidUpdate() {
console.log('componentDidUpdate');
}
onDeleteClick(id) {
this.props.deleteTerritoryGeography(id);
}
static contextTypes = {
router: PropTypes.object
}
renderTerritoryGeographyList() {
return this.props.territoryGeographies.map((geography) => {
return (
<tr key={geography.Id}>
<th scope="row" data-label="Country">
<div className="slds-truncate">{geography.tpslead__Country__c}</div>
</th>
<td data-label="State/Provice">
<div className="slds-truncate">{geography.tpslead__State__c}</div>
</td>
<td data-label="Postal Start">
<div className="slds-truncate">{geography.tpslead__Zip_Start__c}</div>
</td>
<td data-label="Postal End">
<div className="slds-truncate">{geography.tpslead__Zip_End__c}</div>
</td>
<td className="slds-text-align--right" data-label="Action">
<button className="slds-button slds-button--icon" title="edit">
<svg className="slds-button__icon" aria-hidden="true">
<use xlinkHref={editIcon}></use>
</svg>
<span className="slds-assistive-text">Edit</span>
</button>
<button onClick={() => this.onDeleteClick(geography.Id)} className="slds-button slds-button--icon" title="delete" data-aljs="modal" data-aljs-show="PromptConfirmDelete">
<svg className="slds-button__icon" aria-hidden="true">
<use xlinkHref={deleteIcon}></use>
</svg>
<span className="slds-assistive-text">Delete</span>
</button>
</td>
</tr>
);
});
}
render() {
return (
<TerritoryTabs id={this.props.params.id} listTab="geography">
<Link to={"territory/" + this.props.params.id + "/geography/new"} className="slds-button slds-button--brand">
Add New Geography
</Link>
<table className="slds-table slds-table--bordered slds-table--cell-buffer slds-m-top--large">
<thead>
<tr className="slds-text-title--caps">
<th scope="col">
<div className="slds-truncate" title="Country">Country</div>
</th>
<th scope="col">
<div className="slds-truncate" title="State/Provice">State/Provice</div>
</th>
<th scope="col">
<div className="slds-truncate" title="Postal Start">Postal Start</div>
</th>
<th scope="col">
<div className="slds-truncate" title="Postal End">Postal End</div>
</th>
<th className="slds-text-align--right" scope="col">
<div className="slds-truncate" title="Action">Action</div>
</th>
</tr>
</thead>
<tbody>
{this.renderTerritoryGeographyList()}
</tbody>
</table>
</TerritoryTabs>
);
}
}
function mapStateToProps(state) {
//console.log(state);
return { territoryGeographies: state.territoryGeographies.all
};
}
export default connect(mapStateToProps, { getTerritoryGeographies, deleteTerritoryGeography })(TerritoryGeographyList);
UPDATE: I figured out how do remove it by updating my onDeleteClick(), but it seems unnecessarily heavy for a react app. Thoughts?
onDeleteClick(id) {
this.props.deleteTerritoryGeography(id);
var geographyIndex = this.props.territoryGeographies.findIndex(x => x.Id==id)
this.setState(state => {
this.props.territoryGeographies.splice(geographyIndex, 1);
return {territoryGeographies: this.props.territoryGeographies};
});
}
Please post your action and reducer so that we can see what you are doing on the Redux side.
If you are renderings a list from data that is in the Redux store, you can use React-Redux's connect Higher Order Function to wrap the component, thus enabling access to the store as component props. So that part looks correct.
When you are firing the action creator, you should be passing in the id of the data that you would like deleted, and in your reducer, something like this:
case 'DELETE_TERRITORY':
const territoryId = action.data;
return state.filter(territory => territory.id !== territoryId);
When the reducer returns the new, filtered array, your component should update with the list minus the territory you just deleted.
This code controls whether the deletion operation is performed correctly. Returns the state to the first state if the deletion operation fails
<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>
handleDelete = async productId => {
const originalProducts = this.state.products;
const products = this.state.products.filter(p => productId !== p.productId);
this.setState({ products });
try {
const result = await deleteProduct(productId);
if (result.status === 200) {
// codes goes here. for example send notification
}
}
catch (ex) {
if (ex.response && ex.response.status === 404) {
// codes goes here. for example send notification
}
this.setState({ products: originalProducts });
}
}
reactjs
I am currently working on one of FCC's project, specifically to this one:
https://www.freecodecamp.com/challenges/build-a-camper-leaderboard
I was able to get it working just fine--I am able to click either recent or all-time scores and it will re-render the page accordingly.
The only thing I am missing is rendering the rank number appropriately. As of now, it is just current stack of number 1.
Here's my current code:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux'
import { recentData } from '../actions/index'
import { allTimeData } from '../actions/index'
export default class TableBoard extends Component {
constructor(props){
super(props);
}
componentDidMount() {
this.props.recentData() //fetch data most recent top
}
renderData(userData){
const name = userData.username;
const recent = userData.recent;
const allTime = userData.alltime;
let rank = 1;
return(
<tr key={name}>
<td>{rank}</td>
<td>{name}</td>
<td>{recent}</td>
<td>{allTime}</td>
</tr>
)
}
getRecentData(){
console.log('recent data')
this.props.recentData()
}
getAllTimeData(){
console.log('all-time data')
this.props.allTimeData();
}
render(){
return(
<table className="table table-hover">
<thead>
<tr>
<th>#</th>
<th>Camper Name</th>
<th
onClick={this.getRecentData.bind(this)}
>Points in 30 days
</th>
<th
onClick={this.getAllTimeData.bind(this)}
>All-time Posts
</th>
</tr>
</thead>
<tbody>
{this.props.FCCData.map(this.renderData)}
</tbody>
</table>
)
}
}
function mapStateToProps(state){
return {
FCCData: state.collectData
}
}
function mapDispatchToProps(dispatch){
return bindActionCreators({ recentData, allTimeData }, dispatch)
}
export default connect(mapStateToProps, mapDispatchToProps)(TableBoard);
How can I add extra information into renderData function to display the rank correctly? I am assuming I need to an counter?
Pass index into map
renderData(item,index){
const name = item.username;
const recent = item.recent;
const allTime = item.alltime;
return(
<tr key={name}>
<td>{index+1}</td>
<td>{name}</td>
<td>{recent}</td>
<td>{allTime}</td>
</tr>
)
}
Table Body
<tbody>
{this.props.FCCData.map(this.renderData}
</tbody>