React functional component popups based on each id click on edit - reactjs

I have to add and edit person's experience details through reactjs popups, so that I have Experience component, ExperienceAddPopup component, and ExperienceEditPopup component. So on each click on add(+) button there appears a popup to add person's experience details. This thing is working fine.
Also on click of edit button popup shows but without datas. Now my issue is all the added details I have to edit on click on each edit button (pencil image) based on each clicked id's (not working). how to pass ids on edit modal popups here on edit click?
Here is my code
Experience.js component
import React, {useState, useEffect, Component } from "react";
import axios from 'axios';
import { Modal } from 'react-bootstrap';
import ExperienceAddPopup from "./ExperiencePopup";
import ExperienceEditPopup from "./ExperienceEditPopup";
export default function Experience({logged_user}) {
const [modalShow, setModalShow] = React.useState(false);
const [show, setShow] = useState(false);
return (
<div className="exp-details">
{experienceArray.map((experience, i) => {
return(
<span key={experience.id}>
<div className="exph1">{experience.title} <span onClick={() => setModalShow(true)}><FontAwesomeIcon className="nav-icon float-right text-muted" icon={faPencilAlt} /></span></div>
<ExperienceEditPopup logged_user={singleUserDetail.actable_id}
show={modalShow}
onHide={() => setModalShow(false)}
/>
<div className="exph2">{experience.company_name}.</div>
<div className="exph3">{experience.start_year} - {experience.end_year}</div>
<hr/>
</span>
);
})
}
</div>
)
}
ExperienceEditPopup.js
import React, {useState, useEffect, Component } from "react";
import axios from 'axios';
import { Modal } from 'react-bootstrap';
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome';
import { faPencilAlt} from '#fortawesome/free-solid-svg-icons';
const ExperienceEditPopup = props => {
#how to get edit clicked ids based popups here?
let logged_user_id = props.logged_user;
let experience_id = # here i need to get the expereince id on each popup click
const [show, setShow] = useState(false);
const handleClose = () => setShow(false); #here i need to close popups based on edit button click
const handleShow = () => setShow(true); #here i need to show popups based on edit button click
const [experienceArray, setexperienceArray] = React.useState([]);
const getexperienceUserDetails = (experience_id, logged_user_id) => {
axios
.get(`http://localhost:3001/users/${logged_user_id}/experiences/${experience_id}`, { withCredentials: true })
.then((response) => {
const experienceArray = response.data;
setexperienceArray(experienceArray);
console.log(experienceArray);
})
.catch((error) => {
console.log(" error", error);
});
};
React.useEffect(() => {
if (experience_id) {
getexperienceUserDetails(experience_id, logged_user_id);
}
}, [experience_id,logged_user_id]);
return (
<Modal {...props} size="lg" aria-labelledby="contained-modal-title-vcenter" centered >
<div className="modal-header">
<h5 className="modal-title" id="exampleModalLongTitle">Edit Experience</h5>
<button type="button" className="close" onClick={props.onHide} aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<form onSubmit={handleExperienceEditSubmit}>
<div className="modal-body">
<input type="text" class="form-control" defaultValue={experienceArray.title} name="title" onChange={handleChange} placeholder="Designation" />
<input type="text" class="form-control" defaultValue={experienceArray.company_name} name="company_name" onChange={handleChange} placeholder="Company Name" />
<input type="text" defaultValue={experienceArray.location} class="form-control" name="location" onChange={handleChange} placeholder="Location"></input>
</div>
<div className="modal-footer">
<span className="btn_cls"><button className="btn save-btn">Save</button></span>
</div>
</form>
</Modal>
);
}
export default ExperienceEditPopup;

You have a single state for all Popups.
const [modalShow, setModalShow] = React.useState(false);
You could store the experience.id so you could render the right modal.
const [modalShow, setModalShow] = React.useState(-1);
Also, you will need to change the trigger function
<span onClick={() => setModalShow(experience.id)}>
You may pass the experience_id here
<ExperienceEditPopup
logged_user={singleUserDetail.actable_id}
show={modalShow}
onHide={() => setModalShow(false)}
/>
Something like:
<ExperienceEditPopup
logged_user={singleUserDetail.actable_id}
experience_id={experience.id}
show={modalShow === experience.id}
setModalShow={setModalShow}
onHide={() => setModalShow(-1)}
/>
Inside your ExperienceEditPopup you don't need to control the visibility of it, so you don't need:
const [show, setShow] = useState(false)
Instead of that you could use the setModalShow that you might pass as a props to it.
I think you didn't really understand the code you wrote, ask to clarify your mind before asking for solutions, it's always a better approach

Related

how to get data from input form in React js by clicking submit button and create a div element that get all data user input. {.map() Multiple states}

