Input value in child component not being updated by setState - javascript

In this app, I'm fetching images from the Unsplash API (with an Express back end, React front end). On page load, general images appear (rendered inside the react-infinite-scroll-component), and if you search, a special fetch method (fetchSearchImages) is called to get new images. In either case, they're rendered in a react-infinite-scroll-component instance.
My problem is that after the form holding the search input is submitted, the search input isn't getting cleared. In the input I have value={props.inputValue}, and in the parent component, after the form is submitted fetchSearchImages is called. In fetchSearchImages, I'm trying to reset the input value with this.setState() but the value displayed in the input remains unchanged. I also tried to do so in the handleSubmit() else block, and that didn't do anything either.
View live | GitHub repo
Child search input component:
const SearchInput = props => {
const onSubmit = e => {
// Prevents GET request/page refresh on submit
e.preventDefault();
props.onFormSubmit();
};
return (
<form onSubmit={onSubmit}>
<div className="control">
<input autoFocus value={props.inputValue} onChange={e => props.onSearch(e.target.value)} className="input" type="text" placeholder="Search" />
</div>
</form>
);
}
Parent component:
export class Images extends Component {
state = {
images: [],
searchImages: [],
count: 4,
page: 1,
searchPage: 1,
term: '',
search: false,
newSearch: false,
blankSearch: false,
inputValue: ''
};
componentDidMount() {
const { page, count } = this.state;
axios
.get(`/api/photos?page=${page}&count=${count}`)
.then(res => this.setState({ images: res.data }));
// To prevent same images being fetched upon scrolling (in the first call to fetchImages)
this.setState({ page: page + count });
}
fetchImages = () => {
const { page, count, images } = this.state;
this.setState({ page: page + 1 });
axios
.get(`/api/photos?page=${page}&count=${count}`)
.then(res =>
this.setState({ images: images.concat(res.data) })
);
}
fetchSearchImages = () => {
const { searchPage, count, term, searchImages } = this.state;
this.setState({ searchPage: searchPage + 1, inputValue: '' });
axios
.get(`/api/photos/search?term=${term}&page=${searchPage}&count=${count}`)
.then(res =>
this.setState({
searchImages: searchImages.concat(res.data.results)
})
);
}
// Necessary to place fetchSearchImages in a setState callback to ensure other state is set first
handleSubmit = () => {
if (!this.state.inputValue) {
this.setState({
images: [],
blankSearch: true,
newSearch: false,
search: false,
searchImages: [],
searchPage: 1,
page: 1,
}, this.fetchImages);
} else {
this.setState({
term: this.state.inputValue,
searchImages: [],
searchPage: 1,
page: 1,
search: true,
newSearch: true
}, this.fetchSearchImages);
}
}
handleInputChange = (e) => {
this.setState({
inputValue: e
});
}
render() {
return (
<>
<SearchInput onSearch={this.handleInputChange} value={this.state.inputValue} onFormSubmit={this.handleSubmit} />
<div className="images">
<InfiniteScroll
dataLength={this.state.blankSearch ? this.state.images.length : (this.state.newSearch || this.state.search) ? this.state.searchImages.length : this.state.images.length}
next={this.state.search ? this.fetchSearchImages : this.fetchImages}
hasMore={true}
loader={
<div className="loader-dots">
<span className="loader-dot"></span>
<span className="loader-dot"></span>
<span className="loader-dot"></span>
<span className="loader-dot"></span>
</div>
}
>
{this.state.newSearch || this.state.search ? this.state.searchImages.map(image =>
<Image key={image.id + Math.random()} image={image} />
) : this.state.blankSearch ? this.state.images.map(image =>
<Image key={image.id + Math.random()} image={image} />
) : this.state.images.map(image =>
<Image key={image.id + Math.random()} image={image} />
)}
</InfiniteScroll>
</div>
</>
);
}
}

