display button upon typing input react - javascript

I want to be able to type into my input fields, and then have a button show up beside it upon typing that says submit edit. right now, I have a button that always is there, but I want it to only show up upon typing. this is all in react btw. so far, I have tried jquery, but react doesn't like it.
here's the whole page, to avoid any confusion of what I am doing and where my stuff is located.
import React, { Component } from "react";
import axios from "axios";
import "../styles/TourPage.css";
class TourPage extends Component {
constructor(props) {
super(props);
this.state = {
myData: [],
isLoading: true,
};
}
componentDidMount() {
axios
.get("/getResults")
.then((res) => {
this.setState({
myData: res.data
});
})
.catch((error) => {
// Handle the errors here
console.log(error);
})
.finally(() => {
this.setState({
isLoading: false
});
});
}
deleteById = (id) => {
console.log(id)
axios
.post(`/deleteDoc`, {id: id} )
.then(() => {
console.log(id, " worked")
window.location = "/tour"
})
.catch((error) => {
// Handle the errors here
console.log(error);
})
}
editById = (id, siteLocation, Services, cnum) => {
console.log(id, siteLocation, Services, cnum)
axios
.post(`/editDoc`, JSON.stringify({id: id, location: siteLocation, Services: Services, cnum: cnum}),{
headers: {
"Content-Type": "Application/json"
}
} )
.then(() => {
console.log(id, " worked")
window.location = "/tour"
})
.catch((error) => {
// Handle the errors here
console.log(error);
})
}
render() {
// You can handle the loader part here with isLoading flag. In this case No data found will be shown initially and then the actual data
let { myData, isLoading } = this.state;
return (
<table id="customers">
<tr>
<th>siteLocation</th>
<th>Services</th>
<th>cnum</th>
</tr>
{myData.length > 0
? myData.map(({ location, Services, cnum, _id }, index) => (
<tr key={index}>
<td><input type="text" placeholder={location} name="location" id="location" /> </td>
<td><input type="text" placeholder={Services} name="Services" id="Services" /> </td>
<td><input type="text" placeholder={cnum} name="cnumhide" id="cnumhide" /> </td>
<td><input type="hidden" placeholder={cnum} name="cnum" id="cnum" /> </td>
<button
onClick={(e) => {
e.preventDefault();
this.deleteById(_id);
}}
disabled={isLoading}
>
Delete
</button>
<button
onClick={(e) => {
e.preventDefault();
var siteLocation = document.getElementById('location').value
var Services = document.getElementById('Services').value
var cnum = document.getElementById('cnum').value
this.editById(_id, siteLocation, Services, cnum)
}}
>
Submit Edit
</button>
</tr>
))
: "No Data Found"}
</table>
);
}
}
const script = document. createElement("script"); $('input').keyup(function(){
if($.trim(this.value).length > 0)
$('#location').show()
else
$('#location').hide()
});
export default TourPage;
thanks 4 the help in advance.

