try to render todo item as <li>, but todo.name is undefined - javascript

I am writing a todo-list app and encounter this problem:
If I put the Todo component inside the TodoList component and the todo item is passed as a prop
todo = {name: "ssss", status: false, id: 0.028982865008862824}
the todo.name will be undefined if I need to access it inside the Todo component
// TodoList.js
import React from "react";
export default function TodoList({ todos }) {
function Todo({ todo }) {
console.log(todo); // will print todo as an object
// {name: "xxxx", id: "12334", status: false}
console.log(todo.name); // undefined
return (
<div className="todo">
<li className="todo-item"></li>
<button className="complete-button">
<i className="fas fa-check"></i>
</button>
<button className="trash-button">
<i className="fas fa-trash"></i>
</button>
</div>
);
}
return (
<div className="todo-container">
<ul className="todo-list">
{todos.map((todo) => (
<Todo key={todo.id} todo={todo}></Todo>
))}
</ul>
</div>
);
}
But if I changed this to seperate components as two seperate components and pass the todo item as a prop, it will work. Why is this happening?
// TodoList.js
import React from "react";
import Todo from "./Todo";
export default function TodoList({ todos }) {
return (
<div className="todo-container">
<ul className="todo-list">
{todos.map((todo) => (
<Todo key={todo.id} todo={todo}></Todo>
))}
</ul>
</div>
);
}
// Todo.js
import React from "react";
export default function Todo({ todo }) {
console.log(todo);
console.log(todo.name); // will print the name
return (
<div className="todo">
<li className="todo-item"></li>
<button className="complete-button">
<i className="fas fa-check"></i>
</button>
<button className="trash-button">
<i className="fas fa-trash"></i>
</button>
</div>
);
}

