Related
Now I have created this custom hook to perform lazy loading,which takes redux slice action as input and
import { useState, useEffect, useCallback, useRef } from "react";
import { useDispatch } from "react-redux";
function useLazyFetch(fetchAction) {
const dispatch = useDispatch();
const [page, setPage] = useState(1);
const [loading, setLoading] = useState(false);
const loadMoreRef = useRef(null);
const handleObserver = useCallback(async(entries) => {
const [target] = entries;
console.log(target.isIntersecting);
if (target.isIntersecting) {
console.log("INTERSECTING.....");
await new Promise((r) => setTimeout(r, 2000));
setPage((prev) => prev + 1);
}
}, []);
useEffect(() => {
const option = {
root: null,
rootMargin: "0px",
threshold: 1.0,
};
const observer = new IntersectionObserver(handleObserver, option);
if (loadMoreRef.current) observer.observe(loadMoreRef.current);
}, [handleObserver]);
const fetchApi = useCallback(async () => {
try {
setLoading(true);
await new Promise((r) => setTimeout(r, 2000));
dispatch(fetchAction(page))
setLoading(false);
} catch (err) {
console.error(err);
}
}, [page,fetchAction,dispatch]);
useEffect(() => {
fetchApi();
}, [fetchApi]);
return { loading, loadMoreRef };
}
export default useLazyFetch;
I am using this in my component like this, here you can see I am tracking div in the bottom using loadMoreRef from useLazyFetch, Now when I am commenting out the fetchApi(); from custom hook its working as expected, on scroll its logging INTERSECTING... in the console but the moment I try to execute the action through fetchApi() my whole app goes into loop,the div tracker with ref comes to top and it fetches the posts but after immediately that action repeats the tracker comes to top and page becomes empty & it fetches next set of posts,I can see that my list is getting appended new set of posts to state in redux dev tool instead of completely setting new state, but in UI it's rendering all posts again and again whic is causing the loop,how can I avoid this ?
import { CircularProgress, Grid, IconButton, Typography } from "#mui/material";
import { Box } from "#mui/system";
import React, { useEffect,useRef,useState } from "react";
import AssistantIcon from "#mui/icons-material/Assistant";
import Post from "../components/Post";
import { useDispatch, useSelector } from "react-redux";
import { getPosts } from "../redux/postSlice";
import AddPost from "../components/AddPost";
import useLazyFetch from "../hooks/useLazyFetch";
export default function Home() {
const dispatch = useDispatch();
// const api = `https://picsum.photos/v2/list`
const { status, posts } = useSelector((state) => state.post);
const {loading,loadMoreRef} = useLazyFetch(getPosts)
useEffect(() => {
dispatch(getPosts());
}, []);
return (
<Box>
<Box borderBottom="1px solid #ccc" padding="8px 20px">
<Grid container justifyContent="space-between" alignItems="center">
<Grid item>
<Typography variant="h6">Home</Typography>
</Grid>
<Grid item>
<IconButton>
<AssistantIcon />
</IconButton>
</Grid>
</Grid>
</Box>
<Box height="92vh" sx={{ overflowY: "scroll" }}>
<AddPost />
<Box textAlign="center" marginTop="1rem">
{status === "loading" && (
<CircularProgress size={20} color="primary" />
)}
</Box>
{status === "success" &&
posts?.map((post) => <Post key={post._id} post={post} />)}
<div style={{height:"50px",width:"100px",backgroundColor:"red"}} ref={loadMoreRef}>{loading && <p>loading...</p>}</div>
</Box>
</Box>
);
}
And here is my redux action & state update part
const initialState = {
status: "idle",
posts: []
};
export const getPosts = createAsyncThunk("post/getPosts", async (page) => {
console.log(page);
console.log("calling api ...");
const { data } = await axios.get(`/api/posts?page=${page}`);
return data;
});
export const postSlice = createSlice({
name: "post",
initialState,
reducers: {},
extraReducers: {
[getPosts.pending]: (state, action) => {
state.status = "loading";
},
[getPosts.fulfilled]: (state, action) => {
state.status = "success";
state.posts = [...state.posts,...action.payload.response.posts] ;
},
[getPosts.rejected]: (state, action) => {
state.status = "failed";
},
}
this is the solution that is working
import { CircularProgress, Grid, IconButton, Typography } from "#mui/material";
import { Box } from "#mui/system";
import React, { useEffect,useMemo } from "react";
import AssistantIcon from "#mui/icons-material/Assistant";
import Post from "../components/Post";
import { useDispatch, useSelector } from "react-redux";
import { getPosts } from "../redux/postSlice";
import AddPost from "../components/AddPost";
import useLazyFetch from "../hooks/useLazyFetch";
export default function Home() {
const { status, posts } = useSelector((state) => state.post);
const {loading,loadMoreRef} = useLazyFetch(getPosts)
const renderedPostList = useMemo(() => (
posts.map((post) => {
return( <Post key={post._id.toString()} post={post} />)
})
), [posts])
return (
<Box>
<Box borderBottom="1px solid #ccc" padding="8px 20px">
<Grid container justifyContent="space-between" alignItems="center">
<Grid item>
<Typography variant="h6">Home</Typography>
</Grid>
<Grid item>
<IconButton>
<AssistantIcon />
</IconButton>
</Grid>
</Grid>
</Box>
<Box height="92vh" sx={{ overflowY: "scroll" }}>
<AddPost />
<Box textAlign="center" marginTop="1rem">
{status === "loading" && (
<CircularProgress size={20} color="primary" />
)}
</Box>
{renderedPostList}
<div style={{height:"50px",width:"100px",backgroundColor:"red"}} ref={loadMoreRef}>{loading && <p>loading...</p>}</div>
</Box>
</Box>
);
}
}
I used useMemo hook to memoize and it works as expected
I have created an empty object variable named workspacesData through the context api which is available globally across the app. I am trying to set my api data to the variable within the helper function that is below (getWorkspacesData).
This function getWorkspacesData is then being called on another page of the application for when the user wants to pull in the data and have it displayed on the page. Problem is when the user presses the button I get the error "Invalid hook call. Hooks can only be called inside of the body of a function component."
I know the issue is that I am using the useContext hook within this function, but how do I get around this issue? as I want to use the globally available variable to set the data to it.
getWorkspaces.js
import { useContext } from "react";
import { AppContext } from "../../../context/context";
import mavenlinkAPI from "../apiTools";
const GetWorkspaces = async (mavenlinkAccessToken) => {
const {setWorkspacesData} = useContext(AppContext);
try{
const data = await mavenlinkAPI(
'get',
'workspaces?token='+mavenlinkAccessToken,
);
console.log(data)
setWorkspacesData(data);
} catch (error) {
console.log(error)
}
}
export default GetWorkspaces;
MavenlinkPage.jsx
import { Container, Grid, Paper } from '#mui/material';
import React, {useContext, useEffect, useRef, useState} from 'react';
import { getAuth } from "firebase/auth";
import Fab from '#mui/material/Fab';
import AddIcon from '#mui/icons-material/Add';
import {SuccessSnackbar, ErrorSnackbar} from '../components/PopupSnackbar';
import { AppContext } from '../context/context';
import GetWorkspaces from '../helpers/api/mavenlink/getWorkspaces';
import GetTimesheets from '../helpers/api/mavenlink/getTimesheets';
import GetUsers from '../helpers/api/mavenlink/getUsers';
import CreateProject from '../helpers/api/mavenlink/createProject';
import GetMavenlinkAccessToken from '../helpers/api/mavenlink/getMavenlinkAccessToken';
import DummyDataHolder from '../components/dummyDataHolder';
// import { DataGrid } from '#material-ui/x-data-grid';
export const MavenlinkPage = () => {
const { mavenlinkConnected } = useContext(AppContext);
const [errorAlert, setErrorAlert] = useState(false);
const [successAlert, setSuccessAlert] = useState(false);
const { mavenlinkAccessToken, setMavenlinkAccessToken } = useContext(AppContext);
const { workspacesData } = useContext(AppContext);
const auth = getAuth();
const user = auth.currentUser;
const uid = user.uid
const handleAlertClose = (event, reason) => {
if (reason === 'clickaway') {
return;
}
setSuccessAlert(false) && setErrorAlert(false);
};
useEffect(() => {
if(mavenlinkConnected){
GetMavenlinkAccessToken(setMavenlinkAccessToken);
}
}, [])
console.log(mavenlinkAccessToken)
return(
<>
<Container>
<div className="mavenlink-page">
<Grid container spacing={2}>
<Grid item xs={12}>
<h1>Mavenlink</h1>
</Grid>
<Grid item xs={12}>
<Paper className="connection-status" elevation={1}>
<h4 className="title">Connection Status:</h4>
{!mavenlinkConnected ? <h4 className="response-error">{user.email} is not connected</h4> : <h4 className="response-success">{user.email} is connected</h4>}
</Paper>
</Grid>
<Grid item xs={12}>
<Paper elevation={1}>
<h4>Get Workspaces</h4>
<Fab onClick={() => GetWorkspaces(mavenlinkAccessToken)} color="primary" aria-label="add">
<AddIcon />
</Fab>
<h4>Get timesheets</h4>
<Fab onClick={() => GetTimesheets(mavenlinkAccessToken)} color="primary" aria-label="add">
<AddIcon />
</Fab>
<h4>Get users</h4>
<Fab onClick={() => GetUsers(mavenlinkAccessToken)} color="primary" aria-label="add">
<AddIcon />
</Fab>
<h4>Create project</h4>
<Fab onClick={() => CreateProject(mavenlinkAccessToken)} color="primary" aria-label="add">
<AddIcon />
</Fab>
</Paper>
</Grid>
<Grid item xs={12}>
<Paper elevation={10} style={{ height: 400, width: '100%' }}>
{workspacesData != null ?
Object.keys(workspacesData).map((item, index) => {
return(
<div key={index}>
{workspacesData[item].map((m, ind) =>
<div key={ind}>{item}</div>
)}
</div>
)
})
:
<div></div>
}
</Paper>
</Grid>
</Grid>
</div>
{successAlert === true ? <SuccessSnackbar open={successAlert} handleClose={handleAlertClose}/> : <></> }
{errorAlert === true ? <ErrorSnackbar open={errorAlert} handleClose={handleAlertClose}/> : <></> }
</Container>
</>
);
};
You can call the hook inside your component and pass the returned value from it to the function:
const getWorkspaces = async (mavenlinkAccessToken, onSuccess) => {
try{
const data = await mavenlinkAPI(
'get',
'workspaces?token='+mavenlinkAccessToken,
);
onSuccess?.(data)
} catch (error) {
console.log(error)
}
}
And call it as:
export const MavenlinkPage = () => {
const { setWorkspacesData } = useContext(AppContext);
...
onClick={() => getWorkspaces(mavenlinkAccessToken, setWorkspacesData )}
}
Or create a custom hook:
const useGetWorkspaces = (mavenlinkAccessToken) => {
const { setWorkspacesData } = useContext(AppContext);
return async () => {
try{
const data = await mavenlinkAPI(
'get',
'workspaces?token='+mavenlinkAccessToken,
);
setWorkspacesData(data);
} catch (error) {
console.log(error)
}
}
}
And use it like:
export const MavenlinkPage = () => {
const getWorkspaces = useGetWorkspaces(mavenlinkAccessToken);
...
onClick={() => getWorkspaces()}
}
How can I send the value of the checkbox to the checkout.js page? This is the PaymentForm page. I tried my best but it's not working correctly. Basically, I want to use the PaymentForm fields in checkout.js page because my submit button is there.
PaymentForm.js
import React from 'react';
import Typography from '#material-ui/core/Typography';
import Grid from '#material-ui/core/Grid';
import TextField from '#material-ui/core/TextField';
import FormControlLabel from '#material-ui/core/FormControlLabel';
import Checkbox from '#material-ui/core/Checkbox';
import { createStyles } from '#material-ui/core/styles';
import StripeCheckout from 'react-stripe-checkout'
import 'react-toastify/dist/ReactToastify.css';
const styles = createStyles({
formControlLabel: {
fontSize: '1.5rem',
'& label': { fontSize: '5rem' }
}
});
const handleToken = (token) => {
console.log(token);
}
const PaymentForm = ({ PaymentForm, changePaymentForm }) => {
const [state, setState] = React.useState({
checkedA: false,
});
const handleChange = (event) => {
setState({ ...state, [event.target.name]: event.target.checked });
};
return (
<React.Fragment>
<Typography variant="h4" gutterBottom>
Payment method
</Typography><br />
<Grid container spacing={3}>
<Grid item xs={12}>
<FormControlLabel
control={<Checkbox checked={state.checkedA} onChange={handleChange} name="checkedA"/>}
label={<Typography style={styles.formControlLabel}>Cash on delivery</Typography>}
/>
</Grid>
<Grid item xs={12}>
<StripeCheckout
stripeKey="pk_test_51I9XPQAesAg2GfzQyVB7VgP0IbmWwgcfeFJSuCpB2kbNu60AFTbFhC7dxwje8YF4w2ILMJ6o2InB9ENczpd4dCSa00e09XoDbw"
token={handleToken}
amount={2 * 100}
name="All Products"
/>
</Grid>
</Grid>
</React.Fragment>
);
};
export default PaymentForm;
Checkout.js
import React from 'react';
import { makeStyles } from '#material-ui/core/styles';
import CssBaseline from '#material-ui/core/CssBaseline';
import AppBar from '#material-ui/core/AppBar';
import Toolbar from '#material-ui/core/Toolbar';
import Paper from '#material-ui/core/Paper';
import Stepper from '#material-ui/core/Stepper';
import Step from '#material-ui/core/Step';
import StepLabel from '#material-ui/core/StepLabel';
import Button from '#material-ui/core/Button';
import Link from '#material-ui/core/Link';
import Typography from '#material-ui/core/Typography';
import axios from '../../axios-orders';
import AddressForm from './CheckoutForm';
import PaymentForm from './PaymentForm';
import Review from './Review';
const useStyles = makeStyles((theme) => ({
appBar: {
position: 'relative',
},
layout: {
width: 'auto',
marginLeft: theme.spacing(2),
marginRight: theme.spacing(2),
[theme.breakpoints.up(1000 + theme.spacing(2) * 2)]: {
width: 1100,
marginLeft: 'auto',
marginRight: 'auto',
},
},
paper: {
marginTop: theme.spacing(3),
marginBottom: theme.spacing(3),
padding: theme.spacing(2),
[theme.breakpoints.up(700 + theme.spacing(3) * 2)]: {
marginTop: theme.spacing(6),
marginBottom: theme.spacing(6),
padding: theme.spacing(3),
backgroundColor: 'rgb(248, 246, 244)',
},
},
stepper: {
padding: theme.spacing(5, 0, 5),
fontWeight: 'bold',
backgroundColor: 'rgb(248, 246, 244)',
},
buttons: {
display: 'flex',
justifyContent: 'flex-end',
},
button: {
marginTop: theme.spacing(3),
marginLeft: theme.spacing(1),
border: "none"
},
}));
const steps = ['Shipping address', 'Payment details', 'Review your order'];
function Copyright() {
return (
<Typography variant="body2" color="textSecondary" align="center">
{'Copyright © '}
<Link color="inherit" href="https://material-ui.com/">
Your Website
</Link>{' '}
{new Date().getFullYear()}
{'.'}
</Typography>
);
}
function getStepContent(step, formValues = null, changeFormValue = null, paymentValues = null, changePaymentValue = null) {
switch (step) {
case 0:
return <AddressForm addressValues={formValues} changeAddressValue={changeFormValue} />;
case 1:
return <PaymentForm PaymentForm={paymentValues} changePaymentForm={changePaymentValue}/>;
case 2:
return <Review />;
default:
throw new Error('Unknown step');
}
}
export default function Checkout(props) {
const classes = useStyles();
const [addressFormValues, setAddressFormValues] = React.useState({});
const [paymentFormValues, setPaymentFormValues] = React.useState({});
const [paymentFormNewValues, setPaymentFormNewValues] = React.useState({});
const [activeStep, setActiveStep] = React.useState(0);
if(paymentFormValues === true){
setPaymentFormNewValues('Cash')
}
if(paymentFormValues === false){
setPaymentFormNewValues('Online')
}
console.log('[paymentFormNewValues: ]', paymentFormNewValues)
console.log('[paymentFormValues: ]', paymentFormValues)
const handleNext = () => {
setActiveStep(activeStep + 1);
axios.post('/UserPortal/CartItems/checkout_details_check.php', {
customer_id: localStorage.getItem('Id'),
})
.then((response) => {
if(response.data === null)
{
axios.post('/UserPortal/CartItems/checkout_details.php', {
customer_id: localStorage.getItem('Id'),
firstname: addressFormValues.firstname,
lastname: addressFormValues.lastname,
address: addressFormValues.address,
city: addressFormValues.city,
state: addressFormValues.state
})
.then((response) => {
console.log(response.data);
})
}
else{
axios.post('/UserPortal/CartItems/checkout_details_update.php', {
customer_id: localStorage.getItem('Id'),
firstname: addressFormValues.firstname,
lastname: addressFormValues.lastname,
address: addressFormValues.address,
city: addressFormValues.city,
state: addressFormValues.state,
payment_method: paymentFormNewValues
})
.then((response) => {
console.log(response.data);
})
}
})
};
const handleBack = () => {
setActiveStep(activeStep - 1);
};
const changeAddressFormValue = (key, value) => {
let values = { ...addressFormValues };
values[key] = value;
setAddressFormValues(values);
};
const changePaymentFormValue = (key, value) => {
let values = { ...addressFormValues };
values[key] = value;
setPaymentFormValues(values);
};
return (
<React.Fragment>
<CssBaseline />
<AppBar position="absolute" color="default" className={classes.appBar}></AppBar>
<main className={classes.layout}>
<Paper className={classes.paper}>
<Typography component="h1" variant="h3" align="center">
Checkout
</Typography>
<Stepper activeStep={activeStep} className={classes.stepper}>
{steps.map((label) => (
<Step key={label}>
<StepLabel><Typography component="h1" variant="h5" align="center">
{label} </Typography></StepLabel>
</Step>
))}
</Stepper>
<React.Fragment>
{activeStep === steps.length ? (
<React.Fragment>
<Typography variant="h5" gutterBottom>
Thank you for your order.
</Typography>
<Typography variant="subtitle1">
Your order number is #2001539. We have emailed your order
confirmation, and will
send you an update when your order has shipped.
</Typography>
</React.Fragment>
) : (
<React.Fragment>
{activeStep === 0 ? getStepContent(activeStep, addressFormValues, changeAddressFormValue , paymentFormValues, changePaymentFormValue) : getStepContent(activeStep)}
{ <div className={classes.buttons}>
{ activeStep !== 0 && (
<Button variant="contained" style={{outline: 'none'}}
className={classes.button}
onClick={handleBack}
>
Back
</Button>
)}
<Button style={{outline: 'none'}}
variant="contained"
color="secondary"
onClick={handleNext}
className={classes.button}
>
{activeStep === steps.length - 1 ? 'Place order' : 'Next'}
</Button>
</div> }
</React.Fragment>
)}
</React.Fragment>
</Paper>
<Copyright />
</main>
</React.Fragment>
);
}
It seems you have checked the activeStep wrong.
Maybe the right code must be like the following:
<React.Fragment>
{activeStep !== 0 ?getStepContent(activeStep, addressFormValues, changeAddressFormValue , paymentFormValues, changePaymentFormValue) : getStepContent(activeStep)}
Do you ever consider using Context API?
React Context API
But also you can use create a checkboxValue with useState in Checkout.js and pass setCheckBoxValue to PaymentForm in getStepContent as prop. When checkbox checked trigger setCheckBoxValue and it will trigger parent component's state (Checkbox).
I'm trying to use S3 service to upload an image and it's telling me that certain variables aren't defined when I have defined them. I have imported axios and all the other stuff that are required
import React, { useState } from "react";
import ReactDOM from "react-dom";
import axios from "axios";
import Grid from "#material-ui/core/Grid";
import Button from "#material-ui/core/Button";
import Card from "#material-ui/core/Card";
import TextField from "#material-ui/core/TextField";
import CreateIcon from "#material-ui/icons/Create";
import Box from "#material-ui/core/Box";
import CardMedia from "#material-ui/core/CardMedia";
import MuiAlert from "#material-ui/lab/Alert";
import Snackbar from "#material-ui/core/Snackbar";
import { withStyles } from "#material-ui/core/styles";
import { makeStyles } from "#material-ui/core/styles";
import Chip from "#material-ui/core/Chip";
import Avatar from "#material-ui/core/Avatar";
import Slider from "#material-ui/core/Slider";
import Typography from "#material-ui/core/Typography";
import InputAdornment from "#material-ui/core/InputAdornment";
import { connect } from "react-redux";
function mapStateToProps(state) {
return {};
}
const useStyles = makeStyles((theme) => ({
root: {
"& > *": {
margin: theme.spacing(1),
marginLeft: theme.spacing(15),
},
},
input: {
display: "none",
},
}));
const useSliderStyles = makeStyles({
root: {
width: 250,
},
input: {
width: 100,
},
});
const UploadButton = () => {
const classes = useStyles();
return (
<div className={classes.root}>
<input
accept='image/*'
className={classes.input}
id='contained-button-file'
multiple
type='file'
/>
<label htmlFor='contained-button-file'>
<Button variant='contained' color='primary' component='span'>
Upload
</Button>
</label>
</div>
);
};
const StyledCard = withStyles({
root: { height: 600, width: 350 },
})(Card);
const PetitionForm = () => {
const [title, setTitle] = useState("");
const [description, setDescription] = useState("");
const [open, setOpen] = useState(false);
const [petition, validPetition] = useState(false);
const [noTitle, titleError] = useState(false);
const [noDescription, descriptionError] = useState(false);
const [hashtag, setHashtag] = useState("");
const [arrayOfHashtags, addHashtag] = useState([]);
const [money, setMoney] = React.useState(500);
const slider = useSliderStyles();
const handleTitleChange = (event) => setTitle(event.target.value);
const handleDescriptionChange = (event) => setDescription(event.target.value);
const handleClose = (event, reason) => {
if (reason === "clickaway") {
return;
}
};
const Alert = (props) => (
<MuiAlert elevation={6} variant='filled' {...props} />
);
const clearField = (event) => {
setOpen(true);
if (title.length > 0 && description.length > 0) {
validPetition(true);
setTitle("");
setDescription("");
addHashtag([]);
setHashtag("");
axios({
url: `call/s3/backend`,
method: "post",
data: {
images: imageArray.toByteArray(),
},
})
.then((res) => {
imageUrlArr = res.data;
axios({
url: `api/petition_posts`,
method: "post",
data: {
petition_post: {
title: title,
description: description,
hashtags: arrayOfHashtags.join(" "),
amount_donated: 0,
media: imageUrlArr,
goal: money,
card_type: "petition",
org_profile_id: 1,
},
},
})
.then((res) => {
console.log(res.data);
})
.catch((error) => console.log(error));
})
.catch((error) => console.log(error));
}
titleError(true ? title.length === 0 : false);
descriptionError(true ? description.length === 0 : false);
};
const handleDelete = (h) => () => {
addHashtag((arrayOfHashtags) =>
arrayOfHashtags.filter((hashtag) => hashtag !== h)
);
};
const handleHashtagChange = (event) => setHashtag(event.target.value);
const handleSliderChange = (event, newValue) => {
setMoney(newValue);
};
const handleInputChange = (event) => {
setMoney(event.target.value === "" ? "" : Number(event.target.value));
};
const newHashtag = () => {
if (arrayOfHashtags.length < 3) {
addHashtag((arrayOfHashtags) => arrayOfHashtags.concat(hashtag));
} else {
console.log("Too many hashtags");
}
};
const Hashtags = arrayOfHashtags.map((h) => (
<Chip
key={h.length}
size='small'
avatar={<Avatar>#</Avatar>}
label={h}
onDelete={handleDelete(h)}
/>
));
return (
<StyledCard>
<Box mt={1}>
<Grid container justify='center'>
<TextField
id='outlined-multiline-static'
multiline
rows={1}
variant='outlined'
placeholder='Title'
value={title}
onChange={handleTitleChange}
helperText={
open // only displays helper text if button has been clicked and fields haven't been filled
? !noTitle || petition
? ""
: "Can't be an empty field"
: ""
}
/>
</Grid>
</Box>
<Box mt={1}>
<Grid container justify='center'>
<CardMedia title='Petition'>
<UploadButton />
</CardMedia>
</Grid>
</Box>
<div className={slider.root}>
<Typography>Amount to raise</Typography>
<Box>
<Grid container justify='center'>
<Slider
min={500}
max={10000}
value={typeof money === "number" ? money : 0}
onChange={handleSliderChange}
aria-labelledby='input-slider'
/>
<TextField
className={slider.input}
value={money}
onChange={handleInputChange}
InputProps={{
startAdornment: (
<InputAdornment position='start'>$</InputAdornment>
),
}}
helperText={
money < 500 || money > 10000
? "Please enter a value between 500 and 10000"
: ""
}
/>
</Grid>
</Box>
</div>
<Box mt={1} mb={1}>
<Grid container justify='center'>
<TextField
size='small'
inputProps={{
style: { fontSize: 15 },
}}
id='outlined-multiline-static'
multiline
rows={1}
placeholder='Hashtags'
variant='outlined'
value={hashtag}
onChange={handleHashtagChange}
helperText={
arrayOfHashtags.length === 3
? "You reached the maximum amount of hashtags"
: ""
}
/>
<Button color='primary' onClick={newHashtag}>
Create!
</Button>
{arrayOfHashtags.length > 0 ? Hashtags : ""}
</Grid>
</Box>
<Box mt={1} justify='center'>
<Grid container justify='center'>
<TextField
size='small'
inputProps={{
style: { fontSize: 15 },
}}
id='outlined-multiline-static'
multiline
rows={5}
placeholder='Description'
variant='outlined'
value={description}
onChange={handleDescriptionChange}
helperText={
// only displays helper text if button has been clicked and fields haven't been filled
open
? !noDescription || petition
? ""
: "Can't be an empty field"
: ""
}
/>
</Grid>
</Box>
<Box mt={1}>
<Grid container justify='center'>
<Button onClick={clearField}>
<CreateIcon />
Create Petition!
</Button>
{open && petition && (
<Snackbar open={open} autoHideDuration={2000} onClose={handleClose}>
<Alert severity='success'>
You have successfully create a petition!
</Alert>
</Snackbar>
)}
{open && !petition && (
<Snackbar open={open} autoHideDuration={2000} onClose={handleClose}>
<Alert severity='error'>You're missing one or more fields</Alert>
</Snackbar>
)}
</Grid>
</Box>
</StyledCard>
);
};
export default connect(mapStateToProps)(PetitionForm);
This is the error I'm getting, someone mentioned something about the scope and I think that shouldn't matter when I'm trying to assign a value to a variable as far I as know
Line 109:19: 'imageArray' is not defined no-undef
Line 113:11: 'imageUrlArr' is not defined no-undef
Line 123:24: 'imageUrlArr' is not defined no-undef
Search for the keywords to learn more about each error.
i've 2 components 1/ parent ----> which pass a delete function to child Component but it causes undefined
TypeError: Cannot read property 'onHandleDelete' of undefined
i tried every thing that react docs provides and there is nothing works for me
the parent Compnent
import React from 'react';
import PropTypes from 'prop-types';
import get from 'lodash/get';
// import isEmpty from 'lodash/isEmpty';
import { connect } from 'react-redux';
import isObject from 'lodash/isObject';
import classnames from 'classnames';
import moment from 'moment-hijri';
import uuid from 'uuid';
// Material-UI
import { withStyles } from '#material-ui/core/styles';
import Grid from '#material-ui/core/Grid';
import Paper from '#material-ui/core/Paper';
import Typography from '#material-ui/core/Typography';
// components
import Actions from './Actions';
import Supervisor from './Supervisor';
// Forms
import Entity from '~/Components/Entity';
import { BaseFormComponent, Form, FormBreak, FormDivider, FormContainer, FormItem } from '~/Components/FormComponent';
import { LabelAndValue } from '~/Components/FormComponent/Controls';
import {
isRequired,
noEnglish,
isObjectRequired,
} from '~/Services/Validators';
// Redux
import OfficesActions from '~/Redux/Actions/Offices';
import { VisitStatus } from '~/Services/StaticLookups';
import Strings from '~/Services/Strings';
import Notifications from '~/Services/Notifications';
import Message from '~/Components/Message';
import Navigate from '~/Services/Navigate';
import styles from './styles';
#withStyles(styles)
class AssignmentsEditorScreen extends BaseFormComponent {
constructor(props) {
super(props);
this.initState({
supervisors: [],
insititution: [],
inistitutionsLookup: [],
selectedSupervisors: [],
previousSupervisorsFromId: [],
plannedVisitInfoFromId: {},
institutionDetails: {},
account: {},
error: false,
form: {
id: {
type: this.types.string,
value: 0,
},
institutionId: {
type: this.types.number,
validators: [ isRequired() ],
},
visitTypeId: {
type: this.types.number,
validators: [ isRequired() ],
},
startDate: {
type: this.types.object,
validators: [ isObjectRequired() ]
},
endDate: {
type: this.types.object,
validators: [ isObjectRequired() ]
},
office: {
type: this.types.string,
UIOnly: true
},
plannedVisitSupervisors: {
type: this.types.string,
UIOnly: true
},
visitStatus: {
type: this.types.string,
UIOnly: true
},
reason: {
type: this.types.string,
validators: [ noEnglish() ],
},
},
});
}
componentDidMount() {
super.componentDidMount();
const id = get(this, 'props.match.params.id', null);
const { offices, account } = this.props;
this.searchableEntity.get();
if (id) {
this.addFormFields({
referenceNumber: {
type: this.types.string,
UIOnly: true
},
});
this.entity.get({ id });
} else {
this.removeFormFields([ 'referenceNumber' ]);
}
if (!offices.isSet) {
this.props.getOffices();
}
this.setState({ account });
}
componentDidUpdate(prevProps) {
if(this.state.account !== this.props.account) {
const employeeOfficeId = this.props.account.myRoles.find(item => item.roleName === 'PTSupervisionEmployees').officeId;
// // To Switch to employee Office Once he Opens The Editor
this.handleOfficesChange(employeeOfficeId);
this.setFieldValue('office', employeeOfficeId);
this.setState({ account: this.props.account });
}
}
onSubmit() {
const id = get(this, 'props.match.params.id', null);
const { selectedSupervisors, previousSupervisorsFromId } = this.state;
let plannedVisitors = [];
if(selectedSupervisors.length > 0) {
selectedSupervisors.map(item => {
plannedVisitors.push({ supervisorName: item.name });
});
} else {
plannedVisitors = [];
}
// To Append The Previous Supervisors with the newest supervisors
if(previousSupervisorsFromId.length > 0) {
previousSupervisorsFromId.map(item => {
plannedVisitors.push({ supervisorName: item.supervisorName });
});
}
if (this.isFormValid) {
this.entity.post({
...this.formValues,
plannedVisitSupervisors: [ ...plannedVisitors ],
// to set id = 0 --> in create Mode || id Value in update Mode
id: id ? id : 0
});
} else {
console.log('this', this.formValues,);
this.showFormErrors();
}
}
onEntityPosted() {
const id = get(this, 'props.match.params.id', null);
if(id) {
Notifications.notify('success', Strings.assignmentUpdatedSuccessfuly);
} else {
Notifications.notify('success', Strings.assignmentSavedSuccessfully);
}
Navigate.go('/plannedvisits');
}
onEntityPostedError(data) {
if(data === 'Failed to create visit!, another visit is on for the institution!') {
Notifications.notify('error', Strings.duplicatPlannedVisits);
this.setState({ error: true });
}
}
onEntityReceived(data) {
const previousSupervisorsFromId = [ ...data.plannedVisitSupervisors ];
const plannedVisitInfoFromId = { ...data };
if(isObject(data)) {
this.updateFormValues(data, () => {
if(data.hasOwnProperty('visitReasonId')) {
this.addFormFields({
visitReasonId: {
type: this.types.number,
validators: [ isRequired() ],
value: data.visitReasonId,
}
});
}
});
}
this.setState({
previousSupervisorsFromId,
plannedVisitInfoFromId
});
}
onSearchableEntityReceived(data) {
if(data && data.licensingInstitutionsModel) {
const insititution = [ ...data.licensingInstitutionsModel ];
if (data.licensingInstitutionsModel && data.licensingInstitutionsModel.length > 0) {
{
data.licensingInstitutionsModel.map(item => {
this.setState(prevState => ({
inistitutionsLookup: prevState.inistitutionsLookup.concat({
id: item.institutionId,
nameAr: item.fullName
}),
insititution
}));
});
}
}
}
}
onSupervisorsByOfficeEntityReceived(data) {
this.setState({ supervisors: data.result });
}
handleOfficesChange(officeId) {
this.supervisorsByOfficeEntity.get({ officeId });
}
onHandleDelete(member) {
console.log('member', member);
}
x(m) {
console.log('member');
}
handleReasonField(id) {
if(id === 1) {
this.addFormFields({
visitReasonId: {
type: this.types.string,
validators: [ isRequired() ]
},
});
} else {
this.removeFormFields([ 'visitReasonId' ]);
}
}
getInstitutionValues(institutionId) {
const { insititution } = this.state;
if(insititution && insititution.length > 0) {
const institutionDetails = insititution.filter(item => item.institutionId === institutionId);
this.setState({ institutionDetails });
}
}
handleAddMember(value) {
const member = this.state.supervisors.find(item => item.name === value);
if(member) {
// in order not to allow add multiple supervisors withe the same name
const isInPreviousSupervisors = this.state.previousSupervisorsFromId.find(item => item.supervisorName === member.name);
if(!isInPreviousSupervisors) {
const selectedSupervisors = [ ...this.state.selectedSupervisors ];
selectedSupervisors.push(member);
this.setState({ selectedSupervisors });
} else {
Notifications.notify('error', `${member.displayName} ${Strings.userAlreadyInTheList} ${member.name}`);
}
}
}
get offices() {
const { offices } = this.props;
if(offices.list.length >= 1) {
const lookup = {};
offices.list.forEach(item => {
lookup[item.id] = item.name;
});
return lookup;
}
return {};
}
get plannedVisitsSupervisors() {
const lookup = {};
this.state.supervisors.forEach(item => {
const isSelectedBefore = this.state.selectedSupervisors.find(sItem => sItem.name === item.name);
if(!isSelectedBefore) {
lookup[item.name] = item.displayName;
}
});
return lookup;
}
get getSupervisors() {
const { selectedSupervisors, previousSupervisorsFromId } = this.state;
return selectedSupervisors.concat(previousSupervisorsFromId);
}
render() {
// const { form, inistitutions, supervisorsByOffice, previousSupervisorsFromId } = this.state;
const { form, error, inistitutionsLookup, plannedVisitInfoFromId } = this.state;
const { classes } = this.props;
const id = get(this, 'props.match.params.id', null);
const inistitutionsCheck = inistitutionsLookup && inistitutionsLookup.length === 0;
// const supervisorsCheck = supervisorsByOffice && supervisorsByOffice.length === 0;
const disabled = plannedVisitInfoFromId && plannedVisitInfoFromId.plannedVisitActions && !plannedVisitInfoFromId.plannedVisitActions.allowEdit;
const {
TextField,
LookupSelectField,
StaticLookupSelectField,
SelectAutocompleteField,
Date2Field,
} = this;
return (
<React.Fragment>
<Entity
storeId={'Supervision-PlannedVisit-Editor'}
entityRef={ref => { this.entity = ref; }}
onEntityReceived={data => this.onEntityReceived(data)}
onEntityPosted={data => this.onEntityPosted(data)}
onEntityPostedError={data => this.onEntityPostedError(data)}
render={store => (
<Form loading={store.loading}>
<Grid container spacing={24}>
<If condition={error}>
<Grid item xs={12}>
<Message variant={'error'} text={Strings.duplicatPlannedVisits} />
</Grid>
</If>
<Grid item xs={9}>
<Paper elevation={1} className={classes.box1}>
<fieldset className={classes.fieldSet}>
<legend>{Strings.assignmentDetails}</legend>
<FormContainer>
<FormItem lg={6}>
<div className={classnames(classes.placeholder, id || inistitutionsCheck ? classes.disabledField : {})}>
<SelectAutocompleteField
name={'institutionId'}
label={Strings.assignmentInstitutionName}
emptyString={Strings.searchByNameAndLicense}
disabled={id || inistitutionsCheck}
data={inistitutionsLookup}
onChange={field => this.getInstitutionValues(field.value)}
// onSearch={e => console.log('e', e)}
/>
</div>
</FormItem>
<FormItem lg={form.visitTypeId.value === 1 ? 3 : 6}>
<LookupSelectField
name={'visitTypeId'}
label={Strings.assignmentvisitType}
lookup={'VisitType'}
onChange={field => this.handleReasonField(field.value)}
disabled={disabled}
/>
</FormItem>
<If condition={form.visitTypeId.value === 1 && form.visitReasonId}>
<FormItem lg={3}>
<LookupSelectField
name={'visitReasonId'}
label={Strings.assignmentvisitReason}
lookup={'VisitReason'}
disabled={disabled}
/>
</FormItem>
</If>
<FormItem lg={6}>
<Choose>
<When condition={disabled}>
<LabelAndValue
label={Strings.assignmentvisitStartDate}
value={plannedVisitInfoFromId.startDate.hijriDate}
/>
</When>
<Otherwise>
<Date2Field
name={'startDate'}
label={Strings.assignmentvisitStartDate}
minDate={[
moment().iYear(),
moment().iMonth() + 1,
moment().iDate()
]}
/>
</Otherwise>
</Choose>
</FormItem>
<FormItem lg={6}>
<Choose>
<When condition={disabled}>
<LabelAndValue
label={Strings.assignmentvisitEndDate}
value={plannedVisitInfoFromId.endDate.hijriDate}
/>
</When>
<Otherwise>
<Date2Field
name={'endDate'}
label={Strings.assignmentvisitEndDate}
minDate={[
moment().iYear(),
moment().iMonth() + 1,
moment().iDate()
]}
/>
</Otherwise>
</Choose>
</FormItem>
</FormContainer>
</fieldset>
</Paper>
<Paper elevation={1} className={classes.box}>
<fieldset className={classes.fieldSet}>
<legend>
<Choose>
<When condition={disabled}>
{Strings.assignedSupervisors}
</When>
<Otherwise>
{Strings.addSupervisors}
</Otherwise>
</Choose>
</legend>
<FormContainer className={classes.supervisorsContainer}>
<If condition={!disabled}>
<FormItem>
<div className={ classes.lookup }>
<StaticLookupSelectField
name= {'office'}
label={Strings.office}
lookup= {this.offices}
onChange={field => this.handleOfficesChange(field.value)}
/>
</div>
</FormItem>
<FormItem>
<StaticLookupSelectField
name={'plannedVisitSupervisors'}
label={Strings.supervisorListName}
lookup={this.plannedVisitsSupervisors}
onChange={field => this.handleAddMember(field.value)}
/>
</FormItem>
</If>
<If condition={this.getSupervisors.length > 0}>
<If condition={!disabled}>
<FormBreak />
<FormDivider />
<Typography variant='subtitle1' className={classes.supervisorsHeader}>
{Strings.assignedSupervisors}
</Typography>
</If>
<FormContainer className={classes.supervisorsContainer}>
<For each='member' of={this.getSupervisors} index='i'>
<FormItem lg={4} key={`${i}_${uuid()}`}>
<Supervisor
data={member}
onHandleDelete={data => this.onHandleDelete(data)}
disabled={disabled}
/>
</FormItem>
</For>
</FormContainer>
</If>
</FormContainer>
</fieldset>
</Paper>
<Paper elevation={1} className={classes.box}>
<FormItem fullWidth>
<TextField
multiline
name={'reason'}
label={Strings.assignmentvisitreason}
disabled={disabled}
/>
</FormItem>
</Paper>
</Grid>
<Grid item xs={3}>
{/* =========== Sidebar ============= */}
<If condition={form && form.visitStatus}>
<Paper elevation={1} className={classes.box}>
{/* lookup is Needed */}
<FormItem fullWidth>
<StaticLookupSelectField
name={'visitStatus'}
label={Strings.assignmentvisitStatus}
lookup={VisitStatus}
disabled
/>
</FormItem>
</Paper>
</If>
<If condition={form && form.referenceNumber}>
<Paper elevation={1} className={classes.box}>
<FormItem fullWidth>
<TextField
name={'referenceNumber'}
label={Strings.violationReferenceNumber}
disabled
/>
</FormItem>
</Paper>
</If>
<Paper elevation={1} className={classes.box}>
<FormItem fullWidth style={{ marginBottom: 10 }}>
<Actions disabled={disabled} onSubmit={() => this.onSubmit()} />
</FormItem>
</Paper>
</Grid>
</Grid>
</Form>
)}
/>
{/* To get all institutions */}
<Entity
storeId={'Supervision-Assignment-Searchable-Institution'}
entityRef={ref => { this.searchableEntity = ref;}}
onEntityReceived={data => this.onSearchableEntityReceived(data)}
/>
{/* To get supervisors by office */}
<Entity
storeId={'Supervision-Assignment-Supervisors-By-Office'}
entityRef={ref => { this.supervisorsByOfficeEntity = ref;}}
onEntityReceived={data => this.onSupervisorsByOfficeEntityReceived(data)}
/>
</React.Fragment>
);
}
}
AssignmentsEditorScreen.propTypes = {
classes: PropTypes.object,
offices: PropTypes.object,
account: PropTypes.object,
getOffices: PropTypes.func
};
const mapStateToProps = store => ({
offices: store.offices,
account: store.account.data || {}
});
const mapDispatchToProps = dispatch => ({
getOffices: () => dispatch(OfficesActions.get()),
});
AssignmentsEditorScreen.propTypes = {
classes: PropTypes.object,
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(AssignmentsEditorScreen);
the child Component
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { withStyles } from '#material-ui/core/styles';
import Typography from '#material-ui/core/Typography';
import Paper from '#material-ui/core/Paper';
import Icon from '#material-ui/core/Icon';
import IconButton from '#material-ui/core/IconButton';
import DeleteIcon from '#material-ui/icons/Delete';
import Tooltip from '#material-ui/core/Tooltip';
import strings from '~/Services/Strings';
import { supervisor } from './styles';
#withStyles(supervisor)
class Supervisor extends Component {
render() {
const { classes, data, disabled } = this.props;
console.log('da', data);
return (
<Paper className={classes.memberItem}>
<If condition={!disabled}>
<Tooltip title={strings.delete}>
<IconButton className={classes.delete} onClick={() => this.props.onHandleDelete(data)}>
<DeleteIcon />
</IconButton>
</Tooltip>
</If>
<div className={classes.memberIconWrapper}>
<Icon className={classes.memberIcon}>person</Icon>
</div>
<div className={classes.memberDetails}>
<div className={classes.subInfo}>
<Typography gutterBottom className={classes.memberJobTitle} variant='subtitle1'>
{strings.supervisorNameSub}
</Typography>
<Typography className={classes.memberName} variant='subtitle2'>
<Choose>
<When condition={data.displayName}>
{data.displayName}
</When>
<Otherwise>
<Choose>
<When condition={data.name}>
{data.name}
</When>
<Otherwise>
<Choose>
<When condition={data.supervisorName}>
{data.supervisorName}
</When>
<Otherwise>
{strings.emptyString}
</Otherwise>
</Choose>
</Otherwise>
</Choose>
</Otherwise>
</Choose>
</Typography>
</div>
<div className={classes.subInfo}>
<Typography gutterBottom className={classes.memberJobTitle} variant='subtitle1'>
{strings.assignedVisitCountSub}
</Typography>
<Typography className={classes.memberName} variant='subtitle2'>
<Choose>
<When condition={typeof(data.assignedVisitCount) === 'number'}>
{data.assignedVisitCount}
</When>
<Otherwise>
{strings.emptyString}
</Otherwise>
</Choose>
</Typography>
</div>
<div className={classes.subInfo}>
<Typography gutterBottom className={classes.memberJobTitle} variant='subtitle1'>
{strings.requiredVisitsCountPerMonthSub}
</Typography>
<Typography className={classes.memberName} variant='subtitle2'>
<Choose>
<When condition={typeof(data.requiredVisitsCountPerMonth) === 'number'}>
{data.requiredVisitsCountPerMonth}
</When>
<Otherwise>
{strings.emptyString}
</Otherwise>
</Choose>
</Typography>
</div>
</div>
</Paper>
);
}
}
Supervisor.propTypes = {
classes: PropTypes.object,
data: PropTypes.object,
disabled: PropTypes.bool,
key: PropTypes.string,
onHandleDelete: PropTypes.func,
};
export default Supervisor;
here is my parent Component
<For each='member' of={this.getSupervisors} index='i'>
<FormItem lg={4} key={`${i}_${uuid()}`}>
<Supervisor
data={member}
onHandleDelete={data => this.onHandleDelete(data)}
disabled={disabled}
/>
</FormItem>
</For>
here is onHandleDelete Function
onHandleDelete(data) {
console.log('member');
}
here is my Child Component(Supervisor)
<If condition={!disabled}>
<Tooltip title={strings.delete}>
<IconButton className={classes.delete} onClick={() => this.props.onHandleDelete(data)}>
<DeleteIcon />
</IconButton>
</Tooltip>
</If>
this is the error from console
Uncaught TypeError: Cannot read property 'onHandleDeletion' of undefined
at Object.onHandleDeletion (index.js:497)
at onClick (Supervisor.js:24)
at HTMLUnknownElement.callCallback (react-dom.development.js:100)
at Object.invokeGuardedCallbackDev (react-dom.development.js:138)
at Object.invokeGuardedCallback (react-dom.development.js:187)
at Object.invokeGuardedCallbackAndCatchFirstError (react-dom.development.js:201)
at executeDispatch (react-dom.development.js:461)
at executeDispatchesInOrder (react-dom.development.js:483)
at executeDispatchesAndRelease (react-dom.development.js:581)
at executeDispatchesAndReleaseTopLevel (react-dom.development.js:592)
at forEachAccumulated (react-dom.development.js:562)
at runEventsInBatch (react-dom.development.js:723)
at runExtractedEventsInBatch (react-dom.development.js:732)
at handleTopLevel (react-dom.development.js:4476)
at batchedUpdates$1 (react-dom.development.js:16659)
at batchedUpdates (react-dom.development.js:2131)
at dispatchEvent (react-dom.development.js:4555)
at interactiveUpdates$1 (react-dom.development.js:16714)
at interactiveUpdates (react-dom.development.js:2150)
at dispatchInteractiveEvent (react-dom.development.js:4532)
where data is an object that for sure it carries data
i expect logging data in the console