I'd like to create div with data getting from user input by clicking btn submit, But I don't know how. I am new in react js.
This is my App.js file:
import './App.css';
import './RegisterApp.css'
import RegisterApp from './Components/RegisterApp';
function App() {
return (
<div className="App">
<RegisterApp />
</div>
);
}
export default App;
and this is my component file RegisterApp.js:
import React, {useState} from 'react'
function RegisterApp() {
const [name, setName] = useState('Khun Neary')
const [position, setPosition] = useState('Designer')
const [list, setList] = useState({name, position})
const formSubmit = (e) => {
e.preventDefault()
setList(...list, name)
setList(...list, position)
console.log(list);
}
return (
<div className='container'>
<form className='form-box' onSubmit={formSubmit}>
<button>Upload Profile</button>
<input
type="text"
placeholder='Name...'
value={name}
onChange={(e) => setName(e.target.value)}
/>
<input
type="text"
placeholder='Position...'
value={position}
onChange={(e) => setPosition(e.target.value)}
/>
<button>Submit</button>
</form>
<div className='register-box'>
<div className='sub-reg-box'>
<div className='img-box'></div>
<div className='detail-box'>
<h2>{name}</h2>
<h4>{position}</h4>
</div>
</div>
</div>
</div>
)
}
export default RegisterApp
enter image description here
I'd like to create div element after I click submit btn and display all the data get from input by user.
add type="submit" to button
<button type="submit">Submit</button>
then update the list state
const formSubmit = (e) => {
setList( {...list, name, position })
}
you won't see the update to the list immediately since setState in asynchronous. But to check that, you can use useEffect
useEffect(() => {
console.log(list)
},[list])
You don't need to "get" the data. You already have it in the variables name and position. You should create an onClick handler for the button that uses these values.
Note that setList() is misnamed. You should use an object here. In fact, you can get rid of list and setList because you already have name, setName, position and setPosition. You don't need both.

Cannot read properties of undefined (reading 'categoryName') in react

I'm trying to update my category. I want to update only categoryname, categoryDescription and categoryImage inside the category data. For this, I pull the data from the API with the id I wrote in c# on the backend to show the first state of the data to the user, there is no problem there, but when I try to make changes to a data, nothing appears on the screen in the browser and the following errors appear in the console. Actually ı am new in react. How can I fix that problem?
ERRORS
Uncaught TypeError: Cannot read properties of undefined (reading 'categoryName')
The above error occurred in the <UpdateCategory> component:
My CategoryList.js
I send the id of the category I clicked to UpdateCategory.js from here and I do the editing there.
CategoryList.js shows only my categories
import { Button } from "bootstrap";
import React, { useContext } from "react"
import { Link, Router } from "react-router-dom";
import { CategoryContext } from "../Contexts/CategoryContext";
import "../Css/Categories.css"
export default function CategoryList() {
const { Categories } = useContext(CategoryContext)
const truncateOverview = (string, maxLength) => {
if (!string) return null;
if (string.length <= maxLength) return string;
return `${string.substring(0, maxLength)} ...`;
}
return (
<div className="categories">
{Categories.map((category, i) => (
<Link className="category" to={`/ProductList/${category.categoryId}`} key={i}>
<div className="inner-category">
<div className="image-body">
<img src={category.categoryImage} className="image" alt="" />
</div>
<div className="category-body">
<div>
<h5 className="">{category.categoryName}</h5>
<p className="">{truncateOverview(category.categoryDescription, 50)}</p>
<Link to={`/UpdateCategory/${category.categoryId}`}>
<button className ="btn btn-warning" variant="primary" >
EDIT
</button>
</Link>
</div>
</div>
</div>
</Link>
))}
</div>
)
}
My UpdateCategory.js
import React, { useState, useEffect } from 'react';
import { useParams } from 'react-router-dom';
import Button from 'react-bootstrap/Button';
import Col from 'react-bootstrap/Col';
import Form from 'react-bootstrap/Form';
import InputGroup from 'react-bootstrap/InputGroup';
import Row from 'react-bootstrap/Row';
import axios from 'axios';
export default function UpdateCategory() {
const { id } = useParams()
const url = `http://localhost:64082/api/categories/getbyid/${id}`
const [category, setCategory] = useState({})
const fetchData = () => {
axios.get(url)
.then(response => {
setCategory(response.data)
})
.catch(error => {
console.log(error)
})
}
useEffect(() => {
fetchData()
});
const handleInputChange =(e)=>{
setCategory(e.target.category)
}
const handleSubmit = (event) => {
event.preventDefault();
const data = new FormData(event.target);
fetch(`http://localhost:64082/api/categories/update`, {
method: 'POST',
body: data,
})
}
return (
<div>
<form >
<label htmlFor="inputName">Category Name</label>
<input type="text"
className="form-control"
name="categoryName"
value={category.categoryName}
onChange={handleInputChange}
/>
<label htmlFor="inputName">Category Description</label>
<input type="text"
className="form-control"
name="categoryDescription"
value={category.categoryDescription}
onChange={handleInputChange}
/>
<label htmlFor="inputName">Category Image</label>
<input type="text"
className="form-control"
name="categoryImage"
value={category.categoryImage}
onChange={handleInputChange}
/>
<div>
<button onSubmit={handleSubmit} className="btn btn-danger" >EDIT</button>
</div>
</form>
</div>
)
}
The error probably comes about because of the render in UpdateCategory. The code tells it to render three properties of the category object. But initially, category is an empty object. So it will fail there.
There's another point you need to modify though, in the useEffect(). What you have right now will not trigger on the initialisation of the component. Change it to:
useEffect(() => {
fetchData()
}, []);
Since you're not using typescript you'd also probably want some kind of guard on your render to be safe. So for example something like this:
return category.categoryName && category.categoryDescription && category.categoryImage && (
// your render code in here
);
But that's quite long-winded. You could write an if clause before the return () for the render, asking if those properties exist and returning a blank component instead, or some alternate text.
You could also perhaps provide a default version of the category when initialising the hook.
const [category, setCategory] = useState({
categoryName: 'Default',
categoryDescription: 'Default description',
categoryImage: null
});
This would be a simpler effort than the guard block, and you'd still need the useEffect update.
In UpdateCategory.js,edit;
const [category, setCategory] = useState([]) // not useState({})
The default value of this state should be an empty array. Thanks to empty array, It will not be able to return with map until data comes from api, because its length is 0.