You can use onfocus() in the text element. If you want to hide the button, use onfocusout() or in case if you want to track only after input has changed, use onchange() event
...
//class function
onTyping =()=>{
this.setState({
showSubmit:true
})
}
...
//render part
render(){
...
//input which you want to track typing
<input type="text" onfocus={()=>this.onTyping()} placeholder={location} name="location" id="location" />
...
//element submit button
{this.state.showSubmit && <button
onClick={(e) => {
e.preventDefault();
var siteLocation = document.getElementById('location').value
var Services = document.getElementById('Services').value
var cnum = document.getElementById('cnum').value
this.editById(_id, siteLocation, Services, cnum)
}}
>
Submit Edit
</button>}
...

Here is an example that helps you,
const {
useState
} = React;
const Test = () => {
const [show, setShow] = useState(false);
const handleChange = (event) => {
if (event.target.value.length > 0)
setShow(true);
else
setShow(false)
}
return ( <div>
<input type = "text"
onChange = {
(event) => handleChange(event)
}/>
{show && < button > Submit changes now! </button>}
</div>
)
}
ReactDOM.render( < Test / > ,
document.getElementById('root')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>

There is a way to avoid jquery and continue using your react class component to achieve this.
Map over state.myData to render each item with an input and a button.
Use the array index with the input's onChange event callback to add the inputValue into the correct array item's object within state.
Use the array index with the button's onClick event callback to get the item from state.myData before sending it to the server.
If there is an inputValue for the item, you can conditionally render the button.
import React, { Component } from "react";
import axios from "axios";
class TourPage extends Component {
constructor(props) {
super(props);
this.state = {
myData: [],
isLoading: true
};
}
componentDidMount() {
axios
.get("https://rickandmortyapi.com/api/character")
.then((res) => {
this.setState({
myData: res.data.results
});
})
.finally(() => {
this.setState({
isLoading: false
});
});
}
handleChangeInput = ({ target }, index) => {
const newData = [...this.state.myData];
newData[index].inputValue = target.value;
this.setState({
myData: newData
});
};
handleSubmitEdit = (index) => {
const item = this.state.myData[index];
// submit the edit to the api
console.log(
`Clicked on 'submit edit' for ${item.name} with value ${item.inputValue}`
);
};
render() {
let { myData, isLoading } = this.state;
if (isLoading) {
return "loading...";
}
return (
<div>
{myData.map(({ name, status, species, inputValue }, index) => {
return (
<div key={index}>
<p>{`${name} - ${species} - ${status}`}</p>
<input
type="text"
onChange={(e) => this.handleChangeInput(e, index)}
value={inputValue || ""}
/>
{inputValue && (
<button onClick={() => this.handleSubmitEdit(index)}>
Submit Edit
</button>
)}
</div>
);
})}
</div>
);
}
}
export default TourPage;
If you wanted to have an input per field within each row, you could make some small changes and save your edits to the item's state within a nested object.
Then you could check if there was anything inside that row's edits object to conditionally show the submit button per row.
import React, { Component } from "react";
import axios from "axios";
import isEmpty from "lodash.isempty";
import pick from "lodash.pick";
class TourPage extends Component {
constructor(props) {
super(props);
this.state = {
myData: [],
isLoading: true
};
}
componentDidMount() {
axios
.get("https://rickandmortyapi.com/api/character")
.then((res) => {
this.setState({
// here we create an empty 'edits' object for each row
myData: res.data.results.map((d) => ({
...pick(d, ["name", "status", "species"]),
edits: {}
}))
});
})
.finally(() => {
this.setState({
isLoading: false
});
});
}
handleChangeInput = ({ target }, index) => {
const newData = [...this.state.myData];
const { value, name } = target;
newData[index].edits[name] = value;
this.setState({
myData: newData
});
};
handleSubmitEdit = (index) => {
const item = this.state.myData[index];
// submit the edit to the api
console.log(`Clicked on 'submit edit' for ${item.name} with edits:`);
console.log(item.edits);
console.log("Updated item: ");
const { edits, ...orig } = item;
const newItem = { ...orig, ...edits };
console.log(newItem);
// Once saved to api, we can update myData with newItem
// and reset edits
const newData = [...this.state.myData];
newData[index] = { ...newItem, edits: {} };
this.setState({
myData: newData
});
};
showButton = (index) => {
const { edits } = this.state.myData[index];
return !isEmpty(edits);
};
render() {
let { myData, isLoading } = this.state;
if (isLoading) {
return "loading...";
}
return (
<table>
<tbody>
{myData.map((row, index) => {
const { edits, ...restRow } = row;
return (
<tr key={index}>
{Object.keys(restRow).map((col) => {
return (
<td>
<label>
{col}:
<input
name={col}
value={edits[col] || restRow[col]}
onChange={(e) => this.handleChangeInput(e, index)}
/>
</label>
</td>
);
})}
<td>
{this.showButton(index) && (
<button onClick={() => this.handleSubmitEdit(index)}>
Submit Edit
</button>
)}
</td>
</tr>
);
})}
</tbody>
</table>
);
}
}
export default TourPage;

Related

Is there a way to use input data to search through an API response?

I am current working on a web application that when you input your search data and click push search, it will retrieve the data from the api. I was hoping someone can help me by showing me how to use the user input in order to map the response based on the user input and render in on the screen. Nothing is rendering
import axios from 'axios'
import React, { Component } from 'react'
import '../App.css'
import GasStationList from './GasStationsList'
class Search extends Component {
constructor(props) {
super(props)
this.state = {
initialStations: [],
stations: [],
search: ''
}
}
componentDidMount(){
axios.get('http://api.eia.gov/series/?api_key=9b0550c7825c207680e9b8bcc661f666&series_id=TOTAL.MGUCUUS.M')
.then(response => {
this.setState({initialStations: response.data});
console.log(response)
}).catch(error =>{
console.log(error)
this.setState({
errorMsg: 'Error Retriving Data'
})
});
}
handleSearchChange = value => {
const stationsFiltered = this.state.initialStations.filter(initialStation => initialStation.name.includes(value));
this.setState({stations: stationsFiltered, search: value});
console.log(this.state.filter)
}
clickHandler = () => {
this.setState({
stations: this.state.stationsFiltered
})
}
render(){
return (
<div>
<div className='search-container'>
<label> Station Search </label>
<input
type='text'
name='search'
value={this.search}
onChange={this.handleSearchChange}
/>
</div>
<button
onClick={() => this.clickHandler()}
className='Search-Button'>
Search
</button>
<GasStationList stations={this.state.stations}/>
</div>
)
}
}
export default Search;
Supposing that stations objects have a property called name to be filtered, you could use filter function every time input value changes. Something like:
handleSearchChange = value => {
const stationsFiltered = this.state.station.filter(stat => stat.name.includes(value));
this.setState({stations: stationsFiltered, search: value});
}
Note that this solutions overrides stations every time you made a search. This is not good (every time you have to re-fetch all the unfiltered stations).
Why don't you store initial stations and then filter them saving search result in another state variable. Something like:
class Search extends Component {
constructor(props) {
super(props)
this.state = {
initialStations: [],
stations: [],
search: ''
}
}
handleSearchChange = value => {
// now we are filtering always initialStations and store data filtered in stations
const stationsFiltered = this.state.initialStations.filter(stat => stat.name.includes(value));
this.setState({stations: stationsFiltered, search: value});
}
clickHandler = () => {
axios.get('http://api.eia.gov/series/?api_key=9b0550c7825c207680e9b8bcc661f666&series_id=TOTAL.MGUCUUS.M')
.then(response => {
this.setState({initialStations: response.data, stations: response.data});
console.log(response)
}).catch(error =>{
console.log(error)
this.setState({
errorMsg: 'Error Retriving Data'
})
});
}
render(){
return (
<div>
<div className='search-container'>
<label> Station Search </label>
<input
type='text'
name='search'
value={this.state.search}
onChange={this.handleSearchChange}
/>
</div>
<button
onClick={() => this.clickHandler()}
className='Search-Button'>
Search
</button>
<GasStationList />
<div key={this.series_id}>this the gas stations{`${this.state.data} ${this.state.series_id}`}
and their Id
</div>
{this.state.stations}
</div>
)
}
}
export default Search;
But there is another problem. You could say "if value is empty, I want to show all the stations". To do that, you should change handleSearchChange in this way:
handleSearchChange = value => {
if (value === '') {
this.setState({stations: initialStations, search: value});
}
else {
const stationsFiltered = this.state.initialStations.filter(stat => stat.name.includes(value));
this.setState({stations: stationsFiltered, search: value});
}
}
EDIT
After you change code I see that you are using this.state.stationsFiltered in clickHandler but stationsFiltered is not on state. From your code I also see that you want a behaviour like that:
I make a search (writing something on input);
Then I click button and stations are shown;
Ok so I suggest something like this:
import axios from 'axios'
import React, { Component } from 'react'
import '../App.css'
import GasStationList from './GasStationsList'
class Search extends Component {
constructor(props) {
super(props)
this.state = {
initialStations: [], //<-- this is the entire list of available stations (from fetch)
stations: [], //<-- this is the array with all the filtered stations
search: ''
}
}
componentDidMount(){
axios.get('http://api.eia.gov/series/?api_key=9b0550c7825c207680e9b8bcc661f666&series_id=TOTAL.MGUCUUS.M')
.then(response => {
this.setState({initialStations: response.data});
console.log(response)
}).catch(error =>{
console.log(error)
this.setState({
errorMsg: 'Error Retriving Data'
})
});
}
handleSearchChange = value => {
// here we just set the seach value with what you typed on input
this.setState({search: value});
}
clickHandler = () => {
// here we filter initialStations on name property using this.state.search
const stationsFiltered = this.state.initialStations.filter(initialStation => initialStation.name.includes(this.state.search));
this.setState({
stations: stationsFiltered
})
}
render(){
return (
<div>
<div className='search-container'>
<label> Station Search </label>
<input
type='text'
name='search'
value={this.state.search} // <-- note: here is this.state.search (in your code is written this.search and is not correct)
onChange={this.handleSearchChange}
/>
</div>
<button
onClick={() => this.clickHandler()}
className='Search-Button'>
Search
</button>
<GasStationList stations={this.state.stations}/>
</div>
)
}
}
export default Search;

How to toggle a button in React list?

I render a React list and there is an Edit button in each list item. I wanted to toggle to switch from the data to the input form and back. Similar to this application in this article: https://medium.com/the-andela-way/handling-user-input-in-react-crud-1396e51a70bf. You can check out the demo at: https://codesandbox.io/s/fragrant-tree-0t13x.
This is where my React Component display the list:
import React, { Component } from "react";
import PriceBox from "../SinglePricebox/index";
// import SecurityForm from "../SecurityForm/index";
import AddPriceForm from "../AddPriceForm/index";
// import { uuid } from "uuidv4";
export default class PriceForm extends Component {
constructor(props) {
super(props);
this.state = {
priceArr: this.props.pricelist,
// newPriceArr: this.props.updatePrice,
showPricePopup: false,
addPricePopup: false,
isToggleOn: true,
date: props.date || "",
number: props.number || ""
};
}
updateInput = ({ target: { name, value } }) =>
this.setState({ [name]: value });
togglePopup = () => {
this.setState(prevState => ({
showPopup: !prevState.showPopup
}));
};
togglePricePopup = () => {
this.setState(prevState => ({
showPricePopup: !prevState.showPricePopup
}));
};
addPricePopup = () => {
this.setState(prevState => ({
addPricePopup: !prevState.addPricePopup
}));
};
/* adds a new price to the list */
addPrice = newPrice => {
this.setState(prevState => ({
addPricePopup: !prevState.addPricePopup,
// spreads out the previous list and adds the new price with a unique id
priceArr: [...prevState.priceArr, { ...newPrice }]
}));
};
// handlePriceSubmission = () => {
// const { updatePrice } = this.props;
// this.addPricePopup();
// updatePrice(priceArr);
// };
toggleItemEditing = (index) => {
this.setState(prevState => ({
priceArr: prevState.priceArr.map(priceItem => {
// isToggleOn: !state.isToggleOn;
})
}));
};
// toggleItemEditing = index => {
// this.setState({
// items: this.state.items.map((item, itemIndex) => {
// if (itemIndex === index) {
// return {
// ...item,
// isEditing: !item.isEditing
// }
// }
// return item;
// })
// });
// };
render() {
// const { updatePrice } = this.props;
return (
<div className="popup">
<div className="popup-inner">
<div className="price-form">
<h2>Prices</h2>
<div className="scroll-box">
{this.state.priceArr.map((props) => (
<PriceBox
{...props}
key={props.date}
// toggleItemEditing={this.toggleItemEditing()}
onChange={this.handleItemUpdate}
/>
))}
</div>
<div className="buttons-box flex-content-between">
<button
type="button"
onClick={this.addPricePopup}
className="btn add-button">Add +</button>
{this.state.addPricePopup && (
<AddPriceForm
addPrice={this.addPrice}
cancelPopup={this.addPricePopup}
/>
)}
<div className="add-btns">
<button
type="button"
onClick={() => this.props.closeUpdatePopup()}
className="btn cancel-button"
>
Close
</button>
</div>
</div>
</div>
</div>
</div>
);
}
}
The list inside the component above is:
<div className="scroll-box">
{this.state.priceArr.map((props) => (
<PriceBox
{...props}
key={props.date}
// toggleItemEditing={this.toggleItemEditing()}
onChange={this.handleItemUpdate}
/>
))}
</div>
And this is the single list item:
import React, { Component } from "react";
export default class SinglePricebox extends Component {
state = {
showPopup: false, //don't show popup
todaydate: this.props.date
};
/* toggle and close popup edit form window */
togglePopup = () => {
this.setState(prevState => ({
showPopup: !prevState.showPopup
}));
};
toggleEditPriceSubmission = getPriceIndex => {
const { toggleItemEditing, date } = this.props;
// toggle the pop up (close)
// this.showPopup();
toggleItemEditing({ ...getPriceIndex, date });
console.log("date?", date);
};
/* handles edit current security form submissions */
// handleEditSecuritySubmission = editSecurity => {
// const { editCurrentSecurity, id } = this.props;
// // toggle the pop up (close)
// this.togglePopup();
// // sends the editSecurity fields (name, isin, country) + id back to
// // App's "this.editCurrentSecurity"
// editCurrentSecurity({ ...editSecurity, id });
// };
render() {
return (
<div className="pricebox">
<article className="pricetable">
{this.toggleEditPriceSubmission
? "editing" : "not editing"}
<table>
<tbody>
<tr>
<td className="date-width">{this.props.date}</td>
<td className="price-width">{this.props.number}</td>
<td className="editing-btn">
<button
type="button"
className="edit-btn"
onClick={this.toggleEditPriceSubmission}
>
{this.toggleEditPriceSubmission ? "Save" : "Edit"}
</button>
</td>
<td>
<button
type="button"
className="delete-btn">
X
</button>
</td>
</tr>
</tbody>
</table>
</article>
</div>
);
}
}
I have been struggling all afternoon to toggle the edit button in each list item. I was attempting to get the key of each list item which is the this.prop.date.
You can see my code in detail at CodeSandBox: https://codesandbox.io/s/github/kikidesignnet/caissa
I would create a component which will handle the list item as a form and update it as if it was SecurityForm.
{this.state.priceArr.map((props) => {
if(props) {
return <PriceListForm methodToUpdate {...props} />
}else {
retun (
<PriceBox
{...props}
key={props.date}
// toggleItemEditing={this.toggleItemEditing()}
onChange={this.handleItemUpdate}
/>
);
}
})}
and make PriceListForm look like PriceBox but use inputs to capture new data. This way you will have two different components with less complicated logic instead of having a huge component with complex validations to check if you will display an input or not.
create a funtion named Edit to update the State
Edit = (id) => {
this.setState({edit: !this.state.edit, id})
}
and instead of that
<td className="country-width">{this.props.country}</td>
do something like
<td className="country-width">{this.state.edit ? <input type="text" value={this.props.country} onChange={() => UPDATE_THE_CONTENT}/> : this.props.isin}</td>
and call the Edit function onclick of Edit Button and pass ID of that td as a param to update it.
NOTE: by default THE VALUE OF EDIT STATE is false/null.
you can use onChange to update that box or some other techniques like create a button along with it and use that to update it.
hope this might help you

React: How to send data on Popup close?

I have this Popup that I built in my React app. It's, in a sense, another page inside the Popup. In this Popup, there is a list. There are no forms at all in this Popup window. I only have another popup inside this popup with a submission form that adds another item to the list.
Therefore, what I'm attempting to do is to submit the list to the Parent component when I click on the "Close" button to close the Popup window. How do I do that?
Here's my code:
SingleBox.js
import React, { Component } from "react";
import SecurityForm from "../SecurityForm/index";
import PriceForm from "../PriceForm/index";
export default class SingleSecuritybox extends Component {
constructor(props) {
super(props);
this.state = {
showPopup: false,
showPricePopup: false, //don't show popup
pricelist: this.props.price
};
}
/* toggle and close popup edit form window */
togglePopup = () => {
this.setState(prevState => ({
showPopup: !prevState.showPopup
}));
};
togglePricePopup = () => {
this.setState(prevState => ({
showPricePopup: !prevState.showPricePopup
}));
};
/* handles edit current security form submissions */
handleEditSecuritySubmission = editSecurity => {
const { editCurrentSecurity, id } = this.props;
this.togglePopup();
editCurrentSecurity({ ...editSecurity, id });
};
updatePrice = updatePrice => {
const { updatePriceList, id } = this.props;
this.togglePricePopup();
updatePriceList({...updatePrice, id});
console.log("editing price", updatePrice);
};
/* handles delete current security form submissions */
handleDeleteSecurity = () => {
const { deleteSecurity, id } = this.props;
// toggle the pop up (close)
this.togglePopup();
// sends the id back to App's "this.deleteSecurity"
deleteSecurity(id);
};
render() {
return (
<div className="box">
<article className="securitytable">
<div className="title">
<h2>
<strong>{this.props.name}</strong>
</h2>
<hr className="lightgray-line" />
</div>
<table>
<tbody>
<tr>
<td className="isin-width">{this.props.isin}</td>
<td className="country-width">{this.props.country}</td>
<td>
<button type="button" className="price-btn" onClick={this.togglePricePopup}>Prices</button>
{this.state.showPricePopup ? (
<PriceForm
pricelist= {this.props.price}
updatePrice={ this.updatePrice }
addPrice={this.props.addPrice}
closePopup= {this.togglePricePopup}
/>
) : null}
</td>
<td className="editing-btn">
<button
type="button"
className="edit-btn"
onClick={this.togglePopup}
>
Edit
</button>
{this.state.showPopup ? (
<SecurityForm
{...this.props}
handleEditSecuritySubmission={ this.handleEditSecuritySubmission }
handleDeleteSecurity={this.handleDeleteSecurity}
cancelPopup={this.togglePopup}
/>
) : null}
</td>
</tr>
</tbody>
</table>
</article>
</div>
);
}
}
This code in question is this list that'll open in Popup window which is a child componenet:
<button type="button" className="price-btn" onClick={this.togglePricePopup}>Prices</button>
{this.state.showPricePopup ? (
<PriceForm
pricelist= {this.props.price}
updatePrice={ this.updatePrice }
addPrice={this.props.addPrice}
closePopup= {this.togglePricePopup}
/>
) : null}
In this child component, which is Price Popup:
import React, { Component } from "react";
import PriceBox from "../SinglePricebox/index";
import AddPriceForm from "../AddPriceForm/index";
export default class PriceForm extends Component {
constructor(props) {
super(props);
this.state = {
priceArr: this.props.pricelist,
showPricePopup: false,
addPricePopup: false,
isToggleOn: true,
date: props.date || "",
number: props.number || ""
};
}
updateInput = ({ target: { name, value } }) =>
this.setState({ [name]: value });
togglePopup = () => {
this.setState(prevState => ({
showPopup: !prevState.showPopup
}));
};
togglePricePopup = () => {
this.setState(prevState => ({
showPricePopup: !prevState.showPricePopup
}));
};
addPricePopup = () => {
this.setState(prevState => ({
addPricePopup: !prevState.addPricePopup
}));
};
/* adds a new price to the list */
addPrice = newPrice => {
this.setState(prevState => ({
addPricePopup: !prevState.addPricePopup,
// spreads out the previous list and adds the new price with a unique id
priceArr: [...prevState.priceArr, { ...newPrice }]
}));
};
handleListSubmit = () => {
const { priceArr } = this.state;
const { updatePrice } = this.props;
const fields = {priceArr};
this.setState(() => {
// if (addPrice) addPrice(fields);
updatePrice(fields);
});
console.log("submission", fields);
};
render() {
return (
<div className="popup">
<div className="popup-inner">
<div className="price-form">
<h2>Prices</h2>
<div className="scroll-box">
{this.state.priceArr.map((props) => (
<PriceBox
{...props}
key={props.date}
/>
))}
</div>
<div className="buttons-box flex-content-between">
<button
type="button"
onClick={this.addPricePopup}
className="btn add-button">Add +</button>
{this.state.addPricePopup && (
<AddPriceForm
addPrice={this.addPrice}
cancelPopup={this.addPricePopup}
/>
)}
<div className="add-btns">
<button
type="button"
onClick={this.handleListSubmit}
className="btn cancel-button"
>
Close
</button>
</div>
</div>
</div>
</div>
</div>
);
}
}
What I'm attempting to do is to send the data (the list array) back to the Parent component on close, but I notice that I can send the data back but I couldn't close the window...
<button
type="button"
onClick={this.handleListSubmit}
className="btn cancel-button"
>
Close
</button>
How do I do this? I cannot add something like this.props.closePopup(); in the handleListSubmit function because while it can close the window, it prevents the list array from being submitted and passed to the Parent component.
You can use parent callback function to send data from child to parent.
on child
handleListSubmit = () => {
...
this.props.onSummited(data)
}

Rendering One Instance of Component in React

I am trying to create an application that allows users to add patient records that contain name, phone and diagnosis. It gives them the ability to add new patients, edit current patients or entirely delete them.
I am rendering the name, phone, diagnosis, the edit-diganosis button and the EditDiagnosis component. The EditDiagnosis component renders a form in which the input fields are loaded already with the values entered by the user so that they can edit it.
The problem is that if I have more than 2 patient records and I click on the edit-diganosis button on the second record, it loads two instances of the EditDiagnosis component, one under the second record and that is intended and one under the first record and that is not what I want. I only want when I click on the edit-diagnosis button of a certain record, it only opens the form to edit this record, not all records.
I know I am doing something wrong but I cannot figure out how to associate each button with its own record.
The components I need help with are: PatientDiagnosis and EditDiagnosis
import React from 'react';
import Header from './Header';
import PatientDiagnosis from './PatientDiagnosis';
import AddPatient from './AddPatient';
import DiagnosisForm from './DiagnosisForm';
import EditDiagnosis from './EditDiagnosis';
export default class App extends React.Component {
state = {
patientsList: [],
inEditMode: false,
allowDelete: false
}
componentDidMount() {
try {
const data = localStorage.getItem('patientsList');
const parsedData = JSON.parse(data);
if (parsedData) {
this.setState(() => ({ patientsList: parsedData }));
}
} catch (e) {
}
}
componentDidUpdate(undefined, prevState) {
if (prevState.patientsList.length !== this.state.patientsList.length) {
const data = JSON.stringify(this.state.patientsList);
localStorage.setItem('patientsList', data);
}
}
addDiagnose = (patient) => {
if(patient.name && patient.phone && patient.diagnosis) {
this.setState((prevState) => ({ patientsList: prevState.patientsList.concat(patient)}));
}
else {
return 'Please fill out all the fields to continue.';
}
}
editDiagnose = (patient, id) => {
if (!patient.name || !patient.phone || !patient.diagnosis) {
return "Please fill out all the fields to continue.";
}
this.setState(prevState => {
prevState.patientsList[id] = patient;
return {
patientsList: prevState.patientsList,
inEditMode: !this.state.inEditMode
}
}
)
}
toggleEdit = (id) => {
console.log(id);
this.setState(() => ({inEditMode: !this.state.inEditMode}))
}
deleteDiagnose = (patientToDelete) => {
console.log(patientToDelete);
this.setState(
(prevState) => {
prevState.patientsList.map(patient => console.log(patient));
return {patientsList: prevState.patientsList.filter(patient => patientToDelete !==patient)};
}
)
}
render() {
return (
<div className="container">
<Header />
<PatientDiagnosis
patientsList={this.state.patientsList}
toggleEdit={this.toggleEdit}
inEditMode={this.state.inEditMode}
deleteDiagnose={this.deleteDiagnose}
editDiagnose={this.editDiagnose} />
<AddPatient addDiagnose={this.addDiagnose} />
</div>
)
}
}
const PatientDiagnosis = ({patientsList, deleteDiagnose, editDiagnose, inEditMode, toggleEdit}) => (
<div>
{patientsList.map((patient, index) => {
console.log(inEditMode);
return (
<div key={index}>
<h1>{patient.name}</h1>
<h1>{patient.phone}</h1>
<h1>{patient.diagnosis}</h1>
<button onClick={e => {e.preventDefault(); toggleEdit(patient)}}>Edit Diagnosis</button>
{inEditMode && <EditDiagnosis id={index} patient={patient} editDiagnose={editDiagnose} deleteDiagnose={deleteDiagnose}/>}
</div>
)
})}
</div>
)
export default PatientDiagnosis;
export default class EditDiagnosis extends React.Component {
state = {
id: this.props.id,
name: this.props.patient.name,
phone: this.props.patient.phone,
diagnosis: this.props.patient.diagnosis,
error: undefined
}
editDiagnose = (e, id) => {
e.preventDefault();
id = this.state.id;
const name = this.state.name;
const phone = this.state.phone;
const diagnosis = this.state.diagnosis;
const patient = {name, phone, diagnosis}
const error = this.props.editDiagnose(patient, id);
this.setState(()=>({error}));
if (!error) {
e.target.elements.name.value = '';
e.target.elements.phone.value = '';
e.target.elements.diagnosis.value = '';
}
}
toggleEdit = (e) => {
e.preventDefault();
this.setState(() => ({allowEdit: !this.state.allowEdit}))
}
changeName = (e) => {
const name = e.target.value;
this.setState(() => ({name}))
}
changePhone = (e) => {
const phone = e.target.value;
this.setState(() => ({phone}))
}
changeDiagnosis = (e) => {
const diagnosis = e.target.value;
this.setState(() => ({diagnosis}))
}
render() {
return (
<div>
{this.state.error && <p className="alert alert-danger" role="alert">{this.state.error}</p>}
{
<form
className="add-option"
id="patient-form"
onSubmit={this.editDiagnose}>
<DiagnosisForm
changeName={this.changeName}
changePhone={this.changePhone}
changeDiagnosis={this.changeDiagnosis}
name={this.state.name}
phone={this.state.phone}
diagnosis={this.state.diagnosis}
editDiagnose={this.editDiagnose}/>
<button className="btn btn-success" type="submit">Save Changes</button>
<button
className="btn btn-danger"
onClick={
e => {
e.preventDefault();
console.log(this.props.deleteDiagnose);
this.props.deleteDiagnose(this.props.patient);
}
}>Delete Diagnosis</button>
</form>}
</div>
)
}
}
export default class AddPatient extends React.Component {
state = {
error: undefined,
addNewPatient: false
}
addNewPatient = (e) => {
e.preventDefault();
this.setState(() => ({addNewPatient: !this.state.addNewPatient}));
}
cancelForm = (e) => {
e.preventDefault();
this.setState(() => ({error: undefined, addNewPatient: !this.state.addNewPatient}));
}
handleAddOption = (e) => {
e.preventDefault();
const name = e.target.elements.name.value.trim().toLowerCase();
const phone = e.target.elements.phone.value.trim().toLowerCase();
const diagnosis = e.target.elements.diagnosis.value.trim().toLowerCase();
const patient = {name, phone, diagnosis};
const error = this.props.addDiagnose(patient);
// this.setState(()=>({error, patientId: this.state.patientId + 1}));
if(patient.name && patient.phone && patient.diagnosis) {
this.setState(()=>({addNewPatient: !this.state.addNewPatient}));
}
if (!error) {
e.target.elements.name.value = '';
e.target.elements.phone.value = '';
e.target.elements.diagnosis.value = '';
}
}
render() {
return (
<div className="add-patient">
{this.state.error && <p className="alert alert-danger" role="alert">{this.state.error}</p>}
{!this.state.addNewPatient && <button onClick={this.addNewPatient} className="btn btn-primary">Add A New Patient</button>}
{this.state.addNewPatient &&
<form
className="add-option"
id="patient-form"
onSubmit={this.handleAddOption}>
<DiagnosisForm handleAddOption={this.handleAddOption}/>
<button className="btn btn-primary patient-form__btn" type="submit">Save</button>
<button onClick={this.cancelForm} className="btn btn-warning">Cancel</button>
</form>
}
</div>
)
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.0/umd/react-dom.production.min.js"></script>

i cant transfer data from react child to parent ang during click on child set value of input in parent

it is my first React App
i want create simple typeahead(autocomplete)
i want when i click on searched list of item, this item will show in value of my Parent input
now my click doesnt work, working only search by name
it is my parent
`
import React, { Component } from 'react';
import logo from './logo.svg';
import './Search.css';
import Sugg from './Sugg';
class Search extends Component {
constructor(props) {
super(props);
this.onSearch = this.onSearch.bind(this);
this.handleClickedItem = this.handleClickedItem.bind(this);
this.onClick = this.onClick.bind(this);
this.state = {
companies: [],
searchedList: [],
value: ''
}
}
componentDidMount() {
this.fetchApi();
console.log(this.state.companies);
}
fetchApi = ()=> {
const url = 'https://autocomplete.clearbit.com/v1/companies/suggest?query={companyName}';
fetch(url)
.then( (response) => {
let myData = response.json()
return myData;
})
.then((value) => {
let companies = value.map((company, i) => {
this.setState({
companies: [...this.state.companies, company]
})
})
console.log(this.state.companies);
});
}
onSearch(arr){
// this.setState({companies: arr});
};
handleInputChange = () => {
console.log(this.search.value);
let searched = [];
this.state.companies.map((company, i) => {
console.log(company.name);
console.log(company.domain);
const tempName = company.name.toLowerCase();
const tempDomain = company.domain.toLowerCase();
if(tempName.includes(this.search.value.toLowerCase()) || tempDomain.includes(this.search.value.toLowerCase())) {
searched.push(company);
}
})
console.log(searched);
this.setState({
searchedList: searched
})
if(this.search.value == '') {
this.setState({
searchedList: []
})
}
}
handleClickedItem(data) {
console.log(data);
}
onClick = e => {
console.log(e.target.value)
this.setState({ value: e.target.value});
};
render() {
return (
<div className="Search">
<header className="Search-header">
<img src={logo} className="Search-logo" alt="logo" />
<h1 className="Search-title">Welcome to React</h1>
</header>
<form>
<input
placeholder="Search for..."
ref={input => this.search = input}
onChange={this.handleInputChange}
/>
<Sugg searchedList={this.state.searchedList} onClick={this.onClick.bind(this)} />
</form>
</div>
);
}
}
export default Search;
`
and here my child component
i dont know how call correctly click event
import React from 'react';
const Sugg = (props) => {
console.log(props);
const options = props.searchedList.map((company, i) => (
<div key={i} >
<p onClick={() => this.props.onClick(this.props)}>{company.name}</p>
</div>
))
console.log(options);
return <div >{options}</div>
}
export default Sugg;
please help me who knows how it works
thanks a lot
In the parent you could modify your code:
onClick = company => {
console.log('company', company);
this.setState({ value: company.name});
};
and you don't need to bind this because onClick is an arrow function
<Sugg searchedList={this.state.searchedList} onClick={this.onClick} />
and in the child component, you need to use props from the parameters, not from the this context:
<p onClick={() =>props.onClick(company)}>{company.name}</p>

Categories

Resources