React JS, how to show state in two pages - reactjs

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.

Related

ReactJs can't fix missing semicolon error before "render()"

I was following ProgrammingWithMosh's reactJs tutorial, and I am now stuck in the App.js file (I have other children components in my project too, but I have not posted their code in this question) due to the following error:
';' expected. [Line 41 of the following code]
This error occurs on the line where "render()" is found - right after the "render()" keyword and before its first "{" bracket.
Here is the code:
import React, { Component } from 'react';
import NavBar from "./components/navbar";
import Counters from './components/counters';
import './App.css';
function App() {
state = {
counters: [
{ id: 1, value: 4 },
{ id: 2, value: 0 },
{ id: 3, value: 0 },
{ id: 4, value: 0 },
],
};
handleReset = () => {
const counters = this.state.counters.map((c) => {
c.value = 0;
return c;
});
this.setState({ counters: counters });
};
handleIncrement = (counter) => {
// console.log(counter);
const counters = [...this.state.counters];
const index = counters.indexOf(counter);
// counters[index] = { ...counter };
counters[index].value++;
this.setState({ counters: counters });
// console.log(this.state.counters[index]);
};
handleDelete = (counterId) => {
// console.log("Event Handler Called", counterId);
const counters = this.state.counters.filter((c) => c.id !== counterId);
// console.log(counters);
this.setState({ counters: counters });
};
render() {
return (
<React.Fragment>
<NavBar />
<main className="container">
<Counters
counters={this.state.counters}
onReset={this.handleReset}
onIncrement={this.handleIncrement}
onDelete={this.handleDelete}
/>
</main>
</React.Fragment>
);
}
}
export default App;
I cannot figure out what semi colons I am missing and I have also checked to see if I am missing any ending brackets from previous functions (which to my knowledge, I am not). Please help me pinpoint the issue, and thank you so much in advance!
You're mixing a function component with a class component.
change
function App() {
to
class App extends Component {

React hooks: How do I update state with object

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/

Looking to conditionally call a mutation

I'm having troubles conditionally calling a mutation from a handler within my main render class. Unfortunately, I am unable to implement a submit button(limitations of project definition, my DOM inputs must dynamically render the new graph onChange) and have to verify conditions before allowing a mutation to execute, yet I seem to be unable to diagnose a fix for this!
Below, you can find the parent component code. Note that the mutation is still within the validation handler, sitting there temporarily until a fix is established.
I've also had a few of the apollo documentation tutorials pointed my way, but unfortunately they rely on a different project structure that I cannot replicate due to project limitations.
Below, you can find the parent component code. Note that the mutation is still within the validation handler, sitting there temporarily until a fix is established.
import React, { Component } from "react";
import CurrencyInput from "./CurrencyInput";
import SliderInput from "./SliderInput";
import DisplayGraph from "./DisplayGraph";
import "./InputGraphSection.css";
import FrequencyInput from "./FrequencyInput";
import { Mutation } from "react-apollo";
import gql from "graphql-tag";
const SAVINGS_MUTATION = gql`
mutation savingsmutation(
$paymentFrequency: Int!
$initialDeposit: Float!
$monthlyDeposit: Float!
$interestRate: Float!
) {
createSavings(
paymentFrequency: $paymentFrequency
initialDeposit: $initialDeposit
monthlyDeposit: $monthlyDeposit
interestRate: $interestRate
) {
savings {
months {
id
totalInterest
totalValue
}
}
}
}
`;
export default class InputGraphSectionContainer extends Component {
constructor(props) {
super(props);
this.state = {
savT: [{ x: 0, y: 0 }],
intT: [{ x: 0, y: 0 }]
};
}
handleComplete = ({ data: { createSavings } }) => {
this.setState(prevState => ({
savT: [
...prevState.savT,
// month is inside the data returned by the API????
{ x: createSavings.savings.months.id, y: createSavings.savings.months.totalValue }
],
intT: [
...prevState.intT,
{ x: createSavings.savings.months.id, y: createSavings.savings.months.totalInterest }
]
}));
};
render() {
const { savT, intT } = this.state;
return (
<Mutation mutation={SAVINGS_MUTATION} onCompleted={this.handleComplete}>
{savingsmutation => (
<InputGraphSection mutate={savingsmutation} savT={savT} intT={intT} />
)}
</Mutation>
);
}
}
class InputGraphSection extends Component {
constructor(props) {
super(props);
this.state = {
initialDeposit: "",
monthlyDeposit: "",
interestRate: 0,
paymentFrequency: ""
};
}
componentDidUpdate({ mutate }, prevState) {
console.log(this.state);
if (
this.state.initialDeposit !== "" &&
this.state.monthlyDeposit !== "" &&
this.state.paymentFrequency !== "" &&
prevState !== this.state
) {
//If currencyInput elements are returning strings, convert to ints here.
var paymentF = Number(this.state.paymentFrequency);
var initialD = parseFloat(this.state.initialDeposit);
var monthlyD = parseFloat(this.state.monthlyDeposit);
var interestR = parseFloat(this.state.interestRate)/100;
console.log("execute mutation");
mutate({
variables: {
paymentFrequency: paymentF,
initialDeposit: initialD,
monthlyDeposit: monthlyD,
interestRate: interestR
}
});
console.log("Mutation query commencing")
} else {
console.log("Input Requirements not met, will not generate graph.");
}
}
handleChange = evt => {
const { name, value } = evt.target;
this.setState({ [name]: value });
};
render() {
const {
initialDeposit,
monthlyDeposit,
interestRate,
paymentFrequency
} = this.state;
const { savT, intT } = this.props;
return (
<div>
<p className="input-label">
Inputs must be positive and have no more than 15 digits with 2 decimal
places!
</p>
<div className="financial-inputs">
<p className="input-label">What is your initial Deposit?</p>
<CurrencyInput
name="initialDeposit"
value={initialDeposit}
onInputChange={this.handleChange}
/>
<p className="input-label">How much will you save each month?</p>
<CurrencyInput
name="monthlyDeposit"
value={monthlyDeposit}
onInputChange={this.handleChange}
/>
<p className="input-label">
What is the annual interest rate you have acquired?
</p>
<SliderInput
name="interestRate"
value={Number(interestRate)}
onInputChange={this.handleChange}
/>
<p className="input-label">
Specify the frequency of interest compounding.
</p>
<FrequencyInput
name="paymentFrequency"
value={paymentFrequency}
onInputChange={this.handleChange}
/>
</div>
<div className="financial-display">
<DisplayGraph savT={savT} intT={intT} />
</div>
</div>
);
}
}
There are multiple ways you can call an apollo mutation conditionally because there are multiple ways to call a mutation in general. These ways include the Mutation component, calling mutate directly on the client, or using the graphql HOC.
Your example is using the Mutation component which follows the render prop pattern. In order to use this you need to render the component, and then call the mutation it provides:
...
render() {
return (
<Mutation
mutation={SAVINGS_MUTATION}
variables={{
paymentFrequency: paymentF,
initialDeposit: initialD,
monthlyDeposit: monthlyD,
interestRate: interestR
}}
>
{(savingsmutation, { data }) => {
return (
<CurrencyInput
value={initialDeposit}
onInputChange={() => savingsmutation()}
/>
)
}}
</Mutation>
)
}
...
You could also use the withApollo HOC to gain access to the client directly and call mutate on it.
import { withApollo } from 'react-apollo'
class InputGraphSection extends Component {
handleChange() {
this.props.client.mutate({
mutation: SAVINGS_MUTATION,
variables: {
paymentFrequency: paymentF,
initialDeposit: initialD,
monthlyDeposit: monthlyD,
interestRate: interestR
}
})
}
}
export default withApollo(InputGraphSection)
and finally using the graphql HOC
import { graphql } from 'react-apollo'
class InputGraphSection extends Component {
handleChange() {
this.props.mutate({
paymentFrequency: paymentF,
initialDeposit: initialD,
monthlyDeposit: monthlyD,
interestRate: interestR
})
}
}
export default graphql(SAVINGS_MUTATION)(InputGraphSection)

React pass data from mapped child to new component

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;

How to hide react element when specific value is reached?

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

Resources