react js myfn is not a function when called from a button

I've just started learning about react js and this is my first react js app. I'm using api to fetch the data. so far it works, but now I want to add a search keyword to the function that is acquired from a search bar component.
here's my code:
SearchBar.js
const SearchBar = ({ getUsers }) => {
return (
<div className="is-flex flex-align-items-center mb-3">
<input type="text" id="query" className="input search-input" placeholder="search keyword"/>
<Button className="search-btn ps-3 pe-3"
onClick={() => getUsers(document.querySelector('#query').value)}>
<FontAwesomeIcon icon={faMagnifyingGlass} />
</Button>
</div>
);
};
MasterUser.js
import { useState, useEffect } from "react";
import SearchBar from "./SearchBar";
const MasterUser = () => {
const [users, setUsers] = useState([]);
useEffect(() => {
getUsers();
}, []);
const getUsers = async (query='') => {
console.log('get users', query);
try {
let myurl = 'http://localhost:8080/users';
const response = await fetch(myurl);
const data = await response.json();
setUsers(data);
setIsLoading(false);
} catch (e) {
console.log(e.getMessage());
}
};
return (
<div>
<SearchBar onClick={getUsers}/>
</div>
);
};
when the app loads, the console log says get users <empty string> and it returns all the users as expected, but when I clicked on the search button (magnifyingGlass) it gives an error Uncaught TypeError: getUsers is not a function.
any help is appreciated..
<SearchBar onClick={getUsers}/>
You have named the prop onClick not getUsers. That's why you get that error.
Yeah, accessing dom element value using selectors (e.g. document.querySelector('#query').value) is also not typical react. Read about controlled form elements (save form element value in state).
Make your searchBar component more reactive like so
const SearchBar = ({ getUsers }) => {
const [searchValue,setSearchValue]=useState('');
return (
<div className="is-flex flex-align-items-center mb-3">
<input type="text" id="query" className="input search-input" placeholder="search keyword" value={searchValue} onChange={(e)=>setSearchValue(e.target.value)}/>
<Button className="search-btn ps-3 pe-3"
onClick={() => getUsers(searchValue)}>
<FontAwesomeIcon icon={faMagnifyingGlass} />
</Button>
</div>
);
};

How to enable a button with useRef() in reactJs

