I am new with React but not with JS. I am trying to connect Airtable with react JS and Material UI. I have some problems
My App.js function
import "./styles.css";
import { useState } from "react";
import React from "react";
import BooksCard from "./components/BooksCard";
import Grid from "#material-ui/core/Grid";
function App() {
//state
const [dataAirtable, setData] = useState([]);
//1. Copie du state
const dataCopy = [...dataAirtable];
//2. manipulation sur la copie du state
//Get airtable data from a table
fetch(
"https://api.airtable.com/v0/baseID/name_table?api_key=API_KEY"
)
.then((res) => res.json())
.then((res) => {
console.log(res.records);
dataCopy.push(res.records);
setData(dataCopy);
})
.catch((error) => console.log("Erreur", error));
//affichage (render)
return (
<Grid container direction="row" spacing={2}>
{dataAirtable.map((data) => (
<BooksCard {...data.fields} key={data.id} />
))}
</Grid>
);
}
export default App;
My card component:
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 Grid from "#material-ui/core/Grid";
import Button from "#material-ui/core/Button";
import Typography from "#material-ui/core/Typography";
const useStyles = makeStyles((theme) => ({
root: {
flexGrow: 1,
margin: 20
},
gutterTopAndBottom: {
margin: 20
},
card: {
maxWidth: 345
},
media: {
height: 350
}
}));
function BooksCard({ Residence, Ville, Statut, Adresse, Couverture }) {
const classes = useStyles();
return (
<div className={classes.root}>
<Grid item xs={10}>
<Card className={classes.card}>
<CardActionArea>
<CardMedia
className={classes.media}
image={Couverture}
title="React"
/>
<CardContent>
<Typography gutterBottom variant="h5" component="h2">
{Residence}
</Typography>
<Typography variant="body2" color="textSecondary" component="p">
{Ville}
</Typography>
</CardContent>
</CardActionArea>
<CardContent>
<Typography paragraph variant="body2">
{Adresse.length > 150 ? Adresse.slice(0, 150) + `...` : Adresse}
</Typography>
</CardContent>
<CardActions>
<Button size="small" variant="outlined" color="primary">
{Statut}
</Button>
</CardActions>
</Card>
</Grid>
</div>
);
}
export default BooksCard;
I tried to use the setState because I saw that it's better to do like that with react (for the reusability of component ?). Finally, I get an infinite loop or my function BooksCard doesn't read the data I get from airtable.
Could you help me please ?
Best regards
Related
When I am trying to import "makeStyles" using :
import { makeStyles } from '#mui/styles';
it's not working. The page becomes blank when I run.
But It's working when I change the code and use V4:
import { makeStyles } from "#material-ui/core/styles";
I've installed alll the dependencies that is required. Why It's not working using V5 of material-ui?
Here is my code (styles.js):
import { makeStyles } from "#mui/material";
export default makeStyles((theme) => ({
title: {
display: "none",
[theme.breakpoints.up("sm")]: {
display: "block",
},
},
}));
Header.jsx:
import React from "react";
import { AppBar, Toolbar, Typography, InputBase, Box } from "#mui/material";
import SearchIcon from "#mui/icons-material/Search";
import useStyles from "./styles";
const Header = () => {
const classes = useStyles();
return (
<AppBar position="static">
<Toolbar>
<Typography variant="h5" className={classes.title}>
Travel Advsior
</Typography>
<Box display="flex">
<Typography variant="h6" className={classes.title}>
Explore new places
</Typography>
<div>
<div>
<SearchIcon />
</div>
<InputBase placeholder="Search..." />
</div>
</Box>
</Toolbar>
</AppBar>
);
};
export default Header;
The console announce mistake at my IconButton open tag as unexpected token. i have installed material ui...i don't know what is the problem? can anyone help me? Thank you so much!
This is my product.js:
import React from 'react'
import {
Card,
CardMedia,
CardContent,
CardActions,
Typography,
IconButton
} from '#material-ui/core'
import {
AddShoppingCart
} from '#material-ui/icons'
import useStyles from './styles'
const Product = ({
product
}) => {
const classes = useStyles()
return (
<Card className={classes.root}>
<CardMedia className={classes.media} image='' title={product.name} />
<CardContent>
<div className={classes.cardContent}>
<Typography gutterBottom variant="h5" >
{product.name}
</Typography>
<Typography variant="h5" >
{product.price}
</Typography>
</div>
</CardContent>
<CardActions disableSpacing className={classes.cardActions}>
<IconButton aria-label="Add to Cart">
<AddShoppingCart />
</IconButton>
</CardActions>
</Card>
)
}
export default Product
This is my styles.js:
import {
makeStyles
} from '#material-ui/core/styles'
export default makeStyles(() => ({
{
root: {
maxWidth: '100%'
},
media: {
height: 0,
paddingTop: '56.25%'
},
cardActions: {
display: 'flex',
justifyContent: 'space-between'
},
}
}))
Btw i don't get where useStyles in my product.js come from when in my styles.js it's makeStyles not useStyles (i follow a tutorial on yt and he does so)
This: import useStyles from './styles' is named that because you/him wanted to, because you're exporting export default makeStyles.
export default means that you can import it with any name you want, its not a named export.
The problem maybe because here:
// You're returning an object of an object, instead of the inner { root: ... }.
({
{
root: {
maxWidth: '100%'
},
media: {
height: 0,
paddingTop: '56.25%'
},
cardActions: {
display: 'flex',
justifyContent: 'space-between'
},
}
}))
so maybe classes is not defined correctly, it would be needed the error stack to make sure where the error is, or at least a simple jsfiddle with the error would do.
There is nothing wrong with your IconButton component.
Some hints:
You can import your styles as classes right away, otherwise I doubt the class definitions will work at all.
Ensure you have installed #material-ui/icons
I took your code and did above changes:
import React from "react";
import {
Card,
CardMedia,
CardContent,
CardActions,
Typography,
IconButton
} from "#material-ui/core";
import { AddShoppingCart } from "#material-ui/icons";
import classes from "./styles";
const Product = ({ product }) => {
return (
<Card className={classes.root}>
<CardMedia className={classes.media} image="/" title={product.name} />
<CardContent>
<div className={classes.cardContent}>
<Typography gutterBottom variant="h5" component="h2">
{product.name}
</Typography>
<Typography gutterBottom variant="h5" component="h2">
{product.price}
</Typography>
</div>
</CardContent>
<CardActions disableSpacing className={classes.cardActions}>
<IconButton aria-label="Add to Cart">
<AddShoppingCart />
</IconButton>
</CardActions>
</Card>
);
};
export default Product;
See: https://codesandbox.io/s/ecstatic-sea-m9k33?file=/src/Product.js
we have used an arrow function to inline the event handler for out input field
So use
function Product({product}) {...}
instead of
const Product = ({ product }) => {}
I would like the second grid block under the title "Other Projects of Note" to be mapped through as a 3 column grid. How can I do this without creating a new component? Material-UI controls it's columns with the grid item xs={12} sm={6} and on the 3 column grid I'd need it to read as grid item xs={12} sm={6} lg={4}.
It seems like I'd be copying and pasting the <Card /> component and renaming it to achieve this. I'd like to avoid that. Demo below:
codesandbox
Here's the code for my current Card component:
import React from 'react';
import Grid from '#material-ui/core/Grid';
import Card from '#material-ui/core/Card';
import CardActionArea from '#material-ui/core/CardActionArea';
import CardActions from '#material-ui/core/CardActions';
import { makeStyles } from '#material-ui/core/styles';
import { loadCSS } from 'fg-loadcss';
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';
import Box from '#material-ui/core/Box';
import Icon from '#material-ui/core/Icon';
import GitHubIcon from '#material-ui/icons/GitHub';
import Tooltip from '#material-ui/core/Tooltip';
import Zoom from '#material-ui/core/Zoom';
import Link from '#material-ui/core/Link';
const useStyles = makeStyles((theme) => ({
root: {
maxWidth: '100%',
flexGrow: 1,
},
cardGrid: {
paddingTop: theme.spacing(2),
},
media: {
height: 340,
},
button: {
margin: theme.spacing(1),
},
arrow: {
color: theme.palette.common.black,
},
tooltip: {
backgroundColor: theme.palette.common.black,
},
icons: {
'& > .fab': {
marginRight: theme.spacing(4),
},
margin: '1rem 0',
},
}));
function TwoCard(props) {
const classes = useStyles();
React.useEffect(() => {
const node = loadCSS(
'https://use.fontawesome.com/releases/v5.12.0/css/all.css',
document.querySelector('#font-awesome-css')
);
return () => {
node.parentNode.removeChild(node);
};
}, []);
return (
<>
<Grid item xs={12} sm={6}>
<Card>
<CardActionArea>
<CardMedia
className={classes.media}
image={props.card.image}
title='Contemplative Reptile'
/>
<CardContent className={classes.content}>
<Typography gutterBottom variant='h5' component='h2'>
{props.card.project}
</Typography>
<Typography variant='subtitle1' gutterBottom>
{props.card.framework}
</Typography>
<Typography gutterBottom variant='body2' component='p'>
{props.card.description}
</Typography>
<Box className={classes.icons}>
<Typography gutterBottom variant='subtitle2'>
TOOLS USED
</Typography>
<Tooltip
TransitionComponent={Zoom}
arrow
title='REACT'
aria-label='react'
>
<Icon className='fab fa-react fa-3x' color='primary' />
</Tooltip>
<Tooltip
TransitionComponent={Zoom}
arrow
title='HTML5'
aria-label='add'
>
<Icon className='fab fa-html5 fa-3x' color='primary' />
</Tooltip>
<Tooltip
TransitionComponent={Zoom}
arrow
title='CSS3'
aria-label='add'
>
<Icon className='fab fa-css3 fa-3x' color='primary' />
</Tooltip>
</Box>
</CardContent>
</CardActionArea>
<CardActions>
<Button variant='outlined' size='small' color='primary'>
<Link
href={props.card.projectUrl}
target='_blank'
rel='noopener noreferrer'
className={classes.links}
underline='none'
color='inherit'
>
View Project
</Link>
</Button>
<Button
variant='outlined'
size='small'
color='primary'
className={classes.button}
endIcon={<GitHubIcon />}
>
<Link
href={props.card.githubUrl}
target='_blank'
rel='noopener noreferrer'
underline='none'
color='inherit'
>
Code
</Link>
</Button>
</CardActions>
</Card>
</Grid>
</>
);
}
export default TwoCard;
and here's the code that uses that Card component:
import React from "react";
import Grid from "#material-ui/core/Grid";
import Card from "./Card.js";
import { Typography } from "#material-ui/core";
import { makeStyles } from "#material-ui/core/styles";
const useStyles = makeStyles((theme) => ({
headings: {
padding: "20px 0"
}
}));
function Projects(props) {
const classes = useStyles();
let cards = props.data.map((card, i) => {
return <Card card={card} key={i} />;
});
return (
<>
<Typography variant="h4" gutterBottom>
Featured Projects
</Typography>
<Grid container spacing={2}>
{cards}
</Grid>
<Typography variant="h4" className={classes.headings}>
Other Projects of note
</Typography>
<Grid container spacing={2}>
{cards}
</Grid>
</>
);
}
export default Projects;
You can pass a prop to your Card component to control whether it is two-column or three-column. For instance, you can pass a maxColumns prop and use it in the following manner:
<Grid item xs={12} sm={6} lg={maxColumns > 2 ? 4 : undefined}>
Then your Projects component can pass the prop (in my example, I'm defaulting maxColumns to 2, so it doesn't need to be specified in that case):
import React from "react";
import Grid from "#material-ui/core/Grid";
import Card from "./Card.js";
import { Typography } from "#material-ui/core";
import { makeStyles } from "#material-ui/core/styles";
const useStyles = makeStyles((theme) => ({
headings: {
padding: "20px 0"
}
}));
function Projects(props) {
const classes = useStyles();
let twoColumnCards = props.data.map((card, i) => {
return <Card card={card} key={i} />;
});
let threeColumnCards = props.data.map((card, i) => {
return <Card maxColumns={3} card={card} key={i} />;
});
return (
<>
<Typography variant="h4" gutterBottom>
Featured Projects
</Typography>
<Grid container spacing={2}>
{twoColumnCards}
</Grid>
<Typography variant="h4" className={classes.headings}>
Other Projects of note
</Typography>
<Grid container spacing={2}>
{threeColumnCards}
</Grid>
</>
);
}
export default Projects;
Hello I have a problem using a style in my components in materialize:
Error: Invalid hook call. Hooks can only be called inside of the body
of a function component. This could happen for one of the following
reasons:
my code:
import React, { Component } from 'react'
import './style.css'
import {connect} from 'react-redux'
import {fetchProduct} from '../../store/actions/productsFetch';
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({
card: {
maxWidth: 345,
},
});
class Description extends Component {
componentDidMount() {
this.props.fetchProduct();
}
render() {
const classes = useStyles();
return (
<div>
<Card className={classes.card}>
<CardActionArea>
<CardMedia
component="img"
alt="Contemplative Reptile"
height="140"
image="/static/images/cards/contemplative-reptile.jpg"
title="Contemplative Reptile"
/>
<CardContent>
<Typography gutterBottom variant="h5" component="h2">
Lizard
</Typography>
<Typography variant="body2" color="textSecondary" component="p">
Lizards are a widespread group of squamate reptiles, with over 6,000 species, ranging
across all continents except Antarctica
</Typography>
</CardContent>
</CardActionArea>
<CardActions>
<Button size="small" color="primary">
Share
</Button>
<Button size="small" color="primary">
Learn More
</Button>
</CardActions>
</Card>
</div>
)
}
}
const mapStateToProps = (state) => {
return{
products: state.data.filteredProducts,
loading: state.data.loading,
error: state.data.error
}
};
const mapActionsToProps = {
fetchProduct: fetchProduct
};
export default connect(mapStateToProps, mapActionsToProps)(Description);
I would need state data from my store in parts of these components, but I have no idea how to solve this
I'm using MaterialUI. I have my Dashboard that has a list of Cards. My Dash is a class that extends React.Component. My Cards are a function that is using Hooks useState. My dash is using .map() to build the list of cards. My cards have a function to "show data".
Dashboard.js
import React from 'react';
import { withStyles } from '#material-ui/core/styles';
import Container from '#material-ui/core/Container';
import Grid from '#material-ui/core/Grid';
import Service from '../app/Service';
import Copyright from '../components/Copyright'
import CardContact from '../components/CardContact'
const styles = theme => ({
root: {
display: 'flex',
paddingTop: '80px'
},
appBarSpacer: theme.mixins.toolbar,
content: {
flexGrow: 1,
height: '100vh',
overflow: 'auto',
},
container: {
paddingTop: theme.spacing(4),
paddingBottom: theme.spacing(4),
},
});
class Dashboard extends React.Component {
state ={
}
componentDidMount() {
this.contactsFormatted();
}
contactsFormatted = async () => {
try {
const res = await Service.get('/all-contacts');
const contacts = res.data;
this.setState({
Contacts: contacts.map((contact, i) => (
CardContact(contact, i)
))
})
} catch(err){
console.log(err)
}
}
render(){
const { classes } = this.props;
return (
<div className={classes.root}>
<Container maxWidth="lg" className={classes.container}>
<Grid container spacing={3}>
{this.state.Contacts}
</Grid>
<Copyright />
</Container>
</div>
);
}
}
export default withStyles(styles)(Dashboard);
My CardContact.js
import React from 'react'
import Card from '#material-ui/core/Card';
import CardActions from '#material-ui/core/CardActions';
import CardContent from '#material-ui/core/CardContent';
import Button from '#material-ui/core/Button';
import Typography from '#material-ui/core/Typography';
import Grid from '#material-ui/core/Grid';
import Collapse from '#material-ui/core/Collapse';
const CardContact = ({name, office, occupation_area, tel, email}, i) => {
const [expanded, setExpanded] = React.useState(false);
const handleExpandClick = () => {
setExpanded(!expanded);
}
return (
<Grid xs={12} style={{ paddingBottom: 32 }} key={i} item={true}>
<Card >
<CardContent>
<Typography variant="h5" component="h2">
Nome: {name}
</Typography>
<Typography color="textSecondary" gutterBottom>
Cargo: {office}
</Typography>
<Typography color="textSecondary" gutterBottom>
Área de atuação: {occupation_area}
</Typography>
</CardContent>
<CardActions style={{justifyContent: 'flex-end'}} >
<Button size="small"
onClick={handleExpandClick}
aria-expanded={expanded}>Revelar</Button>
</CardActions>
<Collapse in={expanded} timeout="auto" unmountOnExit>
<CardContent>
<Typography color="textSecondary" gutterBottom>
Telefone: {tel}
</Typography>
<Typography color="textSecondary" gutterBottom>
E-mail: {email}
</Typography>
</CardContent>
</Collapse>
</Card>
</Grid>
)
}
export default CardContact;
what's wrong with the Hooks usage?
You are calling CardContact inside contactsFormatted method. Hence React is throwing the error as contactsFormatted is not a valid React component. React components with hooks should only be invoked/rendered from a valid render function of a React component (class or functional).
Try this :
contactsFormatted = async () => {
try {
const res = await Service.get('/all-contacts');
const contacts = res.data;
this.setState({contacts})
} catch(err){
console.log(err)
}
}
And in render :
render(){
const { classes } = this.props;
return (
<div className={classes.root}>
<Container maxWidth="lg" className={classes.container}>
<Grid container spacing={3}>
{this.state.contacts.map((contact, i) => (
<CardContact contact={contact} index={i} />
)}
</Grid>
<Copyright />
</Container>
</div>
);
}
Now the full data will be available in CardContact component as contact prop
As mentioned by an earlier answer, this is not the right approach, to render cards. Instead of storing the components in the Contacts state, you should store the objects corresponding to the cards in the Contacts state, like this:
contactsFormatted = async () => {
try {
const res = await Service.get('/all-contacts');
const contacts = res.data;
this.setState({
Contacts: [...Contacts].concat(contacts),
})
} catch(err){
console.log(err)
}
}
Further, you should render the cards as below:
return (
<div className={classes.root}>
<Container maxWidth="lg" className={classes.container}>
<Grid container spacing={3}>
{(this.state.Contacts || []).map((contact, i) => <CardContact contact={contact} i={i} />)}
</Grid>
<Copyright />
</Container>
</div>
);