ReactJS | Pass Render Contents but ADD onClick - reactjs

So I'm trying to get some re-use out of my modals that I need to drop in various spots. What I want to do is to separate the BUTTON that opens the modal from the modal itself. This way, the button could be an icon button, a proper button or some other render-able thing. I could be thinking about this wrong too. I maybe, should pass the modal to the button? Not sure, looking for advise. I'm new to ReactJS, so don't assume I know a thing or two, I've made it through a few tutorials so far :D.
Here is a simple current implementation of the two components tied up together. The problem child I think is the usage of the open State.
import React, { useState } from "react";
import {
Button,
Dialog,
DialogContent,
DialogTitle,
DialogContentText,
DialogActions,
IconButton,
} from "#mui/material";
import { Edit } from "#mui/icons-material";
import FacilityForm from "./FacilityForm";
function EditFacilityModal(props) {
const [open, setOpen] = React.useState(false);
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
const handleSubmit = async (Facility) => {
let response = await fetch(
props.FacilitiesURL + "/" + String(Facility.ID),
{
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(Facility),
}
);
if (response.ok) {
}
setOpen(false);
};
return (
<div>
<IconButton
aria-label="expand row"
size="small"
onClick={() => handleClickOpen()}
>
<Edit />
</IconButton>
<Dialog open={open} onClose={handleClose}>
<DialogTitle>Edit Facility</DialogTitle>
<DialogContent>
<DialogContentText>
Please provide the following details to edit the existing facility
in the system.
</DialogContentText>
<FacilityForm
submitActions={handleSubmit}
title="Edit Facility"
details="Facility will be saved exactly as shown."
Facility={props.Facility}
FacilitiesURL={props.FacilitiesURL}
/>
</DialogContent>
<DialogActions>
<Button onClick={handleClose}>Cancel</Button>
</DialogActions>
</Dialog>
</div>
);
}
export default EditFacilityModal;

You could outsource your Dialog into a seperate component which takes open and handleClose as props and other content as props i.e dialogContentText and form as a children prop
function App(props) {
const [openEditFacilityModal, setOpenEditFacilityModal] = React.useState(false);
const handleClickOpen = () => {
setOpenEditFacilityModal(true);
};
const handleClose = () => {
setOpenEditFacilityModal(false);
};
const handleSubmit = async (Facility) => {
let response = await fetch(
props.FacilitiesURL + "/" + String(Facility.ID),
{
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(Facility),
}
);
if (response.ok) {
}
setOpenEditFacilityModal(false);
};
return <div>
<IconButton
aria-label="expand row"
size="small"
onClick={() => handleClickOpen()}
>
<Edit />
</IconButton>
<EditFacilityModal open={openEditFacilityModal} handleClose={handleClose}>
<FacilityForm
submitActions={handleSubmit}
title="Edit Facility"
details="Facility will be saved exactly as shown."
Facility={props.Facility}
FacilitiesURL={props.FacilitiesURL}
/>
</EditFacilityModal>
</div>;
}
function EditFacilityModal({open, handleClose, dialogContentText, children}) {
return (
<div>
<Dialog open={open} onClose={handleClose}>
<DialogTitle>Edit Facility</DialogTitle>
<DialogContent>
<DialogContentText>
{dialogContentText}
</DialogContentText>
{children}
</DialogContent>
<DialogActions>
<Button onClick={handleClose}>Cancel</Button>
</DialogActions>
</Dialog>
</div>
);
}
export default EditFacilityModal;