I created a Todo-list app in react
and inside each item placed one radio button and one edit, delete button for each and every item when a user enters in the input text.
Basically, the edit, delete buttons are disabled at first.
when a user clicks on the radio button then the particular item buttons get enabled.
But here I am getting all the buttons in each item gets enabled inside the list
import React,{useState} from 'react';
import Modal from 'react-modal';
import {AiFillDelete,AiFillEdit} from 'react-icons/ai';
import './App.css';
Modal.setAppElement('#root')
function App() {
const [todos,setTodos] = useState([{id:0,text:"item1"},{id:1,text:"item2"}])
const [todo,setTodo] = useState("")
const [todoEditing,setTodoEditing] = useState(null)
const [editingText,setEditingText] = useState("")
const [deleteItem,setDeleteItem] = useState()
const [editItem,setEditItem]=useState()
const [modalIsOpen,setModalIsOpen] = useState()
function handleSubmit(e){
e.preventDefault()
const newTodo = {
id:todos.length,
text : todo,
}
setTodos([newTodo].concat(...todos))
setTodo("")
}
function deleteTodo(id){
const updateTodos = [...todos].filter((todo)=>todo.id !== id)
setTodos(updateTodos)
}
function editTodo(id){
const updateTodos = [...todos].map((todo) => {
if(todo.id===id){
todo.text = editingText
}
return todo
})
setTodos(updateTodos)
setTodoEditing(null)
setEditingText("")
}
const handleRadioBtnItem = (event) =>{
setDeleteItem(event.target.value);
setEditItem(event.target.value);
}
return (
<div className="App">
<div className='todo-head'>
<h1 className='ForHeading'>Todo List</h1>
<form onSubmit={handleSubmit}>
<input className='User-Input' type='text' onChange={(e)=>setTodo(e.target.value)} value={todo}/>
<button className='Add-Btn' type='submit' disabled={!todo}>Add Todo</button>
</form>
</div>
{todos.map((todo)=>
<ul className='ul-Style' key={todo.id} id={todo.id}>
<input className='Rdo' type='radio' onClick={handleRadioBtnItem}/>
{todoEditing === todo.id ?
(
<div>
<Modal
isOpen={modalIsOpen}
shouldCloseOnOverlayClick={false}
style={
{
overlay:{
backgroundColor:'gray'
},
content:{
textAlign:'center'
}
}
}
>
<h2>Edit Items</h2>
<input type='text' onChange={(e)=> setEditingText(e.target.value)} value={editingText}/>
<div>
<button onClick={()=>editTodo(todo.id)} disabled=''>Save</button>
<button onClick={()=>setModalIsOpen(false)}>Close</button>
</div>
</Modal>
{todo.text}
</div>
)
:
(
<p>{todo.text}</p>
)
}
<button
className='Edit-Btn'
onClick={()=>{setTodoEditing(todo.id);setModalIsOpen(true)}}
disabled={!editItem}><AiFillEdit/>
</button>
<button
className='Del-Btn'
onClick={()=>deleteTodo(todo.id)}
disabled={!deleteItem}><AiFillDelete/>
</button>
</ul>)}
</div>
);
}
export default App;

Reactstrap innerRef not setting reference to element

I am trying to use Reactstrap 8.5.1 innerRef attribute along with useRef() to focus the Input within a Modal whenever the Modal opens. The code below shows the Button which opens the Modal, but when clicked I get the error "Cannot read property 'focus' of null". It also writes inputRef to the console, which shows that .current is null.
I've tried various ways to set innerRef, but nothing seems to work. I'd be very grateful if someone can point out to me what I am missing.
import React, { useState, useRef, useEffect } from 'react';
import { Modal, ModalHeader, ModalBody, ModalFooter, Button, Input } from 'reactstrap';
export const ModalSave = (props) => {
const [modalIsOpen, setModalIsOpen] = useState(false);
const toggle = () => setModalIsOpen(!modalIsOpen);
const inputRef = useRef(null);
useEffect(() => {
console.log(inputRef);
if (modalIsOpen === true) {
inputRef.current.focus();
}
}, [modalIsOpen]);
return (
<div>
<Button
onClick={() => { setModalIsOpen(true); }}
>Save</Button>
<Modal isOpen={modalIsOpen} toggle={toggle}>
<ModalHeader toggle={toggle}>Save</ModalHeader>
<ModalBody>
Name:
<Input
innerRef={inputRef.current}
/>
</ModalBody>
<ModalFooter>
<Button>Save</Button>
<Button onClick={toggle}>Close</Button>
</ModalFooter>
</Modal>
</div>
);
}
The issue is that opening the modal doesn't trigger the component to re-render which is needed to get the input ref value, and so, the ref will remain null unless some state is called to trigger the re-render. As a workaround, you can use setTimeout() method to kind of force it like so:
useEffect(() => {
if (modalIsOpen) {
setTimeout(() => inputRef.current.focus(), 0);
}
}, [modalIsOpen]);
A better solution is to use the onOpened method which is called after the modal has opened:
export default function App() {
const inputRef = useRef(null);
const [modalIsOpen, setModalIsOpen] = useState(false);
const toggle = () => setModalIsOpen(!modalIsOpen);
const handleOpen = () => inputRef.current.focus();
return (
<div className="App">
<div>
<Button onClick={() => setModalIsOpen(true)}>Save</Button>
<Modal isOpen={modalIsOpen} toggle={toggle} onOpened={handleOpen}>
<ModalHeader toggle={toggle}>Save</ModalHeader>
<ModalBody>
Name:
<Input innerRef={inputRef} />
</ModalBody>
<ModalFooter>
<Button>Save</Button>
<Button onClick={toggle}>Close</Button>
</ModalFooter>
</Modal>
</div>
</div>
);
}

Resources