I am developing an application in React. I have a parent element wherein I have a child component(Photo) which needs to rendered multiple times(7 in below case). So I have used .map and allPhotos variable is rendered in return function.
Parent Component :
handlePhotos = (event, isSingleMulti, photoIndex) => {
console.log("Upload Photo", event.target.files, photoIndex, isSingleMulti);
}
openFileDialog(isSingleMulti, photoIndex) {
isSingleMulti === 'M' ? document.getElementById('multi-photo-input').click() : document.getElementById('single-photo-input').click();
}
let photosTemp = [1,2,3,4,5,6,7];
let allPhotos = photosTemp.map(ele => {
return <Photo key={ele} photoBoxID={ele} openFileDialog={this.openFileDialog} handlePhotos={this.handlePhotos} onDeletePhoto={this.onDeletePhoto}/>
});
In my Child component (Photo) I have a div, onClick of div I call openFileDialog which internally calls click of hidden input element(#single-photo-input). onChange of input element I call handlePhotos. Both of these functions handlePhotos and openFileDialog are defined in my parent and passed to Child (Photo) as a prop.
Now what I need is that when onChange method handlePhotos is called, I want to return each Photo photoBoxID value. Basically, I want to check which Photo component was clicked. But every time I get value as 1 instead of respective 1,2,3 etc. What wrong am I doing?
Child Component :
const UploadImage = (props) => {
console.log(props.photoBoxID);
return (
<div className="photo-root">
<div className="photo-inner-container" onClick={() => props.openFileDialog('S')}>
<span className="inner-text">+</span>
<form encType="multipart/form-data" id="single-photo-form">
<input type="file" name="file" id="single-photo-input" className="hide" accept="image/jpg, image/jpeg, image/png"
onChange={(event) => props.handlePhotos(event, 'S', props.photoBoxID)}/>
</form>
</div>
</div>
)
}
class Photo extends React.Component {
render() {
return (
true === true ? <UploadImage {...this.props}/> : <ImagePreview {...this.props}/>
)
}
}
In open dialog, you are getting element by id but you don't have unique ids so you can append index to your input ids and pass index to openDialog function and click that particular input only.
Parent component
handlePhotos = (event, isSingleMulti, photoIndex) => {
console.log("Upload Photo", event.target.files, photoIndex, isSingleMulti);
}
openFileDialog(isSingleMulti, photoIndex) {
isSingleMulti === 'M' ? document.getElementById(`multi-photo-input-${photoIndex}`).click() : document.getElementById(`single-photo-input-${photoIndex}`).click(); //appended index here
}
let photosTemp = [1,2,3,4,5,6,7];
let allPhotos = photosTemp.map(ele => {
return <Photo key={ele} photoBoxID={ele} openFileDialog={this.openFileDialog} handlePhotos={this.handlePhotos} onDeletePhoto={this.onDeletePhoto}/>
});
in your photo.js
const UploadImage = (props) => {
console.log(props.photoBoxID);
return (
<div className="photo-root">
<div className="photo-inner-container" onClick={() => props.openFileDialog('S', props.photoBoxID)}>
<span className="inner-text">+</span>
<form encType="multipart/form-data" id={`single-photo-form-${props.photoBoxID}`}>
<input type="file" name="file" id={`single-photo-input-${props.photoBoxID}`} className="hide" accept="image/jpg, image/jpeg, image/png"
onChange={(event) => props.handlePhotos(event, 'S', props.photoBoxID)}/>
</form>
</div>
</div>
)
}
class Photo extends React.Component {
render() {
return (
true === true ? <UploadImage {...this.props}/> : <ImagePreview {...this.props}/>
)
}
}
Child Component :
// as is
<div className="photo-inner-container" onClick={() => props.openFileDialog('S')}>
// to be
<div className="photo-inner-container" onClick={(event) => props.openFileDialog(event, 'S')}>
Parent Component :
// as is
openFileDialog(isSingleMulti, photoIndex) {
isSingleMulti === 'M' ? document.getElementById('multi-photo-input').click() : document.getElementById('single-photo-input').click();
}
// to be
openFileDialog = (event, isSingleMulti) => {
isSingleMulti === 'M' ? document.getElementById('multi-photo-input').click() : event.currentTarget.childNodes[1].children[0].click();
}
Hmmm...
I tried to find another way to catch the exact input a client clicks lol
Related
i am using react.i have 2 inputs that by clicking a button dynamically ganerats.
this the code:
useEffect(() => {
const myFields = fieldsArray.map((field) => {
return (
<div key={field} className="col-12 m-auto d-flex">
<input id={field} name={`question${field}`} onChange={handleChangeFields} placeholder='سوال' className="form-control col-4 m-2" />
<input id={field} name={`answer${field}`} onChange={handleChangeFields} placeholder='جواب' className="form-control col-4 m-2" />
</div>
)
})
setFields(myFields)
}, [fieldsArray])
i need to save value of 2 inputs together in an opjects like this :
[{question : '',answer : ''}]
and the new 2 input's value are going to update the above array like this:
[{question : '',answer : ''}, {question : '',answer : ''}]
i can save each input's value separately like this :
const handleChangeFields = (e) => {
const { name, value } = e.target
setFieldsValue([...fieldsValue, { [name]: value }])
}
but i want each 2 input's value together
i need to save each 2 inputs together.
how do i do that?
This is a general example of how I think you can tie in what you're currently doing, and add in as little new code as possible. This logs the new data state when the button is clicked.
Have two states. One for collecting the updated form information (form), and another for the combined form data array (data).
Have a form with a single onChange listener (event delegation) so you can catch events from all the inputs as they bubble up the DOM. That listener calls the handleChange function which updates the form state.
Have a button with an onClick listener that calls handleClick which takes the current form state and adds it to the data state.
I would be a bit wary of storing JSX in state. You should have that map in the component return and only updating the actual field data with basic information.
One final issue - your inputs cannot have the same id. ids must be unique. I'd get rid of them altogether.
const { useEffect, useState } = React;
function Example() {
// Initialise two states. `data` is an array
// and `form` is an object
const [ data, setData ] = useState([]);
const [ form, setForm ] = useState({});
// Add the form data to the `data` array
function handleClick() {
setData([ ...data, form ]);
}
// We're using event delegation so we need to
// check what element we changed/clicked on
// - in this case the INPUT element. We can then
// update the form state with the name/value pair of that input
function handleChange(e) {
const { nodeName, name, value } = e.target;
if (nodeName === 'INPUT') {
setForm({ ...form, [name]: value });
}
}
// Just logs the data state after a change
useEffect(() => console.log(data), [data]);
return (
<form onChange={handleChange}>
<input name="question" type="text" />
<input name="answer" type="text" />
<button
type="button"
onClick={handleClick}
>Update form
</button>
</form>
);
};
ReactDOM.render(
<Example />,
document.getElementById('react')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>
this problem solved for me :
function App() {
const [results, setResults] = useState([{ question: "", answer: "" }])
// handle input change
const handleInputChange = (e, index) => {
const { name, value } = e.target
const list = [...results]
list[index][name] = value
setResults(list)
}
// handle click event of the Remove button
const handleRemoveClick = (index) => {
const list = [...results]
list.splice(index, 1)
setResults(list)
}
// handle click event of the Add button
const handleAddClick = () => {
setResults([...results, { question: "", answer: "" }])
}
// handle submit to servers
const handlesubmit = () => {
// axios
console.log(results)
}
return (
<div className="App">
{results?.map((result, index) => {
return (
<div key={index}>
<input
name="question"
value={result.question}
onChange={(e) => handleInputChange(e, index)}
/>
<input
name="answer"
value={result.answer}
onChange={(e) => handleInputChange(e, index)}
/>
{results.length !== 1 && (
<button onClick={() => handleRemoveClick(index)}>
Remove
</button>
)}
{results.length - 1 === index && (
<button onClick={handleAddClick}>add</button>
)}
</div>
)
})}
<div style={{ marginTop: 20 }}>{JSON.stringify(results)}</div>
<button onClick={handlesubmit}>submit</button>
</div>
)
}
export default App
For some reason when I submit a form I can't get the value of the checked boxes I don't mean the check value but the value attribute of the HTML element itself.
This is my element in the parent component :
function parent(props){
const [filterCriteria, setFilterCriteria] = useState([]);
const [companies, setCompanies] = useState([]);
const [categories, setCategories] = useState([]);
useEffect(() => {
axios
.get("http://localhost:4000/api/companies")
.then((res) => {
companiesData = res.data.companies;
setCompanies([...new Set(companiesData.map((item) => item.company))]);
})
.finally(() => {
setIslLoading(false);
});
}, []);
useEffect(() => {
axios
.get("http://localhost:4000/api/categories")
.then((res) => {
categoriesData = res.data.categories;
setCategories([
...new Set(categoriesData.map((item) => item.category)),
]);
})
.finally(() => {
setIslLoading(false);
});
}, []);
const onFilteringHandler = (e) => {
e.preventDefault();
//fun fact the e.target is looped useing "for of" not "for in" although it's an
object
for (const element of e.target) {
if (element.type === "checkbox" &&
element.checked === true &&
companies.includes(element.value)) {
setFilterCriteria([...filterCriteria, element.value]);
}
if (element.type === "checkbox" &&
element.checked === true &&
categories.includes(element.value)) {
setFilterCriteria([...filterCriteria, element.value]);
}
}
};
return (
//this element is in the medal of tother elements but for simplification i
removed them
<FilterBox
categoriesCriteria={categories}
settingFilterCriteria={onFilteringHandler}
companiesCriteria={companies}
/>
)
}
This is my element in the child "FilterBox" component :
function FilterBox(props) {
// this is to map the categories into check boxes with values, keys and label
let categoriesFilters = props.categoriesCriteria.map((category, index) => {
return (
<label key={index}>
{category}
//this is the value i'm tring to get in the form submition
<input type="checkbox" value={category} />
</label>
);
});
// this is to map the categories into check boxes with values, keys and label
let companiesFilters = props.companiesCriteria.map((company, index) => {
return (
<label key={index}>
{company}
//this is the value i'm tring to get in the form submition
<input type="checkbox" value={company} />
</label>
);
});
return (
<form
onSubmit={props.settingFilterCriteria}
className="product_creation_form"
>
<div>
<h5 className="filter_title">Categories</h5>
{categoriesFilters}
</div>
<div>
<h5 className="filter_title">Companies</h5>
{companiesFilters}
</div>
<button type="submit" className="form_button">
Apply
</button>
<button type="reset" className="form_button">
Clear All
</button>
</form>
);
}
Nowhere is the issue, I can't get the value in this line:
setFilterCriteria([...filterCriteria, element.value]);
Although the element.value exist on the e.target?
How to do that?
FilterBox Component
function FilterBox(props) {
const categoriesFilters = props.categoriesCriteria.map((category, index) => {
return (
<label key={index}>
{category}
<input type="checkbox" value={category} name={category} />
</label>
);
});
const companiesFilters = props.companiesCriteria.map((company, index) => {
return (
<label key={index}>
{company}
<input type="checkbox" value={company} name={company} />
</label>
);
});
return (
<form
onSubmit={props.settingFilterCriteria}
className="product_creation_form"
>
<div>
<h5 className="filter_title">Categories</h5>
{categoriesFilters}
</div>
<div>
<h5 className="filter_title">Companies</h5>
{companiesFilters}
</div>
<button type="submit" className="form_button">
Apply
</button>
<button type="reset" className="form_button">
Clear All
</button>
</form>
);
}
App Component:
function App() {
const [companies] = React.useState(["companyOne", "companyTwo"]);
const [categories] = React.useState(["categoryOne", "categoryTwo"]);
const onFilteringHandler = (e) => {
e.preventDefault();
const filterCriteria = { companies: [], categories: [] };
for (const element of e.target) {
if (element.type === "checkbox" && element.checked) {
if (companies.includes(element.value)) {
filterCriteria.companies.push(element.name);
}
if (categories.includes(element.value)) {
filterCriteria.categories.push(element.name);
}
}
}
console.log(filterCriteria);
};
return (
<FilterBox
categoriesCriteria={categories}
settingFilterCriteria={onFilteringHandler}
companiesCriteria={companies}
/>
);
}
I took the liberty to disconnect the initial API loading of the companies & categories react state arrays and hardcoded them for simplicity. The below code should be enough to send meaningful data to be able to filter out on the backend.
I took the approach with a non-controlled form state, as your code seemed to favour this kind of approach. You can try and keep the filter values in the react state too but I believe this complicates the solution a bit (especially that state update is async) and it seems that you only need to collect the current filters state value on a button press. If however, you need it for some other purposes, then a controlled form state would most likely be needed.
import React, { Component, createElement } from "react";
export default class TodoList extends Component {
state = {
todo: [],
inputValue: "",
};
addTodo = () => {
this.setState({ todo: [...this.state.todo, this.state.inputValue] });
// I want to insert separate paragraph tags (todos from this.state.todo) into the list element here
};
handleKeyDown = (e) => {
if (e.keyCode === 13) {
if (this.state.inputValue === "") return;
this.addTodo();
}
};
handleChange = (e) => {
this.setState({ inputValue: e.target.value });
};
render() {
return (
<div className="list"></div>
<input
type="text"
className="insertTodo"
placeholder="Add a new todo!"
onChange={this.handleChange}
onKeyDown={this.handleKeyDown}
value={this.state.inputValue}
/>
);
}
}
I am creating a Todo List where the user types into an input, and the todo is then inserted into the div with class list element. I'm new to React so I don't know the best way I should go about doing this.
Any help is appreciated, thanks!
You can map the array, inside the .list div, and render each todo item, by wrapping it in p tag. I have added a button element, to handle the addTodo() function.
Also, you may want to move the .list div, below the input.
import React, { Component, createElement } from "react";
export default class TodoList extends Component {
state = {
todo: [],
inputValue: ""
};
addTodo = () => {
this.setState({ todo: [...this.state.todo, this.state.inputValue] });
// I want to insert separate paragraph tags (todos from this.state.todo) into the list element here
};
handleKeyDown = (e) => {
if (e.keyCode === 13) {
if (this.state.inputValue === "") return;
this.addTodo();
}
};
handleChange = (e) => {
this.setState({ inputValue: e.target.value });
};
render() {
return (
<div>
<div className="list">
{this.state.todo.map((todo) => {
return <p>{todo}</p>;
})}
</div>
<input
type="text"
className="insertTodo"
placeholder="Add a new todo!"
onChange={this.handleChange}
onKeyDown={this.handleKeyDown}
value={this.state.inputValue}
/>
<button onClick={this.addTodo}>Add Todo</button>
</div>
);
}
}
Codesandbox link - https://codesandbox.io/s/busy-pascal-txh55?file=/src/App.js
class TodoList extends Component {
state = {
todo: [],
inputValue: "",
};
addTodo = () => {
// I want to insert separate paragraph tags (todos from this.state.todo) into the list element here
// Hint: when you want to add a todo, you simply set input value to empty here.
this.setState({
todo: [...this.state.todo, this.state.inputValue],
inputValue: "",
});
};
handleKeyDown = (e) => {
if (e.keyCode === 13) {
if (this.state.inputValue === "") return;
this.addTodo();
}
};
handleChange = (e) => {
// Hint: I prefer adding "...this.state" every time before updating.
this.setState({ ...this.state, inputValue: e.target.value });
};
render() {
return (
<>
{
// Hint: use React fragment ("<> ... </>") when there's
more than one element in the first level.
}
<div className="list">
{
// Hint: Adding the current list with map in here
}
<ul>
{this.state.todo.map((t, i) => (
<li key={i}>{t}</li>
))}
</ul>
</div>
{
// I would prefer adding it inside a form element and instead of onKeyDown, use onSubmit on the form
// (on enter it will submit automatically, but you will have to do an e.preventDefault() to not refresh the page).
}
<input
type="text"
className="insertTodo"
placeholder="Add a new todo!"
onChange={this.handleChange}
onKeyDown={this.handleKeyDown}
value={this.state.inputValue}
/>
</>
);
}
}
This is a working example with a few comments. Also setState runs asyncrounously so it's not a good idea to run multiple one at the same time. Hope this helps.
Using map like TechySharnav mentioned is a quick way of doing it. But if you need to do some more complex operations/layout stuff, then writing a custom function and calling it in the render jsx might be cleaner. So, you could have a function like:
renderItems() {
var rows = []
this.state.todo.forEach((elem, idx) => {
// for example
rows.push(
<p>{elem}</p>
)
});
return rows;
}
Then call it inside render:
//...
<div className="list">
{this.renderItems()}
</div>
//...
js map will certainly solve the problem.
this small snippet for printing the list,
render() {
return (
<div className="list">
{ this.state.todo.map((item) => {
return <p>{item}</p>
})}
</div>
<input
type="text"
className="insertTodo"
placeholder="Add a new todo!"
onChange={this.handleChange}
onKeyDown={this.handleKeyDown}
value={this.state.inputValue}
/>
);
}
I have 4 different divs each containing their own button. When clicking on a button the div calls a function and currently sets the state to show a modal. Problem I am running into is passing in the index of the button clicked.
In the code below I need to be able to say "image0" or "image1" depending on the index of the button I am clicking
JS:
handleSort(value) {
console.log(value);
this.setState(prevState => ({ childVisible: !prevState.childVisible }));
}
const Features = Array(4).fill("").map((a, p) => {
return (
<button key={ p } onClick={ () => this.handleSort(p) }></button>
)
});
{ posts.map(({ node: post }) => (
this.state.childVisible ? <Modal key={ post.id } data={ post.frontmatter.main.image1.image } /> : null
))
}
I would suggest:
saving the button index into state and then
using a dynamic key (e.g. object['dynamic' + 'key']) to pick the correct key out of post.frontmatter.main.image1.image
-
class TheButtons extends React.Component {
handleSort(value) {
this.setState({selectedIndex: value, /* add your other state here too! */});
}
render() {
return (
<div className="root">
<div className="buttons">
Array(4).fill("").map((_, i) => <button key={i} onClick={() => handleSort(i)} />)
</div>
<div>
posts.map(({ node: post }) => (this.state.childVisible
? <Modal
key={ post.id }
data={ post.frontmatter.main.[`image${this.state.selectedIndex}`].image }
/>
: null
))
</div>
</div>
);
}
}
This is a good answer which explains "Dynamically access object property using variable": https://stackoverflow.com/a/4244912/5776910
A single click on a button updates all the buttons but I want to change the state of that particular clicked button. Please check the image links below and the code.
import React from 'react';
import './MenuCard.css';
class MenuCard extends React.Component {
constructor(props) {
super(props);
this.state = {
showButton: false,
hideButton: true,
aValue: 1,
breads: [],
category: [],
ids: 1,
btnVal: 'Add'
};
}
onKeyCheck = (e) => {
this.state.breads.map(filt => {
if (filt.id === e.target.id) {
console.log(e.target.id + ' and ' + filt.id)
return (this.setState({showButton: !this.state.showButton, hideButton: !this.state.hideButton}));
}
})
}
onShowButton = () => {
this.setState({showButton: !this.state.showButton, hideButton: !this.state.hideButton})
}
onValueIncrease = () => {
this.setState({aValue: this.state.aValue + 1});
}
onValueDecrease = () => {
this.setState({aValue: this.state.aValue - 1});
}
componentDidMount() {
fetch('http://localhost:3000/menu/food_category', {
method: 'get',
headers: {'content-type': 'application/json'}
})
.then(response => response.json())
.then(menudata => {
this.setState({category: menudata.menu_type})
console.log(this.state.category)
})
fetch('http://localhost:3000/menu', {
method: 'get',
headers: {'content-type': 'application/json'}
})
.then(response => response.json())
.then(menudata => {
this.setState({breads: menudata })
})
}
render() {
return (
<div>
{this.state.category.map(types => {
return (<div>
<div className="menu-head">{types}</div>
< div className="container-menu">
{this.state.breads.map((d, id)=> {
if (d.category === types) {
return (
<div>
<div className="content" key={id} id={d.id}>
<div className="items"> {d.item_name}</div>
<div className="prices"> {d.price} Rs.</div>
{this.state.showButton ?
<div>
<button
className="grp-btn-minus"
onClick={this.state.aValue <= 1 ?
() => this.onShowButton() :
() => this.onValueDecrease()}>-
</button>
<input className="grp-btn-text" type="text"
value={this.state.aValue} readOnly/>
<button id={d.id}
className="grp-btn-plus"
onClick={() => this.onValueIncrease()}>+
</button>
</div> :
<button id={d.id} key={id}
onClick={ this.onKeyCheck}
className="add-menu-btn">
add
</button>
}
</div>
</div>
)
}
})}
</div>
</div>)
})}
</div>
)
}
}
export default MenuCard;
This is the first image of multiple rendering of component Add buttons
Here is the problem that all buttons get updated on single click
You're using an array of items but refering to a single, shared value in handlers. De facto you're using a few shared values: showButton, hideButton, aValue), 2/3 unnecessary ;)
First - aValue for each item should be stored in a structure - array or object. It could be an order = {} - object with id-keyed properties with amounts as values like this:
order = {
'masala_id': 1,
'kebab_id' : 2
}
Event handler (for 'add') should check if id for choosen product already exist in order object (as property name) and update amount (+/-) or create new one with 1 value (and remove property when decreased amount = 0).
In practice order should also contain a price - it seams like duplicating data but it will be much easier to count total order value.
order = {
'masala_id': {
'amount': 1,
'price': 20,
},
'kebab_id' : {
'amount': 2,
'price': 180,
}
}
Item doesn't need to be a component but it's much easier to maintain it, keep it readable etc.
This way we can simply pass already ordered amount and conditionally render buttons:
<Product id={d.id}
name={d.item_name}
price={d.price}
amount={order[d.id] ? order[d.id].amount : 0 }
amountHandler={this.changeAmountHandler}
/>
Product should be slightly improved and simplified (f.e. key is needed on top div):
class Product extends React.Component {
render () {
const (id, name, price, amount, amountHandler} = this.props;
const showIncrease = !!amount; // boolean, it also means "don't show add button"
return (
<div key={id} >
<div className="content">
<div className="items">{name}</div>
<div className="prices">{price} Rs.</div>
{showIncrease ?
<div>
<button
className="grp-btn-minus"
onClick={(e) => { amountHandler(e, id, -1) }}
>-</button>
<input className="grp-btn-text" type="text"
value={amount}
readOnly/>
<button
className="grp-btn-plus"
onClick={(e) => { amountHandler(e, id, 1) }}
>+</button>
</div> :
<button
onClick={(e) => { amountHandler(e, id, 1) }}
className="add-menu-btn"
>add</button>
}
</div>
</div>
)}}
This way you can handle all events in one handler, keep entire order state in main component... in case of performance problems just use PureComponent.
It looks like all the buttons are sharing the same state. You could try breaking the button up into its own component, and then move the state that button needs into there. That way when you click a button the state of that one particular button is updated, and not the state of the parent component that contains all the buttons.