how to increment value by +1 by clicking button in react js - reactjs

am stuck with a problem when I click on like button I want it increment by +1, can anyone tell me how can I do it, I try it but I can't solve it.
below I share my all code that I used to make my small application. if you have question free feel to ask me.
Music.js
This is my music form where I wrote my whole code.
import React, { useState, useRef, useEffect } from 'react';
import { makeStyles } from '#material-ui/core/styles';
import AppBar from '#material-ui/core/AppBar';
import Toolbar from '#material-ui/core/Toolbar';
import Typography from '#material-ui/core/Typography';
import Button from '#material-ui/core/Button';
import IconButton from '#material-ui/core/IconButton';
import MenuIcon from '#material-ui/icons/Menu';
import ExitToAppIcon from '#material-ui/icons/ExitToApp';
import RadioIcon from '#material-ui/icons/Radio';
import firebase from '../Firebase';
import SearchBar from "material-ui-search-bar";
import { useHistory } from 'react-router-dom';
import { Container, Input, TextField } from '#material-ui/core';
import './Music.css';
import Table from '#material-ui/core/Table';
import TableBody from '#material-ui/core/TableBody';
import TableCell from '#material-ui/core/TableCell';
import TableContainer from '#material-ui/core/TableContainer';
import TableHead from '#material-ui/core/TableHead';
import TableRow from '#material-ui/core/TableRow';
import Paper from '#material-ui/core/Paper';
import ThumbUpAltIcon from '#material-ui/icons/ThumbUpAlt';
// import { useSelector } from 'react-redux';
const useStyles = makeStyles((theme) => ({
root: {
flexGrow: 1,
},
menuButton: {
marginRight: theme.spacing(2),
},
title: {
flexGrow: 1,
},
search: {
marginTop: 30,
},
table: {
minWidth: 650,
marginBottom: 10,
},
cells: {
marginTop: 50,
},
thumb: {
color: '#e75480',
},
name: {
color: 'blue',
padding: 10,
fontSize: 20,
},
audios: {
display: 'flex',
margin: 'auto',
height: 50,
width: 500,
alignItems: 'center'
},
icon: {
fontSize: 40,
color: '#e75480',
cursor:'pointer'
}
}));
const Music = () => {
const history = useHistory();
const inputRef = useRef(null);
const [search, setSearch] = useState("");
const [like, setLike] = useState(false);
const [music,setMusics] = useState([
{
id:"1",
name:"Arijit singh",
audiosrc:"https://upload.wikimedia.org/wikipedia/commons/1/15/Bicycle-bell.wav",
},
{
id:"2",
name:"Atif Aslam",
audiosrc:"https://upload.wikimedia.org/wikipedia/commons/1/15/Bicycle-bell.wav",
},
{
id:"3",
name:"Sonu Nigam",
audiosrc:"https://upload.wikimedia.org/wikipedia/commons/1/15/Bicycle-bell.wav",
},
{
id:"4",
name:"Neha kakkar",
audiosrc:"https://upload.wikimedia.org/wikipedia/commons/1/15/Bicycle-bell.wav",
},
])
const likeButton = (id)=>{
const likebuttons = music.find((curElem)=>{
return curElem.id == id
})
setLike({likebuttons:like+1})
console.log(likebuttons);
}
console.log(search);
// Logout Functionality
const Logout = () => {
firebase.auth().signOut().then(() => {
alert("Logout Successfull...");
history.push('/');
}).catch((error) => {
console.log(error);
});
}
const classes = useStyles();
return (
<div className={classes.root}>
<AppBar position="static" style={{ width: '100%' }}>
<Toolbar>
<IconButton edge="start" className={classes.menuButton} color="inherit" aria-label="menu">
<RadioIcon style={{ fontSize: "30px" }} /> React Music
</IconButton>
<Typography variant="h6" className={classes.title}>
</Typography>
<Button color="inherit" onClick={Logout}> <ExitToAppIcon /> Logout </Button>
</Toolbar>
</AppBar>
<Container>
<SearchBar className={classes.search}
value={search}
onChange={(newValue) => setSearch(newValue)}
/>
<Table className={classes.table} aria-label="simple table">
<TableBody>
{music && music.map(mus=>{
return(
<TableRow>
<TableCell style={{ display: 'flex', justifyContent: 'space-around' }}>
<div>
<h3>{like} <ThumbUpAltIcon className={classes.icon} onClick={()=>likeButton(music.id)} /> {mus.name}</h3>
</div>
<div>
<audio ref={inputRef} src={mus.audiosrc} className={classes.audios} controls />
</div>
</TableCell>
</TableRow>
)
})}
</TableBody>
</Table>
</Container>
</div>
);
}
export default Music;

