I cant not use compose withRouter and withAlert
It's work only withRouter but I cant used this.props.alert.success...............................................................................................................................
It show error
Unhandled Rejection (TypeError): Cannot read property 'success' of undefined
import React from "react";
import Container from "#material-ui/core/Container";
import Typography from "#material-ui/core/Typography";
import Grid from "#material-ui/core/Grid";
import { withRouter } from "react-router-dom";
import Paper from "#material-ui/core/Paper";
import Button from "#material-ui/core/Button";
import { withAlert } from "react-alert";
import Select from "#material-ui/core/Select";
import InputLabel from "#material-ui/core/InputLabel";
import MenuItem from "#material-ui/core/MenuItem";
import FormControl from "#material-ui/core/FormControl";
import { compose } from "redux";
import { firestore } from "../../firebase/firebase.utils";
class Updatestatusproperty extends React.Component {
constructor(props) {
super(props);
this.state = {
id: this.props.match.params.id,
status:1
};
}
handleSubmit = async (event) => {
event.preventDefault();
this.props.alert.success("อัพเดทสถานะบ้านเสร็จสิ้น");
// firestore
// .collection("house")
// .doc(this.state.id)
// .update({status:this.state.status})
// .then(() => {
// this.props.alert.success("อัพเดทสถานะบ้านเสร็จสิ้น");
// })
// .catch((err) => {
// console.log(err)
// });
};
handleChange = (event) => {
this.setState({ status: event.target.value });
console.log( event.target.value )
};
render() {
return (
<Container
maxWidth="md"
style={{ paddingTop: "4%", paddingBottom: "4%" }}
>
<Paper
variant="outlined"
style={{
backgroundColor: "#f2f2f2",
backgroundPosition: "center",
backgroundSize: "cover",
backgroundRepeat: "no-repeat",
padding: "4%",
}}
>
<Grid container spacing={3}>
<Grid item xs={6}>
<Typography variant="h4">{"อัพเดทสถานะ"}</Typography>
</Grid>
<Grid item xs={12}>
<Typography variant="h6">{"อัพเดทสถานะบ้าน"}</Typography>
</Grid>
<Grid item xs={4}>
<FormControl fullWidth>
<InputLabel id="demo-simple-select-label">
อัพเดทสถานะบ้าน
</InputLabel>
<Select name="sizefamily" onChange={this.handleChange} value={this.state.status}>
<MenuItem value={1}>พร้อมขาย</MenuItem>
<MenuItem value={2}>อยู่ระหว่างเจรจา</MenuItem>
<MenuItem value={3}>ขายออกไปแล้ว</MenuItem>
</Select>
</FormControl>
</Grid>
<Grid item xs={3}>
<Button
type="submit"
variant="contained"
color="primary"
fullWidth
size="large"
style={{ backgroundColor: "#55aa54", marginTop: "3.5%" }}
onClick={this.handleSubmit}
>
อัพเดทสถานะ
</Button>
</Grid>
</Grid>
</Paper>
</Container>
);
}
}
export default compose(withRouter(Updatestatusproperty),withAlert());
You are using the compose function incorrectly.
You need to pass the HOCs, withRoute as arguments to it without calling it like below
export default compose(withRouter,withAlert())(Updatestatusproperty);
Related
In my application, the home page contains a form that creates workout objects. I am trying to do the same thing with meal objects, but on another page, and after clicking the "meals" button from my navbar, I get an error.
This is my navbar component where I click on the meals button.
import React,{useState, useEffect} from 'react';
import {Link, useLocation} from 'react-router-dom';
import { AppBar, Avatar, Toolbar, Typography, Button} from '#material-ui/core';
import { useDispatch } from 'react-redux';
import decode from 'jwt-decode';
import {useNavigate} from 'react-router-dom';
import useStyles from './styles';
const Navbar = () => {
const classes = useStyles();
const [user, setUser]=useState(JSON.parse(localStorage.getItem('profile')));
const dispatch=useDispatch();
const navigate=useNavigate();
const location=useLocation();
const logout=()=>{
dispatch({type:'LOGOUT'});
navigate('/');
setUser(null);
};
const meals=()=>{
dispatch({type:'MEALS'});
navigate('/meals');
};
useEffect(()=>{
const token=user?.token;
if(token){
const decodedToken=decode(token);
if (decodedToken.exp * 1000 < new Date().getTime()) logout();
}
setUser(JSON.parse(localStorage.getItem('profile')));
},[location])
return (
<AppBar className={classes.AppBar} position="static" color="inherit">
<div className={classes.brandContainer}>
<Typography component={Link} to="/" className={classes.heading} variant="h2" align="center">Workouts</Typography>
</div>
<Toolbar className={classes.toolbar}>
{user ? (
<div className={classes.profile}>
<Avatar className={classes.purple} referrerPolicy="no-referrer" alt={user.result.name} src={user.result.imageUrl}>{user.result.name.charAt(0)}</Avatar>
<Typography className={classes.userName} variant="h6">{user.result.name}</Typography>
<Button variant="contained" className={classes.logout} color="secondary" onClick={meals}>Meals</Button>
<Button variant="contained" className={classes.logout} color="secondary" onClick={logout}>Logout</Button>
</div>
) : (
<Button component={Link} to="/auth" variant="contained" color="primary">Sign In</Button>
)}
</Toolbar>
</AppBar>
)
}
export default Navbar
This is my single Meal component
import React from 'react';
import { Card, CardActions, CardContent, CardMedia, Button, Typography } from '#material-ui/core/';
import ThumbUpAltIcon from '#material-ui/icons/ThumbUpAlt';
import DeleteIcon from '#material-ui/icons/Delete';
import MoreHorizIcon from '#material-ui/icons/MoreHoriz';
import ThumbUpAltOutlined from '#material-ui/icons/ThumbUpAltOutlined';
import moment from 'moment';
import {useDispatch} from 'react-redux';
import useStyles from './styles';
import {deleteMeal, likeMeal} from '../../../actions/meals';
const Meal=({meal, setCurrentId})=> {
const classes=useStyles();
const dispatch=useDispatch();
const user = JSON.parse(localStorage.getItem('profile'));
const Likes = () => {
if (meal.likes.length > 0) {
return meal.likes.find((like) => like === (user?.result?.googleId || user?.result?._id))
? (
<><ThumbUpAltIcon fontSize="small" /> {meal.likes.length > 2 ? `You and ${meal.likes.length - 1} others` : `${meal.likes.length} like${meal.likes.length > 1 ? 's' : ''}` }</>
) : (
<><ThumbUpAltOutlined fontSize="small" /> {meal.likes.length} {meal.likes.length === 1 ? 'Like' : 'Likes'}</>
);
}
return <><ThumbUpAltOutlined fontSize="small" /> Like</>;
};
return (
<Card className={classes.card}>
<CardMedia className={classes.media} image={meal.selectedFile|| 'https://user-images.githubusercontent.com/194400/49531010-48dad180-f8b1-11e8-8d89-1e61320e1d82.png'} title={meal.category}/>
<div className={classes.overlay}>
<Typography variant="h6">{meal.calories}</Typography>
<Typography variant="body2">{moment(meal.createdAt).fromNow()}</Typography>
</div>
{(user?.result?.googleId===meal?.creator || user?.result?._id===meal?.creator) && (
<div className={classes.overlay2}>
<Button style={{color:'white'}} size="small" onClick={()=>setCurrentId(meal._id)}>
<MoreHorizIcon fontSize="medium"/>
</Button>
</div>
)}
<div className={classes.details}>
<Typography className={classes.title} gutterBottom variant="h5" component="h2">{meal.name}</Typography>
</div>
<Typography className={classes.difficulty} variant="body2" color="textSecondary" component="p">{meal.category}</Typography>
<CardContent>
<Typography variant="body2" color="textSecondary" component="p">{meal.calories}</Typography>
</CardContent>
<CardActions className={classes.cardActions}>
<Button size='small' color="primary" disabled={!user?.result} onClick={()=>{dispatch(likeMeal(meal._id))}}>
<Likes/>
</Button>
{(user?.result?.googleId===meal?.creator || user?.result?._id===meal?.creator) && (
<Button size='small' color="primary" onClick={()=>{dispatch(deleteMeal(meal._id))}}>
<DeleteIcon fontSize="small"/>
Delete
</Button>
)}
</CardActions>
</Card>
);
}
export default Meal;
This is my Meals components that stands for all the Meal objects.
import React from 'react';
import {Grid, CircularProgress} from '#material-ui/core';
import { useSelector } from 'react-redux';
import Meal from './Meal/Meal';
import useStyles from './styles';
const Meals=({setCurrentId})=> {
const meals=useSelector((state)=>state.meals);
const classes =useStyles();
return (
!meals.length ? <CircularProgress/>:(
<Grid className={classes.container} container alignItems="stretch" spacing={3}>
{
meals.map((meal)=>(
<Grid key={meal._id} item xs={12} sm={6} md={6}>
<Meal meal={meal} setCurrentId={setCurrentId}/>
</Grid>
))}
</Grid>
)
);
}
export default Meals;
The error says "cannot read properties of undefined (reading 'length')" and length is in this component.
And, below I have my MealsForm which is the form used for creating the objects.
import React, { useState, useEffect } from 'react';
import { TextField, Button, Typography, Paper } from '#material-ui/core';
import { useDispatch, useSelector } from 'react-redux';
import FileBase from 'react-file-base64';
import useStyles from './styles';
import { createMeal, updateMeal } from '../../actions/meals';
const MealForm=({ currentId, setCurrentId=()=>{} })=>{
const [mealData, setMealData]=useState({
category:'',name:'', calories:'',selectedFile:''
});
const meal = useSelector((state) => (currentId ? state.meals.find((meal) => meal._id === currentId) : null));
const dispatch=useDispatch();
const classes=useStyles();
const user=JSON.parse(localStorage.getItem('profile'));
useEffect(()=>{
if(meal) setMealData(meal);
},[meal]);
const clear = () => {
setCurrentId(0);
setMealData({ category:'',name:'', calories:'',selectedFile:'' });
};
const handleSubmit=async (e)=>{
e.preventDefault();
if(!currentId) {
dispatch(createMeal(mealData));
clear();
} else{
dispatch(updateMeal(currentId, mealData));
clear();
}
};
if (!user?.result?.name) {
return (
<Paper className={classes.paper}>
<Typography variant="h6" align="center">
Please Sign In to add meals
</Typography>
</Paper>
);
}
return(
<Paper className={classes.paper}>
<form autoComplete="off" noValidate className={`${classes.root} ${classes.form}`} onSubmit={handleSubmit}>
<Typography variant="h6">{currentId ? `Editing "${meal.calories}"` : 'Creating a meal'}</Typography>
<TextField name="category" variant="outlined" label="Category" fullWidth value={mealData.category} onChange={(e)=>setMealData({...mealData, category:e.target.value})}/>
<TextField name="name" variant="outlined" label="Name" fullWidth value={mealData.name} onChange={(e)=>setMealData({...mealData, name:e.target.value})}/>
<TextField name="calories" variant="outlined" label="Calories" fullWidth value={mealData.calories} onChange={(e)=>setMealData({...mealData, calories:e.target.value})}/>
<div className={classes.fileInput}>
<FileBase
type="file"
multiple={false}
onDone={({base64})=>setMealData({...mealData,selectedFile:base64})}
/>
</div>
<Button className={classes.buttonSubmit} variant="contained" color="primary" size="large" type="submit" fullWidth>Submit</Button>
<Button variant="contained" color="secondary" size="small" onClick={clear} fullWidth>Clear</Button>
</form>
</Paper>
);
}
export default MealForm;
Here I have the code for the actions on the meals object
import { FETCH_ALL, CREATE, UPDATE, DELETE, LIKE } from '../constants/actionTypes';
import * as api from '../api';
export const getMeals=()=> async (dispatch)=>{
try{
const {data}=await api.fetchMeals();
dispatch({type:FETCH_ALL,payload:data});
}catch(error){
console.log(error);
}
}
export const createMeal=(meal)=>async(dispatch)=>{
try{
const {data}=await api.createMeal(meal);
dispatch({type:CREATE,payload:data});
console.log(data);
}catch(error){
console.log(error.message);
}
}
export const updateMeal=(id,meal)=>async(dispatch)=>{
try{
const {data}=await api.updateMeal(id,meal);
dispatch({type:UPDATE,payload:data})
}catch(error){
console.log(error);
}
}
export const deleteMeal=(id)=>async (dispatch)=>{
try{
await api.deleteMeal(id);
dispatch({type:DELETE,payload:id});
}catch(error){
console.log(error);
}
}
export const likeMeal=(id)=>async(dispatch)=>{
try{
const {data}=await api.likeMeal(id);
dispatch({type:LIKE,payload:data})
}catch(error){
console.log(error);
}
}
Below I have the MealsPage component. The flow goes like this: Navbar, MealsPage, Meals, Meal
import React, {useState, useEffect} from 'react'
import {Container, Grow, Grid} from '#material-ui/core';
import {useDispatch} from 'react-redux';
import {getMeals} from '../../actions/meals';
import Meals from "../Meals/Meals";
import MealForm from "../MealForm/MealForm";
import useStyles from './styles';
function MealsPage() {
const [currentId, setCurrentId]=useState(null);
const classes=useStyles();
const dispatch=useDispatch();
useEffect(()=>{
dispatch(getMeals());
},[currentId, dispatch]);
const meals=getMeals();
console.log(meals);
return (
<Grow in>
<Container>
<Grid className={classes.mainContainer} container justifyContent="space-between" alignItems='stretch' spacing={3}>
<Grid item xs={12} sm={7}>
<Meals setCurrentId={setCurrentId}/>
</Grid>
<Grid item xs={12} sm={4}>
<MealForm currentId={currentId} setCurrentId={setCurrentId}/>
</Grid>
</Grid>
</Container>
</Grow>
)
}
export default MealsPage
I cannot see the problem. Could you help me solve this?
How can i make the relevant asp.net controller for this?
I have added the 'load user' functions to App.js also. What I want is to load the user every time the page is refreshed. The login function is working properly but when the page is refreshed the authentication will be null again.
"export const loadUser = () => async (dispatch) => {
if (localStorage.token) {
setAuthToken(localStorage.token);
}
try {
const res = await axios.get('https://localhost:5001/api/auth/user');
dispatch({
type: USER_LOADED,
payload: res.data,
});
} catch (err) {
dispatch({
type: AUTH_ERROR,
});
}
};"
This is the login page
import React, { useState } from 'react';
import Avatar from '#material-ui/core/Avatar';
import Button from '#material-ui/core/Button';
import TextField from '#material-ui/core/TextField';
import FormControlLabel from '#material-ui/core/FormControlLabel';
import Checkbox from '#material-ui/core/Checkbox';
import Link from '#material-ui/core/Link';
import Paper from '#material-ui/core/Paper';
import Box from '#material-ui/core/Box';
import Grid from '#material-ui/core/Grid';
import LockOutlinedIcon from '#material-ui/icons/LockOutlined';
import Typography from '#material-ui/core/Typography';
import { makeStyles } from '#material-ui/core/styles';
import { pink } from '#material-ui/core/colors';
import { connect } from 'react-redux';
import { setAlert } from '../../actions/alert';
import PropTypes from 'prop-types';
import {login} from '../../actions/auth'
import { Redirect } from 'react-router-dom'
function Copyright() {
return (
<Typography variant="body2" color="textSecondary" align="center">
{'Copyright © '}
<Link color="primary" href="./">
NoQueue Website
</Link>{' '}
{new Date().getFullYear()}
{'.'}
</Typography>
);
}
const useStyles = makeStyles((theme) => ({
root: {
height: '100vh',
},
image: {
backgroundImage: 'url(https://invention.si.edu/sites/default/files/styles/story_banner_image/public/blog-guest-fox-susannah-2017-03-09-shutterstock_189632216-banner-edit.jpg?itok=eNxGJoO4)',
backgroundSize: 'cover',
backgroundPosition: 'center',
},
paper: {
margin: theme.spacing(8, 4),
display: 'flex',
flexDirection:'column',
alignItems: 'center',
},
avatar: {
margin: theme.spacing(1),
backgroundColor: pink.A700,
},
form: {
width: '100%',
marginTop: theme.spacing(2),
},
submit: {
margin: theme.spacing(3,0,2),
},
}));
const SignIn = ({setAlert,login,isAuthenticated,user}) => {
const [formData, setFormData] = useState({
email:"",
password:"",
})
const{email,password}=formData;
const onChange=e=>setFormData(
{
...formData, [e.target.name]:e.target.value
}
)
const onSubmit=async e=>{
e.preventDefault();
if (email && password) {
login(email,password)
}
else{
setAlert('Please fill all the fileds','warning');
}
}
const classes = useStyles();
if(isAuthenticated){
//if(user.role === 'User')
//return <Redirect to="/index"/>
//else if(
//user.role ==='Admin'
//)
return <Redirect to="/business"/>
}
return (
<Grid container component="main" className={classes.root}>
<Grid item xs={false} sm={4} md={7} className={classes.image} />
<Grid item xs={12} sm={8} md={5} component={Paper} square>
<div className={classes.paper}>
<Avatar className={classes.avatar}>
<LockOutlinedIcon />
</Avatar>
<Typography component="h1" variant="h5">
Sign in
</Typography>
<form onSubmit = { e=>onSubmit(e)} className={classes.form} noValidate>
<TextField
onChange={e=>onChange(e)}
variant="outlined"
margin="normal"
required
fullWidth
id="email"
label="Email Address"
name="email"
value={email}
/>
<TextField
onChange={e=>onChange(e)}
variant="outlined"
margin="normal"
required
fullWidth
name="password"
label="Password"
type="password"
id="password"
value={password}
/>
<FormControlLabel
control={<Checkbox value="remember" color="primary" />}
label="Remember me"
/>
<Button
type="submit"
fullWidth
variant="contained"
color="Primary"
className={classes.submit}
>
Sign In
</Button>
<Grid container>
<Grid item xs>
<Link href="forgotpassword" variant="body2">
Forgot password?
</Link>
</Grid>
<Grid item>
<Link href="signup" variant="body2">
{"Not a member? Sign Up"}
</Link>
</Grid>
</Grid>
<Box mt={5}>
<Copyright />
</Box>
</form>
</div>
</Grid>
</Grid>
);
}
SignIn.propTypes={
login:PropTypes.func.isRequired,
setAlert:PropTypes.func.isRequired,
isAuthenticated:PropTypes.bool,
user: PropTypes.object.isRequired,
};
const mapStateToProps=state=>({
isAuthenticated:state.auth.isAuthenticated,
user: state.auth.user,
})
export default connect(mapStateToProps,{login,setAlert})(SignIn);
I think in initial state of redux you need to check whether or not localStorage.token is not null/expired and set it back to user. So that whenever you refresh the page user da still can load
I was able to resolve above issue by using jwt_decode
if (localStorage.token) {
setAuthToken(localStorage.token);
var decoded = jwt_decode(localStorage.token);
}
try {
const res = await axios.get(`https://localhost:5001/api/auth/user/${decoded.Id}`);
dispatch({
type: USER_LOADED,
payload: res.data,
});
} catch (err) {
dispatch({
type: AUTH_ERROR,
});
}
};
I seem to be not understand anything lately and having a tough time grasping useContext and/or useState with React/Nextjs and react-map-gl.
Basically, I have two components. A searchbox utilizing Formik & a Map from react-map-gl. What I want to do is be able to have one of the variables from Formik be able to change the viewport on react-map-gl so it redirects the viewport of the map to the new coordinates. I tried researching and reading about what I can do to make this work but I just can't grasp it. I believe it has to be something simple and I'm just too new to understand. Every time I think I have it I get a error saying I can't actually do that.
For reference here's my current code using useState. Whenever I try to use it and hit the submit button I get " 1 of 1 unhandled error
Unhandled Runtime Error
TypeError: setViewport is not a function
"
which doesn't seem to make any sense :( . Other times when I can get it to work, I have to click the submit button twice to get the form to register the new setViewport. What am I doing wrong? Thank you for your help!
index.js
import { Grid } from '#material-ui/core';
import { useState } from 'react';
import SearchBox from '../components/searchbox';
import {
getAllCampgrounds,
getAllCities,
getCampgroundsByCity,
} from '../lib/api';
import Map from '../components/map';
export default function CampList({
graphCampgrounds,
cities,
campgroundsbycity,
}) {
const [viewport, setViewport] = useState({
height: '100vh',
latitude: 44.0456,
longitude: -71.6706,
width: '100vw',
zoom: 8,
});
return (
<Grid container spacing={3}>
<Grid item xs={12} sm={5} md={3} lg={2}>
<SearchBox
graphCampgrounds={graphCampgrounds}
cities={cities}
campgroundsbycity={campgroundsbycity}
setViewport={() => setViewport}
/>
</Grid>
<Grid item xs={12} sm={7} md={9} lg={10}>
<Map
campgrounds={graphCampgrounds}
viewport={viewport}
setViewport={() => setViewport}
/>
<pre style={{ fontSize: '2.5rem' }}>{}</pre>
</Grid>
</Grid>
);
}
I omitted my getServerSideProps area. Here is my Searchbox.js:
import Link from 'next/link';
import { Form, Formik, Field, useFormik } from 'formik';
import {
Paper,
Grid,
FormControl,
InputLabel,
Select,
MenuItem,
Button,
} from '#material-ui/core';
import { makeStyles } from '#material-ui/core/styles';
import { useRouter } from 'next/router';
const useStyles = makeStyles(theme => ({
paper: {
margin: 'auto',
maxWidth: 500,
padding: theme.spacing(3),
},
}));
export default function SearchBox({
graphCampgrounds,
cities,
campgroundsbycity,
viewport,
setViewport,
}) {
const router = useRouter();
const { query } = router;
const handleSubmit = async values => {
setViewport({
...viewport,
latitude: campgroundsbycity
? parseFloat(campgroundsbycity.nodes[0].acfDetails.latitude.toFixed(4))
: 44.43,
longitude: campgroundsbycity
? Math.abs(
parseFloat(campgroundsbycity.nodes[0].acfDetails.longitude.toFixed(4))
) * -1
: -72.352,
zoom: 11,
});
router.push(
{
pathname: '/camps',
query: { ...values, page: 1 },
},
undefined,
{ shallow: true }
);
};
const classes = useStyles();
const smValue = singleColumn ? 12 : 6;
const initialValues = {
city: query.city || 'all'
};
return (
<Formik initialValues={initialValues} onSubmit={handleSubmit}>
{({ values, submitForm }) => (
<Form>
<Paper className={classes.paper} elevation={5}>
<Grid container spacing={3}>
<Grid item xs={12} sm={smValue}>
<FormControl fullWidth variant="outlined">
<InputLabel id="search-cities">City</InputLabel>
<Field
name="city"
as={Select}
labelId="search-cities"
label="Cities"
>
<MenuItem value="all">
<em>All</em>
</MenuItem>
{cities.nodes.map(town => {
return (
<MenuItem
key={town.acfDetails.city}
value={town.acfDetails.city}
>
{town.acfDetails.city}
</MenuItem>
);
})}
</Field>
</FormControl>
</Grid>
<Grid item xs={12}>
<Button
color="primary"
variant="outlined"
type="button"
fullWidth
onClick={submitForm}
>
Search Campgrounds
</Button>
</Grid>
</Grid>
</Paper>
</Form>
)}
</Formik>
);
}
And here's my map component:
import { useContext, useMemo } from 'react';
import ReactMapGL, { Marker, MapContext } from 'react-map-gl';
import { ViewportContext } from '../lib/state';
export default function Map({ campgrounds, viewport, setViewport }) {
const markers = campgrounds.map(({ node }) => {
console.log(node.acfDetails);
return (
<Marker
key={node.title}
longitude={
Math.abs(parseFloat(node.acfDetails.longitude.toFixed(4))) * -1
}
latitude={parseFloat(node.acfDetails.latitude.toFixed(4))}
>
<img src="pin.png" alt={node.title} />
</Marker>
);
});
return (
<ReactMapGL
mapboxApiAccessToken={process.env.NEXT_PUBLIC_MAPBOX_KEY}
mapStyle="mapbox://styles/mapbox/outdoors-v11"
{...viewport}
onViewportChange={nextViewport => setViewport(nextViewport)}
>
{markers}
</ReactMapGL>
);
}
Agreed with #juliomalves's answer.
In you index.js, pass the function setViewport to your SearchBox and Map components by setViewport={setViewport}.
That is how React hooks should be called.
I am trying to use the map function but it wont rerender my select box with the updated selected value.
The filter is on a material ui dialog that pops up when you view a file. The values will update when i close the modal and reopen it but wont update if i dont close the window. Any help would be greatly appriciated.
import React, { useEffect, useState } from 'react';
import Grid from '#material-ui/core/Grid';
import Paper from '#material-ui/core/Paper';
import InputLabel from '#material-ui/core/InputLabel';
import FormControl from '#material-ui/core/FormControl';
import Select from '#material-ui/core/Select';
import { makeStyles } from '#material-ui/core/styles';
import MenuItem from '#material-ui/core/MenuItem';
import filter from '../../../constants/filter-options';
export default function KeywordFilter(props) {
const [selectedFilter, setFilter] = useState(props.selectedFilter);
const [filterOptions, setFilterOptions] = useState(filter);
const useStyles = makeStyles(theme => ({
root: {
flexGrow: 1
},
paper: {
padding: theme.spacing(2),
textAlign: 'center',
color: theme.palette.text.primary,
},
modal: {
height: '80vh',
width: '40vw',
maxWidth: '40vw'
}
}));
const classes = useStyles();
const handleChange = (event, keyword) => {
var temp = selectedFilter;
temp[keyword] = event.target.value;
console.log("TEMP: ", temp)
console.log("keywordList: ", keywordList)
props.onFilterChange(temp);
setFilter(temp)
setFilterOptions(filter)
};
const keywordList = Object.keys(filterOptions)
return (
<div key={keywordList}>
<h4 style={{textAlign:'center'}}>Filters: </h4>
<Grid container spacing={3}>
{keywordList.map((keyword) => {
return (
<Grid item xs={6}>
{console.log("selectedFilter: ", selectedFilter)}
<Paper className={classes.paper}>
{keyword}: <FormControl className={classes.formControl}>
<Select
key={keyword}
labelId="demo-simple-select-label"
id="demo-simple-select"
value={selectedFilter[keyword] ? selectedFilter[keyword] : "None"}
onChange={(e) => handleChange(e, keyword)}
>
{filterOptions[keyword].map(element => <MenuItem value={element}>{element}</MenuItem>)}
</Select>
</FormControl>
</Paper>
</Grid>
)}
)}
</Grid>
</div>
);
}
The filter file looks like the following:
const filter =
{
Schedule : ["None", "Monthly", "Quarterly", "Semi-Annually", "Annually"],
Chemical : ["None", "Copper", "Phosphorus"],
Color : ["None", "Black", "Blue"]
}
export default filter;
It turns out I was modifying the state directly and that was creating a bunch of issues. The following changes to the handleChange function and removing unnecessary code fixed the issue:
import React, { useEffect, useState } from 'react';
import Grid from '#material-ui/core/Grid';
import Paper from '#material-ui/core/Paper';
import FormControl from '#material-ui/core/FormControl';
import Select from '#material-ui/core/Select';
import { makeStyles } from '#material-ui/core/styles';
import MenuItem from '#material-ui/core/MenuItem';
import filter from '../../../constants/filter-options';
export default function KeywordFilter(props) {
const useStyles = makeStyles(theme => ({
root: {
flexGrow: 1
},
paper: {
padding: theme.spacing(2),
textAlign: 'center',
color: theme.palette.text.primary,
},
modal: {
height: '80vh',
width: '40vw',
maxWidth: '40vw'
}
}));
const classes = useStyles();
const handleChange = (event, keyword) => {
//deep copy selected filters to avoid directly changing state
const target = JSON.parse(JSON.stringify(props.selectedFilter))
//set new value to add
const source = { [keyword]: event.target.value};
//merge the 2 objects (this will update target aswell)
const results = Object.assign(target, source)
//update state
props.onFilterChange(results)
};
return (
<>
<h4 style={{textAlign:'center'}}>Filters: </h4>
<Grid container spacing={3}>
{Object.keys(filter).map((keyword) => {
return (
<Grid item xs={6}>
<Paper className={classes.paper}>
{keyword}: <FormControl className={classes.formControl}>
<Select
key={keyword}
labelId="demo-simple-select-label"
id="demo-simple-select"
value={props.selectedFilter[keyword] ? props.selectedFilter[keyword] : "None"}
onChange={(e) => handleChange(e, keyword)}
>
{filter[keyword].map(element => <MenuItem value={element}>{element}</MenuItem>)}
</Select>
</FormControl>
</Paper>
</Grid>
)}
)}
</Grid>
</>
);
}
When I run the code below I expected that styles would be injected inside props, but I always end up getting undefined props.
I am not providing any props to this component.
const styles = theme => ({
root: {
'& .MuiTextField-root': {
margin: theme.spacing(1),
width: '25ch',
},
},
});
class Editor extends React.Component {
render() {
const { classes } = this.props;
return (
<div className="editor">
<form className={classes.root} noValidate autoComplete="off">
<TextField id="standard-basic" label="Standard" />
<Button variant="contained" color="primary">
Hello World
</Button>
</form>
</div>
);
}
}
export default withStyles(styles)(Editor);
Hi you can use makeStyles from '#material-ui/core/styles'. Please check complete example below:
import React from 'react';
import {makeStyles} from '#material-ui/core/styles';
import Grid from '#material-ui/core/Grid';
import FormLabel from '#material-ui/core/FormLabel';
import FormControlLabel from '#material-ui/core/FormControlLabel';
import RadioGroup from '#material-ui/core/RadioGroup';
import Radio from '#material-ui/core/Radio';
import Paper from '#material-ui/core/Paper';
const useStyles = makeStyles((theme) => ({
root: {
flexGrow: 1,
},
paper: {
height: 140,
width: 100,
},
control: {
padding: theme.spacing(2),
},
}));
export default function SpacingGrid() {
const [spacing, setSpacing] = React.useState(2);
const classes = useStyles();
const handleChange = (event) => {
setSpacing(Number(event.target.value));
};
return (
<Grid container className={classes.root} spacing={2}>
<Grid item xs={12}>
<Grid container justify="center" spacing={spacing}>
{[0, 1, 2].map((value) => (
<Grid key={value} item>
<Paper className={classes.paper}/>
</Grid>
))}
</Grid>
</Grid>
<Grid item xs={12}>
<Paper className={classes.control}>
<Grid container>
<Grid item>
<FormLabel>spacing</FormLabel>
<RadioGroup
name="spacing"
aria-label="spacing"
value={spacing.toString()}
onChange={handleChange}
row
>
{[0, 1, 2].map((value) => (
<FormControlLabel
key={value}
value={value.toString()}
control={<Radio/>}
label={value.toString()}
/>
))}
</RadioGroup>
</Grid>
</Grid>
</Paper>
</Grid>
</Grid>
);
}
Source