I am trying to create a submit button in React.
This is the code I have to handle the onClick event:
const handleEditProfileSubmit = () => {
const authContext = useContext(AuthContext);
if (newUserEntityDetails.username !== "") {
newUserEntityDetails.uid = authContext.user.uid;
axios.put(ENDPOINT + authContext.user.uid, newUserEntityDetails).then((res: any) => console.log(res)).catch((e: any) => console.log(e));
}
};
And this is the code I have for the button:
<Button onClick={handleEditProfileSubmit}>Submit</Button>
Which uses the Button material-ui component.
I have tried using onClick={() => {handleEditProfileSubmit}}, however this results in the submit button doing nothing.
I have also tried turning the handleEditProfileSubmit constant into a function and doing onClick={handleEditProfileSubmit()}, however this gets the same error.
I am unsure of what I am doing wrong. The axios API calls work fine in other parts of my code so I think it's not about the API call.
EDIT: This is the code for the entire component.
import React, {useContext} from "react";
import Navbar from "../components/navbar";
import { Redirect } from "react-router-dom";
import { AuthContext } from "../Auth";
import { Container } from "#material-ui/core";
import { Grid } from "#material-ui/core";
import { Paper, Card, CardContent } from "#material-ui/core";
import { MenuList, MenuItem} from "#material-ui/core";
import { Typography } from "#material-ui/core";
import { TextField } from "#material-ui/core";
import { Button } from "#material-ui/core";
const axios = require('axios');
const ENDPOINT = 'http://localhost:3000/api/user/';
var newUserEntityDetails = {
uid: "",
username: ""
}
const authContext = useContext(AuthContext);
const handleEditProfileSubmit = () => {
if (newUserEntityDetails.username !== "") {
newUserEntityDetails.uid = authContext.user.uid;
var a = axios.put(ENDPOINT + authContext.user.uid, newUserEntityDetails).then((res: any) => console.log(res)).catch((e: any) => console.log(e));
console.log(a);
}
};
const Settings = () => {
// If there is no user in the session
if (authContext.user == null) {
return(<Redirect to={"/login"} />);
} else {
return(
<>
<Navbar />
<Container maxWidth="lg">
<Grid container spacing={3} direction="row" style={{ minHeight: "90vh" }}>
<Grid item xs={3}>
<Paper>
<MenuList>
<MenuItem>
<Typography variant="body1">Profile</Typography>
</MenuItem>
<MenuItem>
<Typography variant="body1">Account Settings</Typography>
</MenuItem>
<MenuItem>
<Typography variant="body1">Delete Account</Typography>
</MenuItem>
</MenuList>
</Paper>
</Grid>
<Grid item xs={6}>
<Card>
<CardContent>
<Typography variant="subtitle2" gutterBottom>
Change Username
</Typography>
<TextField
id="outlined-helperText"
label="Change Username"
helperText="Username must be unique"
variant="outlined"
onChange = { input => {
newUserEntityDetails.username = input.target.value;
}}
/>
<Button onClick={() => {handleEditProfileSubmit}}>Submit</Button>
</CardContent>
</Card>
</Grid>
</Grid>
</Container>
</>
);
}
};
export default Settings;
P.S I have been told by my project manager to avoid using classes for components hence I am trying to write functional components
You're calling Hooks inside function, that's what causing the problem.
As per React docs: Don’t call Hooks inside loops, conditions, or nested functions. Instead, always use Hooks at the top level of your React function. By following this rule, you ensure that Hooks are called in the same order each time a component renders
Declare hook top of your function, you should be fine!!.
...all imports
const Settings = () => {
const authContext = useContext(AuthContext); //This is what you need to do.
const handleEditProfileSubmit = () => {
if (newUserEntityDetails.username !== "") {
newUserEntityDetails.uid = authContext.user.uid;
var a = axios.put(ENDPOINT + authContext.user.uid, newUserEntityDetails).then((res: any) => console.log(res)).catch((e: any) => console.log(e));
console.log(a);
}
};
....
You need to have const authContext = useContext(AuthContext); outside the handleEditProfileSubmit.
Related
I am developing a microsoft Teams App. I did a React JS project before starting the Teams app (which is pretty much the same thing) and I had not issues everything was working well. My problem is whenever I try to call a hook in a useEffect function it's not working in my teams App but it was in my other React.js project.
The console tells me: "Hooks can only be called inside of the body of a function component".
I am using react 16.14 btw.
App.js file
import React, { useEffect, useState } from "react";
import { Container, AppBar, Typography, Grow, Grid } from '#material-ui/core';
import { useDispatch } from 'react-redux';
import logo_digiposte from './images/logo_digiposte.png'
import { getContractors } from "./actions/contractors";
import useStyle from './styles'
import Form from './components/Form/Form';
import Contractors from "./components/Contractors/Contractors";
const App = () => {
const [currentId, setCurrentId] = useState(0);
const classes = useStyle();
const dispatch = useDispatch;
/*useEffect(() => {
dispatch(getContractors());
},[currentId, dispatch])*/
useEffect(() => {
dispatch(getContractors());
}, [dispatch]);
return (
<Container>
<AppBar className={classes.appBar} position="static" color="inherit">
<Typography variant="h2" className={classes.heading} align='center'>Digiposte</Typography>
<img src={logo_digiposte} className={classes.image} alt="digiposte" height="60"/>
</AppBar>
<Grow in>
<Container>
<Grid container className={classes.mainContainer} justifyContent="space-between" alignItems="stretch" spacing={3}>
<Grid item xs={12} sm={7}>
<Contractors />
</Grid>
<Grid item xs={12} sm={4}>
<Form />
</Grid>
</Grid>
</Container>
</Grow>
</Container>
)
}
export default App;
folder which contains the getContractor function
import { FETCH_ALL, CREATE, UPDATE, DELETE } from "../constants/actionTypes";
import * as api from '../api/index.jsx';
export const getContractors = () => async (dispatch) => {
try {
const { data } = await api.fetchContractors();
dispatch({ type: FETCH_ALL, payload: data });
} catch (error) {
console.log(error);
}
}
api file:
import axios from 'axios'
const url = "http://localhost:5003/contractors";
export const fetchContractors = () => axios.get(url);
Convert you useEffect method as:
useEffect(() => {
dispatch(getContractors());
}, []);
No need to pass dispatch in your useEffect method. If you are going to call getContractors action only once.
and there is typo I guess. const dispatch = useDispatch()
Convert const dispatch = useDispatch; to const dispatch = useDispatch();
I am using nextjs and mui. I am facing a warning when rendering pages. Here is my code. Please help to solve the issue!!!
import "../styles/globals.scss";
import { AppProps } from "next/app";
import useGetAuthentication from "../hooks/useGetAuthentication";
import States from "../interfaces/states";
import STATUS from "../constants/status";
import { CssBaseline } from "#mui/material";
import { ThemeProvider } from "#mui/material/styles";
import theme from "../styles/theme";
import Layout from "../layouts/Layout";
import Head from "next/head";
import * as React from "react";
import Login from "../components/Login";
import { Box } from "#mui/material";
interface MyAppProps extends AppProps {
emotionCache?: EmotionCache;
}
const checkStatusCode = (statusCode: number): boolean => {
return statusCode === STATUS.NOT_FOUND || statusCode === STATUS.INTERNAL_SERVER_ERROR;
};
function App({ Component, pageProps }: AppProps) {
const { states } = pageProps;
const { statusCode } = pageProps;
const { isAuthorized } = useGetAuthentication(states as States);
console.log("App -> Component", Component);
console.log("App -> pageProps", pageProps);
console.log("App -> states", states);
console.log("App -> statusCode", statusCode);
const drawerWidth: number = 240;
if (!checkStatusCode(statusCode) && !isAuthorized)
return (
<Box
component="main"
sx={{
flexGrow: 1,
p: 3,
width: { lg: "230px", sm: `calc(100% - ${drawerWidth}px)` }
}}
>
<Login />
</Box>
);
return (
<>
<Head>
<meta name="viewport" content="initial-scale=1, width=device-width" />
</Head>
<ThemeProvider theme={theme}>
<CssBaseline />
<Layout>
<Component {...pageProps} />
</Layout>
</ThemeProvider>
</>
);
}
export default App;
Login component is below
import React, { useEffect } from "react";
import authenticationStore from "../../stores/persistences/authenticationStore";
import TestHttp from "../../httpModules/testHttp";
import STATUS from "../../constants/status";
import RequestSignIn from "../../interfaces/test/requestSignIn";
import styles from "./login.module.scss";
import { Alert, FormControlLabel, Grid, Paper, TextField, Typography, Stack, Button, Checkbox } from "#mui/material";
import Image from "next/image";
import useLoginInputs from "../../hooks/useLoginInputs";
import LocalStorageHandler from "../../utils/localStorageHandler";
import RememberId from "../../interfaces/rememberId";
import ERROR_MESSAGE from "../../constants/errorMessage";
import LOGIN_INFO from "../../constants/loginInfo";
const Login: React.FC = () => {
const localStorageHandler = new LocalStorageHandler<RememberId>();
const authorize = authenticationStore((state) => state.authorize);
const testHttp = new TestHttp();
const { inputs, setInputs, isRememberChecked, isError, setIsError, isIdEmpty, setIsIdEmpty, isPasswordEmpty, setIsPasswordEmpty, inputsHandler, checkboxHandler } = useLoginInputs([
"id",
"password"
]);
const { id, password } = inputs;
const onLoginHandler = async (): Promise<void> => {
if (isIdEmpty) {
return setIsError(ERROR_MESSAGE.ID_EMPTY);
}
if (isPasswordEmpty) {
return setIsError(ERROR_MESSAGE.PASSWORD_EMPTY);
}
const signInInfo: RequestSignIn = { id, password };
const { statusCode, jsonResult } = await testHttp.signIn(false, signInInfo);
/*
* 인증 실패 (아이디, 비밀번호 일치 하지 않는 경우 등) 발생 시 코드 작성
*/
if (statusCode !== STATUS.OK) {
setIsError(true);
return;
}
const { userInfo, tokenInfo } = jsonResult;
authorize(statusCode, userInfo, tokenInfo);
if (!isRememberChecked) return localStorageHandler.removeLocalStorageData(LOGIN_INFO.REMEMBER_ID);
localStorageHandler.setLocalStorageData("rememberId", {
id
});
};
useEffect(() => {
console.log("하이");
setIsIdEmpty(id.length <= 0);
setIsPasswordEmpty(password.length <= 0);
setIsError(null);
}, [id, password]);
console.log("id", id);
console.log("password", password);
return (
<Grid>
<Paper elevation={10} className={styles.container}>
<Grid align={"center"}>
<div className={styles.logo}>
<Image src={"/images/logo.svg"} width={"200px"} height={"80px"} alt={"logo"} />
<Typography variant={"h6"}>관리자</Typography>
</div>
</Grid>
<Stack spacing={1} justifyContent={"center"} alignItems={"center"} className={styles["login-container"]}>
<TextField name={"id"} placeholder={"아이디를 입력해주세요."} required value={id} type={"text"} className={styles["login-input"]} onChange={inputsHandler} />
<TextField name={"password"} placeholder={"비밀번호를 입력하세요."} required value={password} type={"password"} className={styles["login-input"]} onChange={inputsHandler} />
</Stack>
<Stack>
<FormControlLabel control={<Checkbox checked={isRememberChecked} />} label={"아이디 저장"} className={styles.checkbox} onChange={checkboxHandler} />
</Stack>
<Stack justifyContent={"center"} alignItems={"center"}>
<Button type={"submit"} color={"primary"} variant={"contained"} className={styles["login-button"]} size={"large"} onClick={onLoginHandler}>
로그인
</Button>
</Stack>
<Stack justifyContent={"center"} alignItems={"center"} className={styles["error-message"]}>
<div>
{isError && (
<Alert severity={"error"}>
<strong>{isError}</strong>
</Alert>
)}
</div>
</Stack>
</Paper>
</Grid>
);
};
export default Login;
the warning is
Warning: Expected server HTML to contain a matching <div> in <div>.
at div
at eval (webpack-internal:///./node_modules/#emotion/react/dist/emotion-element-cbed451f.browser.esm.js:57:66)
at Box (webpack-internal:///./node_modules/#mui/system/esm/createBox.js:36:72)
at Layout (webpack-internal:///./layouts/Layout/index.tsx:16:26)
at InnerThemeProvider (webpack-internal:///./node_modules/#mui/system/esm/ThemeProvider/ThemeProvider.js:21:70)
at ThemeProvider (webpack-internal:///./node_modules/#mui/private-theming/ThemeProvider/ThemeProvider.js:47:5)
at ThemeProvider (webpack-internal:///./node_modules/#mui/system/esm/ThemeProvider/ThemeProvider.js:41:5)
at App (webpack-internal:///./pages/_app.tsx:61:27)
at ErrorBoundary (webpack-internal:///./node_modules/next/dist/compiled/#next/react-dev-overlay/client.js:8:20638)
at ReactDevOverlay (webpack-internal:///./node_modules/next/dist/compiled/#next/react-dev-overlay/client.js:8:23179)
at Container (webpack-internal:///./node_modules/next/dist/client/index.js:323:9)
at AppContainer (webpack-internal:///./node_modules/next/dist/client/index.js:820:26)
at Root (webpack-internal:///./node_modules/next/dist/client/index.js:944:27)
window.console.error # next-dev.js?3515:25
Case 1
Most likely a Server<>Client out of date issue.
Fix
If you are using the development server > Restart it.
If you are getting this production > Rebuild + Restart.
Case 2
The components you are using render differently (due to bad coding) on the Server (SSR) vs Client (CSR). This can be silenced by adding suppressHydrationWarning={true} to the offending component.
Case 3
Another case I've seen is that someone has set dangerouslySetInnerHtml with invalid HTML. The fix is to correct the HTML OR silence it as we did in case 2.
In my case I tried to run localStorage getItem method outside useEffect hook.
So probably you can place your async code inside that hook.
In progress value update from use hook can also causing this problem. To prevent it, just copy the variable value from hook to state from useEffect hook. Here is the example
import { useAccount } from "wagmi";
...
const { address, isConnected, isConnecting } = useAccount();
// the value of these variable above are changed dynamically
const [connectionStat, setConnectionStat] = useState();
const [addr, setAddr] = useState();
// copy the value to state here
useEffect(() => {
setConnectionStat(isConnected);
setAddr(address);
}, [address, isConnected])
// then now we can display the value properly
return (
<div>
<p>Connection status : {connectionStat}</p>
<p>Connected to : {addr}</p>
....
</div>
);
Explanation: passing value directly from hook to JSX/view may causing inconsistent value inside JSX/view so the the page render can not be consistent as well and there will be different value between value in SSR and client.
Hope it helps.
I'm trying to learn about APIs and trying to code a REACT app to go along with it. I am sure the issue is a minor one, but I can't seem to crack it.
The relevant code is pasted below, the API is fetched in index.js.
The contents of the API is printed to the console without issue but I can not seem to get it right when going through my list and event details.
I am new to coding so I would appreciate any feedback given.
App.js
import React, { useState, useEffect } from "react";
import { CssBaseline, Grid } from "#material-ui/core";
import { getEventsData } from "./api";
import Header from "./components/Header/Header";
import List from "./components/List/List";
import EventDetails from "./components/EventDetails/EventDetails";
const App = () => {
const [events, setEvents] = useState([]);
useEffect(() => {
getEventsData()
.then((data) => {
console.log(data);
console.log(Array.isArray(data))
setEvents(data);
})
}, []);
return (
<>
<CssBaseline />
<Header />
<List EventDetails={EventDetails} />
</>
)
}
export default App;
index.js
import axios from "axios";
const URL = 'https://api-football-v1.p.rapidapi.com/v3/fixtures'
const options = {
params: {date: '2022-02-12', league: '39', season: '2021'},
headers: {
'x-rapidapi-host': 'api-football-v1.p.rapidapi.com',
'x-rapidapi-key': xxxXXXxxxXXXxxx'
}
};
export const getEventsData = async () => {
try {
const { data } = await axios.get(URL, options);
// Kan det ha något med options att göra? https://stackoverflow.com/questions/68367352/multiple-url-variable-async-await-axios
return data;
} catch (error) {
}
};
List.jsx
import React, { useState } from "react";
import { CircularProgress, Grid, Typography, InputLabel, MenuItem, FormControl, Select, ButtonGroup, Button } from "#material-ui/core";
import EventDetails from "../EventDetails/EventDetails"
import useStyles from "./styles"
const List = ({ events }) => {
const classes = useStyles();
const [type, setType] = useState("premierleague");
return (
<div className={classes.container}>
<FormControl className={classes.formControl}>
<InputLabel>Sport</InputLabel>
<Select value={type} onChange={(e) => setType(e.target.value)}>
<MenuItem value="premierleague">Premier League</MenuItem>
<MenuItem value="formula1">Formula 1</MenuItem>
</Select>
{/*<ButtonGroup value={type} onClick={(e) => setType(e.target.value)}>
<Button value="premierleague">Premier League</Button>
<Button value="formula1">Formula 1</Button>
</ButtonGroup>*/}
</FormControl>
<Grid container spacing={3} className={classes.list}>
{events?.map((event, i) => (
<Grid item key={i} xs={12}>
<EventDetails event={event} />
</Grid>
))}
</Grid>
</div>
)
}
export default List;
EventDetails.jsx
import React from "react";
const EventDetails = ({ event }) => {
console.log(event)
return (
<h3>{event.league}</h3>
)
}
export default EventDetails;
You're not sending the events to List component.
Try changing in App.js:
return (
<>
<CssBaseline />
<Header />
<List events={events} />
</>
)
https://codesandbox.io/s/large-data-array-with-redux-toolkit-forked-7tp63?file=/demo.tsx
In the given codesandbox example I am using react typescript with redux-toolkit
in codesandbox example. I am trying to bind countries with checkbox and textbox.
When I check on checkboxes it looks slow and textbox edit also feels very slow.
some time it breaks the page.
I am not sure what I am doing wrong.
You should make CountryItem it's own component and make it a pure component:
import React, { FC, useEffect } from "react";
import { createStyles, Theme, makeStyles } from "#material-ui/core/styles";
import List from "#material-ui/core/List";
import ListItem from "#material-ui/core/ListItem";
import ListItemText from "#material-ui/core/ListItemText";
import { countryList } from "./dummyData";
import { Checkbox, Grid, TextField } from "#material-ui/core";
import { useAppDispatch, useAppSelector } from "./store/hooks";
import {
setCountries,
setCountrySelected,
setCountryValue
} from "./store/slice/geography-slice";
import { Country } from "./interface/country.modal";
const useStyles = makeStyles((theme: Theme) =>
createStyles({
root: {
width: "100%",
backgroundColor: theme.palette.background.paper
}
})
);
const CountryItem: FC<{ country: Country }> = ({ country }) => {
console.log('render item',country.name)
const dispatch = useAppDispatch();
const handleCheckboxChange = (
event: React.ChangeEvent<HTMLInputElement>,
country: Country
) => {
const selectedCountry = { ...country, isSelected: event.target.checked };
dispatch(setCountrySelected(selectedCountry));
};
const handleTextChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const selectedCountry = { ...country, value: event.target.value };
dispatch(setCountryValue(selectedCountry));
};
return (
<ListItem button>
<Checkbox
checked={country?.isSelected ?? false}
onChange={(event) => {
handleCheckboxChange(event, country);
}}
/>
<ListItemText primary={country.name} />
<TextField value={country?.value ?? ""} onChange={handleTextChange} />
</ListItem>
);
};
const PureCountryItem = React.memo(CountryItem)
export default function SimpleList() {
const classes = useStyles();
const dispatch = useAppDispatch();
const { countries } = useAppSelector((state) => state.geography);
useEffect(() => {
dispatch(setCountries(countryList));
}, []);
return (
<div className={classes.root}>
<Grid container>
<Grid item xs={6}>
<List component="nav" aria-label="secondary mailbox folders">
{countries.map((country, index) => (
<PureCountryItem country={country} key={`CountryItem__${index}`} />
))}
</List>
</Grid>
</Grid>
</div>
);
}
This might look like a similar question but I'm unable to get the answer of it on Stackoverflow.
I have two components - A.js and B.js
A.js
(The below code is inside return() )
<Link to={{
pathname: `${simulationId}/edit/${ruleName}`,
search: createButtonQuery,
previewFlag = true,
}}>
<IconButton color="primary" size="small">
<PageviewOutlinedIcon/>
</IconButton>
</Link>
B.js
(The below code is inside return() )
<DialogTitle>{name ? 'Print true' : 'Print false'}</DialogTitle>
Issue:
I want to test for previewFlag inside the B.js. I want to check when the previewFlag is true and name exists, 'Print true' should be returned by the <DialogTitle>
I don't know how to use previewFlag inside B.js though.
Kindly note that <PageViewOutlineIcon> is part of material-ui here.
The both components aren't being imported by each other.
To use a context, there are a few things you have to do:
1: Create a context file (I usually put mine in a contexts folder)
import { createContext, useContext, useEffect, useState } from 'react'
import { auth } from '../firebase'
const AuthContext = createContext()
export function useAuth() {
return useContext(AuthContext)
}
export function AuthProvider({ children }) {
const [currentUser, setCurrentUser] = useState()
const [loading, setLoading] = useState(true)
function login(email, password) {
return auth.signInWithEmailAndPassword(email, password)
}
function signOut() {
return auth.signOut();
}
function signUp(email, password) {
return auth.createUserWithEmailAndPassword(email, password)
}
function getUser() {
return auth.currentUser
}
function isAdmin() {
return auth.currentUser.getIdTokenResult()
.then((idTokenResult) => {
if (!!idTokenResult.claims.admin) {
return true
} else {
return false
}
})
}
function isEditor() {
}
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged(user => {
setCurrentUser(user)
setLoading(false)
})
return unsubscribe
}, [])
const value = {
currentUser,
getUser,
login,
signOut,
signUp
}
return (
<AuthContext.Provider value={value}>
{ !loading && children }
</AuthContext.Provider>
)
}
This is the file that can hold whatever you want to check for
Then, you need to put it in your App/_app (depending on how you're using react) as in this case:
import '../styles/globals.scss'
import { motion, AnimatePresence } from 'framer-motion'
import { useRouter } from 'next/router'
import Header from '../components/Header'
import Footer from '../components/Footer'
import { AuthProvider } from '../contexts/AuthContext'
import { CartProvider } from '../contexts/CartContext'
import { ThemeProvider } from '#material-ui/core'
import theme from '../styles/theme'
export default function App({ Component, pageProps }) {
const router = useRouter()
return(
<AnimatePresence exitBeforeEnter>
<CartProvider>
<AuthProvider>
<ThemeProvider theme={theme}>
<Header />
<motion.div key={router.pathname} className="main">
<Component { ...pageProps } />
<Footer />
</motion.div>
</ThemeProvider>
</AuthProvider>
</CartProvider>
</AnimatePresence>
)
}
Then, in any component that you want access to those contexts, you can invoke it to use the values (or helper functions):
import { Alert, Grid, TextField, Button, makeStyles, Typography } from '#material-ui/core'
import { useAuth } from '../contexts/AuthContext'
import { useState } from 'react'
const theme = makeStyles({
form: {
width: '100vw',
maxWidth: '400px',
margin: 'auto',
padding: '1rem',
'& > div': {
paddingBottom: '1rem'
}
}
})
export default function LoginForm() {
const { login } = useAuth();
const styles = theme()
const [state, setState] = useState({
email: "",
password: ""
})
const [error, setError] = useState()
const { googleLogin } = useAuth()
function handleForm(e) {
setState({
...state,
[e.target.name]: e.target.value
})
}
async function handleLogin() {
await login(state.email, state.password)
.catch(err => {
console.log(err)
setError(JSON.stringify(err))
})
}
return(
<Grid container className={styles.form} direction="column" alignContent="stretch" justify="center">
<Grid item>
<Typography variant="h3">Login</Typography>
</Grid>
<Grid item>
{error && <Alert severity="error" variant="filled" >{error}</Alert>}
<TextField fullWidth name="email" label="Email" variant="outlined" onChange={handleForm}/>
</Grid>
<Grid item>
<TextField fullWidth name="password" type="password" label="Password" variant="outlined" onChange={handleForm} />
</Grid>
<Grid item>
<Button variant="contained" color="primary"fullWidth onClick={handleLogin}>Log In</Button>
</Grid>
</Grid>
)
}
notice how I import the useAuth instance (which is just a context I've named that. You can name it whatever) in my loginForm component, and then I can destructure out the values that I've exposed: const { login } = useAuth() Something like this: const { value, helperFunction } = useAuth() - assuming you're exporting a value and a function helperFunction from your context