You can separate the button that handles the opening of the modal by handling the open state in a top level component.
Here's an example of your component that control the open state. This way you can outsource the modal component and use it anywhere with any button you want.
// MyComponent.jsx
import { Edit } from "#mui/icons-material";
import { IconButton } from "#mui/material";
import React from "react";
import EditFacilityModal from "./EditFacilityModal";
function MyComponent() {
const [open, setOpen] = React.useState(false);
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
return (
<>
<IconButton
aria-label="expand row"
size="small"
onClick={() => handleClickOpen()}
>
<Edit />
</IconButton>
<EditFacilityModal
open={open}
handleClose={handleClose}
Facility="Facility"
FacilitiesURL="FacilitiesURL"
/>
</>
);
}
export default MyComponent;
// EditFacilityModal.jsx
import {
Button,
Dialog,
DialogActions,
DialogContent,
DialogContentText,
DialogTitle,
} from "#mui/material";
import FacilityForm from "./FacilityForm";
function EditFacilityModal(props) {
const handleSubmit = async (Facility) => {
const response = await fetch(
props.FacilitiesURL + "/" + String(Facility.ID),
{
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(Facility),
}
);
if (response.ok) {
}
props.handleClose();
};
return (
<Dialog open={props.open} onClose={props.handleClose}>
<DialogTitle>Edit Facility</DialogTitle>
<DialogContent>
<DialogContentText>
Please provide the following details to edit the existing facility in
the system.
</DialogContentText>
<FacilityForm
submitActions={handleSubmit}
title="Edit Facility"
details="Facility will be saved exactly as shown."
Facility={props.Facility}
FacilitiesURL={props.FacilitiesURL}
/>
</DialogContent>
<DialogActions>
<Button onClick={props.handleClose}>Cancel</Button>
</DialogActions>
</Dialog>
);
}
export default EditFacilityModal;

Related

Multiple modal handle in react-js

I am new here and I saw some related answer but it will use another package and class components, i have functional component and use MUI Dialog for popup but the functionality is only for one modal but I have many modal my code
import React from 'react'
import Image from '../../assets/images/banner1.png'
import Dialog from '#mui/material/Dialog';
import DialogActions from '#mui/material/DialogActions';
import DialogContent from '#mui/material/DialogContent';
import DialogContentText from '#mui/material/DialogContentText';
import DialogTitle from '#mui/material/DialogTitle';
import Button from '#mui/material/Button';
export default function Popups() {
const [open, setOpen] = React.useState(false);
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
return (
<div className='image-data-grids'>
<div className='image-and-data-section'>
<img onClick={handleClickOpen} className='image-data-grid-image' src={Image} alt='Img' />
</div>
<Dialog
open={open}
onClose={handleClose}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogContent>
<DialogContentText id="alert-dialog-description">
Content1
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={handleClose}>Disagree</Button>
</DialogActions>
</Dialog>
<div className='image-and-data-section'>
<img onClick={handleClickOpen} className='image-data-grid-image' src={Image} alt='Img' />
</div>
<Dialog
open={open}
onClose={handleClose}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogContent>
<DialogContentText id="alert-dialog-description">
content2
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={handleClose}>Disagree</Button>
</DialogActions>
</Dialog>
</div>
)
}
here handleClickOpen and handleClose are the function for open and close for single modal, i have multiple modals. how customize these two function for multiple modal, i am new in react please help me to solve this issue, Thanks in advance
I know you are talking about having multiple state dependencies within a Component. I think you can do this
function StackOverflow() {
// We create a state dep that holds all our modals open/close state
const [myModals, setMyModals] = useState({
modalA: true,
modalB: false,
})
// This will return an api in which we can toggle, close or open a modal
// ie: set the boolean to true or false
const getModalHanlder = (modalName) => {
return {
isOpen: myModals[modalName],
open: () => setMyModals((state) => ({ ...state, [modalName]: true })),
close: () => setMyModals((state) => ({ ...state, [modalName]: false })),
toggle: () =>
setMyModals((state) => ({ ...state, modalA: !state[modalName] })),
}
}
const modalA = getModalHanlder("modalA")
// Here we invoke our function and pass on the desired modal prop name from
// which we desire to create an api
// We can then create this for another modal, modalB
const modalB = getModalHanlder("modalB")
return (
<div>
<b>MODAL_A: {`${modalA.isOpen}`}</b>
<br />
<button onClick={modalA.toggle}>TOGGLE</button>
<button onClick={modalA.close}>CLOSE</button>
<button onClick={modalA.open}>OPEN</button>
</div>
)
}
This is one approach another one can be to create an state for each modal, like
const [isOpenModalA, setIsOpenModalA] = useState(false)
// And this for each modal state
A fancy one can be to use a hook as useReducer and update each modal state that depends on actions you dispatch, https://reactjs.org/docs/hooks-reference.html#usereducer