It doesn't look like your input is being properly controlled.
In your SearchInput component, you are referencing an invalid prop. You call the prop value in the parent, but reference it as inputValue in the child.
Change the input to:
<input autoFocus value={props.value} onChange={e => props.onSearch(e.target.value)} className="input" type="text" placeholder="Search" />
Or the parent to:
<SearchInput onSearch={this.handleInputChange} inputValue={this.state.inputValue} onFormSubmit={this.handleSubmit} />

Related

Why isn't my child component updating data when changing the state in React?

I have a list of users and I want to display in another component on the same page the user data in input fields for every user that I click on.
When no user is selected, I want the component to just render some text and a button to add a user. When the button is clicked the component renders the form with empty input fields so that we can add a new user.
I tried the following, but it's just showing the data for the first one I click on. It's not updating.
The main page:
const index = (props) => {
const [selectedUser, setSelectedUser] = useState(null);
const [state, setState] = useState("Index");
const onChange = (item) => {
setState("Edit");
setSelectedUser(item);
};
const onClick = (e, item) => {
if (e.type === "click" && e.clientX !== 0 && e.clientY !== 0) {
onChange(item);
} else {
console.log('prevented "onClick" on keypress');
}
};
const renderComponent = () => {
switch (state) {
case "Index":
return (
<>
<div className="btn" onClick={(e) => setState("Edit")}>
+ New Staff
</div>
<img src="/storage/illustrations/collaboration.svg" />
</>
);
case "Edit":
return (
<div>
<StaffForm profile={selectedUser} />
</div>
);
}
};
return (
<>
<div>
<div>
<h1>Staff</h1>
</div>
<div>
<div>
{profiles.map((item, index) => {
return (
<div key={index} onClick={(e) => onClick(e, item)}>
<input
type={"radio"}
name={"staff"}
checked={state === item}
onChange={(e) => onChange(item)}
/>
<span>{item.user.name}</span>
</div>
);
})}
</div>
<div>{renderComponent()}</div>
</div>
</div>
</>
);
};
The Staff Form Component:
const StaffForm = ({ profile }) => {
const { data, setData, post, processing, errors, reset } = useForm({
email: profile ? profile.user.email : "",
name: profile ? profile.user.name : "",
phone_number: profile ? profile.user.phone_number : "",
avatar: profile ? profile.user.avatar : "",
});
const [file, setFile] = useState(data.avatar);
const handleImageUpload = (e) => {
setFile(URL.createObjectURL(e.target.files[0]));
setData("avatar", e.target.files[0]);
};
const onHandleChange = (event) => {
setData(
event.target.name,
event.target.type === "checkbox"
? event.target.checked
: event.target.value
);
};
return (
<div>
<ImageUpload
name={data.name}
file={file}
handleImageUpload={handleImageUpload}
/>
<TextInput
type="text"
name="name"
value={data.name}
autoComplete="name"
isFocused={true}
onChange={onHandleChange}
placeholder={t("Name")}
required
/>
<TextInput
type="text"
name="phone_number"
value={data.phone_number}
autoComplete="phone_number"
placeholder={t("Phone Number")}
onChange={onHandleChange}
required
/>
<TextInput
type="email"
name="email"
value={data.email}
autoComplete="email"
onChange={onHandleChange}
placeholder={t("Email")}
required
/>
</div>
);
};
First of all something you should avoid is the renderComponent() call.Check here the first mistake mentioned in this video. This will most likely fix your problem but even if it doesn't the video explains why it should not be used.
Something else that caught my eye(possibly unrelated to your question but good to know) is the onChange function. When two pieces of state change together it is a potential source of problems, check out this article on when to use the useReducer hook.
Also be careful with naming React Components, they need to be capital case, this question contains appropriate answers explaining it.
(To only solve your problem stick to no.1 of this list, there are some improvements i'd do here overall for code quality and beauty, msg me for more details)

Updating React state with Hooks and tags

I'm having a syntax doubt on how to update React state using hooks in 2 situations.
1) I have a state called company and a form that fills it up. In contact section, there are two inputs referring to the company employee (name and telephone number). But if the company has more than one employee to be contacted, there is an "Add More Contact" button, which must duplicate the same inputs (of course, aiming to a second contact). How can I do that? I mean, to generate another index in the array "contacts" inside the state, increment the totalOfContacts inside the object that has that array and create the input tags so user can type the second contact's data?
2) When I type any inputs, the code triggers the handleChange function. The "name" and "city" already update the state because they are simple states. But how can I update the contact name and his telephone number, since they are part of an index of an array inside the state?
The code below is already working and my 2 questions are exactly the two commented lines (lines 20 and 29).
The "Save" button simply console.log the results so we can monitor them.
Thanks for now.
import React, { useState, useEffect } from "react";
export default () => {
const [company, setCompany] = useState({
name: "", city: "",
contact: {
totalOfContact: 1,
contacts: [
{id: 0, contactName: "", telephoneNumber: ""}
]
}
})
useEffect(() => {
console.log("teste");
})
const handleChange = item => e => {
if (item === "contactName" || "telephone") {
// How can I set company.contact.contacts[<current_index>].contactName/telephoneNumber with the data typed?
} else {
setCompany({ ...company, [item]: e.target.value })
}
}
const handleClick = (e) => {
e.preventDefault();
if (e.target.value === "add") {
// How can I set company.contact.totalOfContact to 2 and create one more set of inputs tags for a second contact?
} else {
console.log(`The data of the company is: ${company}`);
}
}
return (
<div>
<form>
<h3>General Section</h3>
Name: <input type="text" onChange = {handleChange("name")} value = {company.name} />
<br />
City: <input type="text" onChange = {handleChange("city")} value = {company.city} />
<br />
<hr />
<h3>Contacts Section:</h3>
Name: <input type="text" onChange = {handleChange("contactName")} value = {company.contact.contacts[0].name} />
Telephone Numer: <input type="text" onChange = {handleChange("telephone")} value = {company.contact.contacts[0].telephoneNumber} />
<br />
<br />
<button value = "add" onClick = {(e) => handleClick(e)} >Add More Contact</button>
<br />
<br />
<hr />
<button value = "save" onClick = {(e) => handleClick(e)} >Save</button>
</form>
</div>
)
}
To update the state value, you can use functional setState,
const handleChange = item => e => {
//Take the value in a variable for future use
const value = e.target.value;
if (item === "contactName" || "telephone") {
setCompany(prevState => ({
...prevState,
contact: {...prevState.contact, contacts: prevState.contact.contacts.map(c => ({...c, [item]: value}))}
}))
} else {
setCompany({ ...company, [item]: e.target.value })
}
}
To add new set of input on the click of button you can do this,
const handleClick = (e) => {
e.preventDefault();
//This is new set of input to be added
const newSetOfInput = {id: company.contact.contacts.length, contactName: "", telephoneNumber: ""}
if (e.target.value === "add") {
// How can I set company.contact.totalOfContact to 2 and create one more set of inputs tags for a second contact?
setCompany(prevState => ({
...prevState,
contact: {...prevState.contact, contacts: prevState.contact.contacts.concat(newSetOfInput), totalOfContact: prevState.contact.contacts.length + 1}
}))
} else {
console.log(`The data of the company is: ${company}`);
}
}
Finally you need to iterate over your contacts array like,
{company.contact.contacts && company.contact.contacts.length > 0 && company.contact.contacts.map(contact => (
<div key={contact.id}>
Name: <input type="text" onChange = {handleChange("contactName")} value = {contact.contactName} />
<br/>
Telephone Numer: <input type="text" onChange = {handleChange("telephoneNumber")} value = {contact.telephoneNumber} />
</div>
))}
Demo
Note: You should use block elements like div instead of breaking the line using <br/>
To answer your question let us scope down this problem to a much simpler problem, which is how to handle array of contacts.
You just need know the following things:
Map function
How to update array without mutating the original array
I'll use TypeScript so you can understand better.
const [state, setState] = React.useState<{
contacts: {name: string}[]
}>({contacts: []})
return (
<div>
{state.contacts.map((contact, index) => {
return (
<div>
Name:
<input value={contact.name} onChange={event => {
setState({
...state,
contacts: state.contacts.map((contact$, index$) =>
index === index$
? {...contact$, name: event.target.value}
: {...contact$}
)
})
}}/>
</div>
)
}}
</div>
)
Also, this kind of problem is fairly common in React, so understand and memorize this pattern will help you a lot.
You can do something like this.
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
const App = () => {
const [company, setCompany] = useState({
name: "",
city: "",
contact: {
totalOfContact: 1,
contacts: [{id: 0, contactName: "", telephoneNumber: ""}]
}
});
console.log(company);
useEffect(() => {
console.log("teste");
}, []);
const handleChange = (item, e,index) => {
if (item === "contactName" || item === "telephoneNumber") {
const contactsNew = [...company.contact.contacts];
contactsNew[index] = { ...contactsNew[index], [item]: e.target.value };
setCompany({
...company,
contact: { ...company.contact, contacts: contactsNew }
});
// How can I set company.contact.contacts[<current_index>].contactName/telephoneNumber with the data typed?
} else {
setCompany({ ...company, [item]: e.target.value });
}
};
const handleClick = e => {
e.preventDefault();
if (e.target.value === "add") {
const contactNew = {...company.contact};
contactNew.totalOfContact = contactNew.totalOfContact + 1;
contactNew.contacts.push({id:contactNew.totalOfContact -1, contactName: "", telephoneNumber: ""});
setCompany({...company, contact: {...contactNew}});
// How can I set company.contact.totalOfContact to 2 and create one more set of inputs tags for a second contact?
} else {
alert("Push company to somewhere to persist");
console.log(`The data of the company is: ${company}`);
}
};
return (
<div>
<form>
<h3>General Section</h3>
Name:{" "}
<input
type="text"
onChange={(e) => handleChange("name", e)}
value={company.name}
/>
<br />
City:{" "}
<input
type="text"
onChange={(e) => handleChange("city", e)}
value={company.city}
/>
<br />
<hr />
<h3>Contacts Section:</h3>
{company.contact.contacts.map((eachContact, index) => {
return <React.Fragment>
Name:{" "}
<input
type="text"
onChange={(e) => handleChange("contactName",e, index)}
value={eachContact.name}
/>
Telephone Numer:{" "}
<input
type="text"
onChange={(e) => handleChange("telephoneNumber",e, index)}
value={eachContact.telephoneNumber}
/>
<br />
</React.Fragment>
})}
<br />
<button value="add" onClick={e => handleClick(e)}>
Add More Contact
</button>
<br />
<br />
<hr />
<button value="save" onClick={e => handleClick(e)}>
Save
</button>
</form>
</div>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Your state structure looks like an ideal candidate for useReducer hook. I would suggest you try that instead of useState. Your code should look muck readable that way, I suppose. https://reactjs.org/docs/hooks-reference.html#usereducer

How to clear input after form submit (React)

I have a search input I'd like to clear after the value is submitted and the search is performed with the value. In similar questions, it was suggested to set the state of the input value to '', but I think that's what I tried and it didn't do anything.
I only have a parent and child component in my app. The parent component has a method for searching jokes (searchJokes), and it is passed down as a prop with a different name to the child component in the component instance with onFormSubmit={this.searchJokes}. In the child component, when the user enters something into the search input, its event.target.value is passed with onChange={e => props.onInputChange(e.target.value)} corresponding to the onSearchChange method in the parent prop, and the value is used to update the state of searchTerm.
I added searchTerm: '' to the end of the searchJokes method, which fetches a search according to the search term, as you can see in the parent component code below.
Parent component:
class App extends Component {
constructor() {
super();
this.state = {
searchTerm: '',
jokes: [],
isFetchingJokes: false,
isSearch: false
};
this.onSearchChange = this.onSearchChange.bind(this);
this.randomizeJokes = this.randomizeJokes.bind(this);
this.searchJokes = this.searchJokes.bind(this);
}
randomizeJokes() {
this.setState({
isFetchingJokes: true,
isSearch: false
});
fetch(
'https://icanhazdadjoke.com/',
{
method: 'GET',
headers: {
Accept: 'application/json'
}
})
.then(response => response.json())
.then(json => {
let joke = json.joke;
this.setState({
joke,
isFetchingJokes: false
});
});
}
searchJokes(limit = 15) {
// If nothing entered, user gets "Please fill out this field" message due to "required" attribute on input element
if (this.state.searchTerm !== '') {
this.setState({
isFetchingJokes: true,
isSearch: true
});
fetch(
`https://icanhazdadjoke.com/search?term=${
this.state.searchTerm
}&limit=${limit}`,
{
method: 'GET',
headers: {
Accept: 'application/json'
}
})
.then(response => response.json())
.then(json => {
let jokes = json.results;
this.setState({
jokes,
isFetchingJokes: false,
searchTerm: '' // <-- DOESN'T CLEAR INPUT
});
});
}
}
onSearchChange(value) {
this.setState({ searchTerm: value });
}
jokeRender() {
return (
<div>
{this.state.isSearch ?
<ul>{this.state.jokes.map(item => <li key={item.id}>{item.joke}</li>)}
</ul> : <p className="random-joke">{this.state.joke}</p>}
</div>
);
}
render() {
return (
<div>
<h1>Dad Jokes</h1>
<RetrievalForm
onFormSubmit={this.searchJokes}
onInputChange={this.onSearchChange}
isSearching={this.state.isFetchingJokes}
onRandomize={this.randomizeJokes}
/>
{this.state.isFetchingJokes ? <p className="searching-message">Searching for jokes...</p> : this.jokeRender()}
</div>
);
};
}
Child component:
const RetrievalForm = props => {
const onSubmit = e => {
// Prevents GET request/page refresh on submit
e.preventDefault();
props.onFormSubmit();
};
return (
<>
<form onSubmit={onSubmit}>
<input
type="text"
placeholder="Enter search term..."
onChange={e => props.onInputChange(e.target.value)}
required
/>
<div>
{/* Specifying type here since it's good practice; different browsers may use default types for buttons */}
<button type="submit" disabled={props.isSearching}>Search</button>
{/* type="button" stops input validation message from being displayed (on Firefox) when randomize button is clicked without anything entered */}
<button type="button" onClick={props.onRandomize} disabled={props.isSearching} className="randomize-button">
Randomize
</button>
</div>
</form>
</>
);
};
Any help would be greatly appreciated.
You need to pass your searchTerm down to the RetrievalForm and in that input set value={searchTerm} so that it's value will be bound to that state.
Basically, you need to store the input value in the component's state. When onSubmit is called, we should revert that value to an empty string.
Example with some React Hooks goodness:
import React, { Component, useState } from 'react';
const RetrievalForm = props => {
const [searchTerm, setSearchTerm] = useState('');
const onChange = e => {
const { value } = e.target;
props.onInputChange(value);
setSearchTerm(value)
}
const onSubmit = e => {
// Prevents GET request/page refresh on submit
e.preventDefault();
props.onFormSubmit();
setSearchTerm('');
};
return (
<>
<form onSubmit={onSubmit}>
<input
type="text"
value={searchTerm}
placeholder="Enter search term..."
onChange={onChange}
required
/>
<div>
{/* Specifying type here since it's good practice; different browsers may use default types for buttons */}
<button type="submit" disabled={props.isSearching}>
Search
</button>
{/* type="button" stops input validation message from being displayed (on Firefox) when randomize button is clicked without anything entered */}
<button type="button" onClick={props.onRandomize} disabled={props.isSearching} className="randomize-button">
Randomize
</button>
</div>
</form>
</>
);
};
Example here: https://stackblitz.com/edit/react-db5ire

Paginate API call in React Component

I want to paginate the reults of an Api call.
I am making an Api Call by using Axios like this
apiCall() {
const API = `http://www.omdbapi.com/`;
axios.get(API, {
params: {
apikey: process.env.REACT_APP_MOVIECALL_API_KEY,
type: 'movie',
s: 'superhero',
page: this.state.pageCount
}
})
.then(res => {
const superheroes = res.data.Search
const totalResults= parseInt(res.data.totalResults)
this.setState({
totalResults
});
this.setState({
superheroes
})
})
.catch((error) => {
console.log(error);
});
}
When the component is mounted the function called, as such
componentDidMount()
{
this.apiCall();
}
In the render function I map over each search result (in the api call the s param is the search option)
and for each result I display a button, that when clicked displays the related info of that movie.
The api by default displays 10 results per call but this particular search has 123 results in total.
Now by updating the param page in the call which I've set to this.state.pageCount
it displays 10 different movies relating to that page, at the moment I hardcode the pageCount inside the state to make sure it works and the corresponding page number shows the right list of 10 movies.
Now I would like to paginate the results by updating the page number, so when you click on next or the number 3/4/5 then the component loads the correct corresponding results, I've tried a couple of option relating to react but they somehow don't update the page number.
If someone could point me in the right direction or knows a simple solution, I am all ears.
the following code is the whole component, to get an idea of what I am trying to do.
What I have so far seems to be working, so what I am asking is for a
simpler more elegant way of doing pagination for this particular
situation.
export class MovieDetails extends Component {
constructor(props){
super(props)
this.state = {
superheroes: [],
clicked: false,
activeHero: {},
pageCount: 11,
totalResults: null,
currentPage: 1
}
this.handleClick = this.handleClick.bind(this);
}
handleClick(hero) {
const checkActive = this.state.activeHero.imdbID === hero.imdbID
const activeHero = {...hero, active: !checkActive}
this.setState({
clicked: !this.state.clicked,
activeHero
})
}
apiCall() {
const API = `http://www.omdbapi.com/`;
axios.get(API, {
params: {
apikey: process.env.REACT_APP_MOVIECALL_API_KEY,
type: 'movie',
s: 'superhero',
page: this.state.pageCount
}
})
.then(res => {
const superheroes = res.data.Search
const totalResults = parseInt(res.data.totalResults)
this.setState({
totalResults
});
this.setState({
superheroes
})
})
.catch((error) => {
console.log(error);
});
}
componentDidMount() {
this.apiCall();
}
handlePageChange = (page, e) => {
this.setState({
currentPage: page
});
this.apiCall(this.setState({pageCount: page}))
};
render() {
const {superheroes, currentPage } = this.state
return (
<div>
{
superheroes.map((hero, i) =>
<div className="Results" key={i}>
<button onClick={() => {this.handleClick(hero)}}>
<h1>{hero.Title}</h1>
{
this.state.clicked && this.state.activeHero.imdbID === hero.imdbID
? <ul>
{<div key={i}>
Movie Title: <h2> {hero.Title}</h2>
Year of Release: <h2>{hero.Year}</h2>
ID: <h2>{hero.imdbID}</h2>
<div><img className="Poster" alt="movieposter" src={hero.Poster}/></div>
</div>
}
</ul>
: null
}
</button>
</div>)
}
<div className="Pagination">
<Pagination
total={this.state.totalResults}
limit={10}
pageCount={this.state.pageCount}
currentPage={currentPage}
>
{({
pages,
currentPage,
hasNextPage,
hasPreviousPage,
previousPage,
nextPage,
totalPages,
getPageItemProps
}) => (
<div>
<button
{...getPageItemProps({
pageValue: 1,
onPageChange: this.handlePageChange
})}
>
first
</button>
{hasPreviousPage && (
<button
{...getPageItemProps({
pageValue: previousPage,
onPageChange: this.handlePageChange
})}
>
{'<'}
</button>
)}
{pages.map(page => {
let activePage = null;
if (currentPage === page) {
activePage = { backgroundColor: '#fdce09' };
}
return (
<button
{...getPageItemProps({
pageValue: page,
key: page,
style: activePage,
onPageChange: this.handlePageChange
})}
>
{page}
</button>
);
})}
{hasNextPage && (
<button
{...getPageItemProps({
pageValue: nextPage,
onPageChange: this.handlePageChange
})}
>
{'>'}
</button>
)}
<button
{...getPageItemProps({
pageValue: totalPages,
onPageChange: this.handlePageChange
})}
>
last
</button>
</div>
)}
</Pagination>
</div>
</div>
);
}
}
In your axios.get function you are sending page: this.state.pageCount, however in your handlePageChange function you are setting state.currentPage which doesn't seem right to me.
I'm also a bit confused about the onPageChange event on <button />. Is this button a custom component you are importing (in which case it should be capatalised so that it's clear) or is it a HTML button? If it's a HTML button then you need to use the onClick event which will pass the event as and argument to the handlePageChange function. I'm guessing it's custom though from the props you're passing it so just worth checking that it's sending the page value through correctly.

Pass item data to a react modal

I have a map that render few items and one of its line is below
<a onClick={()=> this.setState({"openDeleteModal":true)}>Delete</a>
Obviously I want to open a modal when user click the delete, but I have to pass a few things like the name of the item, id of the item to perform the deletion. How can I pass says the name to the modal?
I can bind the obj name to a like this
Delete
Am I on the right track?
When working on React applications, try not to think in terms of passing values to other components, but rather updating state that your components are exposed to.
In your example, assuming your modal component is a child of the same component your list of a tags belongs to, you could set the values you are interested in exposing to the modal on the state, as well as updating the property that signals whether the modal is open or not. For example:
class Container extends React.Component {
constructor(props) {
super(props)
this.state = {
openDeleteModal: false,
activeItemName: '', //state property to hold item name
activeItemId: null, //state property to hold item id
}
}
openModalWithItem(item) {
this.setState({
openDeleteModal: true,
activeItemName: item.name,
activeItemId: item.id
})
}
render() {
let buttonList = this.props.item.map( item => {
return (<button onClick={() => this.openModalWithItem(item)}>{item.name}</button>
});
return (
<div>
{/* Example Modal Component */}
<Modal isOpen={this.state.openDeleteModal}
itemId={this.state.activeItemId}
itemName={this.state.activeItemName}/>
{ buttonList }
</div>
)
}
}
Copying over my answer from How to pass props to a modal
Similar scenario
constructor(props) {
super(props)
this.state = {
isModalOpen: false,
modalProduct: undefined,
}
}
//****************************************************************************/
render() {
return (
<h4> Bag </h4>
{this.state.isModalOpen & (
<Modal
modalProduct={this.state.modalProduct}
closeModal={() => this.setState({ isModalOpen: false, modalProduct: undefined})
deleteProduct={ ... }
/>
)
{bag.products.map((product, index) => (
<div key={index}>
<div>{product.name}</div>
<div>£{product.price}</div>
<div>
<span> Quantity:{product.quantity} </span>
<button onClick={() => this.props.incrementQuantity(product, product.quantity += 1)}> + </button>
<button onClick={() => this.decrementQuantity(product)}> - </button> // <----
</div>
</div>
))}
)
}
//****************************************************************************/
decrementQuantity(product) {
if(product.quantity === 1) {
this.setState({ isModalOpen: true, modalProduct: product })
} else {
this.props.decrementQuantity(product)
}
}
Try this: this is the form which has the button, and is a child component of some other component that passes the handleButtonAction method as props, and the button takes the input data and invokes this parent component method
handleSubmit = (e) => {
e.preventDefault();
const data = e.target.elements.option.value.trim();
if (!data) {
this.setState(() => ({ error: 'Please type data' }));
} else {
this.props.handleButtonAction(data, date);
}
}
{this.state.error && <p>{this.state.error}</p>}
<form onSubmit={this.handleSubmit}>
<input type="text" name="option"/>
<div>
<button>Get data</button>
</div>
</form>
The parent component:
handleButtonAction = (data) => {
axios.get(`http://localhost:3000/someGetMethod/${data}`).then(response => {
const resData = response.data;
this.setState({
openModal: true,
status: response.status,
data: resData
});
}).catch((error) => {
if (error.message.toLowerCase() === 'network error') {
this.setStateWithError(-1, {});
}
else { // not found aka 404
this.setStateWithError(error.response.status, '', {currency, date: ddat});
}
});
}

Categories