I have two main components. The Recipe.js component which is the parent component, and RecipeCard.js which is the child. I am getting the error Error: Invalid hook call. Hooks can only be called inside of the body of a function component. I am not exactly sure as to why I am getting this error. I was hoping I could get some insight as to what the problem is.
Recipe.js:
import React, { Component } from "react";
import AddRecipe from "./addRecipe";
import "./Recipe.css";
import { Route, BrowserRouter as Router, Switch, Link } from "react-router-dom";
import { useAuth0 } from "#auth0/auth0-react";
import RecipeCard from "./RecipeCard";
class Recipe extends Component {
constructor() {
super();
this.state = {
recipes: [], // State array to hold recipe objects that are fetched from the database
search: ''
};
}
updateSearch(event) {
this.setState({ search: event.target.value.substr(0, 20) });
}
componentDidMount() {
fetch("/getRecipes") //Api call using route from server.js to obtain recipe data
.then((res) => res.json())
.then((recipes) =>
this.setState(
{ recipes },
() =>
//inserts data to the state array of the component
console.log("recipes fetched...", recipes) //confirm that the recipes were fetched in the console
)
);
}
render() {
let filteredRecipes = this.state.recipes.filter(
(recipe) => {
return recipe.recipeName.toLowerCase().indexOf(this.state.search) !== -1;
}
);
return (
<Router>
<div>
<input type="text" value={this.state.search} onChange={this.updateSearch.bind(this)}></input>
<ul>
{filteredRecipes.map((
recipe //iterate through each recipe object in the state array display the id, name and instructions of each recipe
) => (
<li className="Recipes" key={recipe.idrecipe}>
<RecipeCard imgUrl={recipe.imgUrl} id={recipe.idrecipe} name={recipe.recipeName} instructions={recipe.recipeInstruction} />
</li>
))}
</ul>
<Switch>
<Route path="/addRecipe">
<AddRecipe />
</Route>
</Switch>
</div>
</Router>
);
}
}
export default Recipe; //Export the recipe component to be used in the main index.js file
RecipeCard.js:
import React from 'react';
import { makeStyles } from '#material-ui/core/styles';
import Card from '#material-ui/core/Card';
import CardActionArea from '#material-ui/core/CardActionArea';
import CardActions from '#material-ui/core/CardActions';
import CardContent from '#material-ui/core/CardContent';
import CardMedia from '#material-ui/core/CardMedia';
import Button from '#material-ui/core/Button';
import Typography from '#material-ui/core/Typography';
const useStyles = makeStyles({
root: {
maxWidth: 345,
},
media: {
height: 140,
},
});
export default function RecipeCard(props) {
const classes = useStyles();
return (
<Card className={classes.root}>
<CardActionArea>
<CardMedia
className={classes.media}
image={props.imgUrl}
title=""
/>
<CardContent>
<Typography gutterBottom variant="h5" component="h2">
{props.name}
</Typography>
<Typography variant="body2" color="textSecondary" component="p">
{props.instructions}
</Typography>
</CardContent>
</CardActionArea>
<CardActions>
<Button size="small" color="primary">
Share
</Button>
<Button size="small" color="primary">
Learn More
</Button>
</CardActions>
</Card>
);
}
Hooks can be only used in functional components.
Your Recipe.js is a class component which extends from React.Component.
That's why useAuth0 will fail since it is a hook.
You have two options now. You can either change your Recipe.js into a functional component or use an alternative to useAuth0 which I might guess is a Higher-Order-Component provided by #auth0/auth0-react.
withAuth0 might be a good alternative.
Refs :
https://auth0.com/docs/libraries/auth0-react#use-with-a-class-component
https://reactjs.org/docs/hooks-intro.htm
Related
In my react hooks signOut is being passed as props from DashboardNav.js component to App.js. On click on logout icon in App.js is not actually logging out from the system. I am actually using gapi-scirpt for performing the logout operation, could someone please advise me how can i achieve this ?
CodeSandBox link:
https://codesandbox.io/s/fervent-ioana-pz5jyz?file=/src/dashboardNav.js:0-1199
// App.js
import React, { useEffect, useState } from "react";
import DashboardNavbar from "./dashboardNav";
import { gapi } from "gapi-script";
export default function App() {
const [isMobileNavOpen, setMobileNavOpen] = useState(false);
const { signOut } = () => {
alert("hello");
const auth2 = gapi.auth2.getAuthInstance();
if (auth2 != null) {
auth2.signOut().then(
auth2.disconnect().then(console.log("LOGOUT SUCCESSFUL")),
localStorage.removeItem("loginEmail"),
localStorage.removeItem("userImage"),
//history.push("/"),
console.log("Logged out successfully !")
);
}
};
return (
<div className="App">
<DashboardNavbar
logout={signOut}
onMobileNavOpen={() => setMobileNavOpen(true)}
/>
</div>
);
}
// dashboardNav.js
import { useState } from "react";
import { Link as RouterLink } from "react-router-dom";
import PropTypes from "prop-types";
import {
AppBar,
Badge,
Box,
Hidden,
IconButton,
Toolbar
} from "#material-ui/core";
import MenuIcon from "#material-ui/icons/Menu";
import NotificationsIcon from "#material-ui/icons/NotificationsOutlined";
import InputIcon from "#material-ui/icons/Input";
const DashboardNavbar = ({ onMobileNavOpen, signOut, ...rest }) => {
const [notifications] = useState([]);
return (
<AppBar elevation={0} {...rest} style={{ background: "#1976D2" }}>
<Toolbar>
<RouterLink to="/">
<img alt="Logo" src="images/simpro.PNG" width="80px" />
</RouterLink>
<Box sx={{ flexGrow: 1 }} />
<Hidden mdDown>
<IconButton color="inherit" onClick={signOut}>
<InputIcon />
</IconButton>
</Hidden>
<Hidden lgUp>
<IconButton color="inherit" onClick={onMobileNavOpen}>
<MenuIcon />
</IconButton>
</Hidden>
</Toolbar>
</AppBar>
);
};
DashboardNavbar.propTypes = {
onMobileNavOpen: PropTypes.func
};
export default DashboardNavbar;
When clicking on a button, the system raises the following error, rather than navigating to the ProductDetail component:
Uncaught TypeError: Cannot read properties of undefined (reading 'pathname')
The product id in the url is correctly identified, and if I type the url manually, I get the corresponding REST API view, but trying to navigate there through the button does not work.
Any ideas of what am I doing wrong?
Here what I use:
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-router-dom": "^6.2.1",
App.js
import React, { Component } from "react";
import { render } from "react-dom";
import Home from "./Home";
import Header from "./Header";
import {BrowserRouter as Router, Routes, Route} from 'react-router-dom';
import ShowProducts3 from './ShowProducts3';
import ProductDetail from './ProductDetail';
function App() {
return (
<div className="min-h-screen bg-red-700 text-gray-900">
<div>
<Router >
<Header />
<Routes>
<Route exact path="/" element={<ShowProducts3 />} />
<Route exact path="/api/pp/:id" element={<ProductDetail />} />
</Routes>
</Router>
</div>
</div>
);
}
export default App;
ShowProducts3.js
import axios from 'axios';
import React, {useState, useEffect} from 'react';
import Card from '#mui/material/Card';
import { CardActionArea } from '#mui/material';
import CardActions from '#mui/material/CardActions';
import CardContent from '#mui/material/CardContent';
import Button from '#mui/material/Button';
import Typography from '#mui/material/Typography';
import { Link } from 'react-router-dom';
import Container from '#mui/material/Container';
import Grid from '#mui/material/Grid';
const API_URL = "http://localhost:8000/api/pp/"
const ShowProducts3 = () => {
const [products, setProducts] = useState([])
const fetchProducts = async () => {
const result = await axios.get(API_URL);
console.log(result.data)
setProducts(result.data)
}
useEffect(() => {
fetchProducts();
},[])
const goToDetail = () => {
alert("detail page")
}
return (
<div>
<div className="main-products-show">
<Container>
<Grid container spacing={{ xs: 2, md: 3 }} >
{
products.map((product) => (
<Grid item xs={2} sm={4} md={4} key={product.pk}>
<Card key={product.pk} sx={{ minWidth: 155 }}>
<CardActionArea>
<CardContent>
<Typography sx={{ mb: 1.5 }} color="text.secondary">
{product.name}
</Typography>
<Typography sx={{ mb: 1.5 }} color="text.secondary">
{product.description}
</Typography>
</CardContent>
</CardActionArea>
<CardActions>
<Button className="btn btn-secondary mr-2" component={Link} to={`/api/pp/${product.pk}`} size="small">Details</Button>
</CardActions>
</Card>
</Grid>
))
}
</Grid>
</Container>
</div>
</div>
);
};
export default ShowProducts3;
enter image description here
EDIT
The error seems to be linked to the "Delete" button within component "ProductDetail". If I remove this line, the error disappears.
deleteProduct(product.pk)}>Delete
Any idea what's wrong with it?
import axios from 'axios';
import React, {useState, useEffect} from 'react';
import { useParams, useNavigate } from 'react-router';
import { Link } from 'react-router-dom';
const ProductDetail = () => {
const [product, setProduct] = useState([])
const {id} = useParams();
const history = useNavigate();
useEffect(() => {
getSingleProduct();
return () => {
setProduct({});
};
},[])
const API_URL = "http://localhost:8000/api/pp/"
const getSingleProduct = async () => {
const { data } = await axios.put(`http://localhost:8000/api/pp/${id}/`)
console.log(data);
setProduct(data);
}
const deleteProduct = async (id) => {
await axios.delete(`http://localhost:8000/api/pp/${id}/`)
history.push("/")
}
return (
<div>
<h2>Detail of Single Product </h2>
<hr></hr>
<div className="full-product-detail">
<div className="product-detail">
<p>{product.pk}</p>
<p>{product.name}</p>
<p>{product.description}</p>
</div>
</div>
<Link className="btn btn-outline-primary mr-2" to={`/${product.pk}/update`}>Update</Link>
<Link className="btn btn-danger" onClick={() => deleteProduct(product.pk)}>Delete</Link>
</div>
);
};
export default ProductDetail;
Maybe the problem related to your link and button component try using this:
import { Link } from '#mui/material/Link';
<Button component={Link} variant="contained" href={`/api/pp/${product.pk}`}>
Link
</Button>
Im using React with Apollo on the frontend and i'm having trouble displaying the title and the id of the array likes. Im iterating using the method .map over the tracks array and i can access all the values in it except the values stored in the array likes. Here is the code which explains it better then words i guess :)
App.js
import React from "react";
import withStyles from "#material-ui/core/styles/withStyles";
import { Query } from 'react-apollo'
import { gql } from 'apollo-boost'
import SearchTracks from '../components/Track/SearchTracks'
import TrackList from '../components/Track/TrackList'
import CreateTrack from '../components/Track/CreateTrack'
import Loading from '../components/Shared/Loading'
import Error from '../components/Shared/Error'
const App = ({ classes }) => {
return (
<div className={classes.container}>
<SearchTracks />
<CreateTrack />
<Query query={GET_TRACKS_QUERY}>
{({ data, loading, error }) => {
if (loading) return <Loading />
if (error) return <Error error={error} />
return <TrackList tracks={data.tracks} />
}}
</Query>
</div>
);
};
const GET_TRACKS_QUERY = gql`
query getTracksQuery {
tracks {
id
title
description
url
likes {
title
id
}
postedBy {
id
username
}
}
}
`
const styles = theme => ({
container: {
margin: "0 auto",
maxWidth: 960,
padding: theme.spacing.unit * 2
}
});
export default withStyles(styles)(App);
TrackList.js
import React from "react";
import withStyles from "#material-ui/core/styles/withStyles";
import List from "#material-ui/core/List";
import ListItem from "#material-ui/core/ListItem";
import ListItemText from "#material-ui/core/ListItemText";
import Typography from "#material-ui/core/Typography";
import ExpansionPanel from "#material-ui/core/ExpansionPanel";
import ExpansionPanelDetails from "#material-ui/core/ExpansionPanelDetails";
import ExpansionPanelSummary from "#material-ui/core/ExpansionPanelSummary";
import ExpansionPanelActions from "#material-ui/core/ExpansionPanelActions";
import ExpandMoreIcon from "#material-ui/icons/ExpandMore";
import AudioPlayer from '../Shared/AudioPlayer'
import LikeTrack from './LikeTrack'
import CreateTrack from './CreateTrack'
import DeleteTrack from './DeleteTrack'
import UpdateTrack from './UpdateTrack'
import { Link } from 'react-router-dom'
const TrackList = ({ classes, tracks }) => (
<List>
{tracks.map( track => (
<ExpansionPanel key={track.id}>
<ExpansionPanelSummary expandIcon={<ExpandMoreIcon />}>
<ListItem primaryTypographyProps={{
variant: "subheading",
color: "primary"
}} className={classes.root}>
<LikeTrack />
<ListItemText primary={track.title} secondary={
<Link className={classes.link} to={`/profile/${track.postedBy.id}`}>
{track.postedBy.username}
</Link>
} />
<AudioPlayer />
</ListItem>
</ExpansionPanelSummary>
<ExpansionPanelDetails className={classes.details}>
<Typography variant="body1">
{track.description}
{track.likes.id} {/* { Value not displayed } */}
</Typography>
</ExpansionPanelDetails>
<ExpansionPanelActions>
<UpdateTrack />
<DeleteTrack />
</ExpansionPanelActions>
</ExpansionPanel>
) )}
</List>
);
const styles = {
root: {
display: "flex",
flexWrap: "wrap"
},
details: {
alignItems: "center"
},
link: {
color: "#424242",
textDecoration: "none",
"&:hover": {
color: "black"
}
}
};
export default withStyles(styles)(TrackList);
Thanks #xadm use of another map() or single track.likes[0].id works!
I'm trying to add interactivity to a four quadrant chart whereby the user can click a box to highlight it, and the others will deactivate, similar to how radio boxes work in a form.
The idea was to add an onClick event to each card and have a handler function that will check which box was clicked on, activate it, and then deactivate the rest.
The problem I'm having is that e.target seems to be picking up the child nodes of each card instead of the card itself, so I'm having trouble figuring out which card was clicked.
e.g. console log = '>> event.target <li>A</li>'
I was hoping to determine which card was picked by doing something like event.target.id
I've tried a bunch of things and nothing has worked... How do people normally set this type of interaction up?
import React from "react";
import Card from "#material-ui/core/Card";
import CardContent from "#material-ui/core/CardContent";
import CardActionArea from '#material-ui/core/CardActionArea';
import Typography from "#material-ui/core/Typography";
import Paper from "#material-ui/core/Paper";
function MyCard({ title }) {
return (
<CardActionArea onClick={quadrantClickHandler}>
<Card>
<CardContent>
<Typography>{title}</Typography>
</CardContent>
</Card>
</CardActionArea>
);
}
function quadrantClickHandler(e) {
e.stopPropagation();
console.log('>> event.target ',e.target);
//the idea here is that I will click a "card"
//and then determine which card was clicked so that
//I can highlight it similar to a radio box set in a form.
}
function Quadrants() {
return (
<React.Fragment>
<MyCard
title={
<ul>
<li>A</li>
<li>B</li>
<li>C</li>
<li>D</li>
<li>E</li>
<li>F</li>
<li>G</li>
</ul>
} />
<MyCard title="Fast but expensive" />
<MyCard title="Slow but Cheap" />
<MyCard title="Slow but Fast" />
</React.Fragment>
);
}
function FourQuadrants() {
const classes = useStyles();
return (
<div>
<h2>Make a choice:</h2>
<Paper className={classes.paper}>
<Typography className={classes.top}>Big</Typography>
<Typography className={classes.bottom}>Small</Typography>
<Typography align="center" className={classes.left}>Expensive</Typography>
<Typography align="center" className={classes.right}>Cheap</Typography>
<Quadrants />
</Paper>
</div>
);
}
export default FourQuadrants;
Rather than trying to pull information out of the event target, you can have the click handler know everything of importance.
The key aspects in my example below are:
State at the top level (selectedId) to track which card is selected
A click-handler that knows the id of its card and sets the selectedId accordingly
I'm providing each card with its own id and the selectedId, so that it can style itself differently based on whether or not it is selected
import ReactDOM from "react-dom";
import React from "react";
import Card from "#material-ui/core/Card";
import CardContent from "#material-ui/core/CardContent";
import CardActionArea from "#material-ui/core/CardActionArea";
import Typography from "#material-ui/core/Typography";
import { makeStyles } from "#material-ui/core";
const useStyles = makeStyles({
selected: {
border: "1px solid green"
}
});
function MyCard({ title, id, selectedId, handleClick }) {
const classes = useStyles();
return (
<Card className={id === selectedId ? classes.selected : null}>
<CardActionArea onClick={handleClick(id)}>
<CardContent>
<Typography>{title}</Typography>
</CardContent>
</CardActionArea>
</Card>
);
}
function Quadrants() {
const [selectedId, setSelectedId] = React.useState();
const handleClick = id => e => {
setSelectedId(id);
};
const cardProps = { selectedId, handleClick };
return (
<React.Fragment>
<MyCard
{...cardProps}
id={1}
title={
<ul>
<li>A</li>
<li>B</li>
<li>C</li>
<li>D</li>
<li>E</li>
<li>F</li>
<li>G</li>
</ul>
}
/>
<MyCard {...cardProps} id={2} title="Fast but expensive" />
<MyCard {...cardProps} id={3} title="Slow but Cheap" />
<MyCard {...cardProps} id={4} title="Slow but Fast" />
</React.Fragment>
);
}
function FourQuadrants() {
return (
<div>
<h2>Make a choice:</h2>
<Quadrants />
</div>
);
}
function App() {
return <FourQuadrants />;
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Explanation
I am building a simple ReactJS web page where one can login/signup. I've built the home page and have a navbar with login and signup buttons on it. I'm using MaterialUI. I want the login modal to open when clicking on the login button. But till now, I've only been able to open the modal from a button directly inside the modal's code.
What I've done
I've researched a LOT on stackoverflow and the web and tried implementing a few of the approaches like refs in all different ways specified. I've tried reading the ReactJS documentation to understand the concepts.
Code:
I have a separate file for the Login Modal and for the Navbar. Currently, I'm exporting the LoginModal component into the Navbar file. Then exporting the Navbar component into the HomePage file.
Here is the navbar file's code (Navbar.js):
import React from 'react';
import PropTypes from 'prop-types';
import { withStyles } 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 LoginModal from './LoginModal.js';
const styles = {
root: {
flexGrow: 1,
},
grow: {
flexGrow: 1,
},
navbar: {
backgroundColor: 'transparent',
boxShadow: 'none',
color: '#06ABFB'
}
};
class Navbar extends React.Component {
constructor(props) {
super(props);
this.loginmodal = React.createRef();
}
openLoginModal = () => {
console.log(this.loginmodal.current);
};
render() {
const { classes } = this.props;
return (
<div className={classes.root}>
<LoginModal ref={this.loginmodal} />
<AppBar position="static" className={classes.navbar}>
<Toolbar>
<Typography variant="title" color="inherit" className={classes.grow}>
WorkNet
</Typography>
<Button color="inherit" onClick={this.openLoginModal}>Login</Button>
<Button color="inherit">Sign Up</Button>
</Toolbar>
</AppBar>
</div>
);
}
}
Navbar.propTypes = {
classes: PropTypes.object.isRequired,
};
export default withStyles(styles)(Navbar);
and here is the code for the login modal (LoginModal.js)
import React from 'react';
import PropTypes from 'prop-types';
import { withStyles } 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 LoginModal from './LoginModal.js';
const styles = {
root: {
flexGrow: 1,
},
grow: {
flexGrow: 1,
},
navbar: {
backgroundColor: 'transparent',
boxShadow: 'none',
color: '#06ABFB'
}
};
class Navbar extends React.Component {
constructor(props) {
super(props);
this.loginmodal = React.createRef();
}
openLoginModal = () => {
console.log(this.loginmodal.current);
};
render() {
const { classes } = this.props;
return (
<div className={classes.root}>
<LoginModal ref={this.loginmodal} />
<AppBar position="static" className={classes.navbar}>
<Toolbar>
<Typography variant="title" color="inherit" className={classes.grow}>
WorkNet
</Typography>
<Button color="inherit" onClick={this.openLoginModal}>Login</Button>
<Button color="inherit">Sign Up</Button>
</Toolbar>
</AppBar>
</div>
);
}
}
Navbar.propTypes = {
classes: PropTypes.object.isRequired,
};
export default withStyles(styles)(Navbar);
A few things to consider:
In openLoginModal, console.log is not going to do anything other than print to the console.
You probably do not need to use refs for this. Check this out!
Instead, you can set a state in Navbar.js with something like:
handleModal = () => {
this.setState({modalIsOpen: !this.state.modalIsOpen});
};
Then you can pass that to your Modal using props with something like:
<LoginModal open={this.state.modalIsOpen} onClose={this.handleModal} />
<Button color="inherit" onClick={this.handleModal}>Login</Button>