Adding Validation and Function to Input using React and MUI

I am creating a cat APP, that pulls a request from thecatapi, I want my submit button to load my ImageGrid.jsx when they input the correct link for example:
in the dialog box you put:
https://api.thecatapi.com/v1/images/search
then it must have a submit loading function and take you to the images of the cats:
what i have is this:
import React, { useState } from 'react';
import PropTypes from 'prop-types'
import getCat from './ImageGrid'
// Material UI
import DialogActions from '#material-ui/core/DialogActions';
import DialogContent from '#material-ui/core/DialogContent';
import DialogTitle from '#material-ui/core/DialogTitle';
import Input from '#material-ui/core/Input'
import Dialog from '#material-ui/core/Dialog';
import Button from '#material-ui/core/Button';
import Grid from '#material-ui/core/Grid';
const DialogBox = ({ ariaLabel }) => {
const [open, setOpen] = React.useState(false);
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
// const handleGrid = () => {
// React.useState(true)
// }
return (
<div className='modal'>
<Grid container justifyContent='center'>
{/* Button To Open Dialog Box */}
<Button
style={{border: '1px solid #ebc340', color: '#ebc340'}}
variant="outlined"
color="secondary"
onClick={handleClickOpen}>
Welcome to my Case Study, Click to begin
</Button>
</Grid>
{/* Dialog Box Content */}
<Dialog
className='dialog-btn'
open={open}
onClose={handleClose}>
<DialogTitle>
To begin the application, please insert your URL below:
</DialogTitle>
<DialogContent>
<Input
placeholder="Enter Your Link Here"
inputProps={ariaLabel}
fullWidth/>
</DialogContent>
{/* Dialog Box Actions */}
<DialogActions>
<Button
onClick={handleClose}
color="secondary">
Cancel
</Button>
<Button
onClick={ getCat }
color='primary'
autoFocus
type='submit'>
{/* onSubmit={handleGrid} */}
Submit
</Button>
</DialogActions>
</Dialog>
</div>
);
}
Input.inputProps = {
ariaLabel: 'Please insert your URL below',
tertiaryColor: 'tertiary'
}
Input.inputTypes = {
ariaLabel: PropTypes.string.isRequired,
tertiaryColor: PropTypes.string.isRequired,
};
export default DialogBox
and then it needs to load this:
/
/ Fetch data from
import React, { useCallback, useEffect, useState } from "react";
const url = "https://api.thecatapi.com/v1/images/search?limit=9";
function ImageGrid() {
const [catUrls, setCatUrls] = useState();
const getCat = useCallback(() => {
console.log("Hello World");
let catImageUrlList = [];
// fetch http request
fetch(url)
.then((res) => res.json()) //gives data in json
.then((cats) => {
console.log("Cats: ", cats);
for (let i = 0; i < cats.length; i++) {
catImageUrlList.push(cats[i].url);
}
setCatUrls(catImageUrlList);
})
.catch((error) => {
console.log("Error: ", error);
});
}, [setCatUrls]);
useEffect(() => {
console.log("Loading your feline friends....");
getCat();
}, [getCat]);
return (
<>
<h1>Look At These Beautiful Kitty's!</h1>
{catUrls ? (
catUrls.map((catUrl) => <img src={catUrl} alt={"kitty"} />)
) : (
<p>Loading...</p>
)}
<button onClick={getCat}>Refresh</button>
</>
);
}
export default ImageGrid;

