I am using Jest/React Testing Library(RTL) to to unit testing for the UI.
None of my RTL queries work.
It keeps telling me errors like
TestingLibraryElementError: Unable to find an element by: [data-testid="analysis-categories-header"]
no matter how I query it.
I have tried getBy*, findBy*(yes, with async...await), getByTestId and everything.
The only difference is that when I use findBy* I get an extra error that says
Error: Error: connect ECONNREFUSED 127.0.0.1:80
even screen.debug() is ignored and not working.
Out of the 13 tests I have, only one test work surprisingly. The queries go through.
test:
import '#testing-library/jest-dom';
import { render, screen } from '#testing-library/react';
import { HelmetProvider } from 'react-helmet-async';
import { Provider } from 'react-redux';
import configureStore from 'redux-mock-store';
import Login from '../src/pages/login';
test('renders logo and login box with "LOG IN" text and two buttons', () => {
const initState = {};
const mockStore = configureStore();
render(
<HelmetProvider>
<Provider store={mockStore(initState)}>
<Login />
</Provider>
</HelmetProvider>,
);
expect(screen.getByTestId('login-logo')).toBeVisible();
expect(screen.getByTestId('login-title')).toBeVisible();
expect(screen.getByRole('button', { name: 'Sign In' })).toBeVisible();
expect(
screen.getByRole('button', { name: 'Sign In With Intuit' }),
).toBeVisible();
});
component:
import { useAuth0 } from '#auth0/auth0-react';
import {
Box,
Button,
Card,
CardContent,
Container,
Typography,
} from '#material-ui/core';
import Link from 'next/link';
import { useRouter } from 'next/router';
import type { FC } from 'react';
import React, { useEffect } from 'react';
import { Helmet } from 'react-helmet-async';
import Logo from 'src/components/Logo';
import axios from 'src/lib/axios';
import gtm from 'src/lib/gtm';
const Login: FC = () => {
const router = useRouter();
const { isLoading, isAuthenticated, loginWithRedirect } = useAuth0();
useEffect(() => {
gtm.push({ event: 'page_view' });
}, []);
useEffect(() => {
if (!isLoading && isAuthenticated) {
router.push('/recommendations');
}
}, [isLoading, isAuthenticated]);
const handleIntuitLogin = async () => {
try {
const response = await axios.get('/auth/sign-in-with-intuit');
window.location = response.data;
} catch (e) {
throw new Error(e);
}
};
const handleAuth0Login = async () => {
try {
const response = await axios.get('/auth/sign-in-with-auth0');
window.location = response.data;
} catch (e) {
throw new Error(e);
}
};
return (
<>
<Helmet>
<title>Login</title>
</Helmet>
<Box
sx={{
backgroundColor: 'background.default',
display: 'flex',
flexDirection: 'column',
minHeight: '100vh',
}}
>
<Container maxWidth="sm" sx={{ py: '80px' }}>
<Box
sx={{
display: 'flex',
justifyContent: 'center',
mb: 8,
}}
>
<Link href="/login">
<Box>
<Logo height={100} width={300} />
</Box>
</Link>
</Box>
<Card>
<CardContent
sx={{
display: 'flex',
flexDirection: 'column',
p: 4,
}}
>
<Box
sx={{
alignItems: 'center',
display: 'flex',
justifyContent: 'space-between',
mb: 3,
}}
>
<div>
<Typography
color="textPrimary"
gutterBottom
variant="h4"
data-testid="login-title"
>
Log in
</Typography>
</div>
</Box>
<Box
sx={{
flexGrow: 1,
mt: 3,
}}
>
<Button
color="primary"
onClick={handleAuth0Login}
fullWidth
size="large"
type="button"
variant="contained"
>
Sign In
</Button>
</Box>
<Box sx={{ mt: 2 }}>
<Button
color="primary"
onClick={handleIntuitLogin}
fullWidth
size="large"
type="button"
variant="contained"
>
Sign In With Intuit
</Button>
</Box>
</CardContent>
</Card>
</Container>
</Box>
</>
);
};
export default Login;
The only difference is that this component (nextjs page) does not use redux thunk. This is how far I got to find the cause.
Related
I have an input field in child component. If you type in valid wallet address it should display wallet balance in parent component.
I don't know how to pass that e.target.value we are reading from input field.
I tried to do this function in parent component - Dashboard.
const [walletBalance, setWalletBalance] = useState('');
const getWalletBalance = async () => {
try {
const web3 = new Web3(Web3.givenProvider || 'ws://localhost:3000');
for (const wallet of dummyWallets) {
const balance = await web3.eth.getBalance(wallet.address);
console.log(balance);
const balanceConvertedFromWeiToEth = await Web3.utils.fromWei(balance, 'ether');
const shortenedBalance =
balanceConvertedFromWeiToEth.length >= 10
? balanceConvertedFromWeiToEth.slice(0, 10)
: balanceConvertedFromWeiToEth;
setWalletBalance(shortenedBalance);
}
} catch (error) {
console.error(error);
throw new Error('No wallet found');
}
};
render (
<p>Wallet balance: {walletBalance}</p>
and then pass this function down to child component with input field (need to prop drill 3 times for that) and in child component say:
const [inputText, setInputText] = useState('');
const handleInputChange = (e) => {
setInputText(e.target.value);
if (inputText === wallet.address) {
getWalletBalance()
}
};
render (
<input value={inputText} onChange={handleInputChange} />
)
It didn't work. No errors. Just nothing happens.
Then I thought that I need to do the other way around: Instead of trying to pass getWalletBalance() down as props, I need to read data from input field and pass that data to parent component, because parent needs to have access to the input field and read from it to be able to display wallet balance in parent.
I've also tried to create context:
import React, { useState, createContext } from 'react';
import { dummyWallets } from '../mocks/dummyWallets';
import Web3 from 'web3';
export const WalletContext = createContext();
// eslint-disable-next-line react/prop-types
export const TransactionProvider = ({ children }) => {
const [inputText, setInputText] = useState('');
const [walletBalance, setWalletBalance] = useState('');
const handleInputChange = async (e) => {
setInputText(e.target.value);
const web3 = new Web3(Web3.givenProvider || 'ws://localhost:3000');
for (const wallet of dummyWallets) {
if (inputText === wallet.address) {
const balance = await web3.eth.getBalance(wallet.address);
console.log(balance);
const balanceConvertedFromWeiToEth = await Web3.utils.fromWei(balance, 'ether');
const shortenedBalance =
balanceConvertedFromWeiToEth.length >= 10
? balanceConvertedFromWeiToEth.slice(0, 10)
: balanceConvertedFromWeiToEth;
setWalletBalance(shortenedBalance);
}
}
};
return (
<WalletContext.Provider
value={{ walletBalance, handleInputChange, inputText, setInputText,
setWalletBalance }}>
{children}
</WalletContext.Provider>
);
};
Then pass handleInputChange to onChange in child component and inputText as value, then use walletBalance in parent component.
All context things walletBalance, handleInputChange, inputText, setInputText and setWalletBalance threw undefined error.
Here is child component consuming data from context:
import React, { FC, useState, useContext } from 'react';
import { Formik, Form, Field } from 'formik';
import { Button, InputAdornment, Box, Grid } from '#material-ui/core';
import TextField from '#eyblockchain/ey-ui/core/TextField';
import { Search } from '#material-ui/icons';
import SvgEthereum from '../../../images/icon/Ethereum';
import { makeStyles } from '#material-ui/styles';
import { Theme } from '#material-ui/core';
import { dummyWallets } from '../../../shared/mocks/dummyWallets';
import Web3 from 'web3';
import { WalletContext } from '../../../shared/context/WalletContext';
const etherIconBorderColor = '#eaeaf2';
const useStyles = makeStyles((theme: Theme) => ({
etherIconGridStyle: {
padding: '16px 0 0 0'
},
etherIconStyle: {
fontSize: '220%',
textAlign: 'center',
color: theme.palette.primary.light,
borderTop: `solid 1px ${etherIconBorderColor}`,
borderBottom: `solid 1px ${etherIconBorderColor}`,
borderLeft: `solid 1px ${etherIconBorderColor}`,
padding: '5px 0 0 0',
[theme.breakpoints.down('sm')]: {
fontSize: '150%',
lineHeight: '43px'
}
},
buttonStyle: {
paddingTop: theme.spacing(2)
},
searchIconStyle: {
color: theme.palette.primary.light
}
}));
const onSubmitHandler = () => {
return;
};
const SearchInputForm: FC<any> = (props) => {
const { walletBalance, handleInputChange, inputText } = useContext(WalletContext);
const classes = useStyles();
return (
<Formik
onSubmit={onSubmitHandler}
initialValues={{ name: '' }}
render={({ values }) => {
return (
<Form>
<Grid container>
<Grid item xs={1} className={classes.etherIconGridStyle}>
<Box className={classes.etherIconStyle}>
<SvgEthereum />
</Box>
</Grid>
<Grid item xs={11}>
<Field
required
name="name"
placeholder={props.placeholdervalue}
value={inputText}
component={TextField}
onChange={handleInputChange}
InputProps={{
endAdornment: (
<InputAdornment position="end">
<Search fontSize="large" className={classes.searchIconStyle} />
</InputAdornment>
)
}}
/>
</Grid>
<Grid item xs={12}>
<Box
display="flex"
justifyContent="flex-end"
alignItems="flex-end"
className={classes.buttonStyle}>
<Button type="submit" variant="contained" color="secondary" size="large" disabled>
Create Alert
</Button>
</Box>
</Grid>
</Grid>
</Form>
);
}}
/>
);
};
export default SearchInputForm;
Here is parent component where I pull walletBalance from context:
const Dashboard: FC<any> = () => {
const { walletBalance } = useContext(WalletContext);
const classes = useStyles();
return (
<>
<Container maxWidth={false} className={classes.dashboardContainer}>
<Grid>
<Box data-testid="alert-page-heading">
<Typography variant="h3">Alerts</Typography>
</Box>
<Box data-testid="alert-page-subheading">
<Typography className={classes.subHeadingStyle}>
Monitor the Web 3.0 for interesting blockchain events
</Typography>
</Box>
</Grid>
<Grid container spacing={4}>
<Grid item xs={12} sm={8}>
<Box className={classes.dashboardCard}>
<Box className={classes.searchBox}>
<SearchTabs />
</Box>
<Box
sx={{
display: 'flex',
justifyContent: 'space-between'
}}>
<Box
sx={{
height: '60%',
padding: '1%',
fontSize: 20
}}>
<a href="http://localhost:3000/" className={classes.backToAlertsLink}>
Back to my alerts
</a>
<p className={classes.boldText}>Recent transactions</p>
</Box>
<Box sx={{ height: '60%', padding: '4%', fontSize: 20 }}>
<span>Wallet Balance</span>
<span className={classes.balanceNumber}>{walletBalance} ETH</span>
</Box>
</Box>
</Box>
</Grid>
<Grid item xs={12} sm={4}>
<Box className={classes.dashboardCard}>
<Box sx={{ height: '60%', padding: '2%', fontSize: 20, fontWeight: 'bold' }}>
Top 10 alerts
</Box>
</Box>
</Grid>
</Grid>
</Container>
</>
);
};
export default Dashboard;
The problem is that I pull handleChange from context and apply to input field in child component to read the value from the input and then pull walletBalance from context in parent but that walletBalance is not updated (doesn't contain input value in it).
Can someone please share knowledge on how to read data from child component input field and display something from that in parent component?
Any advice is appreciated. Thank you.
We are trying to update our web application from material ui (v4) to mui (v5)
Using the available examples, we have managed for function based components, but it does not seem to work for class based components, and there is very little examples around on class based components and MUI.
Out pattern looks something like the below currently, but none of the styles are applied to the MUI components. The same pattern seems to work on a POC project with functional components.
The container file Login.js
import {connect} from 'react-redux'
import Layout from '../pages/login.js';
import { call } from '../reducers/helloWorld'
import { logIn, logOut } from "../reducers/userManagement"
const mapStateToProps = state => ({
user: state.userManagement.user,
requestState: state.helloWorld.requestState,
})
const mapDispatchToProps = dispatch => ({
helloWorld: () => dispatch(call()),
logIn: (user2) => dispatch(logIn(user2)),
logOut: () => dispatch(logOut()),
})
export default connect(mapStateToProps, mapDispatchToProps)(Layout)
the page file login.js
import React from "react";
import { Typography, Button, FormControlLabel, Checkbox, TextField, Grid, Paper} from '#mui/material';
import { styled } from '#mui/material/styles';
import theme from '../theme';
import classes from '../styles/login';
import logo from '../img/Ergopak-logo.png';
import { Navigate } from 'react-router-dom';
// SXprops
class Login extends React.Component {
state = {
email: "",
password: "",
showPassword: false,
navigateTo: ""
}
componentDidMount(){
const {logOut} = this.props
logOut()
}
goToHome(){
}
handleSubmit(e){
const {logIn} = this.props
console.log("handleSubmit pressed!!")
logIn({name: "Pieka"})
this.setState({navigateTo: <Navigate to="/home"/>})
}
setEmail(){
console.log("handleEmail pressed")
}
setPassword(){
console.log("setPassword pressed")
}
render(){
const {user, requestState} = this.props
const {email, password, showPassword, navigateTo} = this.state
console.log("requestState", requestState)
console.log("user", user)
console.log("that thing is, ", classes(theme).welcome)
return(
<div style={{backgroundColor: "green", height: "100%", position: "fixed", width: "100%"}}>
<Grid container direction = "column" alignItems="stretch" justifyContent="flex-start" style={{backgroundColor: "grey", height: "100%", display: "flex"}} >
{navigateTo}
<Grid item sx = {classes(theme).welcomeDiv}>
<div sx = {classes(theme).welcome} >Welcome To SightPlan </div>
</Grid>
<Grid item >
<img style={{maxHeight: "2em"}} src={logo} alt="logo" />
</Grid>
<Grid item sx = {classes(theme).centerDivs}>
<Typography>Please log in with your email address and password</Typography>
<TextField
sx={{
width: 400
}}
type="email"
value={email}
placeholder="Email"
onChange={(e) => this.setState({email: e.target.value})}
/>
<Grid container direction = "column" alignItems = "center">
<TextField
sx = {classes(theme).loginFields}
type= {showPassword ? "string" : "password"}
variant="standard"
value={password}
placeholder="Password"
onChange={(e) => this.setState({password: e.target.value})}
/>
<FormControlLabel
control={<Checkbox size="small" name="showClosed" />}
checked={showPassword}
label={<span style={{ fontSize: '0.8em' }}> Show password </span>}
onChange={e => this.setState({showPassword: !showPassword})}
style={{margin: "auto"}}
/>
</Grid>
<Button
sx = {classes(theme).loginButton}
type="submit" onClick={this.handleSubmit.bind(this)}
variant="contained"
color="primary"
>
Log In
</Button>
</Grid>
<Paper style = {{backgroundColor: "blue", borderRadius: "0em", display: "flex", flexDirection: "column"}} />
</Grid>
</div>
)}
}
export default styled(Login)({theme})
The styles file login.js
import commonstyles from "./common"
const styles = (theme) => ({
centerDivs: commonstyles(theme).centerDivs,
welcomeDiv: commonstyles(theme).welcomeDiv,
white: commonstyles(theme).white,
logo: {
margin: "auto",
marginTop: "2em",
marginBottom: "2em",
...commonstyles(theme).logo,
},
welcome: {
fontSize: "3em",
fontFamily: "Segoe UI",
color: "white",
marginBottom: "1em",
margin: theme.spacing(2),
textAlign: "center"
},
loginFields: {
...commonstyles(theme).textField,
width: "20em"
},
loginButton: {
...commonstyles(theme).button,
width: "10em"
},
myTextBox: {
...commonstyles(theme).textField,
// "& .MuiInputBase-root": {
// color: 'black',
// borderColor: "green"
// }
}
})
export default styles
the theme file theme.js
import { createTheme } from '#mui/material/styles';
const theme = createTheme({
palette: {
primary: {
main: "#065f92", // blue
},
secondary: {
main: "#F79007", // orange
},
error: {
main: "#d32f2f",
},
warning: {
main: "#ed6c02",
},
info: {
main: "#0288d1",
},
success: {
main: "#2e7d32",
},
},
spacing: (factor) => `${factor}rem`,
});
export default theme;
and finally the App.js file that pulls it all together:
import React from "react";
import { Provider } from 'react-redux'
import theme from './theme';
import { ThemeProvider } from '#mui/material/styles';
import "./App.css";
import store from "./app/store";
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import Login from "./containers/Login";
function App() {
return (
<ThemeProvider theme={theme}>
<Provider store={store}>
<div className="App" style={{maxHeight: "80%"}}>
<Router>
<Routes>
<Route path="/" element={<Login />} />
<Route path="/login" element={<Login />} /></Routes>
</Router>
<div style={{clear:"both", "height": "40px"}}></div>
</div>
</Provider>
</ThemeProvider>
);
}
export default App;
I have been trying to use material ui backdrop element in my code. I am referring to this example: https://mui.com/components/backdrop/#example
As soon as I introduce this code into my component, it fails with error message as below.
Unhandled Runtime Error Error: Invalid hook call. Hooks can only be
called inside of the body of a function component. This could happen
for one of the following reasons:
You might have mismatching versions of React and the renderer (such as React DOM)
You might be breaking the Rules of Hooks
You might have more than one copy of React in the same app See https://reactjs.org/link/invalid-hook-call for tips about how to debug
and fix this problem.
My code is as below.
import React, { useState } from 'react'
import { makeStyles } from '#material-ui/core/styles'
import { Box,Typography } from '#material-ui/core'
import CategoryCard from '../shop-categories/category-card'
import { useSelector } from 'react-redux';
//import NavigateBeforeIcon from '#material-ui/icons/NavigateBefore';
//import ProductsInCategory from '../../../pages/shops/product-in-category';
import {useRouter} from 'next/router'
import Backdrop from '#mui/material/Backdrop'
import CircularProgress from '#mui/material/CircularProgress';
import { withStyles } from '#material-ui/core/styles';
const useStyles = makeStyles({
heading : {
fontFamily: 'Roboto',
fontSize: 14,
fontStyle: 'normal',
fontWeight: 700,
lineHeight: 1,
color: props => props.pColor
}
})
export default function CategoryGrid(props) {
const { categories, shopId, title} = props;
const shopColors = useSelector(state => state.main.currentShop)
const classes = useStyles(shopColors);
const router = useRouter()
const [open, setOpen] = React.useState(false);
const handleClose = () => {
setOpen(false);
};
const gotoSubCategories = (shopId, id,cat_Name) => {
setOpen(true)
router.push(`/shops/shop-sub-categories?shop=${shopId}&category=${id}&categoryName=${cat_Name}`)
}
return (
<>
<div>
<Backdrop sx={{color: '#fff', zIndex: (theme) => theme.zIndex.drawer +1}}
open={open}
onClick={handleClose}
>
<CircularProgress color='inherit' />
</Backdrop>
</div>
<Typography variant="body2" style={{ fontWeight: 'bold' }}
className={classes.heading}>
{title}
</Typography>
<Box display="flex" mb={2} mx={1} flexDirection="column">
<Box display="flex" fontWeight='fontWeightBold' width={1/2} mb={1}>
</Box>
<Box display="flex" flexWrap="wrap">
{
categories.map((category, index) => {
return <CategoryCard key={index}
name={category.name}
onClick={() => gotoSubCategories(shopId, category.id,category.name)}
imageURL={category.url}
shopId={shopId} callParent={props.callParent}
/>
})
}
</Box>
</Box>
</>
)
}
If I remove out this portion, then it works fine.
<div>
<Backdrop sx={{color: '#fff', zIndex: (theme) => theme.zIndex.drawer +1}}
open={open}
onClick={handleClose}
>
<CircularProgress color='inherit' />
</Backdrop>
</div>
How to fix this?
i have a problem i have the famous react error : Uncaught (in promise) Error: Rendered fewer hooks than expected. This may be caused by an accidental early return statement.
But i'm looking for the issue but i didnt find because i think my hooks order are well but its not the case.... Do you have an idea where is the mistake?
Thanks
import { useNavigate, Link, useParams } from 'react-router-dom';
import { useEffect, useState, useCallback, useMemo } from 'react';
import { Formik } from 'formik';
import { useIntl, FormattedMessage } from 'react-intl';
import { useQuery, gql, useMutation } from '#apollo/client';
import { sub } from 'date-fns';
import {
Text,
Button,
TextField,
DateField,
Banner,
Loader,
Icon,
} from '#customer-portal/components';
import { StandardField } from '../../components/FormikFields';
import { useError } from '../../hooks/useError';
import { toISODate, toStartOfDay } from '../../utils/date';
import { useFilteredContracts } from '../../components/ContractsFilter';
export function MeterReadingToEnterForm() {
const [date, setDate] = useState();
const [forceResult, setForceResult] = useState(false);
const [successBanner, setSuccessBanner] = useState(false);
let navigate = useNavigate();
let { serial } = useParams();
const intl = useIntl();
const contract = useFilteredContracts();
const initialValues = {};
const getError = useError(error);
useEffect(() => {
return () => {
navigate('/meter-reading');
};
}, [contract.id]);
const {
loading,
data: queryData,
error,
refetch,
} = useQuery(QUERY, {
notifyOnNetworkStatusChange: true,
fetchPolicy: 'network-only',
variables: {
date: toStartOfDay(Date.now()),
contractId: contract.id,
},
});
const [enterMeterReading, { loading: enterLoading, error: enterError }] =
useMutation(MUTATION_QUERY, {
onCompleted: () => setSuccessBanner(true),
});
useEffect(() => {
refetch({ variables: { date: toStartOfDay(date) } });
}, [date]);
if (error) {
return (
<Banner data-test="BannerNoContracts" type="error" iconName="Alert">
<Text>{getError()}</Text>
</Banner>
);
}
if (enterError) {
return (
<Banner type="error" iconName="Alert">
<Text>{getError()}</Text>
</Banner>
);
}
const formattedValues = (valeur) => {
let array = Object.keys(valeur).map((key) => ({
id: key,
result: valeur[key],
}));
return array;
};
const handle = useCallback((values) => {
mutationQuery({
variables: {
data: {
serial: serial,
results: formattedValues(values),
},
Id: id,
},
refetchQueries: ['newQuery'],
});
});
return (
<div sx={{ display: 'flex', flexDirection: 'column', width: '100%', p: 2 }}>
<div sx={{ display: 'flex', pt: 6, width: '100%' }}>
<Icon
sx={{ display: 'flex', alignItems: 'center', m: 3 }}
color="primary"
name="Counter"
size="large"
/>
<Text weight="bold" sx={{ display: 'flex', alignItems: 'center' }}>
<FormattedMessage
defaultMessage="N° {number}"
values={{
number: `${serial}`,
}}
/>
</Text>
</div>
{successBanner && (
<div sx={{ mb: 4 }}>
<Banner
iconName="SuccessOutline"
type="success"
>
<Text>
<FormattedMessage
defaultMessage="Done"
/>
</Text>
</Banner>
</div>
)}
<div sx={{ display: 'flex', justifyContent: 'flex-start', py: 2 }}>
<DateField
name="datefoield"
value={date || Date.now()}
onChange={(date) => setDate(date)}
required={true}
label={intl.formatMessage({
defaultMessage: 'Date du relevé',
})}
defaultSelected={date}
disabledDays={[
{
before: sub(new Date(toISODate(Date.now())), {
days: 60,
}),
},
{
after: new Date(Date.now()),
},
]}
/>
</div>
{enterLoading && <Loader type="radiance" overlay={true} />}
{loading ? (
<Loader sx={{ mx: [0, 11] }} />
) : (
<Formik
onSubmit={handleEnterMeterReading}
initialValues={initialValues}
>
{({ values, handleSubmit }) => {
const query = useMemo(() => {
return queryDatafind(
(serialNumber) => serialNumber === serial
);
}, [queryData]);
return (
<form
noValidate={true}
onSubmit={handleSubmit}
>
<div
sx={{
display: 'flex',
flexWrap: 'wrap',
width: '100%',
py: 2,
}}
>
{query.map((value) => (
<div sx={{ mr: 4 }}>
<StandardField
as={TextField}
id={id}
label={label}
required
name={id}
size="standard"
type="number"
variant="standard"
/>
</div>
))}
</div>
<div sx={{ display: 'flex', py: 6 }}>
<Link to="/">
<Button
sx={{ my: 4, mr: 4 }}
startIcon="ErrorOutline"
size="standard"
variant="outlined"
>
{intl.formatMessage({
defaultMessage: 'Annuler',
})}
</Button>
</Link>
<Button
sx={{ whiteSpace: 'nowrap', my: 4 }}
startIcon="SuccessOutline"
size="standard"
type="submit"
disabled={loading}
>
{loading
? intl.formatMessage({
defaultMessage: 'loading',
})
: intl.formatMessage({
defaultMessage: 'Validate',
})}
</Button>
</div>
</form>
);
}}
</Formik>
)}
</div>
);
}
-- GRAPHQL REQUEST---
The issue is that you call useCallback after some if conditionals that may return prematurely. You could either remove the useCallback call (just set handle to a new function closure each rendering), or move the useCallback call to above the if ... returns.
I need to reuse some basic components to avoid code duplication but some examples are not serving me
the problem is here const [cpf, setCpf] = useState (data.cpf);
this data.cpf is getting information from an api but not filling the const as initial value
HOC
const onHandleChange = onChange => event => {
const { value } = event.target;
onChange(value);
};
Field:
export const Field = ({ component: Component, onChange, ...props }) => (
<div>
<Component onChange={onHandleChange(onChange)} {...props} />
</div>
);
InputField:
import React, { useState } from "react";
//Componentes
import { InputGroup, Button, FormControl } from "react-bootstrap";
import EditIcon from "#material-ui/icons/Edit";
import CheckIcon from "#material-ui/icons/Check";
export function InputField({ value, name, type, onChange }) {
const [teste, setTeste] = useState(true);
function click(e) {
setTeste(!teste);
}
return (
<div>
<InputGroup>
<InputGroup.Prepend>
<InputGroup.Text
style={{
backgroundColor: "white",
borderStyle: "hidden"
}}
>
{name}
</InputGroup.Text>
</InputGroup.Prepend>
<FormControl
style={{ borderStyle: "hidden" }}
disabled={teste}
value={value}
type={type}
onChange={onChange}
/>
<InputGroup.Append>
{teste === true ? (
<Button variant="white" onClick={click}>
<EditIcon />
</Button>
) : (
<Button variant="white" onClick={click}>
<CheckIcon />
</Button>
)}
</InputGroup.Append>
</InputGroup>
</div>
);
}
ViewClient:
import React, { useState } from "react";
//UI-Components
import { Modal, Card, ListGroup, Button } from "react-bootstrap";
import Account from "#material-ui/icons/AccountCircle";
//Reecriação de componentes
import { Field } from "./Components/Input";
import { InputField } from "./Components/InputFieldComponent";
//Redux
import { connect } from "react-redux";
import { bindActionCreators } from "redux";
//Creators
import { Creators as ClientActions } from "../../../redux/store/ducks/cliente";
import { Creators as MapActions } from "../../../redux/store/ducks/map";
import { Creators as CaboActions } from "../../../redux/store/ducks/cabo";
function ViewClient(props) {
const { viewClient } = props.redux.client;
const { data } = viewClient; //Informações do usuario.
const [cpf, setCpf] = useState(data.cpf);
const [name, setName] = useState(data.name);
function handleHideModal() {
const { hideClientViewModal } = props;
hideClientViewModal();
}
function changeActive() {
console.log(cpf);
}
return (
<>
<Modal size="lg" show={viewClient.visible} onHide={handleHideModal}>
<Modal.Header
style={{
justifyContent: "center",
display: "flex",
flexDirection: "column",
alignItems: "center",
backgroundColor: "#F7D358"
}}
>
<h6 style={{ fontSize: "10px" }}>{data.created_at}</h6>
<Account
style={{
display: "block",
fontSize: "50px",
marginTop: "10px",
marginBottom: "10px"
}}
/>
<Modal.Title style={{ color: "#585858" }}>{data.name}</Modal.Title>
</Modal.Header>
<Modal.Body style={{ backgroundColor: "#FFFFFF" }}>
<Card style={{ width: "100%" }}>
<Card.Header
style={{ backgroundColor: "#D8D8D8", textAlign: "center" }}
>
Informações do cliente
</Card.Header>
<ListGroup variant="flush">
<ListGroup.Item>
<Field
component={InputField}
name={cpf}
type={"text"}
value={cpf}
onChange={setCpf}
/>
</ListGroup.Item>
<ListGroup.Item>
<Field
component={InputField}
name={"Nome"}
type={"text"}
value={name}
onChange={setName}
/>
</ListGroup.Item>
</ListGroup>
</Card>
</Modal.Body>
<Modal.Footer>
<Button variant="info">Salvar Alterações</Button>
{data.status === null ? (
<Button variant="primary" onClick={changeActive}>
Ativar cliente
</Button>
) : (
<Button variant="danger" onClick={changeActive}>
Desativar
</Button>
)}
<Button variant="danger">Excluir</Button>
<Button variant="secondary">Adicionar Cabo</Button>
<Button variant="secondary">Fechar</Button>
</Modal.Footer>
</Modal>
</>
);
}
const mapStateToProps = state => ({
redux: state
});
//Ações
const mapDispatchToProps = dispatch =>
bindActionCreators(
{ ...ClientActions, ...MapActions, ...CaboActions },
dispatch
);
export default connect(
mapStateToProps,
mapDispatchToProps
)(ViewClient);