Reputation: 5403
I need to be able to track number of instances of my component, how do I do that in React using functional components?
I tried to use useRef()
but it seems like even though it preserves the value between the renders - it does not share the value between the component instances.
So far the only solution I came up with is this sily one, I hope there is a way to store it somehow in more elegant way.
const [ ident, setIdent ] = useState(0);
useEffect(() => {
if (document.sometring === undefined) {
document.sometring = 0;
} else {
document.sometring++;
}
setIdent(document.sometring);
}, []);
Update to the question: The use case is more actademical, I want to know how to do it, rather than practical. I want every instance of my independent component to have unique sequential ID (like "button-42") so this is why solutions like "give it a random code" also won't work for me. Global state managers like redux or context also cannot be a solution because, let's say, If i open-source my component on GitHub I should not ask users to install also redux or use React.Context. And of course this ID should not change if component re-renders.
Upvotes: 5
Views: 2846
Reputation: 191986
You can use the initialise function of useState
or with useEffect
(if you don't need the updated value in the component) to increment the counter, and set the initialState
to the new value:
/** export **/ const count = { val: 0 };
const Comp = ({ force }) => {
// if you don't need the value inside the component on render, you can replace with useEffect(() => (count.val++, count.val), [])
const [id] = React.useState(() => ++count.val);
return <div>{force} Count {id}</div>;
}
const Demo = () => {
const [force, setForce] = React.useState(0);
return (
<div>
<Comp force={force} />
<Comp force={force} />
<Comp force={force} />
<button onClick={() => setForce(force + 1)}>Force Render</button>
</div>
);
}
ReactDOM.render(
<Demo />,
root
)
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id="root"></div>
Upvotes: 3
Reputation: 1413
Redux solution
Dom Output:
hi my id is 0
hi my id is 1
hi my id is 2
hi my id is 3
hi my id is 4
Total instances: 5
React Side:
SomeTrackedComponent.js
export default const SomeTrackedComponent = ({id}) => (
<div> hi my id is {id} </div>
)
App.js
const App = ({instances , addInstance}) =>{
const [trackedComponents, setTrackedComponents] = useState([]);
useEffect(()=>{
const justSomeArray = Array.from(Array(5).keys())
//wont have access to updated instance state within loop so initialize index
const someJSX = justSomeArray.map((_, id = instances.currentId )=>{
addInstance({ id , otherData: 'otherData?'})
return <SomeTrackedComponent key={id} id={id} />
})
setTrackedComponents(someJSX)
},[])
return(
<div>
{trackedComponents}
Total instances: {instances.items.length}
</div>
)
}
export default connect(
({instances})=>({instances}),
actions
)(App);
Redux Side:
actions.js
export const addInstance = (payload) =>(
{type:'CREATE_INSTANCE' , payload}
)
export const removeInstance = (payload) =>(
{type:'REMOVE_INSTANCE' , payload}
)
reducers.js
const instanceReducer = (state = { items : [] , currentId : 1} , action) => {
switch (action.type) {
case 'CREATE_INSTANCE':
return {
currentId: state.currentId + 1
items:[...state.items , action.payload],
}
case 'REMOVE_INSTANCE':
return {
...state,
items: [...state.items].filter(elm => elm.id !== action.payload.id)
}
default:
return state;
}
}
export default combineReducers({
instances: instanceReducer
})
Index.js:
// import React from 'react';
// import ReactDOM from 'react-dom';
// import { Provider } from 'react-redux';
// import { createStore } from 'redux';
// import reducers from './redux/reducers';
// import App from './components/App';
ReactDOM.render(
<Provider store={createStore(reducers)}>
<App />
</Provider>,
document.querySelector('#root')
);
Upvotes: 0
Reputation: 4739
If you want to track the number of live components in the app (ignoring those that were rendered before but not anymore)
const count = { value: 0 }
export { count }
const incrementCounter = () => count.value++
const decrementCounter = () => count.value--
// ...
useEffect(() => {
incrementCounter();
return decrementCounter; // will run on "unmount"
}, []); // run this only once
Sure if you want to display this count somewhere else in the app you will need to make it reactive - for example, pass incrementCounter
and decrementCounter
functions as a prop and update counter somewhere in the state of your components or Redux (or leave it up to whoever is using this component)
Upvotes: 0