Material UI Dialog turned to Hook flickering

I'm trying to turn Material UI's dialog into a "useDialog" hook so it keeps track of it's own open state.
Unfortunately I've encountered a problem that whenever I update a state further up the hierarchy, the dialog flickers and I'm not exactly sure why and how to circumvent it. I feel like a useRef is needed there somewhere, but I'm not sure. Here's a reproduced minimal example: https://codesandbox.io/s/flickering-dialog-minimal-example-ehruf?file=/src/App.js
And the code in question:
import React, { useState } from "react";
import {
Button,
Dialog,
DialogActions,
DialogContent,
DialogTitle
} from "#material-ui/core";
export default function App() {
const [openDialog, Dialog] = useDialog();
const [counter, setCounter] = useState(0);
return (
<div>
<Dialog title="Hello">
<div>{counter}</div>
<button onClick={() => setCounter(counter => counter + 1)}>
Increase
</button>
</Dialog>
<button onClick={openDialog}>Open dialog</button>
</div>
);
}
const useDialog = () => {
const [open, setOpen] = useState(false);
const handleClose = () => {
setOpen(false);
};
const someDialog = ({ title, children }) => {
return (
<Dialog open={open} onClose={handleClose}>
<DialogTitle>{title}</DialogTitle>
<DialogContent>{children}</DialogContent>
<DialogActions>
<Button onClick={handleClose} color="primary">
Close
</Button>
</DialogActions>
</Dialog>
);
};
return [
() => {
setOpen(true);
},
someDialog
];
};
The reason the dialog flickers is that a new Dialog component is created on every render(as a result of state change) in App. The old Dialog is unmounted and replaced by the new Dialog.
A rule of thumb is you should never define components while rendering.
That's why I suggest you separate your custom dialog component from useDialog hook:
const MyDialog = ({ open, handleClose, title, children }) => {
return (
<Dialog open={open} onClose={handleClose}>
<DialogTitle>{title}</DialogTitle>
<DialogContent>{children}</DialogContent>
<DialogActions>
<Button onClick={handleClose} color="primary">
Close
</Button>
</DialogActions>
</Dialog>
);
};
You can, however, keep some of the logic inside useDialog and reuse them:
const useDialog = () => {
const [open, setOpen] = useState(false);
const openDialog = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
const props = {
open,
handleClose
};
return [openDialog, props];
};
More about why returning components from hook can be a bad idea.
Custom hooks are not made for returning a component, instead they are used to create a common logic which will be shared by different components.
In your case I would suggest you to create a common component for your dialog. And use this component wherever you want. Like this:
<CustomDialog open={open}>
// Your jsx here
</CustomDialog>
const CustomDialog = ({children}) => {
return <Dialog open={open} onClose={handleClose}>
<DialogTitle>{title}</DialogTitle>
<DialogContent>{children}</DialogContent>
<DialogActions>
<Button onClick={handleClose} color="primary">
Close
</Button>
</DialogActions>
</Dialog>
}
For more information about custom hooks:
https://reactjs.org/docs/hooks-custom.html
To whomever finds this issue because of my mistake. I was using a styled Dialog and defined the styled Dialog inside the functional component. Just put the custom styling outside of any component.
// This needed to be outstide of the BetterDialog component
const CustomDialog = styled(Dialog)({
"& .MuiDialog-paper": {
zIndex: 90
}
})
...
function BetterDialog(props: BetterDialogProps) {
...

loading Data fetched from the API after refreshing page in react using redux

I am fetching array of objects from an API endpoint,
I am using react-redux for state management.
I have two problems.
data gets fetched but only loads and displays after I refresh the page.
console logging single statement 4 times 2 blank array before setting state and 2 with data after setting state where it should have been only once I guess.
Following is my code.
Dashboard Action
import dashboardAPI from "../../../API/ShopUserAPI/dashboardAPI"
export const CURRENT_ORDER_LOAD = "CURRENT_ORDER_LOAD"
export const CURRENT_ORDER_FETCH = "CURRENT_ORDER_FETCH"
export const CURRENT_ORDER_ERROR = "CURRENT_ORDER_ERROR"
export const currentOrderAction = () => {
return dispatch => {
dispatch({ type: CURRENT_ORDER_LOAD, payload: '' })
dashboardAPI.get('/orders')
.then(resp => {
console.log(resp)
dispatch({ type: CURRENT_ORDER_FETCH, payload: resp.data.data.orders })
})
.catch(err => {
console.log(err)
dispatch({ type: CURRENT_ORDER_ERROR, payload: 'error occured. Retry !' })
})
}
}
Dashboard Reducer
import { CURRENT_ORDER_LOAD, CURRENT_ORDER_FETCH, CURRENT_ORDER_ERROR } from "../actions/dashboardActions";
const initialState = {
isLoading: true,
currentOrderData: [],
currentOrdererror: ''
}
const dashboardReducer = (state = initialState, action) => {
switch (action.type) {
case CURRENT_ORDER_LOAD:
return {
...state,
isLoading: true
}
case CURRENT_ORDER_FETCH:
return {
...state,
isLoading: false,
currentOrderData: action.payload
}
case CURRENT_ORDER_ERROR:
return {
...state,
isLoading: false,
currentOrdererror: action.payload
}
default:
return state
}
}
export default dashboardReducer
Dashboard page
import React from 'react'
import { Link } from 'react-router-dom'
import { makeStyles } from '#material-ui/core/styles';
import { Button, Typography, Grid, DialogContent, DialogActions, Dialog, DialogTitle, Divider, ExpansionPanel, ExpansionPanelSummary, ExpansionPanelDetails } from "#material-ui/core";
import ExpandMoreIcon from '#material-ui/icons/ExpandMore';
import { connect } from "react-redux";
const useStyles = makeStyles((theme) => ({
contentCenter: {
display: 'flex',
justifyContent: 'center',
marginTop: '1rem'
},
newSection: {
marginTop: '1rem'
},
innerSection: {
padding: '1rem'
},
heading: {
fontSize: theme.typography.pxToRem(15),
color: theme.palette.text.secondary,
},
secondaryHeading: {
fontSize: theme.typography.pxToRem(15),
color: theme.palette.text.secondary,
},
}))
const CurrentOrders = (props) => {
console.log(props.currentOrderData) // console.log printing 4 times
const [expanded, setExpanded] = React.useState(false);
const handleChange = (panel) => (event, isExpanded) => {
setExpanded(isExpanded ? panel : false);
};
const classes = useStyles()
return (
<div className={classes.newSection}>
{/* current orders list */}
<Typography className={classes.contentCenter}>
Current orders
</Typography>
{/* Orders expansion panel list */}
{props.currentOrderData.map(currentOrders => (
<ExpansionPanel expanded={expanded === 'panel1'} onChange={handleChange('panel1')} className={classes.newSection}>
<ExpansionPanelSummary
expandIcon={<ExpandMoreIcon />}
aria-controls="panel1bh-content"
id="panel1bh-header"
>
<Typography className={classes.heading}>Order ID: #{currentOrders.orderID}</Typography>
</ExpansionPanelSummary>
<ExpansionPanelDetails>
<Grid container justify="space-between">
<Grid item>
<Link to="/order" style={{ textDecoration: "none" }}>
<Button size="small" variant="contained" color="primary">
View Order
</Button>
</Link>
</Grid>
</Grid>
</ExpansionPanelDetails>
</ExpansionPanel>
))}
</div>
)
}
const mapStateToProps = state => {
return {
currentOrderData: state.dashboard.currentOrderData
}
}
export default connect(mapStateToProps)(CurrentOrders)
I've dispatched dashboard action in the parent component of this page
DIspatching of Dashboard
import React, { useEffect } from "react";
import { Container } from "#material-ui/core";
import CurrentOrders from "./CurrentOrders";
import TodaysOrders from "./TodaysOrders";
import { makeStyles } from '#material-ui/core/styles';
import { Button, Typography, Grid, DialogContent, DialogActions, Dialog, DialogTitle, Divider } from "#material-ui/core";
import { connect } from "react-redux";
import { currentOrderAction } from "../../../store/ShopUserStore/actions/dashboardActions";
const useStyles = makeStyles((theme) => ({
contentCenter: {
display: 'flex',
justifyContent: 'center',
marginTop: '1rem'
},
newSection: {
marginTop: '1rem'
},
innerSection: {
padding: '1rem'
},
buttonSuccess: {
color: theme.palette.success.main
}
}))
const Home = (props) => {
const [open, setOpen] = React.useState(false);
useEffect(() => {
props.getCurrentOrders()
})
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
const classes = useStyles()
return (
<div>
<Container>
{/* shop status */}
<Grid container justify="flex-start" className={classes.newSection}>
<Button variant="outlined" color="primary" onClick={handleClickOpen} className={classes.buttonSuccess}>
Online
</Button>
</Grid>
{/* shop status confirmation dialog */}
<Dialog onClose={handleClose} aria-labelledby="customized-dialog-title" open={open}>
<DialogTitle id="customized-dialog-title" onClose={handleClose}>
Shop status
</DialogTitle>
<Divider />
<DialogContent>
<Typography className={classes.newSection}>
Are you sure that you want to change shop status ?
</Typography>
</DialogContent>
<DialogActions>
<Button autoFocus onClick={handleClose} color="primary">
No
</Button>
<Button autoFocus onClick={handleClose} color="primary">
Yes
</Button>
</DialogActions>
</Dialog>
<Grid container spacing={3}
>
<Grid item xs={12} md={6}>
<CurrentOrders />
</Grid>
<Grid item xs={12} md={6}>
<TodaysOrders />
</Grid>
</Grid>
</Container>
</div>
)
}
const mapDispatchToProps = (dispatch) => {
return {
getCurrentOrders: () => dispatch(currentOrderAction())
}
}
export default connect(null, mapDispatchToProps)(Home)
When you run your application, you should stumble into a nasty loop. The effect hook runs when the component mounts but also when the component updates. Because we are setting the state after every data fetch, the component updates and the effect runs again. It fetches the data again and again. That's a bug and needs to be avoided. We only want to fetch data when the component mounts. That's why you can provide an empty array as second argument to the effect hook to avoid activating it on component updates but only for the mounting of the component.
Try this:
useEffect(async () => {
props.getCurrentOrders()
}, []);

React-redux Hooks and function component logic

I'm trying to get the hang of react/redux with a login page to expand my knowledge. I am having some issues with the following error:
Invalid hook call. Hooks can only be called inside of the body of a function component.
I know this has been posted on here a lot but none of the answers are sticking for me. i can get the store to work just fine in other parts of of app just having some trouble with the logic here. any help is appreciated. My login page is this:
import React, { useState } from "react";
import { Grid, CircularProgress, Typography, Button, Tabs, Tab, TextField, Fade } from "#material-ui/core";
import { withRouter } from "react-router-dom";
import useStyles from "./styles";
import logo from "./logo.svg";
import { LoginUser } from "../../comps/Userauth";
function Login(props) {
var classes = useStyles();
var [isLoading, setIsLoading] = useState(false);
var [error, setError] = useState(null);
var [activeTabId, setActiveTabId] = useState(0);
var [loginValue, setLoginValue] = useState("");
var [passwordValue, setPasswordValue] = useState("");
return (
<Grid container className={classes.container}>
<div className={classes.logotypeContainer} style={{zIndex: '1'}} >
<img src={logo} alt="logo" className={classes.logotypeImage} />
<Typography className={classes.logotypeText}>test app</Typography>
</div>
<div className={classes.formContainer}>
<div className={classes.form}>
<Tabs
value={activeTabId}
onChange={(e, id) => setActiveTabId(id)}
indicatorColor="primary"
textColor="primary"
centered
>
<Tab label="Login" classes={{ root: classes.tab }} />
</Tabs>
{activeTabId === 0 && (
<React.Fragment>
<Fade in={error}>
<Typography color="secondary" className={classes.errorMessage}>
Please try again.
</Typography>
</Fade>
<TextField
id="username"
InputProps={{
classes: {
underline: classes.textFieldUnderline,
input: classes.textField,
},
}}
value={loginValue}
onChange={e => setLoginValue(e.target.value)}
margin="normal"
placeholder="Username"
type="text"
fullWidth
/>
<TextField
id="password"
InputProps={{
classes: {
underline: classes.textFieldUnderline,
input: classes.textField,
},
}}
value={passwordValue}
onChange={e => setPasswordValue(e.target.value)}
margin="normal"
placeholder="Password"
type="password"
fullWidth
/>
<div className={classes.formButtons}>
{isLoading ? (
<CircularProgress size={26} className={classes.loginLoader} />
) : (
<Button
disabled={
loginValue.length === 0 || passwordValue.length === 0
}
onClick={() =>
LoginUser(
loginValue,
passwordValue,
props.history,
setIsLoading,
setError,
)
}
variant="contained"
color="primary"
size="large"
>
Login
</Button>
)}
</div>
</React.Fragment>
)}
</div>
</div>
</Grid>
);
}
export default withRouter(Login);
And the userauth req:
import React from "react";
import axios from "axios";
import {useSelector, useDispatch} from 'react-redux'
import allActions from '../actions'
var jwtDecode = require('jwt-decode');
function LoginUser(login, password, history, setIsLoading, setError) {
const currentUser = useSelector(state => state.currentUser)
const dispatch = useDispatch()
try {
setError(false);
setIsLoading(true);
axios.post('/login', {username: login, password: password}, {
}).catch(function (error) {
if (error.response) {
setError(true);
console.log(error.response.data);
console.log(error.response.status);
console.log(error.response.headers);
} else if (error.request) {
setError(true);
console.log(error.request);
} else {
setError(true);
console.log('Error', error.message);
}
}).then(function(response) {
if (response.status == '200') {
setTimeout(() => {
setError(null)
setIsLoading(false)
let token1 = jwtDecode(response.data.token);
dispatch(allActions.userActions.setUser(token1.username))
history.push('/app/dashboard')
}, 2000);
} else {
setError(true);
setIsLoading(false);
}
})
} catch (error) {
setError(true);
setIsLoading(false);
}
}
function signOut(dispatch, history) {
dispatch(allActions.userActions.logOut())
history.push("/login");
}
export { LoginUser, signOut };
LoginUser is not a React component, it's just a function that handles an event. And, as the message states, you can't use hooks unless react is rendering a component.
You'll have to either pass in everything your login function needs as arguments, or refactor things.
One way to refactor this would be to create a custom hook that provides this login function to you.
export default useLoginHandler(history, setIsLoading, setError) {
const currentUser = useSelector(state => state.currentUser)
const dispatch = useDispatch()
return {
onLogin(login, password) {
doStuff().then(() => {
// use values from above hooks
dispatch(yourActionMaker(whatever))
})
},
onLogout() {
dispatch(allActions.userActions.logOut())
history.push("/login");
},
}
}
Now in your component, use that like any other hook:
function Login(props) {
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(null);
const {onLogin, onLogOut} = useLoginHandler(props.history, setIsLoading, setError)
// other hooks...
return <React.Fragment>
{/* other rendering... */}
<div onClick={() => onLogin(loginValue, passwordValue)}>
login
</div>
</React.Fragment>
}

Resources