I'm planning to make "online shopping cart" template in react. However, I can't figure out how to hide "counters" element when "count" reaches zero
I managed to make separated button which hides element on click, but that's not suitable for this project.
counter.jsx
import React, { Component } from 'react';
import Counters from './counters';
class Counter extends Component {
state = {
count: 1
};
handleIncrement = () => {
this.setState({count: this.state.count + 1});
}
handleDelete = () => {
this.setState({count: this.state.count - 1});
if (this.state.count == 0) {
this.state.counters = {
isHidden: true
}
}
}
render() {
let classes = 'badge m-2 badge-';
classes += this.state.count === 1 ? 'primary' : 'primary';
return (
<div>
<span className={classes}>{this.formatCount()}</span>
<button onClick={() => this.handleIncrement()} className='btn btn-primary m-2'>+</button>
<button onClick={() => this.handleDelete()} className="btn btn-danger m-2">-</button>
</div>
);
}
formatCount() {
const {count} = this.state;
return count === 0 ? 'Reached' : count;
}
}
export default Counter;
counters.jsx
import React, { Component } from 'react';
import Counter from './counter';
class Counters extends Component {
state = {
counters: [
{ id: 1, value: 1 },
{ id: 2, value: 1 },
{ id: 3, value: 1 },
{ id: 4, value: 1 }
]
}
render() {
return (
<div>
{this.state.counters.map(
counter => (
<Counter key={counter.id} value={counter.value} id={counter.id} />
))}
</div>
);
}
}
export default Counters;
So, when delete button is pressed it should go from 1 to 0, and when it reaches zero, counters element should disappear(based on Id).
Generally https://stackoverflow.com/a/54649508/10868273 answer should be the way forward but you in order not to change too much you could try this (i've included only the changes you want to make)
counter.jsx
handleIncrement = () => {
this.setState((state) => ({
count: state.count + 1
}));
}
handleDelete = () => {
this.setState((state) => ({
count: state.count - 1
}),() => {
if (this.state.count <= 0){
this.props.handleCounterRemove(this.props.id);
}
});
}
Counters.jsx
import React, { Component } from 'react';
import Counter from './counter';
class Counters extends Component {
state = {
counters: [
{ id: 1, value: 1 },
{ id: 2, value: 1 },
{ id: 3, value: 1 },
{ id: 4, value: 1 }
]
}
handleCounterRemove = (counterId) =>{
const { counters } = this.state;
this.setState((state)=> ({
counters: state.counters.filter(counter => counter.id !== counterId)
}));
}
render() {
return (
<div>
{this.state.counters.map(
counter => (
<Counter key={counter.id} value={counter.value} id={counter.id} handleCounterRemove={this.handleCounterRemove} />
))}
</div>
);
}
}
export default Counters;
As has been mentioned in the other answers, you do not want to just call setState if the next value depends on the pervious one: you can read more on this here React SetState documentation
Also, notice the introduction of handleCounterRemove in the Counters component it's like the only way a child component can affect it's parent component
As Joe mentioned in the comments, you are mutating the state directly, which is very anti-pattern. Always use setState().
Also, whenever you are setting a new state that depends on a previous state variable, pass in a callback to setState() rather than an object. This can save you a few headaches in certain cases.
handleDelete = () => {
this.setState(prevState => ({
count: prevState.count - 1,
isHidden: prevState.count === 1 // Hide if we are decreasing count from 1 to 0
}));
}
Keep Counter as a stateless component. Store the counter state in the Counters component itself. Move the increment, decrement functions to Counters component. In decrement function, write logic such that when it hits 0, remove the corresponding counter object from this.state.counters.
You can also return null from your render() method in counter.jsx, when this.state.coun is 0
You should redevelop the method handleDelete. Here is the possible version
handleDelete = () => {
let currentCount = this.state.count - 1;
this.setState({
count: currentCount
counters: {
isHidden: currentCount === 0
}
});
}
Related
I have build a small application using class component and it's working fine also.
While I convert that application to function component to use react hooks, it's giving me error.
Please check and let me know where it went wrong.
function component
import React ,{useState} from 'react';
import './App.css';
import Counters from './component/counters';
import Navbar from './component/navbar';
function App(props) {
const initialState = [
{ id: 1, value: 0 },
{ id: 2, value: 10 },
{ id: 3, value: 20 },
{ id: 4, value: 30 },
];
const [counters, setCounters] = useState(initialState);
const handleIncrement = (counter) => {
// const counters = [...counters];
// const index = counters.indexOf(counter);
// counters[index] = { ...counter };
// counters[index].value++;
setCounters({ counter : counter.value +1 });
};
const handleDecrement = (counter) => {
// // const counters = [...counters];
// const index = counters.indexOf(counter);
// counters[index] = { ...counter };
// counters[index].value--;
// setCounters({ counters });
};
const handleDelete = (counterId) => {
// const counters = counters.filter((c) => c.id !== counterId);
// setCounters({ counters });
};
return (
<div className="container">
{/* <Navbar totalCounters={counters.reduce((a,c)=>a + c.value,0 )}/> */}
<Navbar totalCounters={counters.filter((c) => c.value > 0).count()}/>
<Counters
counters={counters}
onIncrement={handleIncrement}
onDecrement={handleDecrement}
onDelete={handleDelete}
/>
</div>
);
}
export default App;
class component
import './App.css';
import Counters from './component/counters';
import Navbar from './component/navbar';
import React from 'react';
class App extends React.Component {
state = {
counters: [
{ id: 1, value: 0 },
{ id: 2, value: 10 },
{ id: 3, value: 20 },
{ id: 4, value: 30 },
],
};
handleIncrement = (counter) => {
const counters = [...this.state.counters];
const index = counters.indexOf(counter);
counters[index] = { ...counter };
counters[index].value++;
this.setState({ counters });
};
handleDecrement = (counter) => {
const counters = [...this.state.counters];
const index = counters.indexOf(counter);
counters[index] = { ...counter };
counters[index].value--;
this.setState({ counters });
};
handleDelete = (counterId) => {
const counters = this.state.counters.filter((c) => c.id !== counterId);
this.setState({ counters });
};
render() {
return (
<div className="container">
<Navbar totalCounters={this.state.counters.reduce((a,c)=>a + c.value,0)}/>
<Counters
counters={this.state.counters}
onIncrement={this.handleIncrement}
onDecrement={this.handleDecrement}
onDelete={this.handleDelete} />
</div>
);
}
}
export default App;
In case, if you want to see the full code then just let me know.
Issue
The issue is your counters state is an array but you are mutating the state invariant to be an object in your handlers.
Solution
App - Use a functional state update to map the counters array from the previous state to the next state, using the id to match the specific counter you want to increment/decrement/delete. This shallow copies the previous array. Notice also that the counter being updated is also shallow copied into a new object reference. When deleting a counter you are correct to use .filter to remove the specific element and return a new array.
function App(props) {
const initialState = [
{ id: 1, value: 0 },
{ id: 2, value: 10 },
{ id: 3, value: 20 },
{ id: 4, value: 30 },
];
const [counters, setCounters] = useState(initialState);
const handleIncrement = (id) => {
setCounters(counters => counters.map(counter => counter.id === id
? {
...counter,
value: counter.value + 1,
}
: counter
));
};
const handleDecrement = (id) => {
setCounters(counters => counters.map(counter => counter.id === id
? {
...counter,
value: counter.value - 1,
}
: counter
));
};
const handleDelete = (id) => {
setCounters(counters => counters.filter((c) => c.id !== id));
};
return (
<div className="container">
<Navbar totalCounters={counters.filter((c) => c.value > 0).count()}/>
<Counters
counters={counters}
onIncrement={handleIncrement}
onDecrement={handleDecrement}
onDelete={handleDelete}
/>
</div>
);
}
Counters - pass the counter id to the handlers
class Counters extends Component {
render() {
const { onIncrement, onDecrement, onDelete, counters } = this.props;
return (
<div>
{counters.map((counter) => (
<Counter
key={counter.id}
counter={counter}
onIncrement={() => {
onIncrement(counter.id);
}}
onDecrement={() => {
onDecrement(counter.id);
}}
onDelete={() => {
onDelete(counter.id);
}}
/>
))}
</div>
);
}
}
Counter - since the id is closed in callback scope above just call the event handlers
class Counter extends Component {
...
render() {
return (
<div>
<span className={this.getBadge()}>{this.getCount()}</span>
<button
className="btn btn-secondary m-2"
onClick={this.props.onIncrement}
>
Increment
</button>
<button
className="btn btn-secondary m-2"
onClick={this.props.onDecrement}
>
Decrement
</button>
<button className="btn btn-danger m-2" onClick={this.props.onDelete}>
Delete
</button>
</div>
);
}
}
In your handleIncrement() function, you're setting an object, when the setCounters method is expecting an array of objects.
You can use ES6 destructuring. Also note the object key should be the id not the object.
I expect the below to fix the error.
setCounters([...counters, { counter.id : counter.value +1 }]);
And I believe below is the implementation you're looking for.
otherCounters = counters.filter(c => c.id != counter.id)
setCounters([...otherCounters, { counter.id : counter.value +1 }])
EDIT: One thing I noticed in your counters.jsx and counter.jsx is that you're calling a function with variables that do not expect one.
From counters.jsx
<Counter
...
// onIncrement of "Counter" (the 1st onIncrement on the left)
// is a function that do not expect a variable.
onIncrement={() => {onIncrement(counter);}}
/>
From counter.jsx
<button>
...
// "onIncrement" is called with "this.props.counter". However this "onIncrement" is not expecting a variable.
onClick={() => this.props.onIncrement(this.props.counter)}
</button>
For the correct syntax, you can change either, but as example you can change counter.jsx to
<button>
...
onClick={() => this.props.onIncrement()}
</button>
In functional component on react when you wanna make state you must be use useState, you can follow my code here:
import './App.css';
import Counters from './component/counters';
import Navbar from './component/navbar';
import { useState } from 'react';
const App = () => {
const [countersState, setCountersState] = useState([
{ id: 1, value: 0 },
{ id: 2, value: 10 },
{ id: 3, value: 20 },
{ id: 4, value: 30 },
]);
const handleIncrement = (counter) => {
const counters = countersState
const index = counters.indexOf(counter);
counters[index] = { ...counter };
setCountersState(counters);
};
const handleDecrement = (counter) => {
const counters = countersState
const index = counters.indexOf(counter);
console.log(index)
counters[index] = { ...counter };
counters[index].value--;
setCountersState(counters);
};
const handleDelete = (counterId) => {
const counters = countersState.filter((c) => c.id !== counterId);
setCountersState(counters)
}
return (
<div className="container">
<Navbar totalCounters={this.state.counters.reduce((a,c)=>a + c.value,0)}/>
<Counters
counters={countersState}
onIncrement={handleIncrement}
onDecrement={handleDecrement}
onDelete={handleDelete} />
</div>
);
};
export default App;
I have 3 components like this, how I update state in App component
How I update state onclick in Counter component
import React, { useState } from 'react'
import Header from './components/Header'
import Counters from './components/Counters'
const App = () => {
const initialCounters = [
{ id: 1, value: 0 },
{ id: 2, value: 0 },
{ id: 3, value: 0 },
{ id: 4, value: 0 },
]
const [counters, setCounters] = useState(initialCounters)
const onIncrement = (counter) => {
console.log(counter)
}
return (
<>
<Header totalCounters={counters.length} />
<main className='container'>
<Counters counters={counters} onIncrement={onIncrement} />
</main>
</>
)
}
export default App
In your Counters component when you call the OnIncrement method you need to pass it's id as reference.
Then in your OnIncrement you do this
const onIncrement = (counterId) => {
const updatededCounters = counters.map((counter) => {
if(counter.id === counterId){
counter.value++
return counter
}
return counter
})
setCounters(updatededCounters)
}
Just to be clear in your Counters component
import React from "react";
const Counters = ({ counters, onIncrement }) => {
return (
<>
{counters.map((counter) => (
<div key={counter.id}>
<p>My counter : {counter.value}</p>
<button onClick={() => onIncrement(counter.id)}>Increment</button>
</div>
))}
</>
);
};
Full code for the parent component
import React, { useState } from "react";
import Counters from "./Counters";
const App = () => {
const initialCounters = [
{ id: 1, value: 0 },
{ id: 2, value: 0 },
{ id: 3, value: 0 },
{ id: 4, value: 0 }
];
const [counters, setCounters] = useState(initialCounters);
const onIncrement = (counterId) => {
const updatededCounters = counters.map((counter) => {
if (counter.id === counterId) {
counter.value++;
return counter;
}
return counter;
});
setCounters(updatededCounters);
};
return (
<>
<main className="container">
<Counters counters={counters} onIncrement={onIncrement} />
</main>
</>
);
};
export default App;
Codesandbox link: https://codesandbox.io/s/sad-chebyshev-l4hez?file=/src/App.js:0-725
Explanation
What i do the onIncrement method is simple:
i will create a new array with values i want to edit inside of it, then i'll set the state with this new array.
In the .map()
Each instance of counter will be looped, so for each instance i check if one of them is the one im looking for to update (with the same id as the counterId i receive as parameter)
If so, i edit the value of the current counter and then return the counter instance to allow the loop to continue to the next one.
When the counter id is not the same as the counterId received, i just return the counter without any modification.
So at the end, you will get the same array of values, exepct for the specific counter you incremented where you will see it's value updated by one count
I advice you to read some documentation about .map() function since it's really used in react : https://fr.reactjs.org/docs/lists-and-keys.html
And aswell you coud look into Object.keys() beceause it's often used with .map() aswell if you need to loop through object properties : https://reedbarger.com/how-to-transform-javascript-objects-the-power-of-objectkeys-values-entries/
I am facing a problem in resetting the count to 0 in my counter app. It's not resetting the count to 0 for any counter and I have tried console.log each counter.count but it's showing undefined.
App.js:
import React from "react";
import "./App.css";
import Counter from "./components/Counter";
export class App extends React.Component {
state = {
counters: []
};
addCounter = () => {
this.setState({
counters: [...this.state.counters, Counter]
});
};
reset = () => {
const counters = this.state.counters.map(counter => {
counter.count = 0;
return counter;
});
// console.log(counters.count);
this.setState({ counters });
};
render() {
return (
<div className="App">
<button onClick={this.reset}>Reset</button>
<button onClick={this.addCounter}>Add Counter</button>
{this.state.counters.map((Counter, index) => (
<Counter key={index} />
))}
</div>
);
}
}
export default App;
Counter.js:
import React, { Component } from 'react'
export class Counter extends Component {
state={
count:0,
}
increment=()=>{
this.setState({ count: this.state.count + 1 });
}
decrement=()=>{
this.setState({ count: this.state.count - 1 });
}
render() {
return (
<div>
<span><button onClick={this.increment}>+</button></span>
<span>{this.state.count}</span>
<span><button onClick={this.decrement}>-</button></span>
</div>
)
}
}
export default Counter
Reset counter should reset the count to 0 for all the counters.
When you use
this.setState({
counters: [...this.state.counters, Counter]
});
you saved a React Class into counters. So when you map to reset to 0, you need New Class to get state.count like this:
reset = () => {
const counters = this.state.counters.map(counter => {
(new counter()).state.count = 0;
return counter;
});
for (let counter of counters) {
console.log((new counter()).state);
}
this.setState({ counters });
};
It's time to lift the state up.
Move all your increment and decrement and reset functions to parent, App.
Also, add a separate array count state to monitor current count for each counter.
// App
export class App extends React.Component {
state = {
counters: [],
count: [] // additional state to store count for each counter
};
addCounter = () => {
this.setState({
counters: [...this.state.counters, Counter],
count: [...this.state.count, 0]
});
};
reset = () => {
this.setState({
count: this.state.count.map(c => 0)
});
};
// lifted up from Counter
increment = index => {
this.setState({
count: this.state.count.map((c, i) => (i === index ? c + 1 : c))
});
};
// lifted up from Counter
decrement = index => {
this.setState({
count: this.state.count.map((c, i) => (i === index ? c - 1 : c))
});
};
render() {
return (
<div className="App">
<button onClick={this.addCounter}>Add Counter</button>
<button onClick={this.reset}>Reset</button>
{this.state.counters.map((Counter, index) => (
<Counter
key={index}
increment={() => this.increment(index)}
decrement={() => this.decrement(index)}
count={this.state.count[index]}
/>
))}
</div>
);
}
}
// Counter
export class Counter extends React.Component {
render() {
return (
<div>
<span>
<button onClick={this.props.increment}>+</button>
</span>
<span>{this.props.count}</span>
<span>
<button onClick={this.props.decrement}>-</button>
</span>
</div>
);
}
}
Demo
I have a shopping catalog, I want to get data from the child and pass to a new component. I've mapped data from JSON (later to be an api call) and I want to get a few props from the mapped child component ie name, price, and the count (which is part of the component, not the original data)
data(name/price) -> mainComponent -> mapped to childComponent(has count)-> mainComponent
should I be using forms? some lifecycle methods? redux? Router? not really sure how to go about this.
Data
const marketData = [
{
id: 1,
name: "product1",
price: "$2",
},
{
id: 2,
name: "product2",
price: "$3",
},
{
id: 2,
name: "product3",
price: "$3",
}
]
Parent
import React, { Component } from 'react';
import MarketItem from './MarketItem.js'
import MarketData from './MarketData.js'
class MarketContainer extends Component {
constructor() {
super()
this.state = {
market: MarketData
}
}
render() {
const marketItems = this.state.market.map(item => <MarketItem key={item.id} product={item} />)
return (
<div>
{marketItems}
</div>
);
}
}
export default MarketContainer;
child
import React, {Component} from 'react';
class MarketItem extends Component {
constructor(props){
super(props)
this.state = {
count : 0
}
}
IncrementItem = () => {
this.setState({ count: this.state.count + 1 });
}
IncrementItemBy10 = () => {
this.setState({ count: this.state.count + 10 });
}
render(){
return (
<div className="MarketItem">
<div className="market__content">
<h1>{this.props.product.name + " " + this.props.product.price}</h1>
</div>
<div className="market__counter">
<button className="market__button--minus" onClick={this.DecreaseItem}>-</button>
<p>{this.state.count}</p>
<button className="market__button--plus" onClick={this.IncrementItem}>+</button>
</div>
</div>
);
}
}
export default MarketItem;
Within parent (marketContainer) I want to get the count, name, and price from every child component, make a new component. I've tried forms, but wasn't really happy with that should I use lifecycle methods? redux? Router?
class MarketContainer extends Component {
state = {
count : 0
}
IncrementItem = () => {
this.setState({count: count + 1})
}
DecreaseItem = () => {
this.setState({count: count - 1})
}
render() {
const marketItems = this.state.market.map(item => <MarketItem key={item.id}
product={item} IncrementItem={this.IncrementItem} DecreaseItem={DecreaseItem} count={this.state.count}/>)
return (
<div>
{marketItems}
</div>
);
}
}
now you can use IncrementItem and DecreaseItem props on MarketItem component when user click on increment and decrement icon. also we're passing value of count from parent component so you can show in child component.
You need to use Lifting State Up in React
You can define the state in MarketContainer for counts and other fields that you need to pass to other child components.
I have tried to include the codes to illustrate how I would do that.
//MarketData.js
//after ajax request
const marketData = [
{
id: 1,
name: "product1",
price: "$1"
},
{
id: 2,
name: "product2",
price: "$2"
}
];
//We can format market data as per we need it in components
const formattedMarketData = marketData.map(e => {
e.count = 0;
return e;
});
//parent component
//let us define all the things which we might need to pass to other components in here
// ...
constructor()
{
super();
this.state = {
markets: formattedMarketData
}
}
/* we are handling all state changes in parent component
we have updated state in parent component
we can deliver the changed state to any other components */
IncrementItem = (id) => {
this.setState((prevState) => {
const markets = {...prevState.markets};
const index = markets.findIndex(x => x.id ===id);
markets[index].count = prevState.markets[index].count + 1;
return {
markets: markets
};
});
}
DecrementItem = (id) => {
this.setState((prevState) => {
const markets = {...prevState.markets};
const index = markets.findIndex(x => x.id ===id);
markets[index].count = prevState.markets[index].count - 1;
return {
markets: markets
};
});
}
render() {
const marketItems = this.state.market.map(item => <MarketItem IncrementItem={this.IncrementItem} DecrementItem={this.DecrementItem} key={item.id} product={item} />)
return (
<div>
{marketItems}
</div>
);
}
//child component
export default (props) => {
const {IncrementItem, DecreaseItem, product} = props;
<div className="MarketItem">
<div className="market__content">
<h1>{product.name + " " + product.price}</h1>
</div>
<div className="market__counter">
<button className="market__button--minus" onClick={() => {DecreaseItem(product.id)}}>-</button>
<p>{product.count}</p>
<button className="market__button--plus" onClick={() => {IncrementItem(product.id)}}>+</button>
</div>
</div>
};
I think this is the way you should design your state to keep the count relevant to the products.
import React, { Component } from 'react';
import MarketItem from './MarketItem.js'
class MarketContainer extends Component {
constructor() {
super()
this.state = {
market: [
{
id: 1,
name: "product1",
price: "$2",
count:0,
},
{
id: 2,
name: "product2",
price: "$3",
count:0,
},
{
id: 2,
name: "product3",
price: "$3",
count:0,
}
]
}
}
IncrementItem = (i) => {
let market = this.state.market;
market[i].count += 1;
this.setState({market});
}
DecreaseItem = (i) => {
let market = this.state.market;
market[i].count -= (market[i].count > 0) ? 1: 0;
this.setState({market});
}
render() {
const marketItems = this.state.market.map(item => <MarketItem key={item.id} product={item} i={i} IncrementItem={this.IncrementItem} DecreaseItem={this.DecreaseItem}/>)
return (
<div>
{marketItems}
</div>
);
}
}
export default MarketContainer;
child
import React, {Component} from 'react';
class MarketItem extends Component {
constructor(props){
super(props);
}
render(){
return (
<div className="MarketItem">
<div className="market__content">
<h1>{this.props.product.name + " " + this.props.product.price}</h1>
</div>
<div className="market__counter">
<button className="market__button--minus" onClick={() => this.props.DecreaseItem(this.props.i)}>-</button>
<p>{this.state.count}</p>
<button className="market__button--plus" onClick={() => this.props.IncrementItem(this.props.i)}>+</button>
</div>
</div>
);
}
}
export default MarketItem;
I am currently developing a website for counting points, the user can give points to each player. The total of all points are calculated and shown below the counters on the administrator page. Now what I want is that I also want to show the total value in a different page, where players can see how much points their team has scored. But this total value has to be in sync with the total value in the administrator page. How can I do this? I heard about axios, but have no idea how this works. Can someone help me?
My Code:
Counter
import React, { Component } from "react";
import ReactDOM from "react-dom";
export default class Counter extends Component {
render() {
const { onIncrement, onDecrement } = this.props;
return (
<div>
<span>{this.formatCount()}</span>
<button onClick={() => onIncrement(this.props.counter)}>
Add
</button>
<button
onClick={() => onDecrement(this.props.counter)}
disabled={this.props.counter.value === 0 ? "disabled" : ""}
>
Delete
</button>
</div>
);
}
formatCount() {
const { value } = this.props.counter;
return value;
}
}
if (document.getElementById("counter")) {
ReactDOM.render(<Counter />, document.getElementById("counter"));
}
Counters
import React, { Component } from "react";
import ReactDOM from "react-dom";
import Counter from "./counter";
class Counters extends Component {
constructor() {
super();
this.state = {
counters: [
{ id: 1, value: 0 },
{ id: 2, value: 0 },
{ id: 3, value: 0 },
{ id: 4, value: 0 },
{ id: 5, value: 0 }
],
total: 0
};
}
handleIncrement(counter) {
const total = this.state.total + 1;
const counters = [...this.state.counters];
const index = counters.indexOf(counter);
counters[index] = { ...counter };
counters[index].value++;
this.setState({ counters: counters, total: total });
}
handleDecrement(counter) {
const total = this.state.total - 1;
const counters = [...this.state.counters];
const index = counters.indexOf(counter);
counters[index] = { ...counter };
counters[index].value--;
this.setState({ counters: counters, total: total });
}
handleReset() {
const total = 0;
const counters = this.state.counters.map(c => {
c.value = 0;
return c;
});
this.setState({ counters: counters, total: total });
}
render() {
return (
<div>
<button onClick={this.handleReset.bind(this)}>Reset</button>
{this.state.counters.map(counter => (
<Counter
key={counter.id}
onIncrement={this.handleIncrement.bind(this)}
onDecrement={this.handleDecrement.bind(this)}
counter={counter}
/>
))}
<span>{this.state.total}</span>
</div>
);
}
}
export default Counters;
if (document.getElementById("counters")) {
ReactDOM.render(<Counters />, document.getElementById("counters"));
}
Axios is an http client so that wouldn't apply to your problem.
Theres multiple solutions to your problem.
Option #1: Callbacks
Depending on how your admin and non-admin page is structured/nested this option might be easy or difficult to implement.
You could have a parent component that holds all your counter value data and renders either your admin or non-admin component that shows your counters.
class Parent extends Component {
constructor(props) {
super(props);
this.state = {
counters: [
{ id: 1, value: 0 },
{ id: 2, value: 0 },
{ id: 3, value: 0 },
{ id: 4, value: 0 },
{ id: 5, value: 0 }
],
total: 0
};
}
...
render() {
return (
<Switch>
<Route exact path="/admin" render={() => <Admin counters={this.state.counters} total={this.state.total} />}/>
<Route exact path="/user" render={() => <Admin counters={this.state.counters} total={this.state.total} />}/>
</Switch>
);
}
}
This will keep in sync because the counters are only being stored in one place, the Parent component.
Option #2: Global state management library
The alternative is a state management library like Redux. This gives you a global state that does not get deleted when a component is unmounted unlike your local this.state. You would put counters into this global state then your admin would perform actions on it, like increment and decrement, and admin and non-admin components would fetch counter values from it similar to local state by calling this.props.reduxStateCounters, for example.
More can be read about Redux here.