TL;DR You cannot define components inside another component.
I changed this to separate components and it works
That is because that is the correct way to do it! You cannot define your components inside the functions of another component. You can however do a function similar to this:
const renderTodo = (todo) => {
return (
<div className="todo">
<li className="todo-item"></li>
<button className="complete-button">
<i className="fas fa-check"></i>
</button>
<button className="trash-button">
<i className="fas fa-trash"></i>
</button>
</div>
);
}
return (
<div className="todo-container">
<ul className="todo-list">
{todos.map((todo) => (
{ renderTodo(todo) }
))}
</ul>
</div>
);
But I would still advise against it. React is the most useful when split into components, as you did in your 2nd example, like this:
Todo.js
export default ({ todo }) => <p>{todo.name}</p>
TodoList.js
import Todo from './Todo.js'
export default ({ todos }) => <div>{todos.map((todo) => <Todo todo={todo} />}</div>
The reason 1st example is not working is because your Component, which you've defined inside another component is constantly re-rendering, and thus you lose your todo props, making todo.name yield undefined.

Related

Deleting component with api only disappears after refresh Reactjs

I have a spring boot api with crud functionalities, on my react frontend I have this, which is a dashboard component and inside i am rendering a list of ProjectItem components and passing them to the dashboard component as props.
When I delete a project I'd like it to immediately remove the project from the component without having to refresh for it to happen.
Since I am passing the props down to my Dashboard component I am a bit confused on how to achieve this.
ProjectItem.js
BackendService is a service class with axios calls for the crud operations
import React, { useEffect, useState } from 'react'
import BackendService from '../services/BackendService';
import { Link } from 'react-router-dom';
import { useParams } from 'react-router';
const ProjectItem = ({projectName, projectIdentifier, description}) => {
const onDeleteClick = (id) => {
if (window.confirm("Are you sure you want to delete this project?")) {
BackendService.deleteProject(id)
.then()
.catch((err) => {
console.log(err.response);
});
alert("Project with ID " + id + " was deleted successfully");
}
};
return (
<div className="container">
<div className="card card-body bg-light mb-3">
<div className="row">
<div className="col-2">
<span className="mx-auto">{projectIdentifier}</span>
</div>
<div className="col-lg-6 col-md-4 col-8">
<h3>{projectName}</h3>
<p>{description}</p>
</div>
<div className="col-md-4 d-none d-lg-block">
<ul className="list-group">
<Link to="">
<li className="list-group-item update">
<i className="fa fa-edit pr-1"> Update Project Info</i>
</li>
</Link>
<button
className="list-group-item delete"
onClick={() => onDeleteClick(projectIdentifier)}
>
<i className="fa fa-minus-circle pr-1"> Delete Project</i>
</button>
</ul>
</div>
</div>
</div>
</div>
);
};
export default ProjectItem;
Dashboard.js
Where the ProjectItem components are rendered
import React, { useEffect, useState } from 'react'
import { Link } from 'react-router-dom'
import BackendService from '../services/BackendService'
import AppNavbar from './AppNavbar'
import ProjectItem from './ProjectItem'
const Dashboard = () => {
const [project, setProject] = useState({
projectName: "",
projectIdentifier: "",
description: "",
});
useEffect(() => {
BackendService.getProjects().then((res) => {
setProject(res.data);
});
}, []);
return (
<div className="projects">
<AppNavbar />
<div className="container">
<div className="row">
<div className="col-md-12">
<h1 className="display-4 text-center">Projects</h1>
<Link to="/addProject">
<button className="btn btn-warning">Create Project</button>
</Link>
{project &&
Object.values(project).map((prj) => {
return (
<div>
<ProjectItem key={prj.id}
projectName={prj.projectName}
projectIdentifier={prj.projectIdentifier}
description={prj.description}
/>
</div>
);
})}
<hr />
</div>
</div>
</div>
</div>
);
};
export default Dashboard
If you want to remove item without refresh the page then you have to call setProject and set it to new project list after BackendService.deleteProject request done in onDeleteClick.
https://reactjs.org/docs/state-and-lifecycle.html

want to call two functions in change and can't pass the event

i am trying to call on change(search in an array on objects and give me the id) in the same class and trying to call a function in the context to update the state and pass the object id found alse trying to pass value to the context function
i have tried to make a function that calls on change(in the same class) and call the function in the context but can't pass the event and i get an error in cannot read property target
import React, { Component, useState } from 'react'
import {Link} from 'react-router-dom'
import styled from 'styled-components'
// import Seasonal from './Seasonal';
import TopBar from './TopBar'
import {storeProducts} from '../data';
import { ProductConsumer } from '../context';
export default class NavBar extends Component {
constructor (props) {
super();
}
state={
text:'',
no: 0
}
onSubmit = (e) =>{
this.state.text = e.target.value
const found = storeProducts.find(function(element) {
return element.title === e.target.value
});
if(found != null){
this.setState(()=>{
return (
this.state.no = found.id,
console.log(this.state.text)
)
})
}
else{
this.state.no = 0;
console.log(this.state.text)
console.log(this.state.no)
}
}
contextFun = (e) =>{
this.onSubmit(),
thi
return null
}
render() {
return (
<div>
<ProductConsumer>
{value => {
// const {modalOpen,closeModal}= value;
return(
<div>
<TopBar></TopBar>
<NavWrapper className="navbar navbar-expand-sm bg-dark navbar-dark px-sm-5 justify-content-between">
<Link to='/' className="nav-li">
<h2 className="text-danger">SHOP MATE</h2>
</Link>
< ul className="navbar-nav align-items-center">
<li className="nav-item ml-5">
<Link to="/" className="nav-link">
products
</Link>
</li>
<li className="nav-item ml-5">
<Link to="/nature" className="nav-link">
nature
</Link>
</li>
<li className="nav-item ml-5">
<Link to="/seasonal" className="nav-link">
seasonal
</Link>
</li>
</ul>
<form className="form-inline">
<input className="form-control text-danger mr-sm-2" type="search" onChange={this.contextFun(this)} placeholder="Search" aria-label="Search"/>
{/* <button className="btn btn-outline-danger my-2 my-sm-0" onClick={this.onSubmit}>Search</button> */}
</form>
</NavWrapper>
</div>
)
}
}
</ProductConsumer>
</div>
)
}
}
Consider this:
<input className="form-control text-danger mr-sm-2" type="search" onChange={e => this.contextFun(e)} placeholder="Search" aria-label="Search"/>
the syntax {e => this.contextFun(e)}, it's an arrow function that recieves the event, it will be called as a callback function for the onChange event, it will take the e, and pass it to your contextFun, which will do what you want.
onChange={this.contextFun(this)} will invoke contextFun on every render, and will pass the component as e - what you want is onChange={this.contextFun}. That would fix the first problem, however you've then got another issue in that inside contextFun you call onSubmit but don't pass the event object i.e.
this.onSubmit(); // should be this.onSubmit(e)
contextFun is unnecessary, if you change your onChange to use onSubmit directly, then this would work
<input onChange={onSubmit} />

How ReactJS display fetch response onClick

I am trying to generate a random user information when pressing the button, and display the information above the button. In ProfilePanel.js, I created a avatar and user constants, which will use to show the information. In index.js, the avatar constant works for that since it doesn't need to use the Button. however, for user constant, it doesn't work. In below's code, I am fetching a api data to display user name, but it didn't show anything, I am not sure where wrong, is something wrong in Button.js or index.js. and how can I fix it. Can somebody help me out? Thanks.
<Button title="name" >
<p key={contact.name} user={contact.name}></p>
</Button>
index.js
import React, { Component } from "react";
import ReactDOM from "react-dom";
import Panel from "./ProfilePanel";
import axios from 'axios';
import './index.css';
import Button from './Button';
const url = 'https://randomuser.me/api/';
class App extends Component {
constructor(props) {
super(props);
this.state = {
contacts: []
}
}
componentDidMount() {
this.fetchdata();
}
fetchdata() {
axios.get(url)
.then(res => {
console.log(res);
this.setState({ contacts: res.data.results});
});
}
render(){
const {contacts} = this.state;
return (
<div className="panel">
{contacts.map(contact => (
<div class="panel">
<Panel
key={contact.picture} avatar={contact.picture.medium}
/>
<li class="flex-container">
<Button title="name" >
<p key={contact.name} user={contact.name}></p>
</Button>
<Button title="location" onClick={this.fetchdata}>
</Button>
<Button key={contact.email} title="email">
</Button>
<Button key={contact.phone} title="phone">
</Button>
<Button key={contact.login.password} title="password">
</Button>
</li>
</div>
))}
</div>
);
}
}
ReactDOM.render(
<App />,
document.getElementById("root")
);
ProfilePanel.js
import React, { Component } from "react";
import PropTypes from "prop-types";
import './index.css';
import Button from './Button';
const style={
borderRadius: 150,
margin: 15,
}
class Panel extends Component {
render() {
const { avatar, user } = this.props;
return (
<div className="Panel">
<div class="panels">
<div className="avatar">
<img src={avatar} class="imageStyle" alt="" width={"200%"} height={"auto"}/>
</div>
</div>
<div class="center">
<h2 className="user">{user}</h2>
</div>
</div>
);
}
}
export default Panel;
Button.js
import './index.css';
import React, { Component } from 'react';
class Button extends Component {
constructor(props) {
super(props);
this.state = {
open:false,
};
}
render() {
const { title } = this.props;
const {open} = this.state;
return (
<button className={` ${open ? 'open' : ''}`}
class='button' onClick={(e) => this.handleClick(e)}>
<div className="panel-heading">
<h2 class='buttoncenter'>{title}</h2>
</div>
</button>
);
}
handleClick(e) {
e.preventDefault();
this.setState({
open: this.state.open
})
}
}
export default Button;
You're not changing state in the handle click. You need to set open to true;
handleClick(e) {
e.preventDefault();
this.setState({
open: true
})
}
You need to pass your user information in index.js. I think you have missed to pass the user props to the panel component, so that it shows the avatar alone. Without passing the users props, you are trying to destructure there in panel component.
//index.js should be like this
render(){
const {contacts} = this.state;
return (
<div className="panel">
{contacts.map(contact => (
<div class="panel">
<Panel
key={contact.picture} user={contact.name} avatar={contact.picture.medium}
/>
<li class="flex-container">
<Button title="name" >
<p key={contact.name} user={contact.name}></p>
</Button>
<Button title="location" onClick={this.fetchdata}>
</Button>
<Button key={contact.email} title="email">
</Button>
<Button key={contact.phone} title="phone">
</Button>
<Button key={contact.login.password} title="password">
</Button>
</li>
</div>
))}
</div>
);
}

React Props Undefined

I am working on the React Recipe Box project from Free Code Camp. I have a parent component that displays recipe names, which it receives as props. The recipe name can be clicked to display a child component that then has the same props passed down to the child component to display information about the ingredients. My problem is, when I click the recipe name, the props in the parent component become undefined. Ive googled a bunch, but I cant figure out why this is happening. Has anyone run into this before?
import React, { Component } from 'react';
import ShowRecipe from './recipeDetail';
class RecipeDetail extends Component {
constructor(props){
super(props)
this.state = {
isHidden:true
}
}
render() {
return (
<div className="card">
<div className="card-header">
<h5>
<button
className="btn btn-link"
onClick={() => {
this.setState({isHidden: !this.state.isHidden})
}}
>
{this.props.recipe.recipeName}
</button>
</h5>
</div>
{ !this.state.isHidden &&
<ShowRecipe
ingredients={this.props.recipe.ingredientsList}
/>
}
</div>
);
}
}
export default RecipeDetail;
this is where the props for RecipeDetail are coming from:
import React from 'react';
import RecipeDetail from './recipeDetail';
import { Jumbotron, ListGroup } from 'react-bootstrap';
const RecipeBoxHolder = ({recipes}) =>{
const recipesItems = recipes.map((recipe) => {
console.log(recipes);
return(
<RecipeDetail
key={recipe.recipeName}
recipe={recipe}
/>
)
})
return(
<div>
<Jumbotron className="jtron">
<h5>Recipes</h5>
<ListGroup>
{recipesItems}
</ListGroup>
</Jumbotron>
</div>
)
}
export default RecipeBoxHolder;
TypeError: Cannot read property 'recipeName' of undefined
RecipeDetail.render
src/components/recipeDetail.js:22
19 | <button
20 | className="btn btn-link"
21 | onClick={() => {this.setState({isHidden:
!this.state.isHidden})}} >
> 22 | {this.props.recipe.recipeName}
23 | </button>
24 |
25 | </h5>
this comes from a different component where the user enters inputs and its passed to the main parent component for the app
let recipeObject={
recipeName: this.state.recipe,
ingredientsList: this.state.ingredients
};
component where user inputs data, takes data and passes it back up to main parent component
import React, {Component} from 'react';
import {ListGroupItem} from 'react-bootstrap';
class AddModal extends Component{
constructor(props){
super(props)
this.state={
recipe: '',
ingredients: ''
}
}
render(){
let recipeObject={
recipeName: this.state.recipe,
ingredientsList: this.state.ingredients
};
return(
<ListGroupItem>
<div className="card">
<div className="card-header">
<span>Add Recipe</span> <i className="fas fa-utensils"></i>
</div>
<div className="card-body">
<label>Recipe</label>
<input value={this.state.recipe} onChange={event =>
this.setState({recipe: event.target.value})} type="text"
className="form-control"/>
<label className="add-ingredients-label">Add Ingredients</label>
<textarea value={this.state.ingredients} onChange={event =>
this.setState({ingredients: event.target.value})}
className="form-control"></textarea>
</div>
<div className="card-footer">
<button className="close-button btn btn-outline-danger btn-
sm">
Close
<i className="fas fa-times"></i>
</button>
<button onClick={e =>{this.props.addRecipe(recipeObject)}}
className="btn btn-outline-success btn-sm">
Add Recipe
<i className="fas fa-plus"></i>
</button>
</div>
</div>
</ListGroupItem>
);
}
}
export default AddModal;
this is the main parent component:
import React, {Component} from 'react';
import ReactDOM from 'react-dom';
import RecipeBoxHolder from './components/recipeContainer';
import AddModal from './components/addRecipeModal';
import './style/index.css';
import {Button, Modal} from 'react-bootstrap';
class App extends Component{
constructor(props){
super(props);
this.state={
recipes:[],
showAddModal: false
}
}
render(){
const addRecipe = (recipeObject) =>{
this.setState(prevState => ({
recipes:[...prevState.recipes, recipeObject]}
))
}
return(
<div className="container">
<RecipeBoxHolder recipes={this.state.recipes} />
<Button
bsStyle="primary"
bsSize="large">
</Button>
<AddModal addRecipe={addRecipe} />
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById('root'));
#Esteban Trevino I put my findings into an answer because the comments become too long, however I do not have a solution as I could not reproduce your issue.
I copied your code into a create-react-app generated react scaffold, and it runs fine. In this gist you can see the file I made:
https://gist.github.com/femans/22324382a8e04390f6a0ece49b867708
I do want to point out that you should not use the recipeName as a key, because they are not unique by design. Better use the following construction:
const recipesItems = recipes.map((recipe, key) => {
return(
<RecipeDetail
key={key}
recipe={recipe}
...

How to display a react-bootstrap modal from another component [duplicate]

This question already has answers here:
Call child method from parent
(23 answers)
Closed 5 years ago.
I have a Meteor+React application which I'm developing where I want to implement the login/registration functionality in a modal. I'm not sure how to open the modal from clicking my sign up or log in buttons
I have the following two components:
ui/components/main-layout/header/LoggedOutNav.jsx
import React, { Component } from 'react'
export default class LoggedOutNav extends Component {
render() {
return(
<ul className="nav navbar-nav">
<li>
<a href="#">
<i className="fa fa-sign-in" aria-hidden="true"></i>
Log In
</a>
</li>
<li>
<a href="#loginRegistration">
<i className="fa fa-user-plus" aria-hidden="true"></i>
Sign Up
</a>
</li>
</ul>
)
}
}
ui/components/modals/LoginRegistration.jsx
import React, { Component } from 'react'
import { Modal } from 'react-bootstrap'
export default class LoginRegistration extends Component {
getInitialState() {
return { showModal: false }
}
close() {
this.setState({ showModal: false })
}
open() {
this.setState({showModal: true})
}
render() {
return (
<Modal show={this.state.showModal} onHide={this.close}>
{/* Irrelevant modal code here */}
</Modal>
)
}
}
How could I accomplish opening the modal from my other component?
import React, { Component } from 'react'
import { Modal } from 'react-bootstrap'
export default class LoggedOutNav extends Component {
constructor(){
this.state = {
showModal: false,
activeModal: ''
}
this.modalDisplay = this.modalDisplay.bind(this);
}
modalDisplay(e){
this.setState({
showModal: !this.state.showModal,
activeModal: e.target.getAttribute('data-tab')
});
}
render() {
return(
<div>
<ul className="nav navbar-nav">
<li
onClick={ this.showModal }
data-tab = 'login'>
<a href="#">
<i className="fa fa-sign-in" aria-hidden="true"></i>
Log In
</a>
</li>
<li
onClick={ this.showModal }
data-tab = 'signup'>
<a href="#loginRegistration">
<i className="fa fa-user-plus" aria-hidden="true"></i>
Sign Up
</a>
</li>
</ul>
<div>
{
this.state.showModal
&&
<Modal modalType={ this.state.activeModal } onHide={this.modalDisplay} data-tab= ""/>
}
</div>
</div>
)
}
}
You could pass the modal type into the Modal component, or use a ternary operator to render
{
this.state.activeModal === 'login' ?
<Login /> :
<SignUp />
}

Categories