I was trying to build simple react-redux app using reducers. But every time I open the website it is popping up this error and I couldn't open the console. I tried to clean cookies and caches but no help.
Whenever I change <Posts /> and <Form /> to simple <h1> tags it works perfectly fine, but I can't find the bug.
My code in
index.js:
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import { reducers } from './reducers/index.js';
import App from './App';
const store = createStore(reducers, compose(applyMiddleware(thunk)));
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root'),
);
App.js
import React,{ useEffect, useState } from 'react';
import { Container, AppBar, Typography, Grid, Grow } from '#material-ui/core';
import { useDispatch } from 'react-redux';
import Posts from '../src/components/Posts';
import Form from './components/form';
import { getPosts } from './actions/action';
function App() {
const [currentId, setCurrentId] = useState();
const dispatch = useDispatch();
useEffect(() =>{
dispatch(getPosts());
},[dispatch, currentId]);
return (
<div>
<Container maxWidth='lg'>
<AppBar position='static' color='inherit'>
<Typography variant='h2' align='center' >SimplePRATICE</Typography>
</AppBar>
<Grow in>
<Container>
<Grid container justify='space-between' alignItems='stretch' spacing={3}>
<Grid item xs={12} sm={4}>
<Form currentId={currentId} setCurrentId={setCurrentId} />
</Grid>
</Grid>
</Container>
</Grow>
</Container>
</div>
);
}
export default App;
form.js
import React, { useEffect, useState } from 'react';
import { Paper, Button, TextField, Typography } from '#material-ui/core';
import { useSelector, useDispatch } from 'react-redux';
import { createPost, updatePost } from '../actions/action.js';
function Form({ currentId, setCurrentId }) {
const [postData, setpostData] = useState({ name:'', message:'' });
const post = useSelector((state) => (currentId ? state.posts.find((message) => message._id === currentId ) :null ));
const dispatch = useDispatch();
useEffect(() => {
if (post) setpostData(post);
}, [post]);
const clear = () =>{
setCurrentId(0);
setpostData({ name: '', message:''});
};
const handleSubmit = async (e) => {
e.preventDefault();
if (currentId === 0){
dispatch(createPost(postData));
}else{
dispatch(updatePost(currentId, postData));
}
clear();
};
return (
<Paper>
<Form onSubmit={handleSubmit}>
<Typography>Creating Post</Typography>
<TextField name="name" variant="outlined" label="Name" fullWidth value={postData.name} onChange={(e) => setpostData({ ...postData, name: e.target.value })} />
<TextField name="message" variant="outlined" label="Message" fullWidth multiline rows={4} value={postData.message} onChange={(e) => setpostData({ ...postData, message: e.target.value })} />
<Button varient='contained' color='primary' size='large' type='submit' fullWidth>Submit</Button>
</Form>
</Paper>
)
}
export default Form
Posts.js
import React from 'react';
import Post from './post.js';
import { Grid } from '#material-ui/core';
import { useSelector } from 'react-redux';
function Posts({ setCurrentId }) {
const posts = useSelector((state) => state.posts);
return (
<Grid container alignItems='stretch' spacing={3}>
{posts.map((post) => (
<Grid key={post._id} item xs={12} sm={6} md={6}>
<Post post={post} setCurrentId={setCurrentId} />
</Grid>
))}
</Grid>
)
}
export default Posts
Post.js
import React from 'react';
import { Card, CardActions, CardContent, Button, Typography } from '#material-ui/core/';
import DeleteIcon from '#material-ui/icons/Delete';
import MoreHorizIcon from '#material-ui/icons/MoreHoriz';
import { useDispatch } from 'react-redux';
import { deletePost } from '../actions/action.js';
function Post({ post, setCurrentId }) {
const dispatch = useDispatch();
return (
<Card>
<div>
<Typography varient='h6'>{post.name}</Typography>
</div>
<di>
<Button style={{ color:'white' }} size='small' onClick={()=> setCurrentId(post._id)}><MoreHorizIcon fontSize='default' /></Button>
</di>
<CardContent>
<Typography vaarient='body2' color='textSecondary' component='p'>{post.message}</Typography>
</CardContent>
<CardActions>
<Button size='small' color='primary' onClick={()=> dispatch(deletePost(post._id))} ><DeleteIcon fontSize='small'>Delete</DeleteIcon></Button>
</CardActions>
</Card>
)
}
export default Post
import axios from 'axios';
const url = 'http://localhost:5000/posts';
export const fetchPosts = () => axios.get(url);
export const createPost = (newPost) => axios.post(url, newPost);
export const updatePost = (id, updatedPost) => axios.patch(`${url}/${id}`, updatedPost);
export const deletePost = (id) => axios.delete(`${url}/${id}`);
Your Form component renders itself:
return (
<Paper>
<Form onSubmit={handleSubmit}>
<Typography>Creating Post</Typography>
<TextField name="name" variant="outlined" label="Name" fullWidth value={postData.name} onChange={(e) => setpostData({ ...postData, name: e.target.value })} />
<TextField name="message" variant="outlined" label="Message" fullWidth multiline rows={4} value={postData.message} onChange={(e) => setpostData({ ...postData, message: e.target.value })} />
<Button varient='contained' color='primary' size='large' type='submit' fullWidth>Submit</Button>
</Form>
</Paper>
)
I think you meant <form> which is not a react component.
return (
<Paper>
<form onSubmit={handleSubmit}>
<Typography>Creating Post</Typography>
<TextField name="name" variant="outlined" label="Name" fullWidth value={postData.name} onChange={(e) => setpostData({ ...postData, name: e.target.value })} />
<TextField name="message" variant="outlined" label="Message" fullWidth multiline rows={4} value={postData.message} onChange={(e) => setpostData({ ...postData, message: e.target.value })} />
<Button varient='contained' color='primary' size='large' type='submit' fullWidth>Submit</Button>
</form>
</Paper>
)
You must have selected a wrong element in your html which is not going on well with the code.
Hence please check all the elements properly.
If it is a functional component , check it again.
I had the same issue and it got resolved.
Related
i'm trying to register user on some website and save its data in local storage on every page i need to use context for that, so when i tried to do this without context it all worked and i was saving data in local storage but now i do it with context and it broke and in console.log when i type something in input it shows undefined and useState doesn't work and add items to the object
below code is app.js
import "./App.css";
import * as React from "react";
import { BrowserRouter as Router, Route, Routes } from "react-router-dom";
import { BrowserRouter } from "react-router-dom";
import Question from "./Question";
import Form from "./Form";
import Language from "./Language";
import Photo from "./Photo";
import Fin from "./Fin";
import CustomizedSteppers from "./Step";
import { createContext, useMemo, useState } from "react";
export const StepperContext = createContext({ step: 0, setStep: () => {} });
export const PagesContext = createContext({ info: {}, setInfo: () => {} });
function App() {
const [info, setInfo] = useState({});
const val = useMemo(() => ({ info, setInfo }), [info]);
const [step, setStep] = useState(0);
const value = useMemo(() => ({ step, setStep }), [step]);
return (
<div className="App">
<StepperContext.Provider value={val}>
<StepperContext.Provider value={value}>
<CustomizedSteppers>
<BrowserRouter>
<Routes>
<Route path="/" index element={<Form />} />
<Route path="Question" index element={<Question />} />
<Route path="Language" index element={<Language />} />
<Route path="Photo" index element={<Photo />} />
<Route path="Finish" index element={<Fin />} />
</Routes>
</BrowserRouter>
</CustomizedSteppers>
</StepperContext.Provider>
</StepperContext.Provider>
</div>
);
}
export default App;
this is the form :
import * as React from "react";
import Box from "#mui/material/Box";
import TextField from "#mui/material/TextField";
import { useState, useContext, createContext,useEffect} from "react";
import MaterialUIPickers from "./Datepicker";
import Stack from "#mui/material/Stack";
import Button from "#mui/material/Button";
import CustomizedSteppers from "./Step";
import { PagesContext, StepperContext } from "./App";
import { useNavigate } from "react-router-dom";
function Form() {
const { step, setStep } = useContext(StepperContext);
const navigate = useNavigate();
const { info, setInfo } = useContext(PagesContext);
console.log(info)
console.log(step)
useEffect(() => {
console.log(info)
}, [info])
const next = () => {
localStorage.setItem("info", JSON.stringify(info));
setStep(1);
navigate("Question");
};
return (
<div className="App">
{/* <CustomizedSteppers /> */}
<Box
component="form"
sx={{
"& > :not(style)": { m: 1, width: "25ch" },
}}
noValidate
autoComplete="off"
/>
<div className="input">
<TextField
color="secondary"
id="outlined-basic"
label="First name"
variant="outlined"
value={info ? info.firstName : ""}
onChange={(e) => {
console.log(info.firstName)
setInfo((prev) => ({ ...prev, firstName: e.target.value }));
}}
/>
<TextField
color="secondary"
id="outlined-basic"
label="Last name"
variant="outlined"
value={info ? info.lastName : ""}
onChange={(e) => {
setInfo((prev) => ({ ...prev, lastName: e.target.value }));
}}
/>
<TextField
id="outlined-number"
type="number"
color="secondary"
placeholder="Phone number"
InputLabelProps={{
shrink: true,
}}
value={info ? info.phoneNumber : ""}
onChange={(e) => {
setInfo((prev) => ({ ...prev, phoneNumber: e.target.value }));
}}
/>
<TextField
id="outlined-basic"
label="City"
color="secondary"
variant="outlined"
type="text"
value={info ? info.city : ""}
onChange={(e) => {
setInfo((prev) => ({ ...prev, city: e.target.value }));
}}
/>
<TextField
id="outlined-basic"
label="Email"
color="secondary"
variant="outlined"
type="email"
value={info ? info.email : ""}
onChange={(e) => {
setInfo((prev) => ({ ...prev, email: e.target.value }));
}}
/>
<MaterialUIPickers></MaterialUIPickers>
</div>
<Button
onClick={next}
style={{ width: "700px" }}
color="secondary"
variant="contained"
>
Next
</Button>
</div>
);
}
export default Form;
In my application, the home page contains a form that creates workout objects. I am trying to do the same thing with meal objects, but on another page, and after clicking the "meals" button from my navbar, I get an error.
This is my navbar component where I click on the meals button.
import React,{useState, useEffect} from 'react';
import {Link, useLocation} from 'react-router-dom';
import { AppBar, Avatar, Toolbar, Typography, Button} from '#material-ui/core';
import { useDispatch } from 'react-redux';
import decode from 'jwt-decode';
import {useNavigate} from 'react-router-dom';
import useStyles from './styles';
const Navbar = () => {
const classes = useStyles();
const [user, setUser]=useState(JSON.parse(localStorage.getItem('profile')));
const dispatch=useDispatch();
const navigate=useNavigate();
const location=useLocation();
const logout=()=>{
dispatch({type:'LOGOUT'});
navigate('/');
setUser(null);
};
const meals=()=>{
dispatch({type:'MEALS'});
navigate('/meals');
};
useEffect(()=>{
const token=user?.token;
if(token){
const decodedToken=decode(token);
if (decodedToken.exp * 1000 < new Date().getTime()) logout();
}
setUser(JSON.parse(localStorage.getItem('profile')));
},[location])
return (
<AppBar className={classes.AppBar} position="static" color="inherit">
<div className={classes.brandContainer}>
<Typography component={Link} to="/" className={classes.heading} variant="h2" align="center">Workouts</Typography>
</div>
<Toolbar className={classes.toolbar}>
{user ? (
<div className={classes.profile}>
<Avatar className={classes.purple} referrerPolicy="no-referrer" alt={user.result.name} src={user.result.imageUrl}>{user.result.name.charAt(0)}</Avatar>
<Typography className={classes.userName} variant="h6">{user.result.name}</Typography>
<Button variant="contained" className={classes.logout} color="secondary" onClick={meals}>Meals</Button>
<Button variant="contained" className={classes.logout} color="secondary" onClick={logout}>Logout</Button>
</div>
) : (
<Button component={Link} to="/auth" variant="contained" color="primary">Sign In</Button>
)}
</Toolbar>
</AppBar>
)
}
export default Navbar
This is my single Meal component
import React from 'react';
import { Card, CardActions, CardContent, CardMedia, Button, Typography } from '#material-ui/core/';
import ThumbUpAltIcon from '#material-ui/icons/ThumbUpAlt';
import DeleteIcon from '#material-ui/icons/Delete';
import MoreHorizIcon from '#material-ui/icons/MoreHoriz';
import ThumbUpAltOutlined from '#material-ui/icons/ThumbUpAltOutlined';
import moment from 'moment';
import {useDispatch} from 'react-redux';
import useStyles from './styles';
import {deleteMeal, likeMeal} from '../../../actions/meals';
const Meal=({meal, setCurrentId})=> {
const classes=useStyles();
const dispatch=useDispatch();
const user = JSON.parse(localStorage.getItem('profile'));
const Likes = () => {
if (meal.likes.length > 0) {
return meal.likes.find((like) => like === (user?.result?.googleId || user?.result?._id))
? (
<><ThumbUpAltIcon fontSize="small" /> {meal.likes.length > 2 ? `You and ${meal.likes.length - 1} others` : `${meal.likes.length} like${meal.likes.length > 1 ? 's' : ''}` }</>
) : (
<><ThumbUpAltOutlined fontSize="small" /> {meal.likes.length} {meal.likes.length === 1 ? 'Like' : 'Likes'}</>
);
}
return <><ThumbUpAltOutlined fontSize="small" /> Like</>;
};
return (
<Card className={classes.card}>
<CardMedia className={classes.media} image={meal.selectedFile|| 'https://user-images.githubusercontent.com/194400/49531010-48dad180-f8b1-11e8-8d89-1e61320e1d82.png'} title={meal.category}/>
<div className={classes.overlay}>
<Typography variant="h6">{meal.calories}</Typography>
<Typography variant="body2">{moment(meal.createdAt).fromNow()}</Typography>
</div>
{(user?.result?.googleId===meal?.creator || user?.result?._id===meal?.creator) && (
<div className={classes.overlay2}>
<Button style={{color:'white'}} size="small" onClick={()=>setCurrentId(meal._id)}>
<MoreHorizIcon fontSize="medium"/>
</Button>
</div>
)}
<div className={classes.details}>
<Typography className={classes.title} gutterBottom variant="h5" component="h2">{meal.name}</Typography>
</div>
<Typography className={classes.difficulty} variant="body2" color="textSecondary" component="p">{meal.category}</Typography>
<CardContent>
<Typography variant="body2" color="textSecondary" component="p">{meal.calories}</Typography>
</CardContent>
<CardActions className={classes.cardActions}>
<Button size='small' color="primary" disabled={!user?.result} onClick={()=>{dispatch(likeMeal(meal._id))}}>
<Likes/>
</Button>
{(user?.result?.googleId===meal?.creator || user?.result?._id===meal?.creator) && (
<Button size='small' color="primary" onClick={()=>{dispatch(deleteMeal(meal._id))}}>
<DeleteIcon fontSize="small"/>
Delete
</Button>
)}
</CardActions>
</Card>
);
}
export default Meal;
This is my Meals components that stands for all the Meal objects.
import React from 'react';
import {Grid, CircularProgress} from '#material-ui/core';
import { useSelector } from 'react-redux';
import Meal from './Meal/Meal';
import useStyles from './styles';
const Meals=({setCurrentId})=> {
const meals=useSelector((state)=>state.meals);
const classes =useStyles();
return (
!meals.length ? <CircularProgress/>:(
<Grid className={classes.container} container alignItems="stretch" spacing={3}>
{
meals.map((meal)=>(
<Grid key={meal._id} item xs={12} sm={6} md={6}>
<Meal meal={meal} setCurrentId={setCurrentId}/>
</Grid>
))}
</Grid>
)
);
}
export default Meals;
The error says "cannot read properties of undefined (reading 'length')" and length is in this component.
And, below I have my MealsForm which is the form used for creating the objects.
import React, { useState, useEffect } from 'react';
import { TextField, Button, Typography, Paper } from '#material-ui/core';
import { useDispatch, useSelector } from 'react-redux';
import FileBase from 'react-file-base64';
import useStyles from './styles';
import { createMeal, updateMeal } from '../../actions/meals';
const MealForm=({ currentId, setCurrentId=()=>{} })=>{
const [mealData, setMealData]=useState({
category:'',name:'', calories:'',selectedFile:''
});
const meal = useSelector((state) => (currentId ? state.meals.find((meal) => meal._id === currentId) : null));
const dispatch=useDispatch();
const classes=useStyles();
const user=JSON.parse(localStorage.getItem('profile'));
useEffect(()=>{
if(meal) setMealData(meal);
},[meal]);
const clear = () => {
setCurrentId(0);
setMealData({ category:'',name:'', calories:'',selectedFile:'' });
};
const handleSubmit=async (e)=>{
e.preventDefault();
if(!currentId) {
dispatch(createMeal(mealData));
clear();
} else{
dispatch(updateMeal(currentId, mealData));
clear();
}
};
if (!user?.result?.name) {
return (
<Paper className={classes.paper}>
<Typography variant="h6" align="center">
Please Sign In to add meals
</Typography>
</Paper>
);
}
return(
<Paper className={classes.paper}>
<form autoComplete="off" noValidate className={`${classes.root} ${classes.form}`} onSubmit={handleSubmit}>
<Typography variant="h6">{currentId ? `Editing "${meal.calories}"` : 'Creating a meal'}</Typography>
<TextField name="category" variant="outlined" label="Category" fullWidth value={mealData.category} onChange={(e)=>setMealData({...mealData, category:e.target.value})}/>
<TextField name="name" variant="outlined" label="Name" fullWidth value={mealData.name} onChange={(e)=>setMealData({...mealData, name:e.target.value})}/>
<TextField name="calories" variant="outlined" label="Calories" fullWidth value={mealData.calories} onChange={(e)=>setMealData({...mealData, calories:e.target.value})}/>
<div className={classes.fileInput}>
<FileBase
type="file"
multiple={false}
onDone={({base64})=>setMealData({...mealData,selectedFile:base64})}
/>
</div>
<Button className={classes.buttonSubmit} variant="contained" color="primary" size="large" type="submit" fullWidth>Submit</Button>
<Button variant="contained" color="secondary" size="small" onClick={clear} fullWidth>Clear</Button>
</form>
</Paper>
);
}
export default MealForm;
Here I have the code for the actions on the meals object
import { FETCH_ALL, CREATE, UPDATE, DELETE, LIKE } from '../constants/actionTypes';
import * as api from '../api';
export const getMeals=()=> async (dispatch)=>{
try{
const {data}=await api.fetchMeals();
dispatch({type:FETCH_ALL,payload:data});
}catch(error){
console.log(error);
}
}
export const createMeal=(meal)=>async(dispatch)=>{
try{
const {data}=await api.createMeal(meal);
dispatch({type:CREATE,payload:data});
console.log(data);
}catch(error){
console.log(error.message);
}
}
export const updateMeal=(id,meal)=>async(dispatch)=>{
try{
const {data}=await api.updateMeal(id,meal);
dispatch({type:UPDATE,payload:data})
}catch(error){
console.log(error);
}
}
export const deleteMeal=(id)=>async (dispatch)=>{
try{
await api.deleteMeal(id);
dispatch({type:DELETE,payload:id});
}catch(error){
console.log(error);
}
}
export const likeMeal=(id)=>async(dispatch)=>{
try{
const {data}=await api.likeMeal(id);
dispatch({type:LIKE,payload:data})
}catch(error){
console.log(error);
}
}
Below I have the MealsPage component. The flow goes like this: Navbar, MealsPage, Meals, Meal
import React, {useState, useEffect} from 'react'
import {Container, Grow, Grid} from '#material-ui/core';
import {useDispatch} from 'react-redux';
import {getMeals} from '../../actions/meals';
import Meals from "../Meals/Meals";
import MealForm from "../MealForm/MealForm";
import useStyles from './styles';
function MealsPage() {
const [currentId, setCurrentId]=useState(null);
const classes=useStyles();
const dispatch=useDispatch();
useEffect(()=>{
dispatch(getMeals());
},[currentId, dispatch]);
const meals=getMeals();
console.log(meals);
return (
<Grow in>
<Container>
<Grid className={classes.mainContainer} container justifyContent="space-between" alignItems='stretch' spacing={3}>
<Grid item xs={12} sm={7}>
<Meals setCurrentId={setCurrentId}/>
</Grid>
<Grid item xs={12} sm={4}>
<MealForm currentId={currentId} setCurrentId={setCurrentId}/>
</Grid>
</Grid>
</Container>
</Grow>
)
}
export default MealsPage
I cannot see the problem. Could you help me solve this?
I'm new to React and I've been trying to redirect to a different component after getting a response from my API.
I've tried using history, location, and Redirect, but the redirect never happens.
Also, I get undefined when using all of the above.
I'm not sure if this is because my App is defined outside the Router, if it is the reason I'm still unable to fix the issue.
Here is my code:
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { AppProvider } from './Context'
import { BrowserRouter as Router } from 'react-router-dom'
ReactDOM.render(
<React.StrictMode>
<AppProvider>
<App />
</AppProvider>
</React.StrictMode>,
document.getElementById('root')
);
App.js
import React from 'react';
import './App.css';
import {Home, JoinRoom, CreateRoom, Room } from './pages';
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';
function App() {
return (
<div className="App">
<Router>
<Switch>
<Route path="/" exact={true}>
<Home />
</Route>
<Route path="/join">
<JoinRoom />
</Route>
<Route path="/create">
<CreateRoom />
</Route>
<Route path="/room/:roomCode">
<Room />
</Route>
</Switch>
</Router>
</div>
);
}
export default App;
Context.js
Here, in the handleRoomButtonPressed, I'm getting data from the API and trying to redirect.
import React, { useState, useContext } from 'react';
import axios from 'axios';
import { Redirect, useHistory } from "react-router-dom";
const AppContext = React.createContext()
const AppProvider = ({ children }) => {
// const history = useHistory();
const [guestCanPause, setGuestCanPause] = useState(true);
const [votesToSkip, setVotesToSkip] = useState(2);
const [isHost, setIsHost] = useState(false);
const handleVotesChange = (e) => {
e.preventDefault();
setVotesToSkip(e.target.value);
}
const handleGuestCanPauseChange = (e) => {
e.preventDefault();
setGuestCanPause(e.target.value)
}
const handleRoomButtonPressed = async (props) => {
const roomData = { guest_can_pause: guestCanPause, votes_to_skip: votesToSkip };
const response = await axios.post('/api/create-room/', roomData);
console.log(response.data)
const redirectUrl = "/room/" + response.data.code;
console.log(props)
return <Redirect to={redirectUrl} />
}
const getRoomDetails = async (roomCode) => {
axios
.get("/api/get-room?code=" + roomCode)
.then((res) => {
console.log(res.data)
setVotesToSkip(res.data.votes_to_skip);
setGuestCanPause(res.data.guest_can_pause);
setIsHost(res.data.is_host);
})
.catch((err) => console.log(err));
}
return <AppContext.Provider value={{ guestCanPause,
votesToSkip,
isHost,
handleGuestCanPauseChange,
handleVotesChange,
handleRoomButtonPressed,
getRoomDetails, }}>
{children}
</AppContext.Provider>
}
export const useGlobalContext = () => {
return useContext(AppContext)
}
export { AppContext, AppProvider }
The onClick is called in CreateRoom.js
import React, { useState, } from 'react';
import { useGlobalContext } from '../Context'
import { Link } from 'react-router-dom';
import { Button, Grid, Typography, TextField, FormHelperText, FormControl, Radio, RadioGroup, FormControlLabel } from '#material-ui/core'
function CreateRoom() {
const defaultVotes = 2;
const { handleGuestCanPauseChange, handleVotesChange, handleRoomButtonPressed } = useGlobalContext();
return (
<Grid container spacing={1}>
<Grid item xs={12} align="center">
<Typography component="h4" variant="h4">
Create A Room
</Typography>
</Grid>
<Grid item xs={12} align="center">
<FormControl component="fieldset">
<FormHelperText>
<div align="center">Guest Control of Playback state</div>
</FormHelperText>
<RadioGroup row defaultValue="true" onChange={handleGuestCanPauseChange}>
<FormControlLabel value="true"
control={<Radio color="primary" />}
label="Play/Pause" labelPlacemment="bottom" />
<FormControlLabel value="false"
control={<Radio color="secondary" />}
label="No Control" labelPlacemment="bottom" />
</RadioGroup>
</FormControl>
</Grid>
<Grid item xs={12} align="center">
<FormControl>
<TextField required={true}
type="number" onChange={handleVotesChange}
defaultValue={defaultVotes}
inputProps={{ min: 1,
style: { textAlign: "center" },
}}
/>
<FormHelperText>
<div align="center">Votes Required To Skip Song</div>
</FormHelperText>
</FormControl>
</Grid>
<Grid item xs={12} align="center">
<Button
color="primary"
variant="contained"
onClick={handleRoomButtonPressed}
>
Create A Room
</Button>
</Grid>
<Grid item xs={12} align="center">
<Button color="secondary" variant="contained" to="/" component={Link}>
Back
</Button>
</Grid>
</Grid>
)
}
export default CreateRoom
If I understood a subject correctly your AppProvider is located above the router in the component tree. Thus, the react router cannot inject its dependencies into your AppProvider. If you want to access react-router API, such as useHistory hook or others, you should call it from one of the Router children, then it will works.
i am working on react app and lifted a value from child component to parent component using callback. Now when i am trying to retrive value from the callBack function to a global variable by doing
this is parent component
import React , {Component} from 'react';
import SignIn from './SignIn'
import About from './WebPageFour'
import SimpleModal from './Modal'
import {BrowserRouter as Router,Route,Switch} from 'react-router-dom'
import Header from './Header';
import {isVisited} from './Global';
import Banner from './Banner'
var user = null
const callBack=(isSignedIn)=>{
user = isSignedIn
// console.log(user)
return user
}
console.log(user)
class Main extends Component {
constructor(props){
super(props)
this.visit_checked=this.visit_checked.bind(this);
this.state={
count: 0,
isModalOpen:false,
isSignedIn:false
}
}
render() {
return(
<>
{/* material-ui navbar */}
<Header/>
<Banner/>
{/* routes for pages */}
<Route path="/SignIn" render={()=>(
<React.Fragment>
<SignIn callBack={callBack}/>
</React.Fragment>
)}
/>
</Switch>
</>
);
}
}
export default (Main);
this is child component
// Import FirebaseAuth and firebase.
import React from 'react';
import StyledFirebaseAuth from 'react-firebaseui/StyledFirebaseAuth';
import 'firebase/auth';
import firebase from 'firebase';
import {Grid, Card, Typography, CardContent, CardActions, TextField, Button, Box} from '#material-ui/core'
// import {auth} from '../helpers/Firebase'
class SignIn extends React.Component {
// The component's Local state.
state = {
isSignedIn: false, // Local signed-in state.
email: '',
password:''
};
// Configure FirebaseUI.
uiConfig = {
// Popup signin flow rather than redirect flow.
signInFlow: 'popup',
// We will display Google and Facebook as auth providers.
signInOptions: [
firebase.auth.GoogleAuthProvider.PROVIDER_ID,
firebase.auth.FacebookAuthProvider.PROVIDER_ID
],
callbacks: {
// Avoid redirects after sign-in.
signInSuccessWithAuthResult: () => false
}
};
// Listen to the Firebase Auth state and set the local state.
componentDidMount() {
this.unregisterAuthObserver = firebase.auth().onAuthStateChanged(
(user) => this.setState({isSignedIn: !!user})
);
}
// Make sure we un-register Firebase observers when the component unmounts.
componentWillUnmount() {
this.unregisterAuthObserver();
}
handleChange=(e)=>{
this.setState({
[e.target.name]:e.target.value
})
}
handleSubmit(e){
e.preventDefault()
firebase.auth().signInWithEmailAndPassword(this.state.email, this.state.password).catch(function(error) {
// Handle Errors here.
var errorCode = error.code;
var errorMessage = error.message;
console.log('errors'+errorCode+' error '+errorMessage)
// ...
})
}
render() {
**this.props.callBack(this.state.isSignedIn)**
if (!this.state.isSignedIn) {
return (
<React.Fragment>
<div style={{position:'relative'}}>
<Box mt={5}>
<Grid item container justify="center" xs={12} md={12} lg={12}>
<Card info={this.info}>
<form onSubmit={(values) => this.handleSubmit(values)}>
<CardActions>
<CardContent>
<Box mt={2} mb={4}>
<Typography variant="h5" className="text-capitalize text-center">sign in</Typography>
</Box>
<Box mb={2}>
<TextField label="email" variant="outlined" name="email" size="small" onChange={this.handleChange} /><br/>
</Box>
<Box>
<TextField type="password" name="password" label="password" variant="outlined" size="small" onChange={this.handleChange} />
</Box>
</CardContent>
</CardActions>
<CardActions>
<Box ml={2} mb={2} mt={0} pl={8}>
<Button
type="submit"
value="submit"
variant="contained"
color="primary"
size="small"
className="text-capitalize"
>sign in</Button>
</Box>
</CardActions>
</form>
<br/>
<hr/>
<br/>
<StyledFirebaseAuth uiConfig={this.uiConfig} firebaseAuth={firebase.auth()}/>
</Card>
</Grid>
</Box>
</div>
</React.Fragment>
);
}
return (
<div>
<Grid container justify="center">
<Grid item className="bg-color" xs={12}>
<h2 style={{textAlign:'center'}}>My App</h2>
</Grid>
<Grid item xs={12} md={8} lg={5} >
<br/>
<p>Welcome {this.state.email} ! You are now signed-in!</p>
<Button onClick={() => firebase.auth().signOut()} color="primary">Sign-out</Button>
</Grid>
</Grid>
</div>
);
}
}
export default SignIn
the console.log inside the function is returning the required value but outside the function it returns null
you declared the call back function but you didn't invoke it
try this out
var user = null
const callBack=(isSignedIn)=>{
user = isSignedIn
// console.log(user)
return user
}
callback(boolean)
console.log(user)
below is the parent component
import React , {Component} from 'react';
import SignIn from './SignIn'
import About from './WebPageFour'
import SimpleModal from './Modal'
import {BrowserRouter as Router,Route,Switch} from 'react-router-dom'
import Header from './Header';
import {isVisited} from './Global';
import Banner from './Banner'
var user = null
const callBack=(isSignedIn)=>{
user = isSignedIn
// console.log(user)
return user
}
console.log(user)
class Main extends Component {
constructor(props){
super(props)
this.visit_checked=this.visit_checked.bind(this);
this.state={
count: 0,
isModalOpen:false,
isSignedIn:false
}
}
render() {
return(
<>
{/* material-ui navbar */}
<Header/>
<Banner/>
{/* routes for pages */}
<Route path="/SignIn" render={()=>(
<React.Fragment>
<SignIn callBack={callBack}/>
</React.Fragment>
)}
/>
</Switch>
</>
);
}
}
export default (Main);
this is child component
// Import FirebaseAuth and firebase.
import React from 'react';
import StyledFirebaseAuth from 'react-firebaseui/StyledFirebaseAuth';
import 'firebase/auth';
import firebase from 'firebase';
import {Grid, Card, Typography, CardContent, CardActions, TextField, Button, Box} from '#material-ui/core'
// import {auth} from '../helpers/Firebase'
class SignIn extends React.Component {
// The component's Local state.
state = {
isSignedIn: false, // Local signed-in state.
email: '',
password:''
};
// Configure FirebaseUI.
uiConfig = {
// Popup signin flow rather than redirect flow.
signInFlow: 'popup',
// We will display Google and Facebook as auth providers.
signInOptions: [
firebase.auth.GoogleAuthProvider.PROVIDER_ID,
firebase.auth.FacebookAuthProvider.PROVIDER_ID
],
callbacks: {
// Avoid redirects after sign-in.
signInSuccessWithAuthResult: () => false
}
};
// Listen to the Firebase Auth state and set the local state.
componentDidMount() {
this.unregisterAuthObserver = firebase.auth().onAuthStateChanged(
(user) => this.setState({isSignedIn: !!user})
);
}
// Make sure we un-register Firebase observers when the component unmounts.
componentWillUnmount() {
this.unregisterAuthObserver();
}
handleChange=(e)=>{
this.setState({
[e.target.name]:e.target.value
})
}
handleSubmit(e){
e.preventDefault()
firebase.auth().signInWithEmailAndPassword(this.state.email, this.state.password).catch(function(error) {
// Handle Errors here.
var errorCode = error.code;
var errorMessage = error.message;
console.log('errors'+errorCode+' error '+errorMessage)
// ...
})
}
render() {
**this.props.callBack(this.state.isSignedIn)**
if (!this.state.isSignedIn) {
return (
<React.Fragment>
<div style={{position:'relative'}}>
<Box mt={5}>
<Grid item container justify="center" xs={12} md={12} lg={12}>
<Card info={this.info}>
<form onSubmit={(values) => this.handleSubmit(values)}>
<CardActions>
<CardContent>
<Box mt={2} mb={4}>
<Typography variant="h5" className="text-capitalize text-center">sign in</Typography>
</Box>
<Box mb={2}>
<TextField label="email" variant="outlined" name="email" size="small" onChange={this.handleChange} /><br/>
</Box>
<Box>
<TextField type="password" name="password" label="password" variant="outlined" size="small" onChange={this.handleChange} />
</Box>
</CardContent>
</CardActions>
<CardActions>
<Box ml={2} mb={2} mt={0} pl={8}>
<Button
type="submit"
value="submit"
variant="contained"
color="primary"
size="small"
className="text-capitalize"
>sign in</Button>
</Box>
</CardActions>
</form>
<br/>
<hr/>
<br/>
<StyledFirebaseAuth uiConfig={this.uiConfig} firebaseAuth={firebase.auth()}/>
</Card>
</Grid>
</Box>
</div>
</React.Fragment>
);
}
return (
<div>
<Grid container justify="center">
<Grid item className="bg-color" xs={12}>
<h2 style={{textAlign:'center'}}>My App</h2>
</Grid>
<Grid item xs={12} md={8} lg={5} >
<br/>
<p>Welcome {this.state.email} ! You are now signed-in!</p>
<Button onClick={() => firebase.auth().signOut()} color="primary">Sign-out</Button>
</Grid>
</Grid>
</div>
);
}
}
export default SignIn
the main code in this is Bold
I have a simple component with some input and dropdown elements, I am trying to test that the elements are rendered. By finding the element 'input' in my expect statement.
Here is what I tried in my test file:
import React from 'react'
import { shallow } from 'enzyme'
import AddUser from '../Components/AddUser'
describe('AddUser', () => {
let wrapper
let mockFetchDetailsActions
let mockHandleCancel
const match = {
params: { id: '1212212' }
}
beforeEach(() => {
mockFetchDetailsActions = jest.fn()
mockHandleCancel = jest.fn()
wrapper = shallow(
<AddUser
match={match}
actions={{
fetchDetailsActions: mockFetchDetailsActions,
handleCancel: mockHandleCancel
}}
/>
)
})
describe('Component rendered', () => {
it('Elements rendered correctly', () => {
expect(wrapper.find('input').length).toBe(6)
})
})
})
Here is my component:
/* eslint-disable no-invalid-this */
import React, { Fragment } from 'react'
import PropTypes from 'prop-types'
import { withStyles } from '#material-ui/core/styles'
import GridContainer from './Grid/GridContainer'
import GridItem from './Grid/GridItem'
import { TextField } from 'formik-material-ui'
import { Field, Form } from 'formik'
import dashboardStyle from '../styles/dashboardStyle'
import Card from './Card/Card'
import CardBody from './Card/CardBody'
import * as Constants from '../actions/actionTypes'
import SaveAndCancelButtons from './Common/saveAndCancelButtons'
class AddUser extends React.Component {
componentDidMount () {
if (this.props.match.params.id) {
this.props.actions.fetchDetailsActions(Constants.FETCH_DETAILS_API_CALL_REQUEST, this.props.match.params.id)
} else {
this.props.actions.handleCancel()
}
}
handleChange = name => event => {
this.props.actions.handleInputChangeAction(name, event.target.value)
}
onSave = () => {
const userDetails = {
user: this.props.values.user
}
if (userDetails && userDetails.user.id) {
this.props.actions.updateDetailsActions(Constants.UPDATE_USER_API_CALL_REQUEST, userDetails.user.id, userDetails)
} else {
this.props.actions.addNewUserAction(Constants.ADD_USER_API_CALL_REQUEST, userDetails)
}
}
handleCancel = () => {
this.props.history.push('/admin_console')
this.props.actions.handleCancel()
}
render () {
const { classes, isFetching } = this.props
return (
<Form>
<Field
name="user"
render={feildProps => (
<Fragment>
<GridContainer>
<GridItem xs={12} sm={12} md={12}>
<Card>
<h2 className={classes.cardTitleWhite}>Add User</h2>
<CardBody isFetching={isFetching}>
<GridContainer>
<GridItem xs={12} sm={12} md={4}>
<Field
label="First Name"
name={`user.first_name`}
className={this.props.classes.textField}
margin="normal"
variant="outlined"
component={TextField}
/>
<Field
label="Secondary Email"
name={`user.email_secondary`}
className={this.props.classes.textField}
margin="normal"
variant="outlined"
component={TextField}
/>
</GridItem>
<GridItem xs={12} sm={12} md={4}>
<Field
label="Last Name"
name={`user.last_name`}
className={this.props.classes.textField}
margin="normal"
variant="outlined"
component={TextField}
/>
<Field
label="Mobile Phone"
name={`user.mobile_phone`}
className={this.props.classes.textField}
margin="normal"
variant="outlined"
component={TextField}
/>
</GridItem>
<GridItem xs={12} sm={12} md={4}>
<Field
label="Email"
name={`user.email`}
className={this.props.classes.textField}
margin="normal"
variant="outlined"
component={TextField}
/>
<Field
label="Work Phone"
name={`user.work_phone`}
className={this.props.classes.textField}
margin="normal"
variant="outlined"
component={TextField}
/>
</GridItem>
</GridContainer>
</CardBody>
</Card>
<SaveAndCancelButtons
handleSave={() => {
this.onSave()
}}
routingLink="/people"
label="Save"
/>
</GridItem>
</GridContainer>
</Fragment>
)}
/>
</Form>
)
}
}
AddUser.propTypes = {
classes: PropTypes.object.isRequired
}
export default withStyles(dashboardStyle)(AddUser)
Here is my withFormik() wrapper:
import { withStyles } from '#material-ui/core/styles'
import { withFormik } from 'formik'
import * as Yup from 'yup'
import AddUser from './AddUser'
const styles = theme => ({
textField: {
width: '100%'
}
})
const validations = Yup.object().shape({
user: Yup.object().shape({
first_name: Yup.string().required('Required'),
last_name: Yup.string().required('Required')
})
})
const withFormikWrapper = withFormik({
validationSchema: validations,
enableReinitialize: true
})(AddUser)
export default withStyles(styles)(withFormikWrapper)
Expected result:
Found 6 elements.
Actual Results:
AddUser › Component rendered › Elements rendered correctly
expect(received).toBe(expected) // Object.is equality
Expected: 6
Received: 0
26 | describe('Component rendered', () => {
27 | it('Elements rendered correctly', () => {
> 28 | expect(wrapper.find('input').length).toBe(6)
| ^
29 | })
30 | })
31 | })
Try this with mount instead of shallow.
I was able to make it work by using mount and also imported the component from withFormikWrapper() instead of importing from its own.
In test file:
before:
import AddUser from '../Components/AddUser'
Now:
import AddUser from '../Components/AddUserWithFormik'