I am trying to create a test, that checks wether a component draws correctly or not. But when i try to run the test i get this error message:
TypeError: Cannot read property 'searchForGames' of undefined
I have tried adding som mock data in searchForGames, but i cant get it to work:(
Here is my code:
search.tsx
import * as React from "react";
import { Component } from "react-simplified";
import { CardGroup, GameCard, Container, NavBar, SubHeader, Alert } from "./widgets";
import { igdbService, Game } from "./services";
export class Search extends Component<{
match: { params: { searchString: string } };
}> {
games: Game[] = [];
render() {
return (
<Container>
{this.games.length !== 0 ? (
<CardGroup>
{this.games.map((game) => {
return (
<div key={game.id} className="col">
<NavBar.Link to={"/games/" + game.id}>
<GameCard
name={game.name}
url={game.cover ? game.cover.url.replace("thumb", "cover_big") : ""}
/>
</NavBar.Link>
</div>
);
})}
</CardGroup>
) : (
<div className="d-flex flex-column justify-content-center">
<h4 className="mx-auto">The game you are looking for could not be found in the IGDB database..</h4>
<img className="w-40 mx-auto" alt="Sadge" src="https://c.tenor.com/kZ0XPsvtqw8AAAAi/cat-farsi-sad.gif"/>
</div>
)}
</Container>
);
}
mounted() {
this.getGames();
}
getGames() {
igdbService
.searchForGames(this.props.match.params.searchString)
.then((response) => (this.games = response))
}
}
search.test.tsx
import {Search} from "../src/Search"
import * as React from "react";
import { shallow } from 'enzyme';
import { NavBar, GameCard } from "../src/widgets";
jest.mock("../src/services", () => {
class Service {
searchForGames(string: searchString) {
return Promise.resolve(
[
{
id:143755,
cover: {
id:1311803,
url: '//images.igdb.com/igdb/image/upload/t_thumb/co2tp7.jpg'
},
name: "Terrorist Killer",
}
]
)
}
getGames() {
return Promise.resolve();
}
}
return new Service();
})
test("Search draws correctly", (done) => {
const wrapper = shallow (<Search match={{params: {searchString: "Terrorist"} }}/>)
setTimeout(() => {
expect(
wrapper.containsAllMatchingElements([
<div>
<NavBar.Link to="/games/143755">
<GameCard
name="Terrorist Killer"
url="//images.igdb.com/igdb/image/upload/t_thumb/co2tp7.jpg"
/>
</NavBar.Link>
</div>
])
).toEqual(true);
done();
})
})
Related
I am currently busy working on a personal project with a dashboard using NextJS, which I believe allows me to learn NextJS and the fundementals of typescript. I am trying to work out how to set a welcome message if a response sent back from the server is set to True, I did manage to code this in, but I got cannot set headers after they are sent to the client.
My entire code file for the dashboard is the following snippet:
import styles from "./index.module.css";
import { type NextPage } from "next";
import Head from "next/head";
import Link from "next/link";
import Container from 'react-bootstrap/Container';
import Nav from 'react-bootstrap/Nav';
import Navbar from 'react-bootstrap/Navbar';
import NavDropdown from 'react-bootstrap/NavDropdown';
import { signIn, signOut, useSession } from "next-auth/react";
import Image from 'react-bootstrap/Image'
import Router from "next/router";
import { useEffect, useState } from "react";
import { library } from '#fortawesome/fontawesome-svg-core'
import { faHouse, fas, faServer } from '#fortawesome/free-solid-svg-icons'
import "#fortawesome/fontawesome-svg-core/styles.css";
import { Offline, Online } from "react-detect-offline";
import { config } from "#fortawesome/fontawesome-svg-core";
// Tell Font Awesome to skip adding the CSS automatically
// since it's already imported above
config.autoAddCss = false;
{/* The following line can be included in your src/index.js or _App.js file*/}
import 'bootstrap/dist/css/bootstrap.min.css';
library.add(fas, faServer, faHouse)
// import the icons you need
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome'
const Protected: NextPage = () => {
const { status, data } = useSession()
const { data: sessionData2 } = useSession();
useEffect(() => {
if (status === "unauthenticated") Router.replace("/auth/signin");
}, [status]);
// const isBreakpoint = useMediaQuery(768)
// return (
// <div>
// { isBreakpoint ? (
// <div>
// <HamburgerMenu />
// </div>
// ) : (
// <div>
// <FullMenu />
// </div>
// )
if (status === "authenticated")
return (
<><Navbar bg="dark" expand="lg" variant="dark" className="justify-content-end flex-grow-1 pe-3">
<Container>
<Navbar.Brand href="#home" style={{ fontSize: '25px' }}><strong>Litika.</strong> </Navbar.Brand>
{/* <Navbar.Toggle aria-controls="basic-navbar-nav" /> */}
<Navbar.Collapse id="basic-navbar-nav" className={styles.rightNavbar}>
<Nav className={styles.rightNavbar}>
<Nav.Link href="#home" className={styles.body}><FontAwesomeIcon icon={faHouse} /></Nav.Link>
<Nav.Link href="#link" className={styles.body}>Link</Nav.Link>
<NavDropdown className={styles.body} title={<Image
src={sessionData2 ? `https://cdn.pixabay.com/photo/2015/10/05/22/37/blank-profile-picture-973460_640.png` : 'https://cdn.pixabay.com/photo/2015/10/05/22/37/blank-profile-picture-973460_640.png'}
roundedCircle
style={{ width: '30px' }} />}
id="basic-nav-dropdown">
<NavDropdown.Item href="#action/3.1" onClick={sessionData2 ? () => void signOut() : () => void signIn()}>
{sessionData2 ? "Sign out" : "Sign in"}
</NavDropdown.Item>
<NavDropdown.Item href="#action/3.2">
Another action
</NavDropdown.Item>
<NavDropdown.Item href="#action/3.3">Something</NavDropdown.Item>
<NavDropdown.Divider />
<NavDropdown.Item href="#action/3.4">
Separated link
</NavDropdown.Item>
</NavDropdown>
</Nav>
</Navbar.Collapse>
</Container>
</Navbar>
<div className={styles.connectivityStatus}>
<br></br>
<div className={styles.connectivityStatusBox}>
<Online>š¢ You are connected to the internet.</Online>
<Offline>š“ You are not connected to the internet.</Offline>
</div>
<WelcomeContainer />
</div>
<main className={styles.main}>
<h1 className={styles.title}>
Litika. </h1>
<div className={styles.container}>
<div className={styles.cardRow}>
<div className={styles.card}>
<h1 className={styles.cardTitle}><FontAwesomeIcon icon={faServer} /> HELLO!</h1>
<p className={styles.cardText}>How are you? I am under the water.</p>
</div>
</div>
</div>
</main>
<main className={styles.main2}>
<div className={styles.container}>
</div>
</main>
</>
);
return (
<>
<main className={styles.main}>
<div className={styles.container}>
<h1 className={styles.title}>
Loading the Dashboard.
</h1>
</div>
</main>
<main className={styles.main2}>
<div className={styles.container}>
<h1 className={styles.title}>
</h1>
</div>
</main>
</>
);
};
export default Protected;
const WelcomeContainer: React.FC = () => {
const { data: sessionData } = useSession();
var [welcomeMessage, setWelcomeMessage] = useState(null);
const payload = JSON.stringify({
email: sessionData.user?.email,
});
fetch('/api/userService/isuserwelcome', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: payload
})
.then(response => response.json())
.then(data => {
console.log(data);
if (data === true) {
setWelcomeMessage(
<div className={styles.welcomeContainer}>
<h1>Welcome to the Los pollos hermanos family.</h1>
</div>
);
}
})
.catch(error => {
console.error(error);
});
return (
<>
{welcomeMessage}
</>
);
};
The code for the IsUserWelcome API Route is the following:
import type { NextApiRequest, NextApiResponse } from 'next';
import { PrismaClient } from '#prisma/client';
const prisma = new PrismaClient();
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
try {
const { email } = req.body;
const cliresponse = await prisma.user.findFirst({
where: {
email,
},
});
console.log(cliresponse.newUser)
if (cliresponse.newUser == true) {
res.status(200).json(cliresponse.newUser)
}
res.status(200).json({ cliresponse });
} catch (err) {
console.log(err)
res.status(500).json({ error: err});
}
}
I have identified the issue to be coming from the WelcomeContainer Functional Component and do understand that NextJS & React are dynamic, which will lead to updates through the DOM. However, I haven't really tried anything yet to fix this issue because nothing from doing a simple Google search could lead me to fix this issue, so any guidance & help will be appreciated!
The parts that are different in this question is that it pertains to react and NextJS in general, not express.
You are sending a response twice, after a response is send you should return from the function.
import type { NextApiRequest, NextApiResponse } from 'next';
import { PrismaClient } from '#prisma/client';
const prisma = new PrismaClient();
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
try {
const { email } = req.body;
const cliresponse = await prisma.user.findFirst({
where: {
email,
},
});
console.log(cliresponse.newUser)
if (cliresponse.newUser == true) {
res.status(200).json(cliresponse.newUser)
return; // you need to return here so the code below doesn't get executed
}
res.status(200).json({ cliresponse });
} catch (err) {
console.log(err)
res.status(500).json({ error: err});
}
}
Im developing an app where I need to login and after click tha Login button I need to redirect to another view that is in another layout, I've tried using this.props.history.push("/Inicio") to redirect when the Login is succesful. In this case this is the path.
{
path: "/Inicio",
name: "Inicio",
component: Inicio,
layout: "/admin"
},
This is the entire code
import React, { Component } from "react";
import {
Grid,
Col
} from "react-bootstrap";
import { Card } from "components/Card/Card.jsx";
import { FormInputs } from "components/FormInputs/FormInputs.jsx";
import Button from "components/CustomButton/CustomButton.jsx";
class Login extends Component {
constructor(props) {
super(props)
this.state = {
users: [],
user: '',
pass: '',
msg: '',
apiResponse:''
}
this.logChange = this.logChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this)
}
handleSubmit(event) {
event.preventDefault()
var data = {
user: this.state.user,
pass: this.state.pass,
msg: this.state.msg,
apiResponse: this.state.apiResponse
}
console.log(data)
fetch("http://localhost:9000/log/Login", {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(data)
}).then(function(response) {
if (response.status >= 400) {
throw new Error("Bad response from server");
}
return response.json();
}).then((data) => {
if(data == "success"){
console.log(data)
this.setState({users: data});
this.props.history.push("/Inicio");
window.location.reload();
}
else{
if(data == 'El usuario o la contraseƱa no coinciden'){
this.setState({ apiResponse: data })
}
}
}).catch(function(err) {
console.log(err)
});
}
logChange(e) {
this.setState({[e.target.name]: e.target.value});
}
render() {
return (
<div className="content">
<p class="col-md-4"></p>
<Grid >
<Col md={5}>
<Card
title="Login"
content={
<form method='POST' onSubmit= {this.handleSubmit}>
<p class="col-md-2"></p>
<FormInputs
ncols={["col-md-7"]}
properties={[
{
label: "Usuario",
type: "text",
bsClass: "form-control",
placeholder: "Usuario",
maxlength: 20 ,
name: "user",
onChange: this.logChange
}
]}
/>
<p class="col-md-2"></p>
<FormInputs
ncols={["col-md-7"]}
properties={[
{
label: "ContraseƱa",
type: "password",
bsClass: "form-control",
placeholder: "ContraseƱa",
maxlength: 20,
name: "pass",
onChange: this.logChange
}
]}
/>
<p >{this.state.apiResponse}</p>
<br/>
<br/>
<Button bsStyle="info" pullRight fill type="submit">
Login
</Button>
<Button bsStyle="info" pullLeft fill type="submit">
Olvide mi ContraseƱa
</Button>
</form>
}
/>
</Col>
</Grid>
</div>
);
}
}
export default Login;
but in the handleSubmit() everything is working fine except for this.props.history.push("/Inicio") because it doesn't do anything.
The index.js code
import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter, Route, Switch, Redirect } from "react-router-dom";
import "bootstrap/dist/css/bootstrap.min.css";
import "./assets/css/animate.min.css";
import "./assets/sass/light-bootstrap-dashboard-react.scss?v=1.3.0";
import "./assets/css/demo.css";
import "./assets/css/pe-icon-7-stroke.css";
import AdminLayout from "layouts/Admin.jsx";
import LoginLayout from "layouts/LoginLayout.jsx";
import EfoodLayout from "layouts/EFoodLayout.jsx";
ReactDOM.render(
<BrowserRouter>
<Switch>
<Route path="/admin" render={props => <AdminLayout {...props} />} />
<Route path="/login" render={props => <LoginLayout {...props} />} />
<Route path="/Efood" render={props => <EfoodLayout {...props} />} />
<Redirect from="/" to="/login/Login" />
</Switch>
</BrowserRouter>,
document.getElementById("root")
);
LoginLayout
import React, { Component } from "react";
import { Route, Switch } from "react-router-dom";
import LoginNavbar from "components/Navbars/LoginNavbar";
import Footer from "components/Footer/Footer";
import routes from "routes.js";
class Login extends Component {
getRoutes = routes => {
return routes.map((prop, key) => {
if (prop.layout === "/login") {
return (
<Route
path={prop.layout + prop.path}
render={props => (
<prop.component
{...props}
handleClick={this.handleNotificationClick}
/>
)}
key={key}
/>
);
} else {
return null;
}
});
};
getBrandText = path => {
return "Bienvenido a E Food";
};
render() {
return (
<div className="wrapper">
<LoginNavbar
brandText={this.getBrandText(this.props.location.pathname)}
/>
<Switch>{this.getRoutes(routes)}</Switch>
<Footer />
</div>
);
}
}
export default Login;
I hope you can help me, and thanks to everyone who answer.
PD: If you need more from my code, please let me know.
Ok, since it's obvious the component you want to do the navigation in is a deeply nested component there are a couple options.
Prop Drilling (not recommended)
prop drilling
The cons of prop drilling is that if the right props aren't passed to begin with, or if any component along the way forgets to pass on props, then some child component won't receive what they need and it may be difficult to track down the what or why it's missing. It's more difficult to maintain as now any changes to this component necessitate needing to be concerned about every other component around it. Prop drilling is actually a react anti-pattern.
LoginLayout
Your LoginLayout component appears to consume a routes config object and dynamically render routes. It needs to pass on the route props it received from its parent. Any component between here and the Login where you want to use history.push would need to keep passing all the props down.
class Login extends Component {
getRoutes = routes => {
return routes.map((prop, key) => {
if (prop.layout === "/login") {
return (
<Route
path={prop.layout + prop.path}
render={props => (
<prop.component
{...props}
{...this.props} // <-- Pass on all props passed to this component
handleClick={this.handleNotificationClick}
/>
)}
key={key}
/>
);
} else {
return null;
}
});
};
...
render() {
return (
...
);
}
}
Use withRouter Higher Order Component (recommended)
withRouter
You can get access to the history objectās properties and the closest
<Route>'s match via the withRouter higher-order component. withRouter
will pass updated match, location, and history props to the wrapped
component whenever it renders.
import React, { Component } from "react";
import { withRouter } from 'react-router-dom'; // <-- import HOC
import { Grid, Col } from "react-bootstrap";
import { Card } from "components/Card/Card.jsx";
import { FormInputs } from "components/FormInputs/FormInputs.jsx";
import Button from "components/CustomButton/CustomButton.jsx";
class Login extends Component {
constructor(props) {
...
}
handleSubmit(event) {
event.preventDefault();
var data = {
user: this.state.user,
pass: this.state.pass,
msg: this.state.msg,
apiResponse: this.state.apiResponse
};
console.log(data);
fetch("http://localhost:9000/log/Login", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data)
})
.then(function(response) {
if (response.status >= 400) {
throw new Error("Bad response from server");
}
return response.json();
})
.then(data => {
if (data == "success") {
console.log(data);
this.setState({ users: data });
this.props.history.push("/Inicio"); // <-- history prop should now exist
window.location.reload();
} else {
if (data == "El usuario o la contraseƱa no coinciden") {
this.setState({ apiResponse: data });
}
}
})
.catch(function(err) {
console.log(err);
});
}
logChange(e) {
this.setState({ [e.target.name]: e.target.value });
}
render() {
return (
<div className="content">
...
</div>
);
}
}
export default withRouter(Login); // <-- decorate Login with HOC
Are you using react-router-dom to handle your routing ? If so, you can use the hooks to navigate.
import { useHistory } from "react-router-dom";
...
const history = useHistory()
...
history.push('/Inicio')
Note that this will only work with React version >= 16.8
The reason why your code is not working is because of the bindings.
When using this.props.history.push("/Inicio"), this refers to the function you're in, not to the Component history.
If you want to keep your code that way, you can simply turn your function into an arrow function:
.then((data) => {
if(data == "success"){
console.log(data)
self.setState({users: data});
**this.props.history.push("/Inicio")** // now this will be correct
**window.location.reload();** // no need, react handles the reload
}
else{
if(data == 'El usuario o la contraseƱa no coinciden'){
self.setState({ apiResponse: data })
}
}
Although, I'd strongly advise using the latest features of react (check the hooks, it's really useful).
I have a UserFeed component and EditForm component. As soon as I edit the form, I need to be redirected to the UserFeed and the updated data should be shown on UserFeed(title and description of the post).
So, the flow is like-UserFeed, which list the posts, when click on edit,redirected to EditForm, updates the field, redirected to UserFeed again, but now UserFeed should list the posts with the updated data, not the old one.
In this I'm just redirectin to / just to see if it works. But I need to be redirected to the feed with the updated data.
EditForm
import React, { Component } from "react";
import { connect } from "react-redux";
import { getPost } from "../actions/userActions"
class EditForm extends Component {
constructor() {
super();
this.state = {
title: '',
description: ''
};
handleChange = event => {
const { name, value } = event.target;
this.setState({
[name]: value
})
};
componentDidMount() {
const id = this.props.match.params.id
this.props.dispatch(getPost(id))
}
componentDidUpdate(prevProps) {
if (prevProps.post !== this.props.post) {
this.setState({
title: this.props.post.post.title,
description: this.props.post.post.description
})
}
}
handleSubmit = () => {
const id = this.props.match.params.id
const data = this.state
this.props.dispatch(updatePost(id, data, () => {
this.props.history.push("/")
}))
}
render() {
const { title, description } = this.state
return (
<div>
<input
onChange={this.handleChange}
name="title"
value={title}
className="input"
placeholder="Title"
/>
<textarea
onChange={this.handleChange}
name="description"
value={description}
className="textarea"
></textarea>
<button>Submit</button>
</div>
);
}
}
const mapStateToProps = store => {
return store;
};
export default connect(mapStateToProps)(EditForm)
UserFeed
import React, { Component } from "react"
import { getUserPosts, getCurrentUser } from "../actions/userActions"
import { connect } from "react-redux"
import Cards from "./Cards"
class UserFeed extends Component {
componentDidMount() {
const authToken = localStorage.getItem("authToken")
if (authToken) {
this.props.dispatch(getCurrentUser(authToken))
if (this.props && this.props.userId) {
this.props.dispatch(getUserPosts(this.props.userId))
} else {
return null
}
}
}
render() {
const { isFetchingUserPosts, userPosts } = this.props
return isFetchingUserPosts ? (
<p>Fetching....</p>
) : (
<div>
{userPosts &&
userPosts.map(post => {
return <Cards key={post._id} post={post} />
})}
</div>
)
}
}
const mapStateToPros = state => {
return {
isFetchingUserPosts: state.userPosts.isFetchingUserPosts,
userPosts: state.userPosts.userPosts.userPosts,
userId: state.auth.user._id
}
}
export default connect(mapStateToPros)(UserFeed)
Cards
import React, { Component } from "react"
import { connect } from "react-redux"
import { compose } from "redux"
import { withRouter } from "react-router-dom"
class Cards extends Component {
handleEdit = _id => {
this.props.history.push(`/post/edit/${_id}`)
}
render() {
const { _id, title, description } = this.props.post
return (
<div className="card">
<div className="card-content">
<div className="media">
<div className="media-left">
<figure className="image is-48x48">
<img
src="https://bulma.io/images/placeholders/96x96.png"
alt="Placeholder image"
/>
</figure>
</div>
<div className="media-content" style={{ border: "1px grey" }}>
<p className="title is-5">{title}</p>
<p className="content">{description}</p>
<button
onClick={() => {
this.handleEdit(_id)
}}
className="button is-success"
>
Edit
</button>
</div>
</div>
</div>
</div>
)
}
}
const mapStateToProps = state => {
return {
nothing: "nothing"
}
}
export default compose(withRouter, connect(mapStateToProps))(Cards)
updatePost action
export const updatePost = (id, data, redirect) => {
return async dispatch => {
dispatch( { type: "UPDATING_POST_START" })
try {
const res = await axios.put(`http://localhost:3000/api/v1/posts/${id}/edit`, data)
dispatch({
type: "UPDATING_POST_SUCCESS",
data: res.data
})
redirect()
} catch(error) {
dispatch({
type: "UPDATING_POST_FAILURE",
data: { error: "Something went wrong"}
})
}
}
}
I'm not sure if my action is correct or not.
Here's the updatePost controller.
updatePost: async (req, res, next) => {
try {
const data = {
title: req.body.title,
description: req.body.description
}
const post = await Post.findByIdAndUpdate(req.params.id, data, { new: true })
if (!post) {
return res.status(404).json({ message: "No post found "})
}
return res.status(200).json({ post })
} catch(error) {
return next(error)
}
}
One mistake is that to set the current fields you need to use $set in mongodb , also you want to build the object , for example if req.body does not have title it will generate an error
updatePost: async (req, res, next) => {
try {
const data = {};
if(title) data.title=req.body.title;
if(description) data.description=req.body.description
const post = await Post.findByIdAndUpdate(req.params.id, {$set:data}, { new: true })
if (!post) {
return res.status(404).json({ message: "No post found "})
}
return res.status(200).json({ post })
} catch(error) {
return next(error)
}
}
I'm doing a simple tutorial about React and Node, by building a CRUD app.
TypeError: Cannot read property 'length' of undefined
Error in this file: PostsManager.js:102
return (
<Fragment>
<Typography variant="display1">Posts Manager</Typography>
{this.state.posts.length > 0 ? (
<Paper elevation={1} className={classes.posts}>
<List>
This is my file PostsManager.js, it come from this tutorial https://developer.okta.com/blog/2018/07/10/build-a-basic-crud-app-with-node-and-react
import React, { Component, Fragment } from 'react';
import { withAuth } from '#okta/okta-react';
import { withRouter, Route, Redirect, Link } from 'react-router-dom';
import {
withStyles,
Typography,
Button,
IconButton,
Paper,
List,
ListItem,
ListItemText,
ListItemSecondaryAction,
} from '#material-ui/core';
import { Delete as DeleteIcon, Add as AddIcon } from '#material-ui/icons';
import moment from 'moment';
import { find, orderBy } from 'lodash';
import { compose } from 'recompose';
import PostEditor from '../components/PostEditor';
const styles = theme => ({
posts: {
marginTop: 2 * theme.spacing.unit,
},
fab: {
position: 'absolute',
bottom: 3 * theme.spacing.unit,
right: 3 * theme.spacing.unit,
[theme.breakpoints.down('xs')]: {
bottom: 2 * theme.spacing.unit,
right: 2 * theme.spacing.unit,
},
},
});
const API = process.env.REACT_APP_API || 'http://localhost:3001';
class PostsManager extends Component {
state = {
loading: true,
posts: [],
};
componentDidMount() {
this.getPosts();
}
async fetch(method, endpoint, body) {
try {
const response = await fetch(`${API}${endpoint}`, {
method,
body: body && JSON.stringify(body),
headers: {
'content-type': 'application/json',
accept: 'application/json',
authorization: `Bearer ${await this.props.auth.getAccessToken()}`,
},
});
return await response.json();
} catch (error) {
console.error(error);
}
}
async getPosts() {
this.setState({ loading: false, posts: await this.fetch('get', '/posts') });
}
savePost = async (post) => {
if (post.id) {
await this.fetch('put', `/posts/${post.id}`, post);
} else {
await this.fetch('post', '/posts', post);
}
this.props.history.goBack();
this.getPosts();
}
async deletePost(post) {
if (window.confirm(`Are you sure you want to delete "${post.title}"`)) {
await this.fetch('delete', `/posts/${post.id}`);
this.getPosts();
}
}
renderPostEditor = ({ match: { params: { id } } }) => {
if (this.state.loading) return null;
const post = find(this.state.posts, { id: Number(id) });
if (!post && id !== 'new') return <Redirect to="/posts" />;
return <PostEditor post={post} onSave={this.savePost} />;
};
render() {
const { classes } = this.props;
return (
<Fragment>
<Typography variant="display1">Posts Manager</Typography>
{this.state.posts.length > 0 ? (
<Paper elevation={1} className={classes.posts}>
<List>
{orderBy(this.state.posts, ['updatedAt', 'title'], ['desc', 'asc']).map(post => (
<ListItem key={post.id} button component={Link} to={`/posts/${post.id}`}>
<ListItemText
primary={post.title}
secondary={post.updatedAt && `Updated ${moment(post.updatedAt).fromNow()}`}
/>
<ListItemSecondaryAction>
<IconButton onClick={() => this.deletePost(post)} color="inherit">
<DeleteIcon />
</IconButton>
</ListItemSecondaryAction>
</ListItem>
))}
</List>
</Paper>
) : (
!this.state.loading && <Typography variant="subheading">No posts to display</Typography>
)}
<Button
variant="fab"
color="secondary"
aria-label="add"
className={classes.fab}
component={Link}
to="/posts/new"
>
<AddIcon />
</Button>
<Route exact path="/posts/:id" render={this.renderPostEditor} />
</Fragment>
);
}
}
export default compose(
withAuth,
withRouter,
withStyles(styles),
)(PostsManager);
Try to replace
state = {
loading: true,
posts: [],
};
With:
this.state = {
loading: true,
posts: [],
};
If you can get the length using a console.log(this.state.posts.lenght). But when you run the code if length is undefined. Try this one,
return (
<Fragment>
<Typography variant="display1">Posts Manager</Typography>
{ (this.state && this.state.posts && this.state.posts.length) > 0 ? (
<Paper elevation={1} className={classes.posts}>
<List>
I'm trying to figure out why my code isn't working but I'm still not understanding why I'm getting this type error.
import React, { Component } from 'react';
import axios from 'axios'
class List extends Component {
state = {
title: '',
description: ''
}
componentDidMount(){
const initialState = {
_id: this.props.list._id,
title: this.props.list.title,
description: this.props.list.description
}
this.setState(initialState)
}
handleChange = (event) => {
const { value, name } = event.target
this.setState({[name]: value})
}
handleDelete = () => {
axios.delete(`/api/lists/${this.state._id}`).then(() => {
this.props.getAllLists()
})
}
handleUpdate = () => {
axios.patch(`/api/lists/${this.state._id}`, this.state).then(() => {
console.log("Updated List")
})
}
render() {
return (
<div>
<input onBlur={this.handleUpdate}
onChange={this.handleChange}
type="text" name="title"
value={this.state.title}
/>
<textarea onBlur={this.handleUpdate}
onChange={this.handleChange}
name="description" value={this.state.description}
/>
<button onClick={this.handleDelete}>X</button>
</div>
)
}
}
export default List
This is the Error msg at this link
Added the other part
import React, { Component } from 'react';
import axios from 'axios'
import List from './List';
class ListPage extends Component {
state = {
user: {},
lists: []
}
componentDidMount() {
this.getAllLists()
}
getAllLists = () => {
// make an api call to get one single user
// On the server URL is '/api/users/:userId'
const userId = this.props.match.params.userId
axios.get(`/api/users/${userId}`).then(res => {
this.setState({
user: res.data,
lists: res.data.lists
})
})
}
handleCreateNewList = () => {
const userId = this.props.match.params.userId
const payload = {
title: 'List Title',
description: 'List Description'
}
axios.post(`/api/users/${userId}/lists`, payload).then(res => {
const newList = res.data
const newStateLists = [...this.state.lists, newList]
this.setState({ lists: newStateLists })
})
}
render() {
return (
<div>
<h1>{this.state.user.username}'s List Page</h1>
onClick={this.handleCreateNewList}
New Idea
{this.state.lists.map(list => (
<List getAllLists={this.getAllLists} key={list._id} list={list}/>
))}
</div>
)
}
}
export default ListPage;
Sorry I can't comment yet. The error is because no 'list' prop is being passed to the component. Are you using the component like this:
<List list={{_id: 'someId', title: 'someTitle', description: 'someDesc'}} />
Also, why are you overwriting the state when the component mounts instead of setting the initial state?
I think you should first check if "this.state.lists" is empty or not, before passing the props.