Make following changes in your code:
const [like, setLike] = useState(false);
to
const [like, setLike] = useState(0);
and
setLike({likebuttons:like+1})
to
setLike(like+1);
and try again.

If you want to have like count for all music data,
First you should change like state to store object
const [like, setLike] = useState({});
Change your like button function to store data in object
const likeButton = (musicId)=>{
setLike({...like, like[musicId]: like[musicId] + 1 || 1 })
}
Now change how you are showing like count as below:
<div>
<h3>{like[music.id]} <ThumbUpAltIcon className={classes.icon} onClick={()=>likeButton(music.id)} /> {mus.name}</h3>
</div>

first, you should change your states.
each music has it's own like number, so you can't use 1 like state for all.
each item in your music array should have a value to store it's own like
numbers.
something like :
const [music,setMusics] = useState([
{
id:"1",
name:"Arijit singh",
audiosrc:"https://uploa...",
likeNumbers:0,
},
...
]
then your onClick should iterate the music array, find the clicked item and change it's likeNumbers value.
something like this:
cosnt handleClick = (id) => {
setMusic((prevState) => prevState.map((item) => {
if (item.id === id ) {
return {...item, likeNumbers:likeNumbers + 1 }
} else {
return item
}
})
}
to be clear:
first you should take the last version of your state, then in the process of iterating, find the clicked item and update it. to avoid mutate your state ( because items are objects ), you should make a copy first, then changed it. I mean here:
return {...item, likeNumbers:likeNumbers + 1 }
after all a part of your jsx code should be changed to :
<h3>{mus.likeNumber} <ThumbUpAltIcon className={classes.icon} onClick={()=>likeButton(music.id)} /> {mus.name}</h3>

First you need to make type as number instead of boolean
const [like, setLike] = useState(0);
and then
const likeButton = (id)=>{
const likebuttons = music.find((curElem)=>{
return curElem.id == id
})
setLike(likebuttons+1);
console.log(likebuttons);
}
Or
you can add no. of likes in same array object and just update that like field
You can check below example created for you. I hope it will help you.
It's having only like functionality
import "./styles.css";
import React, { useState } from "react";
export default function App() {
const [likedSong, setLikedSong] = useState({
likes: 0,
id: 0
});
const [music, setMusics] = useState([
{
id: "1",
name: "Arijit singh",
audiosrc:
"https://upload.wikimedia.org/wikipedia/commons/1/15/Bicycle-bell.wav",
likes: 0
},
{
id: "2",
name: "Atif Aslam",
audiosrc:
"https://upload.wikimedia.org/wikipedia/commons/1/15/Bicycle-bell.wav",
likes: 0
},
{
id: "3",
name: "Sonu Nigam",
audiosrc:
"https://upload.wikimedia.org/wikipedia/commons/1/15/Bicycle-bell.wav",
likes: 0
},
{
id: "4",
name: "Neha kakkar",
audiosrc:
"https://upload.wikimedia.org/wikipedia/commons/1/15/Bicycle-bell.wav",
likes: 0
}
]);
const handleLike = (list) => {
if (likedSong.id === list.id) {
setLikedSong({ ...likedSong, id: list.id, likes: ++likedSong.likes });
} else {
setLikedSong({ ...likedSong, id: list.id, likes: 0 });
}
};
return (
<div className="App">
{music.map((mus) => (
<div key={mus.id} className="music-list">
<div>
{mus.id === likedSong.id && (
<span className="no-likes">{likedSong.likes}</span>
)}
<span className="btn-like" onClick={() => handleLike(mus)}>
Like
</span>
{mus.name}
</div>
</div>
))}
</div>
);
}
Live working demo

Related

"TypeError: undefined is not a function" in ReactJS Test (useContext() is undefined?)

I am currently trying to test photos.js component. However, it shows an error at render. I haven't even put a test case to it. Here is what's inside of photos.test.js:
import { render, cleanup } from '#testing-library/react';
import Photos from '../photos';
it('should render', () => {
const component = render(<Photos />);
});
The error I get:
TypeError: undefined is not a function
134 | let history = useHistory();
135 | const [search, setSearch] = useState("");
> 136 | const [state, setPhotos] = usePhotoContext();
| ^
137 |
138 | useEffect(() => {
139 | getPhotos().then((res) => {
I don't know why usePhotoContext() is said undefined since it says "undefined is not a function". I have no idea how to solve this and what is wrong since I am a total newbie in React. I've also added the the PhotoProvider to my main (App.js) file.
photos.js:
import { getPhotos } from "../api";
import { usePhotoContext } from "../context/PhotoContext";
import { Link as RouterLink, useHistory } from "react-router-dom";
import { makeStyles } from "#material-ui/core/styles";
import Paper from "#material-ui/core/Paper";
import Typography from "#material-ui/core/Typography";
import Button from "#material-ui/core/Button";
import InputBase from "#material-ui/core/InputBase";
import Grid from "#material-ui/core/Grid";
const PhotosGrid = () => {
let history = useHistory();
const [search, setSearch] = useState("");
const [state, setPhotos] = usePhotoContext();
useEffect(() => {
getPhotos().then((res) => {
setPhotos({
...state,
photos: res.data.response,
});
});
}, []);
return (
<>
<div data-testid="photos" className={classes.root}>
<Typography
align="center"
variant="h4"
color="primary"
style={{ marginTop: "20px" }}
gutterBottom
>
Photo Feed
</Typography>
<div
style={{
display: "flex",
justifyContent: "center",
width: "100",
marginTop: "10px",
marginBottom: "20px",
}}
>
<Paper
component="form"
style={{
marginRight: "10px",
}}
>
<InputBase
className={classes.input}
onChange={handleSearch}
placeholder="Search Photo"
inputProps={{ "aria-label": "search photo" }}
onKeyDown={keyPressHandler}
/>
</Paper>
<RouterLink className={classes.noDecoration} to={`/${search}`}>
<Button
style={{ marginRight: "20px" }}
variant="contained"
size="small"
color="primary"
>
Search
</Button>
</RouterLink>
</div>
<Grid
container
spacing={2}
alignItems="stretch"
className={classes.grid}
>
{state.photos.map((photo, index) => (
<Grid item xs={12} md={3} sm={4}>
<PhotoCard key={photo.id + index} {...photo} />
</Grid>
))}
</Grid>
</div>
</>
);
};
export default PhotosGrid;
PhotoContext.js
import React, { useState, createContext, useContext } from "react";
const photoState = {
photo: {
title: "",
description: "",
year: null,
duration: null,
genre: "",
rating: null,
review: "",
image_url: "",
},
photos: [],
};
export const PhotoContext = createContext(photoState);
export const PhotoProvider = ({ children }) => {
const photo = useState(photoState);
return (
<PhotoContext.Provider value={photo}>{children}</PhotoContext.Provider>
);
};
export const usePhotoContext = () => useContext(PhotoContext);

ReactJs adding active class to DIV

I have 3 DIVs, dynamically created. My target is: when any div is clicked to add active class to it, and of course if any other has that active class to remove it. How can I achieve that?
import React, { useState } from "react";
import "./styles/App.css";
import { Container, Box, Typography, makeStyles } from "#material-ui/core";
const useStyles = makeStyles({
mainBox: {
display: "flex",
justifyContent: "space-evenly"
},
mybox: {
backgroundColor: "#9fa8da",
padding: "40px",
color: "#fff",
maxWidth: 300
}
});
function App() {
const clasess = useStyles();
const [active, setactive] = useState("");
const mydata = [
{
id: 1,
name: "Ganesh",
des: "UI Developer"
},
{
id: 2,
name: "Suresh",
des: "Accountant"
},
{
id: 3,
name: "Srikanth",
des: "Digital"
}
];
const onClick = index => {
setactive("active");
};
return (
<Container>
<Box className={clasess.mainBox}>
{mydata.map((val, index) => {
return (
<>
<Box
boxShadow={1}
key={index}
onClick={e => {
onClick(index);
}}
className={active}
>
<Typography variant="h4">{val.name}</Typography>
<Typography>{val.des}</Typography>
</Box>
</>
);
})}
</Box>
</Container>
);
}
export default App;
<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>
I have 3 DIVs, dynamically created. My target is: when any div is clicked to add active class to it, and of course if any other has that active class to remove it. How can I achieve that?
I have 3 DIVs, dynamically created. My target is: when any div is clicked to add active class to it, and of course if any other has that active class to remove it. How can I achieve that?
Try to check when active id is the clicked id:
import React, { useState } from "react";
import "./styles/App.css";
import { Container, Box, Typography, makeStyles } from "#material-ui/core";
const useStyles = makeStyles({
mainBox: {
display: "flex",
justifyContent: "space-evenly"
},
mybox: {
backgroundColor: "#9fa8da",
padding: "40px",
color: "#fff",
maxWidth: 300
}
});
function App() {
const clasess = useStyles();
const [active, setActive] = useState();
const mydata = [
{
id: 1,
name: "Ganesh",
des: "UI Developer"
},
{
id: 2,
name: "Suresh",
des: "Accountant"
},
{
id: 3,
name: "Srikanth",
des: "Digital"
}
];
const onClick = id => {
setActive(id);
};
return (
<Container>
<Box className={clasess.mainBox}>
{mydata.map((val, index) => {
return (
<>
<Box
boxShadow={1}
key={index}
onClick={e => {
onClick(val.id);
}}
className={val.id == id ? active : deactive}
>
<Typography variant="h4">{val.name}</Typography>
<Typography>{val.des}</Typography>
</Box>
</>
);
})}
</Box>
</Container>
);
}
export default App;
You already managed on your Box component to add an "active" classname when one of them is Clicked.
But, you need to add some style or something to show for those "active" elements.
Inside of useStyle add the class active and test it out with some styling:
i.e)
const useStyles = makeStyles({
mainBox: {
display: "flex",
justifyContent: "space-evenly"
},
mybox: {
backgroundColor: "#9fa8da",
padding: "40px",
color: "#fff",
maxWidth: 300
},
active {
backgroundColor: "red"
}
});
I'm not sure if you need to add "active" class using classes, something like :
<Box
boxShadow={1}
key={index}
onClick={e => {
onClick(index);
}}
className={classes.active}
>
<Typography variant="h4">{val.name}</Typography>
<Typography>{val.des}</Typography>
</Box>

useEffect running on mount causing error and blank object

I keep getting a blank avatarlistitem when I click add for the first time and then after that the appropriate item will display after the second click. If I add a new URL and click the third time the item won't appear until the fourth click. I am most likely doing something wrong with my useEffect and state values.
import React, { useState, useEffect } from 'react';
import { makeStyles } from '#material-ui/core/styles';
import List from '#material-ui/core/List';
import Divider from '#material-ui/core/Divider';
import AvatarListItem from './AvatarListItem';
import AddIcon from '#material-ui/icons/Add';
import TextField from '#material-ui/core/TextField';
import Button from '#material-ui/core/Button';
import axios from 'axios';
const useStyles = makeStyles((theme) => ({
root: {
display: 'flex',
justifyContent: 'space-between',
flexDirection: 'column',
flexWrap: 'wrap',
backgroundColor: theme.palette.background.paper,
},
inline: {
display: 'inline',
},
formControl: {
width: '100%',
margin: theme.spacing(1),
marginBottom: '50px',
minWidth: 120,
},
extendedIcon: {
margin: '10px',
marginRight: theme.spacing(1),
},
}));
export default function AvatarList() {
const classes = useStyles();
const [url, setUrl] = useState('');
const [itemDetails, setItemDetails] = useState({});
const [items, setItems] = useState([]);
const [search, setSearch] = useState('');
useEffect(() => {
const fetchData = async () => {
const result = await axios(
`http://127.0.0.1:5000/api/resources/products?url=${url}`,
)
setItemDetails(result.data[0]);
}
fetchData()
}, [search])
const addItem = (itemDetails) => {
const newItems = [...items, itemDetails];
setItems(newItems);
};
let handleSubmit = (e) => {
e.preventDefault();
console.log(itemDetails);
addItem(itemDetails);
};
let removeItem = (index) => {
const newItems = [...items];
newItems.splice(index, 1);
setItems(newItems);
};
return (
<div>
<form className={classes.formControl} onSubmit={e => handleSubmit(e)}>
<TextField id="outlined-basic" label="Amazon Url" variant="outlined" name='newItem' onChange={e => setUrl(e.target.value)} value={url} />
<Button variant="contained" color="primary" type="submit" value="Submit" onClick={() => setSearch(url)}>
<AddIcon className={classes.extendedIcon} />
</Button>
</form>
<List className={classes.root}>
{items.map((item, index) => (
<>
<AvatarListItem
itemDetails={item}
key={index}
index={index}
removeItem={removeItem}
/>
<Divider variant="middle" component="li" />
</>
))}
</List>
</div >
);
}
A better approach may be to purely rely on the onSubmit callback instead of relying on the useEffect which may run more often than needed. Also, it doesn't look like you need to use the search or itemDetails state at all.
import React, { useState, useEffect } from 'react';
import { makeStyles } from '#material-ui/core/styles';
import List from '#material-ui/core/List';
import Divider from '#material-ui/core/Divider';
import AvatarListItem from './AvatarListItem';
import AddIcon from '#material-ui/icons/Add';
import TextField from '#material-ui/core/TextField';
import Button from '#material-ui/core/Button';
import axios from 'axios';
const useStyles = makeStyles((theme) => ({
root: {
display: 'flex',
justifyContent: 'space-between',
flexDirection: 'column',
flexWrap: 'wrap',
backgroundColor: theme.palette.background.paper,
},
inline: {
display: 'inline',
},
formControl: {
width: '100%',
margin: theme.spacing(1),
marginBottom: '50px',
minWidth: 120,
},
extendedIcon: {
margin: '10px',
marginRight: theme.spacing(1),
},
}));
export default function AvatarList() {
const classes = useStyles();
const [url, setUrl] = useState('');
const [items, setItems] = useState([]);
const addItem = (itemDetails) => {
const newItems = [...items, itemDetails];
setItems(newItems);
};
let handleSubmit = async (e) => {
e.preventDefault();
const result = await axios(
`http://127.0.0.1:5000/api/resources/products?url=${url}`,
)
addItem(result.data[0]);
};
let removeItem = (index) => {
const newItems = [...items];
newItems.splice(index, 1);
setItems(newItems);
};
return (
<div>
<form className={classes.formControl} onSubmit={handleSubmit}>
<TextField id="outlined-basic" label="Amazon Url" variant="outlined" name='newItem' onChange={e => setUrl(e.target.value)} value={url} />
<Button variant="contained" color="primary" type="submit">
<AddIcon className={classes.extendedIcon} />
</Button>
</form>
<List className={classes.root}>
{items.map((item, index) => (
<React.Fragment key={index}>
<AvatarListItem
itemDetails={item}
key={index}
index={index}
removeItem={removeItem}
/>
<Divider variant="middle" component="li" />
</React.Fragment>
))}
</List>
</div >
);
}
if your intention is fetching data every time user click submit, based on your useEffect, you should not base your useEffect on search but on items itself (as when user submit, you add something to items)
your code should look like this
useEffect(() => {
const fetchData = async () => {
const result = await axios(
`http://127.0.0.1:5000/api/resources/products?url=${url}`,
)
setItemDetails(result.data[0]);
}
fetchData()
}, [items])

Next js: Error: Objects are not valid as a React child (found: Error: Response not successful: Received status code 401)

This app shows Github issues with graphql API.
I didn't change anything after finishing the app but I got this error.
I used Next js, Typescript, Material UI, Tailwind css and GraphQL for this project.
Index Component
import React, { useState } from "react"
import { Typography, Container, makeStyles } from "#material-ui/core"
import SearchBar from "../components/SearchBar/SearchBar"
import RepositoryList from "../components/RepositoryList/RepositoryList"
import Head from "next/head"
const useStyles = makeStyles({
title: {
marginTop: "1rem",
marginBottom: "1rem",
textAlign: "center",
},
})
const App = () => {
const classes = useStyles()
const [searchTerm, setSearchTerm] = useState<string>("")
return (
<>
<Head>
<title>GraphQL Github Client</title>
</Head>
<Container maxWidth={"sm"}>
<div className="mt-10 mb-5">
<Typography variant={"h3"} className={classes.title}>
GraphQL Github Client
</Typography>
</div>
<SearchBar
className="mb-10"
value={searchTerm}
onChange={setSearchTerm}
/>
<RepositoryList searchTerm={searchTerm} />
</Container>
</>
)
}
export default App
RepositoryList Component
import React, { useEffect, useState } from "react"
import { Typography, CircularProgress, makeStyles } from "#material-ui/core"
import { useQuery } from "#apollo/react-hooks"
import { SEARCH_FOR_REPOS } from "../../Queries/queries"
import Repository from "../Repository/Repository"
interface RepositoryListProps {
searchTerm?: string
}
const useStyles = makeStyles({
note: {
marginTop: "1rem",
textAlign: "center",
},
spinnerContainer: {
display: "flex",
justifyContent: "space-around",
marginTop: "1rem",
},
})
const RepositoryList: React.FC<RepositoryListProps> = ({ searchTerm }) => {
const classes = useStyles()
const [expandedRepo, setExpandedRepo] = useState(null)
const { data, loading, error } = useQuery(SEARCH_FOR_REPOS, {
variables: { search_term: searchTerm },
})
useEffect(() => {
setExpandedRepo(null)
}, [data])
if (loading) {
return (
<div className={classes.spinnerContainer}>
<CircularProgress />
</div>
)
}
if (error) {
return (
<Typography
variant={"overline"}
className={classes.note}
component={"div"}
color={"error"}
>
{error}
</Typography>
)
}
if (!data.search.repositoryCount) {
return (
<Typography
variant={"overline"}
className={classes.note}
component={"div"}
>
There are no such repositories!
</Typography>
)
}
return (
<div>
{data.search.edges.map(
(
repo: { edges: { id: number } },
i: string | number | ((prevState: null) => null) | null | any
) => (
<>
<Repository
repo={repo}
expanded={expandedRepo === i}
onToggled={() => setExpandedRepo(i)}
key={repo.edges.id}
/>
</>
)
)}
</div>
)
}
export default RepositoryList
Repository Component
import React from "react"
import {
ExpansionPanel,
ExpansionPanelSummary,
ExpansionPanelDetails,
Typography,
Chip,
makeStyles,
} from "#material-ui/core"
import StarIcon from "#material-ui/icons/Star"
import PeopleIcon from "#material-ui/icons/People"
import IssueList from "../IssueList/IssueList"
const useStyles = makeStyles({
root: {
marginTop: "1rem",
},
summaryContainer: {
flexDirection: "column",
},
summaryHeader: {
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
marginBottom: "1rem",
},
chip: {
marginLeft: "0.5rem",
},
})
interface RepositoryProps {
repo: any
expanded: boolean
onToggled: any
}
const Repository: React.FC<RepositoryProps> = ({
repo,
expanded,
onToggled,
}) => {
const {
node: {
name,
descriptionHTML,
owner: { login },
stargazers: { totalCount: totalStarCount },
},
} = repo
const classes = useStyles()
return (
<ExpansionPanel
expanded={expanded}
onChange={onToggled}
className={classes.root}
>
<ExpansionPanelSummary classes={{ content: classes.summaryContainer }}>
<div className={classes.summaryHeader}>
<Typography variant={"h6"}>{name}</Typography>
<div>
<Chip
label={`by ${login}`}
avatar={<PeopleIcon />}
className={classes.chip}
/>
<Chip
label={totalStarCount}
avatar={<StarIcon />}
className={classes.chip}
/>
</div>
</div>
<Typography
variant={"caption"}
dangerouslySetInnerHTML={{ __html: descriptionHTML }}
component={"div"}
/>
</ExpansionPanelSummary>
<ExpansionPanelDetails>
{expanded && <IssueList repoName={name} repoOwner={login} />}
</ExpansionPanelDetails>
</ExpansionPanel>
)
}
export default Repository
These are my components.
What should I do for fixing this problem?
It looks like the issue is in this spot where you do {error}. I would double check what error actually is but it looks like its an object and not a string like you are using it
<Typography
variant={"overline"}
className={classes.note}
component={"div"}
color={"error"}
>
{error}
</Typography>

How to use React Redux state for conditionally showing and Disabling a Button

I have made a shopping cart with React-Redux. Everything works as it should, but I am struggling with 1 particular thing. That is changing the "Add to Cart" button when an item is added to the cart. I want this button to be either disabled, or change this button in a "Delete Item" button.
How can I change this after an item is added?
My Reducer ("ADD_TO_CART" and "REMOVE_ITEM"):
import bg6 from '../../images/bg6.svg'
import bg7 from '../../images/bg7.svg'
import bg8 from '../../images/bg8.svg'
const initState = {
items: [
{ id: 1, title: 'Landing Page', desc: "Template", price: 10, img: bg6 },
{ id: 2, title: 'Adidas', desc: "Lore", price: 10, img: bg7 },
{ id: 3, title: 'Vans', desc: "Lorem ip", price: 10, img: bg8 },
],
addedItems: [],
total: 0,
}
const cartReducer = (state = initState, action) => {
if (action.type === "ADD_TO_CART") {
let addedItem = state.items.find(item => item.id === action.id)
//check if the action id exists in the addedItems
let existed_item = state.addedItems.find(item => action.id === item.id)
if (existed_item) {
addedItem.quantity = 1
return {
...state,
total: state.total + addedItem.price
}
}
else {
let addedItem = state.items.find(item => item.id === action.id)
addedItem.quantity = 1;
//calculating the total
let newTotal = state.total + addedItem.price
return {
...state,
addedItems: [...state.addedItems, addedItem],
total: newTotal
}
}
}
if (action.type === "REMOVE_ITEM") {
let itemToRemove = state.addedItems.find(item => action.id === item.id)
let new_items = state.addedItems.filter(item => action.id !== item.id)
//calculating the total
let newTotal = state.total - (itemToRemove.price * itemToRemove.quantity)
return {
...state,
addedItems: new_items,
total: newTotal
}
}
else {
return state
}
}
export default cartReducer
My Home Component where i want to change the button:
import React, { Component } from 'react';
import { connect } from 'react-redux'
import { addToCart, removeItem } from '../../../store/actions/cartActions'
import { withStyles } from '#material-ui/core/styles';
import { Link, Redirect, withRouter, generatePath } from 'react-router-dom'
import CardActions from '#material-ui/core/CardActions';
import CardContent from '#material-ui/core/CardContent';
import CardMedia from '#material-ui/core/CardMedia';
import CssBaseline from '#material-ui/core/CssBaseline';
import Grid from '#material-ui/core/Grid';
import Typography from '#material-ui/core/Typography';
import Container from '#material-ui/core/Container';
import Button from '#material-ui/core/Button';
import Card from '#material-ui/core/Card';
import Appbar from '../../home/Appbar'
import CheckIcon from '#material-ui/icons/Check';
import DeleteIcon from '#material-ui/icons/Delete';
const styles = theme => ({
main: {
backgroundColor: '#121212',
maxHeight: 'auto',
width: '100%',
padding: '0',
margin: '0',
},
cardGrid: {
height: "auto",
paddingTop: theme.spacing(8),
paddingBottom: theme.spacing(8),
},
card: {
backgroundColor: "#272727",
height: '100%',
display: 'flex',
flexDirection: 'column',
},
cardMedia: {
paddingTop: '56.25%', // 16:9
},
cardContent: {
flexGrow: 1,
},
});
class Home extends Component {
render() {
const { classes, items, addedItems, addedItemID } = this.props
const { } = this.state
console.log(addedItemID)
let allItems =
items.map(item => {
return (
<Grid item key={item.id} xs={12} sm={6} md={4}>
<Card className={classes.card}>
<CardMedia
className={classes.cardMedia}
image={item.img}
alt={item.title}
title={item.title}
/>
<CardContent className={classes.cardContent}>
<Typography gutterBottom variant="h5" component="h2">
{item.title} {item.price}
</Typography>
<Typography>
{item.desc}
</Typography>
</CardContent>
<CardActions className={classes.cardActions}>
<Button
onClick={() => { this.props.addToCart(item.id) }}
style={{ color: "#d84", border: "1px solid #d84" }}
size="small"
color="primary"
disables={} //// HERE I NEED TO DISABLE BUTTON WHEN ITEM IS ADDED ////
>
Add to Cart //// THIS NEEDS TO BE DELETE ITEM ////
</Button>
</CardActions>
</Card>
</Grid>
)
})
return (
<React.Fragment>
<CssBaseline />
<Appbar />
<main className={classes.main}>
<Container className={classes.cardGrid} maxWidth="lg">
<Grid container spacing={4}>
{allItems}
</Grid>
</Container>
</main>
</React.Fragment>
)
}
}
const mapStateToProps = (state) => {
return {
items: state.cartReducer.items,
addedItems: state.cartReducer.addedItems,
addedItemID: state.cartReducer.addedItemID,
}
}
const mapDispatchToProps = (dispatch) => {
return {
addToCart: (id) => { dispatch(addToCart(id)) },
removeItem: (id) => { dispatch(removeItem(id)) },
}
}
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(withStyles(styles)(Home)));
I can't seem to wrap my around around it. I hope you guys can help.
Thanks in advance.
You can use the this.props.addedItems and check in it has the item you are currently trying to add, add a method and use it for Disabled State.
const isItemExist = (itemToFindId) => {
return this.props.addedItems.findIndex(item => item.id === itemToFindId) === -1;
}
<Button
onClick={() => { this.props.addToCart(item.id) }}
style={{ color: "#d84", border: "1px solid #d84" }}
size="small"
color="primary"
disables={()=>isItemExist(item.id)} //// HERE I NEED TO DISABLE BUTTON WHEN ITEM IS ADDED ////
>
Add to Cart //// THIS NEEDS TO BE DELETE ITEM ////
</Button>
you can also use the same logic and create another button use
{isItemExist(item.id) ? <RemoveCartButton/> : <AddToCartButton/>}
let me know if you have any more issues.
I'll try to solve them
use Conditional Rendering in JSX:
first make additional state for handling the state of the button.
i call this for example 'is_add_to_cart' and every time the button is clicked in both reducer sections we should change the value of is_add_to_cart to the opposite value
i mean if you initially set : is_add_to_cart : true in the init state then you should revert its value and it is something like this : is_add_to_cart = !is_add_to_cart in your new state.
in return part of the jsx code we should do conditional rendering so here how it looks :
is_add_to_cart ? <Button . . . >Add To Cart</Button>
: <Button . . . >Delete Item</Button>
you can do this by checking addedItems length or total's value.
first of all, include total in your mapStateToProps function:
const mapStateToProps = (state) => {
return {
items: state.cartReducer.items,
addedItems: state.cartReducer.addedItems,
addedItemID: state.cartReducer.addedItemID,
total: state.cartReducer.total
}
}
then extract it from component's props:
const { classes, items, addedItems, addedItemID, total } = this.props;
now your button can be something like this:
<Button
onClick={() => { this.props.addToCart(item.id) }}
style={{ color: "#d84", border: "1px solid #d84" }}
size="small"
color="primary"
disabled={total > 0 || addedItems.length > 0 ? true : false}
>
{total > 0 || addedItems.length > 0 ? "Delete Item" : "Add to Cart" }
</Button>

Resources