I am trying to create a select dropdown that onChange updates all the select dropdowns in the table.
State is being set correctly. However when I change any of the selects in the table the value does not remain selected (displayed).
It sets state correctly fro both children and master selects. Its just not displaying the option selected.
import React, { Component } from 'react';
// import ReactDOM from 'react-dom';
// import { Select } from 'element-react';
import Select from 'react-select';
import logo from './logo.svg';
import './App.css';
class App extends Component {
constructor(props) {
super(props);
this.state = {
products: [],
categories: [],
filterText: '',
};
this.handleChange = this.handleChange.bind(this);
this.handleFilterTextChange = this.handleFilterTextChange.bind(this);
this.masterCategoryChange = this.masterCategoryChange.bind(this);
}
handleFilterTextChange(filterText) {
this.props.handleFilterTextChange(filterText)
this.setState({
filterText: filterText
});
}
handleChange(product, index, e){
product.category_id = e.value;
let selectValues = this.state.selectValues;
selectValues[index] = e.value;
this.setState({selectValues});
}
masterCategoryChange(e){
let selectValues = this.state.selectValues;
selectValues.forEach(function(sv, index) {
selectValues[index] = e.value;
});
console.log(selectValues)
this.setState({selectValues});
}
/* Inside your component */
componentDidMount() {
const apiRequest = url => fetch(url).then(response => response.json())
const apiRequestProducts = () => {
return apiRequest("http://docker.for.mac.localhost:4000/api/products?filter[limit]=5").then(function(response) {
return response;
});
};
const apiRequestCategories = () => {
return apiRequest("http://docker.for.mac.localhost:4000/api/categories?filter[limit]=7").then(function(response) {
return response;
});
};
this.setState({loading: true});
Promise.all([
apiRequestProducts(),
apiRequestCategories()
]).then(results => {
var selectValues = [];
for (var i=0; i < results[0].length; i++) {
selectValues[i] = '';
}
this.setState({
selectValues,
products: results[0],
categories: results[1],
loading: false
});
}).catch(err => {
console.log('Oops, something went wrong', err);
});
}
render() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h1 className="App-title">Welcome to React</h1>
</header>
<FilterableProductTable
masterCategoryChange={this.masterCategoryChange}
handleChange={this.handleChange}
products={this.state.products}
categories={this.state.categories}
filterText={this.state.fliterText}
categorySelectValues={this.state.selectValues}
/>
</div>
);
}
}
class FilterableProductTable extends React.Component {
constructor(props) {
super(props);
this.handleFilterTextChange = this.handleFilterTextChange.bind(this);
this.handleCategoryChange = this.handleCategoryChange.bind(this);
this.handleMasterCategoryChange = this.handleMasterCategoryChange.bind(this);
}
handleFilterTextChange(filterText) {
this.props.handleFilterTextChange(filterText)
}
handleCategoryChange(product, index, e){
this.props.handleChange(product, index, e)
}
handleMasterCategoryChange(e){
this.props.masterCategoryChange(e)
}
render() {
return (
<div>
<SearchBar
filterText={this.props.filterText}
onFilterTextChange={this.handleFilterTextChange}
/>
<ProductTable
handleMasterCategoryChange={this.handleMasterCategoryChange}
handleCategoryChange={this.handleCategoryChange}
handleFilterTextChange={this.handleFilterTextChange}
products={this.props.products}
categories={this.props.categories}
filterText={this.props.filterText}
selectValues={this.props.categorySelectValues}
/>
</div>
);
}
}
class ProductTable extends React.Component {
constructor(props) {
super(props);
// This binding is necessary to make `this` work in the callback
this.handleClick = this.handleClick.bind(this);
this.onProductCategoryChange = this.onProductCategoryChange.bind(this);
this.masterCategoryChange = this.masterCategoryChange.bind(this);
}
handleClick = () => {
console.log('this is:', this);
}
onProductCategoryChange(product, index, e){
this.props.handleCategoryChange(product, index, e)
}
masterCategoryChange(e){
this.props.handleMasterCategoryChange(e)
}
render() {
console.log('Rendering');
console.log(this.props.selectValues);
let options = this.props.categories.map(function (category) {
return { value: category.id, label: category.name };
})
console.log(options);
let rows = this.props.products.map((product, i)=> {
console.log(this.props.selectValues[i])
return (
<tr key={i}>
<td>{product.name}</td>
<td>{product.price}</td>
<td><Select
options={options}
value={this.props.selectValues[i]}
// value={product.category_id}
onChange={this.onProductCategoryChange.bind(this, product, i)}
/></td>
</tr>
)
});
return (
// <h3>MasterCategory : {masterCategoryId}</h3>
<table>
<thead>
<tr>
<th>Name</th>
<th>Price</th>
<th><CategorySelect
onChange={this.masterCategoryChange.bind(this)}
className='masterCategorySelect'
categories={this.props.categories}/></th>
</tr>
</thead>
<tbody>{rows}</tbody>
<tfoot>
<tr>
<td><div className='pull-right'><button onClick={this.handleClick}>Categorize</button></div></td>
</tr>
</tfoot>
</table>
);
}
}
class CategorySelect extends React.Component {
render() {
let options = this.props.categories.map(function (category) {
return { value: category.id, label: category.name };
})
return (
<Select
onChange={this.props.onChange}
options={options}
// value={value}
/>
);
}
}
class SearchBar extends React.Component {
constructor(props) {
super(props);
this.handleFilterTextChange = this.handleFilterTextChange.bind(this);
}
handleFilterTextChange(e) {
this.props.onFilterTextChange(e.target.value);
}
render() {
return (
<form>
<input
type="text"
placeholder="Search..."
value={this.props.filterText}
onChange={this.handleFilterTextChange}
/>
</form>
);
}
}
export default App;
Related
Good day so I have a question about firebase and perhaps my code as well I wrote some code in JSX and React linked to Firebase and the Button that I'm using to delete is not working properly.
I'm using Parent Child props to pass the function into the page that is needed to be deleted but there is no functionality. I need help thanks!
this is the parent where the function is located :
import React from 'react';
import fire from '../config/firebase';
import Modal from 'react-modal';
// import "firebase/database";
// import 'firebase/auth';
import NotesCard from './note-card';
Modal.setAppElement('#root');
export default class Notes extends React.Component {
_isMounted = false;
constructor(props) {
super(props);
this.state = {
notes: [],
showModal: false,
loggedin: false
};
this.handleOpenModal = this.handleOpenModal.bind(this);
this.handleCloseModal = this.handleCloseModal.bind(this);
this.handleAddNote = this.handleAddNote.bind(this);
this.handleRemoveNote = this.handleRemoveNote.bind(this);
}
componentDidMount() {
this._isMounted = true;
fire.auth().onAuthStateChanged((user) => {
if(user){
// call firebase from import fire
// grab userData and push it to the dataArray
fire.database().ref(`users/${user.uid}/notes`).on('value', (res) => {
const userData = res.val()
const dataArray = []
for(let objKey in userData) {
userData[objKey].key = objKey
dataArray.push(userData[objKey])
}
// set in the state
if(this._isMounted){
this.setState({
notes: dataArray,
loggedin: true
})
}
});
}else {
this.setState({loggedin: false})
}
});
};
componentWillUnmount() {
this._isMounted = false;
}
handleAddNote (e) {
e.preventDefault()
const note = {
title: this.noteTitle.value,
text: this.noteText.value
}
// reference where we can push it
const userId = fire.auth().currentUser.uid;
const dbRef = fire.database().ref(`users/${userId}/notes`);
dbRef.push(note)
this.noteTitle.value = ''
this.noteText.value = ''
this.handleCloseModal()
}
handleRemoveNote(key) {
const userId = fire.auth().currentUser.uid;
const dbRef = fire.database().ref(`users/${userId}/notes/${key}`);
dbRef.remove();
}
handleOpenModal (e) {
e.preventDefault();
this.setState({
showModal: true
});
}
handleCloseModal () {
this.setState({
showModal: false
});
}
render() {
return (
<div>
<button onClick={this.handleOpenModal}>create Note</button>
<section className='notes'>
{
this.state.notes.map((note, indx) => {
return (
<NotesCard
note={note}
key={`note-${indx}`}
handleRemoveNote={this.handleRemoveNote}
/>
)
}).reverse()
}
</section>
<Modal
isOpen={this.state.showModal}
onRequestClose={this.handleCloseModal}
shouldCloseOnOverlayClick={false}
style={
{
overlay: {
backgroundColor: '#9494b8'
},
content: {
color: '#669999'
}
}
}
>
<form onSubmit={this.handleAddNote}>
<h3>Add New Note</h3>
<label htmlFor='note-title'>Title:</label>
<input type='text' name='note-title' ref={ref => this.noteTitle = ref} />
<label htmlFor='note-text'>Note</label>
<textarea name='note-text' ref={ref => this.noteText = ref} placeholder='type notes here...' />
<input type='submit' onClick={this.handleAddNote} />
<button onClick={this.handleCloseModal}>close</button>
</form>
</Modal>
</div>
)
}
}
and this is where the function is being called :
import React from 'react';
import fire from '../config/firebase';
export default class NotesCard extends React.Component {
constructor(props) {
super(props);
this.state = {
editing: false,
note: {}
}
this.handleEditNote = this.handleEditNote.bind(this);
this.handleSaveNote = this.handleSaveNote.bind(this);
}
handleEditNote() {
this.setState({
editing: true
})
}
handleSaveNote(e) {
e.preventDefault()
const userId = fire.auth().currentUser.uid;
const dbRef = fire.database().ref(`users/${userId}/notes/${this.props.note.key}`);
dbRef.update({
title: this.noteTitle.value,
text: this.noteText.value
})
this.setState({
editing: false
})
}
render() {
let editingTemp = (
<span>
<h4>{this.props.note.title}</h4>
<p>{this.props.note.text}</p>
</span>
)
if(this.state.editing) {
editingTemp = (
<form onSubmit={this.handleSaveNote}>
<div>
<input
type='text'
defaultValue={this.props.note.title}
name='title'
ref={ref => this.noteTitle = ref}
/>
</div>
<div>
<input
type='text'
defaultValue={this.props.note.text}
name='text'
ref ={ref => this.noteText = ref}
/>
</div>
<input type='submit' value='done editing' />
</form>
)
}
return (
<div>
<button onClick={this.handleEditNote}>edit</button>
<button onClick={this.props.handleRemoveNote(this.state.note.key)}>delete</button>
{editingTemp}
</div>
)
}
}
Thank you in advance for taking a look at this code.
Second iteration answer
Working sandbox
Problem
looking at https://codesandbox.io/s/trusting-knuth-2og8e?file=/src/components/note-card.js:1621-1708
I see that you have this line
<button onClick={()=> this.props.handleRemoveNote(this.state.note.key)}>delete
Yet your state.note declared as an empty map in the constructor:
this.state = {
editing: false,
note: {}
}
But never assigned a value using this.setState in the component
Solution
Change it to:
<button onClick={()=> this.props.handleRemoveNote(**this.props.note.key**)}>delete</button>
First iteration answer
NotesCard's buttons is firing the onClick callback on render instead on click event.
This is because you have executed the function instead of passing a callback to the onClick handler
Change
<button onClick={this.props.handleRemoveNote(this.state.note.key)}>delete</button>
To
<button onClick={()=> this.props.handleRemoveNote(this.state.note.key)}>delete</button>
The state (open) is not updating from another component with props, the only way I made it work is with a settimeout, but I want it to close (update the open property to false) instantly. The strange thing is that when it is given in the X of the modal if it is updated (Close). (Everything else works perfectly)
ProductList.tsx
class ProductList extends React.Component<IProductListProps, IState> {
constructor(props: IProductListProps) {
super(props);
this.state = {
index: 0,
open: false,
product: undefined
}
this.handleKeyDown = this.handleKeyDown.bind(this);
}
handleKeyDown(e: React.KeyboardEvent<HTMLInputElement>) {
const ref: HTMLElement = this.refs['pd-' + 0] as HTMLElement
if(e.keyCode === 13) {// OPEN MODAL
this.setState({
...this.state, open: true,
product: this.props.products[ref.dataset.id],
})
}
}
handleClose() {
this.setState({
...this.state,
open: false
})
}
render() {
const { open, product } = this.state
return (
<Container>
<div>
<input type="text" name='search'
value={...} onChange={...}
onKeyDown={this.handleKeyDown} />
</div>
<table>
<tbody>
{this.props.products.map((x, i) =>
<tr key={i} data-id={x._id} ref={"pd-" + i}>
//...item-data
</tr>
)}
</tbody>
</table>
<ModalECP product={product as IDataProduct} open={open} close=
{() => this.handleClose()} />
</Container>
);
}
}
ModalECP.tsx
export default class ModalECP extends React.Component<IProps, IState> {
constructor(props: IModalECPProps) {
super(props);
this.state = { amount: '' }
this.handleKeyDown = this.handleKeyDown.bind(this);
}
handleKeyDown(e: React.KeyboardEvent<HTMLInputElement>) {
if (e.keyCode === 13) {
e.preventDefault()
const { amount } = this.state
const { product, close, open } = this.props
if (Number(amount) > 0 && Number(amount) <= product.stock) {
// HERE IS THE ERROR *************
setTimeout(() => close(), 500);
// *******************************
this.setState({ amount: '' })
}
}
}
render() {
const { open, close } = this.props
return (
// HERE IT WORKS / UPDATES THE PROPERTY ON
<Modal show={open} onHide={close} animation={false}>
<Modal.Header closeButton>
</Modal.Header>
<Modal.Body>
<input type="number" value={...} onChange={...}
onKeyDown={this.handleKeyDown} />
</Modal.Body>
</Modal>
);
}
}
I have a page contains multiple Bootstrap Cards and each card is a component and each card footer is also a component. Card Footer contains buttons. When you click on a button, drop down will be opened like below
At any point of time when I click on a button, other drop downs should be in closed state. But its happening like this...
Requirement: One more thing is when I click on the same button, the respective drop down should be closed.
Requirement: When I click on any item inside drop down the respective drop down should be closed
My Architecture is like below
HOME PAGE COMPONENT CODE -START
class HomePage extends React.Component {
constructor(props) {
super(props);
this.state = {
items: [],
activatedIdStoredInParent: ""
};
}
toggleCountersMenu = (name) => {
var name1 = name;
this.setState((prevState) => {
return {
activatedIdStoredInParent: name1
}
});
}
render() {
const products = this.state.items.map((item, index) => {
return <div>
<Card
product={item}
activatedIdStoredInParent={this.state.activatedIdStoredInParent}
toggleCountersMenu={this.toggleCountersMenu}
>
</Card>;
</div>
});
return (
<div>
<div className="card-columns">
{products}
</div>
</div >
);
}
}
export default HomePage;
HOME PAGE COMPONENT CODE - END
CARD COMPONENT CODE - START
class Card extends React.Component {
handleActionClick = (name) => {
this.props.toggleCountersMenu(name);
}
render() {
return (
<div key={this.props.product.name}>
<CardHeader product={this.props.product} />
<CardBody product={this.props.product} />
<CardFooter
product={this.props.product}
onActionItemClick={this.handleActionClick}
activatedIdStoredInParent={this.props.activatedIdStoredInParent}
/>
</div>
);
}
}
export default Card;
CARD FOOTER COMPONENT CODE - START
class CardFooter extends React.Component {
handleActionItemClick = (name) => {
this.props.onActionItemClick(name);
}
render() {
console.log('Card Footer Drop Down comp rendered');
return (
<div className=" card-footer text-center">
<ButtonDropdown text="F" className="danger"
product={this.props.product}
onActionItemClick={this.handleActionItemClick}
activatedIdStoredInParent={this.props.activatedIdStoredInParent}
></ButtonDropdown>
</div>
);
}
}
export default CardFooter;
ButtonDropdown COMPONENT CODE - START
class ButtonDropdown extends React.Component {
constructor(props) {
super(props);
this.state = {
open: false,
show: ' none',
localActivatedId: 'none'
}
}
toggleOpen = (e) => {
var name = e.target.name;
this.setState((prevState, props) => {
var item = {
localActivatedId: name
}
if (props.activatedIdStoredInParent === name) {
if (prevState.show === ' show') {
item.show = ' none';
}
else {
item.show = ' show';
}
}
return item;
});
this.props.onActionItemClick(name);
}
numberClick = (e) => {
var qty = e.target.innerText;
this.setState((prevState, props) => {
var item = {
show: ' none'
}
return item;
});
}
render() {
return (
<div className="btn-group" >
<button type="button" className={`btn btn-${this.props.className} mr-1`} name={this.props.product.name + '$$' + this.props.text} onClick={this.toggleOpen}>
{this.props.text} (classAdded={this.state.show})
</button>
<div className={`dropdown-menu ${this.state.show}`}>
<span className="dropdown-item cursor-pointer " onClick={this.numberClick}>
-1
</span>
<span className="dropdown-item cursor-pointer" onClick={this.numberClick}>
-2
</span>
</div>
</div>
);
}
}
export default ButtonDropdown;
When I add multiple buttonDropdown components in Card Footer the end product is like this. How can I close other dropdowns.
I would like to know is my architecture is correct.. I am not using Redux/Flux etc..
You can use the componentDidUpdate lifecycle, in order to update your state's property that is opening the dropdown.
I don't know if it's the open or show property that displays the content of the dropdown but here's my logic.
class ButtonDropdown extends React.Component {
constructor(props) {
super(props);
this.state = {
//
};
}
componentDidUpdate(prevProps) {
const name = this.props.product.name + '$$' + this.props.text;
if (prevProps.activatedIdStoredInParent !== this.props.activatedIdStoredInParent && this.props.activatedIdStoredInParent !== name) {
this.closeDropDown();
}
}
closeDropDown = () => this.setState({ isOpen: false });
toggleOpen = (e) => {
//
}
numberClick = (e) => {
//
}
render() {
//
}
}
export default ButtonDropdown;
Very very new to React and I seem to be stuck. This is a simple Todo app, I basically have 3 components, the base component, an input component and a task component. I have figured out how to edit the state within each component but I am having trouble passing state from component to component.
class App extends Component {
render() {
return (
<div id="appContainer">
<HeaderTitle />
<TaskInput />
<Task taskState={true} text="task one" />
<Task taskState={true} text="task two" />
<Task taskState={true} text="task three" />
</div>
);
}
}
class TaskInput extends React.Component {
constructor(props) {
super(props);
this.state = {}
}
update(e) {
this.setState({inputValue: e.target.value});
console.log(this.state);
}
taskCreate(e) {
this.setState({text: this.state.inputValue, completeState: false});
console.log('button clicked');
console.log(this.state);
}
render () {
return (
<div className="taskInputContainer">
<TaskInputField update={this.update.bind(this)} taskCreate={this.taskCreate.bind(this)} />
</div>
)
}
}
class Task extends Component {
constructor(props) {
super();
this.state = {
completeState: false
}
}
toggleTask (e) {
this.setState({
completeState: !this.state.completeState
});
}
delete (item) {
}
render() {
return (
<div className="taskContainer" onClick={this.toggleTask.bind(this)}>
<div className={"taskState " + this.state.completeState}></div>
<div className={"taskText " + this.state.completeState }>{this.props.text}</div>
<div className="taskDelete"><i className="fa fa-times-circle-o" aria-hidden="true"></i></div>
</div>
);
}
}
const TaskInputField = (props) =>
<div className="taskInputContainer">
<input type="text" className="taskInputField" onChange={props.update}/>
<i className="fa fa-plus-circle" aria-hidden="true" onClick={props.taskCreate}></i>
</div>;
Task.propTypes = {
text: PropTypes.string.isRequired,
completeState: PropTypes.bool
};
Task.defaultProps = {
text: 'Task',
completeState: false
};
const HeaderTitle = () => (
<h1>Davids Todo List</h1>
);
export default App;
So in the TaskInput has its own state that I can update but how do I pass that up to the parent component to update and add a Task component? Also how do I add a Task component without re-rendering the whole thing?
This issue is documented in detail in the article 'lifting the state up' in React's documentation.
TLDR, you create a handler that updates the state of the current component and pass it to children as props. In the example below (a modified version of your code), I passed down the methods that changes the state of component App, into its children components (TaskInput and Tasks).
class App extends React.Component {
constructor() {
super();
this.state = {
tasks: [],
}
}
addTask = (e, text) => {
e.preventDefault();
const newTask = {
id: new Date().getTime(),
done: false,
text
};
const newTasks = this.state.tasks.concat([newTask]);
this.setState({
tasks: newTasks
})
}
toggleTask = (id) => {
const updatedTask = this.state.tasks.filter(task => task.id === id);
updatedTask[0].done = !updatedTask[0].done;
const newTasks = this.state.tasks.map(task => {
if (task.id === id) {
return updatedTask[0];
}
return task;
});
this.setState({
tasks: newTasks
});
}
render() {
return (
<div id="appContainer">
<HeaderTitle />
<TaskInput addTask={this.addTask} />
{
this.state.tasks.length > 0 ? <Tasks tasks={this.state.tasks} toggleTask={this.toggleTask}/> : <div>no tasks yet</div>
}
</div>
);
}
}
class TaskInput extends React.Component {
constructor(props) {
super(props);
this.state = {
currentInput: ''
}
}
handleChangeText = (e) => {
this.setState({
currentInput: e.target.value,
})
}
render() {
return (<form>
<input type="text" value={this.state.currenInput} onChange={this.handleChangeText}/><input type="submit" onClick={(e) => this.props.addTask(e, this.state.currentInput)} value="Add Task"/></form>)
}
}
const Tasks = (props) => (
<div>
{
props.tasks.map(task => (
<div
style={ task.done ? { textDecoration: 'line-through'} : {} }
onClick={() => props.toggleTask(task.id)}
>{task.text}</div>
))
}
</div>
);
const HeaderTitle = () => (
<h1>Davids Todo List</h1>
);
ReactDOM.render(<App />, document.getElementById('app'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>
due to certain factors(SalesForce) I am looking of only using reactjs and not something like redux.
I going to have something like this
<script type="text/babel">
class Main extends React.Component {
constructor() {
super();
this.state = {
items : [
{
year: 2016,
},
{
year: '',
},
]
};
}
componentDidMount() {
}
handleChange(event, index){
let items = this.state.items;
let item = items[index];
item[event.target.name] = event.target.value;
items[index] = item;
this.setState({ items: items});
}
render() {
return (
<div className="main">
{
this.state.items.map((item, i) => {
return <Item item={item} index={i} handleChange={(event,index) => this.handleChange(event,index)} />
})
}
</div>
)
}
}
class Item extends React.Component {
constructor() {
super();
}
componentDidMount() {
}
handleChange(event){
this.props.handleChange(event, this.props.index);
}
render() {
return (
<div className="item">
<Sub item={this.props.item} index={this.props.index} handleChange={(event) => this.handleChange(event)} />
</div>
)
}
}
class Sub extends React.Component {
constructor() {
super();
}
componentDidMount() {
}
handleChange(event) {
this.props.handleChange(event, this.props.index);
}
render() {
return (
<div className="container">
<div>
<label>Year </label>
<input type="text" name="year" value={asset.year} className={year} onChange={(event) => this.handleChange(event)}/>
</div>
</div>
)
}
}
ReactDOM.render(<Main />, document.getElementById("Container"));
</script>
I am wondering is there away so that when someone types something in "year" in sub component I don't have to have handleChange that goes up to the Item handleChange and finally to the Main handleChange.