useEffect running on mount causing error and blank object - reactjs

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])

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

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

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

React Virtuoso Load More Button

I am reading the react virtuoso documentation and it has the following example.
When replicating it in, I get the error that this generateUsers function is not defined.
Its my first time using virtuoso so I am a bit lost with the docu.
Any hint is super welcome
Thanks
import { Virtuoso } from 'react-virtuoso'
import {useState, useEffect, useCallback} from 'react'
const App=()=> {
const [users, setUsers] = useState(() => [])
const [loading, setLoading] = useState(false)
const loadMore = useCallback(() => {
setLoading(true)
return setTimeout(() => {
setUsers((users) => ([...users, ...generateUsers(100, users.length)]) )
setLoading(() => false)
}, 500)
}, [setUsers, setLoading])
useEffect(() => {
const timeout = loadMore()
return () => clearTimeout(timeout)
}, [])
return (
<Virtuoso
style={{height: 300}}
data={users}
itemContent={(index, user) => { return (<div style={{ backgroundColor: user.bgColor }}>{user.name}</div>) }}
components={{
Footer: () => {
return (
<div
style={{
padding: '2rem',
display: 'flex',
justifyContent: 'center',
}}
>
<button disabled={loading} onClick={loadMore}>
{loading ? 'Loading...' : 'Press to load more'}
</button>
</div>
)
}
}}
/>
)
}
export default App

TypeError: theme is undefined - When trying to render Material UI component

I am having trouble rendering my react component since I separated my jss file and changed it from makeStyles to withStyles to avoid a hook problem.
I am getting an error message in my jss styling file as a couple of the methods have a 'theme' in the parenthesis for the styling to work off.
How do I go about changing this so that it renders correctly?
accessControl.component.js
import {connect, useSelector} from "react-redux";
import DataTable from "./userListTable.component";
import {Paper} from "#material-ui/core";
function AdminAccessControl(props) {
const { children, value, index, ...other } = props;
// select user object from redux
const user = useSelector(state => state.user);
// select school object from redux
const school = useSelector(state => state.diveSchool);
const classes = useStyles();
const [role, setRole] = useState({
userRole: "",
});
const handleChange = (property) => (e) => {
setRole({
// override the changed property and keep the rest
...role,
[property]: e.target.value,
});
}
return (
<div className={classes.root}>
<StyledTabs
value={value}
onChange={handleChange}
indicatorColor="primary"
textColor="primary"
aria-label="styled tabs example"
centered>
<StyledTab label="User Access Control" />
{/*<DataTable />*/}
<StyledTab label="Scuba School Access Control" />
{/*<DataTable />*/}
</StyledTabs>
<Typography className={classes.padding} />
</div>
);
}
function mapStateToProps(state){
const { user } = state.auth;
const { school } = state.diveSchool;
return {
user,
school,
};
}
export default compose(
connect(
mapStateToProps,
),
withStyles(useStyles)
)(AdminAccessControl);
myJss-style.js
export const useStyles = (theme) => ({
root: {
flexGrow: 1,
},
padding: {
padding: theme.spacing(3),
},
demo1: {
backgroundColor: theme.palette.background.paper,
},
demo2: {
backgroundColor: '#2e1534',
},
});
export const StyledTabs = () => ({
indicator: {
display: 'flex',
justifyContent: 'center',
backgroundColor: 'transparent',
'& > span': {
maxWidth: 40,
width: '100%',
backgroundColor: '#635ee7',
},
},
})((props) => <StyledTabs {...props} TabIndicatorProps={{ children: <span /> }} />);
export const StyledTab = (theme) => ({
root: {
textTransform: 'none',
color: '#fff',
fontWeight: theme.typography.fontWeightRegular,
fontSize: theme.typography.pxToRem(15),
marginRight: theme.spacing(1),
'&:focus': {
opacity: 1,
},
},
})((props) => <StyledTab disableRipple {...props} />);

Conditional Redirect is not working in react-router-dom

I am using React as Front-End and Firebase for Backend and Database. I login.js component, when I signed in, I expect to redirect it to some formfield component. But, here when I try to Redirect the user after Success message from firebase, Redirection does not work conditionally. I used condition Operator for
redirection. Here's the core for Login.js
import React from 'react';
import { makeStyles } from '#material-ui/core/styles';
import { BrowserRouter as Router, Redirect} from 'react-router-dom';
import { Button, Card, CardContent, Paper, Typography, TextField, Tabs, Tab,FormControl } from '#material-ui/core';
import DateFnsUtils from '#date-io/date-fns';
import {
MuiPickersUtilsProvider,
KeyboardDatePicker,
} from '#material-ui/pickers';
import DataField from './DataField';
import * as firebase from 'firebase';
const useStyles = makeStyles(theme => ({
root: {
flexGrow: 1,
margin: 'auto',
display: 'flex',
flexDirection: 'column',
alignItems: 'center'
},
cards: {
maxWidth: 300,
margin: 'auto',
display: 'flex',
flexDirection: 'column',
alignItems: 'center'
},
formControl: {
padding: theme.spacing(2),
margin: 'auto',
display: 'flex',
flexDirection: 'column',
justigyContent: 'space-around'
},
field: {
minWidth: 250
}
}));
function Login(){
const classes = useStyles();
const [email, setMail] = React.useState();
const [pass, setPass] = React.useState(new Date( '1995-08-18T21:11:54' ));
const auth = firebase.auth();
const handleDateChange = Date => {
const values = Date.toString();
setPass(values)
}
const handlerMail = e => {
setMail(e.target.value);
}
const verify = () => {
const promise = auth.signInWithEmailAndPassword(email, pass);
promise.catch(e => console.log(e.message));
firebase.auth().onAuthStateChanged((user) => {
return ( user ? <Redirect from='/' to='/datafield' /> : <Redirect from='/' to='/' /> )
})
}
return(
<React.Fragment className={classes.root}>
<Card className={classes.cards} elevation={0}>
<Typography variant='h5' color='primary'>Login</Typography>
<CardContent>
<MuiPickersUtilsProvider utils={DateFnsUtils}>
<FormControl className={classes.formControl}>
<TextField id="standard-basic" value={email} onChange={handlerMail} className={classes.field} label="Email" />
<KeyboardDatePicker
margin="normal"
id="date-picker-dialog"
label="Date of Birth"
format="MM/dd/yyyy"
value={pass}
onChange={handleDateChange}
KeyboardButtonProps={{
'aria-label': 'change date',
}}
/>
</FormControl>
</MuiPickersUtilsProvider>
</CardContent>
<Button color='third' onClick={verify} variant='contained'>Login</Button>
</Card>
</React.Fragment>
)
};
export default Login;
I've been working for couples of days to solve it! Not yet found!!!
Can anybody help me to resolve this issue? Thank You!
Try histroy.replace, doc: https://reacttraining.com/react-router/web/api/history
Wrap Login component in withRouter HOC. export default withRouter(Login);
const verify = () => {
const { history } = props;
const promise = auth.signInWithEmailAndPassword(email, pass);
promise.catch(e => console.log(e.message));
firebase.auth().onAuthStateChanged(user => {
if (user) {
history.replace("/datafield");
}
});
};

Resources