I'm struggling to create Checkbox filtering in React. I want my products to be filtered by brand,I have created checkboxes dynamically from database. So each checkbox corresponds to the brand. The quantity of checkboxes equals to quantity of brands.
The problem is when I click on one of the checkboxes, the other products are disappearing from the screen, and this is what I want, but at the same time, the other checkboxes is disappearing as well, and I see only the clicked one.
Also, when I click in that checkbox again I want the products of the brand to be back on the screen.
Any ideas how to do it?
Category.js component:
/** #format */
import { useState, useEffect, createContext } from "react";
import { Link, useParams } from "react-router-dom";
import Header from "../components/Header";
import React from "react";
// Importing styles
import "./styles/Category.css";
import Footer from "../components/Footer";
import Filter from "./Filter";
export const CatgContext = createContext();
export const Category = ({ onAdd }) => {
const [products, setProducts] = useState([]);
const params = useParams();
const getProducts = async () => {
try {
const res = await fetch(`/api/products/${params.type}`);
const data = await res.json();
setProducts(data);
} catch (err) {
console.log(err);
}
};
useEffect(() => {
getProducts();
}, []);
return (
<>
<Header />
<h1>{params.type}</h1>
<aside className="catalogue-aside">
<CatgContext.Provider value={{ setProducts, products }}>
<Filter products={products} />
</CatgContext.Provider>
</aside>
<div className="category-wrapper">
{products.map((product) => {
return (
<div
className="product-card"
key={product.product_id}
id={product.product_id}
>
<Link to={`/product/${product.product_id}`}>
<img className="img-card" src={product.product_image} />
<h3 className="title-card">{product.product_type}</h3>
</Link>
<p>{product.product_brand}</p>
<p>{product.product_name}</p>
<p>{product.product_description}</p>
<p>${product.product_price}</p>
<button onClick={() => onAdd(product)}>Add to Cart</button>
</div>
);
})}
</div>
<Footer />
</>
);
};
Filter.js component:
import { useState, useContext, useEffect } from "react";
import { CatgContext } from "./Category";
const Filter = (props) => {
const { products, setProducts } = useContext(CatgContext);
const handleChange = (e, value) => {
const filteredByBrand = products.filter((product) => {
return product.product_brand === value;
});
setProducts(filteredByBrand);
};
const renderCheckboxLists = () =>
products.map((product) => (
<div className="checkbox-wrapper" key={product.product_id}>
<input
onChange={(e) => handleChange(e, product.product_brand)}
type="checkbox"
/>
<span>{product.product_brand}</span>
</div>
));
return <>{renderCheckboxLists()}</>;
};
export default Filter;
Screenshot 1
Screenshot 2
I am recently getting started with Next JS. for styling, I am using Material UI. one issue I am facing is with the fonts. I couldn't able to change the font family to a different font. as per the below example (Github link), I created a _document.js page inside my pages folder
https://github.com/mui-org/material-ui/tree/master/examples/nextjs/pages
_document.js
in the below code I tried changing Roboto with Quicksand
import React from "react";
import Document, { Html, Head, Main, NextScript } from "next/document";
import { ServerStyleSheets } from "#material-ui/core/styles";
// import theme from "../components/Theme.js";
export default class MyDocument extends Document {
render() {
return (
<Html lang="en">
<Head>
{/* PWA primary color */}
{/* <meta name="theme-color" content={theme.palette.primary.main} /> */}
<link
rel="stylesheet"
// href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"
href="https://fonts.googleapis.com/css?family=Quicksand:300,400,500,700&display=swap"
/>
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
// `getInitialProps` belongs to `_document` (instead of `_app`),
// it's compatible with server-side generation (SSG).
MyDocument.getInitialProps = async (ctx) => {
const sheets = new ServerStyleSheets();
const originalRenderPage = ctx.renderPage;
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: (App) => (props) => sheets.collect(<App {...props} />),
});
const initialProps = await Document.getInitialProps(ctx);
return {
...initialProps,
// Styles fragment is rendered after the app and page rendering finish.
styles: [
...React.Children.toArray(initialProps.styles),
sheets.getStyleElement(),
],
};
};
Also, I customized my MUI theme object as below
const theme = createMuiTheme({
palette: {
type: "dark",
background: {
default: "#212121",
},
typography: {
fontFamily: "Quicksand",
},
},
});
Layout.js
import { createMuiTheme } from "#material-ui/core/styles";
import { ThemeProvider } from "#material-ui/styles";
import { makeStyles } from "#material-ui/core/styles";
import CssBaseline from "#material-ui/core/CssBaseline";
import Container from "#material-ui/core/Container";
import Divider from "#material-ui/core/Divider";
import NavBar from "./NavBar.js";
import Footer from "./Footer.js";
const useStyles = makeStyles((theme) => ({
root: {
display: "flex",
flexDirection: "column",
minHeight: "100vh",
},
main: {
marginTop: theme.spacing(0),
marginBottom: theme.spacing(10),
},
}));
const theme = createMuiTheme({
palette: {
type: "dark",
background: {
default: "#212121",
},
typography: {
fontFamily: "Quicksand",
},
},
});
function Layout({ children }) {
const classes = useStyles();
return (
<div className={classes.root}>
<ThemeProvider theme={theme}>
<CssBaseline />
<Container component="main" className={classes.main} maxWidth="md">
<NavBar />
<Divider />
{children}
<Divider />
<Footer />
</Container>
</ThemeProvider>
</div>
);
}
export default Layout;
But no luck. can anyone please advise?
Thanks In Advance
Venk
You can wrap your in _app.js with a ThemeProvider.
Example _app.js:
import '../styles/globals.scss'
import { motion, AnimatePresence } from 'framer-motion'
import { useRouter } from 'next/router'
import Header from '../components/Header'
import { AuthProvider } from '../contexts/AuthContext'
import { CartProvider } from '../contexts/CartContext'
import { ThemeProvider } from '#material-ui/core'
import theme from '../styles/theme'
export default function App({ Component, pageProps }) {
const router = useRouter()
return(
<AnimatePresence exitBeforeEnter>
<CartProvider>
<AuthProvider>
<ThemeProvider theme={theme}>
<Header />
<motion.div key={router.pathname} className="main">
<Component { ...pageProps } />
</motion.div>
</ThemeProvider>
</AuthProvider>
</CartProvider>
</AnimatePresence>
)
}
Example theme file:
import { createTheme, responsiveFontSizes } from "#material-ui/core"
let theme = createTheme({
palette: {
primary: {
main: '#0277bd',
},
secondary: {
main: '#b2ff59',
},
darkGray: {
main: '#333333'
},
},
})
theme = responsiveFontSizes(theme)
export default theme
I'm not sure how you understand the Mui theme concept. Let's just talk about NextJs first.
NextJs is no different from React. To set the global font-family you just have to put font-family: your desired font into the html, body {} block inside the global.css which is already imported at the top level of _app.js. Every component will use that global font unless it has custom styles. Example:
html, body {
padding: 0;
margin: 0;
font-family: Quicksand, cursive;
}
Now, with Mui, components use a designed style system which conclude default font option as well. To inject your custom style into every single Mui's component you need to wrap all of them inside a top/root level <ThemeProvider theme={yourCutomTheme}> component to override the defaults, reference: Custom Theming from Mui official doc.
Things are not that complicated tho.
In Nextjs V10+ with material-ui
_document.js
Don't forget to add this link tag before your fonts for improving the performance
<link rel="preconnect" href="https://fonts.gstatic.com" />
_app.js
add the override object to make your font a global font in your app instead of Roboto the default
const theme = responsiveFontSizes(
createMuiTheme({
// your custom UI Config ,
typography: {
fontFamily: "Quicksand",
},
overrides: {
MuiCssBaseline: {
"#global": {
"#font-face": [
{
fontFamily: "Quicksand",
fontStyle: "normal",
fontDisplay: "swap",
fontWeight: // your font weight,
},
],
},
},
},
})
)
Here is what worked for me with Google Fonts, MUI v5, NextJs v13 (TS)
https://nextjs.org/docs/api-reference/next/font
https://mui.com/material-ui/customization/typography/#adding-amp-disabling-variants
Add primary and secondary variant to theme
import { Barlow, Cormorant } from "#next/font/google";
export const barlow = Barlow({
weight: ["200", "400", "500", "600", "700"],
display: "swap",
fallback: ["Helvetica", "Arial", "sans-serif"],
});
export const cormorant = Cormorant({
weight: ["300", "400", "500", "600", "700"],
display: "swap",
fallback: ["Times New Roman", "Times", "serif"],
});
declare module "#mui/material/styles" {
interface TypographyVariants {
primary: React.CSSProperties;
secondary: React.CSSProperties;
}
interface TypographyVariantsOptions {
primary?: React.CSSProperties;
secondary?: React.CSSProperties;
}
}
declare module "#mui/material/Typography" {
interface TypographyPropsVariantOverrides {
primary: true;
secondary: true;
}
}
let theme = createTheme({
typography: {
fontFamily: barlow.style.fontFamily,
body1: {
fontFamily: barlow.style.fontFamily,
},
primary: {
fontFamily: barlow.style.fontFamily,
},
secondary: {
fontFamily: cormorant.style.fontFamily,
},
},
});
in component as sx prop
<Typography
sx={{
fontFamily: theme.typography.secondary.fontFamily,
}}
>
More About Us:
</Typography>
cormorant overides the default
or props
<Typography
variant="secondary"
>
More About Us:
</Typography>
After I got the focus on Material UI Link through keyboard tab key using tabIndex, the click action is not working when I hit the enter button. I don't understand why. Can anyone help me with this? Thank you.
sandbox link: https://codesandbox.io/s/material-demo-forked-4evbh?file=/demo.tsx
code:
/* eslint-disable jsx-a11y/anchor-is-valid */
import React from "react";
import { makeStyles, createStyles, Theme } from "#material-ui/core/styles";
import Link from "#material-ui/core/Link";
import Typography from "#material-ui/core/Typography";
const useStyles = makeStyles((theme: Theme) =>
createStyles({
root: {
"& > * + *": {
marginLeft: theme.spacing(2)
}
}
})
);
export default function Links() {
const classes = useStyles();
const [state, setState] = React.useState(true);
const handleClick = () => {
setState(!state);
};
return (
<Typography className={classes.root}>
<Link onClick={handleClick} tabIndex={0}>
Link
</Link>
<Typography>Click on above link to display hidden content..</Typography>
{state ? "" : "focus on Link using Tab key?"}
</Typography>
);
}
Set component prop to button as follows:
export default function Links() {
const classes = useStyles();
const [state, setState] = React.useState(true);
const handleClick = () => {
setState(!state);
};
return (
<Typography className={classes.root}>
<Link onClick={handleClick} tabIndex={0} component="button">
Link
</Link>
</Typography>
);
}
I have used onKeyPress attribute to achieve the click action through keyboard tab key without using component="button" or href like below.
function goToDetails(event, id)
{
if(event.key === 'Enter'){
event.preventDefault();
history.push(`/`);
}
else {
event.preventDefault();
history.push(`/`);
}
}
<Link tabIndex={0} onKeyPress={(event) => goToDetails(event, row.id)} onClick={(event) => goToDetails(event, row.id)}>
Go to details
</Link>
Got an odd error with FormContext when trying to run my tests. All I'm trying to do is render a component.
So this is the error that I am getting and this is the test that I have written.
import React from 'react';
import UserReportQuestion from './UserReportQuestion';
import { render } from '#testing-library/react';
import { useForm, FormContext } from 'react-hook-form';
describe('(Component) UserReport', () => {
let UserReportQuestionRender;
// jest.mock('react-hook-form', () => ({
// FormContext: jest.fn(),
// useForm: jest.fn(),
// }));
beforeEach(() => {
const form = useForm();
UserReportQuestionRender = render(
<FormContext {...form}>
<UserReportQuestion />
</FormContext>
)
});
it('Should render without crashing', () => {
expect(UserReportQuestionRender);
});
});
In the component I am testing I am using FormContext and passing it useForm as its methods. I've commented everything else out so it is just the FormContext in the component to make sure that it is 100% this that is causing the error.
Wondering if anyone has any ideas on how to work round this?
Update with component
import { useForm, FormContext } from "react-hook-form";
const UserReportQuestion = ({ text }) => {
const { t } = useTranslation();
const [isModalOpen, setModal] = useState(false);
const methods = useForm();
return (
<>
<Question>
{t('UserReport.criteria')}:
{' '}
{/* Placeholder text */}
{/* {text} */}
Find new, creative ways of completing tasks
<ModalIcon onClick={() => setModal(true)}>
<Icon
icon="info"
color="#a3a3a3"
modifiers={['size-large', 'solid']}
/>
</ModalIcon>
</Question>
<InfoModal
isModalOpen={isModalOpen}
closeModal={() => setModal(false)}
title={t('UserReport.modalTitle')}
>
<FormContext {...methods}>
{/* <form onSubmit={methods.handleSubmit()}>
<InfoModalParagraph>
{t('UserReport.modalDescription')}
</InfoModalParagraph>
<FeedbackQuestions
questions={mockData}
selfAssessment={false}
submitButtonDisabled
noSubmitButton
/>
</form> */}
</FormContext>
</InfoModal>
</>
);
}
I think you're importing a component which doesn't exist, in the react-hook-form documentation we have a FormProvider if we want to use useFormContext.
read More about it here: https://react-hook-form.com/api/#useFormContext
but in the mean time you need to change
import {FormContext} from 'react-hook-form'
to this:
import { FormProvider } from 'react-hook-form'
I want to use React router but I have a problem with meteor mongo
I use Meteor 1.5.1
main.js:
Meteor.startup(() => {
Tracker.autorun(() => {
let translates = Translates.find().fetch();
ReactDom.render(<App translates={translates}/>, document.getElementById('app'));
});
});
App.js
import React from 'react';
import AddTranslate from './AddTranslate';
import TranslateList from './TranslateList';
export default class App extends React.Component {
render() {
return (
<div>
<p>Firts text</p>
<h1>Hello :D</h1>
<TranslateList translates={this.props.translates}/>
<AddTranslate/>
</div>
);
}
};
App.propTypes = {
translates: React.PropTypes.array.isRequired
};
I know, I need something like this:
export const history = createBrowserHistory({
forceRefresh: true
});
export const routes = (
<Router history={history}>
<Switch>
<Route path="/beginner" component={Beginner}/>
<Route path="/" component={App}/>
</Switch>
</Router>
);
and change:
ReactDom.render(<App translates={translates}/>, document.getElementById('app'));
ReactDom.render(<routes/>, document.getElementById('app'));
but what with translates={translates}?
thanks for help :)