I have a drop down component that looks like this:
{...}
this.state = {
isVisible: false
}
}
toggleDisplay() {
this.setState({isVisible: !this.state.isVisible});
}
render() {
return (
<div>
<button onClick={this.toggleDisplay()}>click</button>
{this.state.isVisible ? <MenuElements toggleDisplay={this.toggleDisplay} /> : '' }
</div>
)
}
}
"MenuElements" is just a ul that has a li. On another page i am using this component multiple times, so whenever i click on the button, "MenuElements" is shown for each click. The problem is that i want only one component to be displayed. So if a MenuElements component is already displayed, if i click on another button, it closes the previous component, and opens the second one.
How could this be implemented in my code?
Thanks.
You will somehow need to have a single state that defines which MenuItem is displayed. You could go with a global state with something like Redux, but if you are trying to build a reusable component, I guess it'd be best to wrap all of the MenuItem components in a parent component and keep a state there. That, I think, is the React way of doing it. Read this for an idea of how to design components: https://facebook.github.io/react/docs/thinking-in-react.html.
BTW, I think there is an error in the Button onClick handler. It should be:
<button onClick={this.toggleDisplay.bind(this)}> // or bind it somewhere else
Also, the correct way to change state based on previous state is this:
// Correct
this.setState((prevState, props) => ({
counter: prevState.counter + props.increment
}));
// Wrong
this.setState({
counter: this.state.counter + this.props.increment,
});
I'd say this is du to the context of your callbacks. Have you tried forcing the context ?
<div>
<button onClick={this.toggleDisplay.bind(this)}>
click
</button>
{this.state.isVisible ?
<MenuElements toggleDisplay={this.toggleDisplay.bind(this)} />
: '' }
</div>
Related
I am trying to set an array to a state hook. Basically I want to keep a track of a per-row (of grid sort of) Edit Dialog Open State. Basically per row, I have a Edit button, launches a . As all seems rendered initially, I am trying to manage the show hide by keeping an array in the parent grid component. When user clicks on the Edit button, per row, I want to pass the rowData as props.data and want to provide the Edit functionality.
To keep the state of the editDialogs (show/hide), I am making a array of objects useState hook as follows:
const [editDialogsModalState, setEditDialogsModalState] = useState([{}]); // every edit dialog has it's own state
...
function initializeEditDialogsModalState(dataSet) {
let newState = [];
dataSet.map((item) => newState.push({ id: item.id, state: false }));
return setEditDialogsModalState(newState); // **PROBLEM->Not setting**
}
function addUDButtons(currentRowDataMovie) { // my edit/delete button UI code
const currRowDataId = currentRowDataMovie.id;
return (
<span>
<button
type="button"
className="btn btn-info"
onClick={() => setEditDialogsState(currRowDataId)}
>
Edit
</button>
{editDialogsModalState[currRowDataId].state && ( // **PROBLEM->null data even after set call**
<EditMovieComponent
open={editDialogsModalState[currRowDataId].state}
onToggle={toggleEditDialogsModalState(currentRowDataMovie)}
movie={currentRowDataMovie}
/>
)}
}
......
function buildGrid() {
{
if (!ready) {
// data is not there, why to build the grid
return;
}
initializeEditDialogsModalState(movies);
...........
}
However not able to get the editStates. A screen shot from debugger where I can see the movies (REST output), ready, but not the editDialogsModalState state array.
In general, is there a better ways of implementing such per-row basis functionality where on click of a button I want to open a React-bootstrap and pass the row-specific dataitem for doing operations ? (I am learning React, so may not not yet fully aware of all pointers).
Thanks,
Pradip
My particular use case of React is thus:
I wish to add a small React Component to a card that is an existing, fully-functional HTML element, per all the cards on the page. This React Component shall serve to implement a new feature on those cards : reverting changes.
The HTML (well, the MVCE version of it)
is something like this:
<div id="some-id" class="card float-sm-left menu-visual-card " onclick="(function(event) { console.log('I got clicked, and a modal will spawn' ) })(event)">
<div class=card-block>
<h5 class="card-title format-text">Some title</h5>
<!-- some business elements here -->
</div>
<!-- card footer -->
<div class=customized-indicator-react></div>
</div>
The React Component
in its tl;dr version is the following:
class CustomizedIndicatorComponent extends React.Component {
constructor(props) {
super(props)
// business logic
let active = this.props.active
this.state = {
active : active
}
}
toggleActive = () => {
this.setState({
...this.state,
active : !this.state.active
})
}
// setup
componentDidMount() {
// here's where I tried to add a jQuery onclick listener to stop propagation, only to have the React Component listener get stopped
}
// teardown
componentWillUnmount() {
console.log("CustomizedIndicatorComponent destroyed!")
}
// the UI logic
render() {
if (this.state.active) {
return (
<div>
<div
className="badge badge-sm badge-info float-sm-left customized"
style={{marginRight:"10px"}}
>Customized</div>
<div
onClick={(e) => {
e.stopPropagation()
this.toggleActive()
}}
title="Click to undo customizations">
<i className="fa fa-undo" aria-hidden="true"></i>
</div>
</div>
)
}
return <div />
}
}
What happens when you run this?
When I run this, it renders. However, when I click the widget to "de-activate" the element, the container's event-handler still fires!!
I know there is a slew of internet questions about this issue or something close to it, but none of the ones I could find seem to be about this exact use case.
Also, adding an event listener in componentDidMount doesn't work, as that prevents anything from firing!
Is there any way I can make this work without wasting developer-hours refactoring everything including the parent HTMLElements?
A "hacky" way you may consider is to get the parent's id from inside the React component and disable the click event from there.
If id could not be passed as a property to the React component, you can try using ReactDOM.findDOMNode(this).parentNode.getAttribute("id") to get it and then disable the event using:
document.getElementById(id).style.pointerEvents = 'none';
I have started an application which I want to work same as weather.com next 36 hours section. The idea is when you click on each weatherCard which has a seperate component in my app you will update the below section which is my weatherDetails component based on the selected weatherCard /weather box. So I made the entire component clickable by giving it the click event via props from my stateful component which is my weatherLocation component. This is my WeatherCard component:
const WeatherCard = (props) => (
<div id={props.date} className="weatherCard" onClick={props.clicked}>
<h2 className="cardDate">{props.date}</h2>
<h4>{props.forcast}</h4>
<div className="minmaxDeg">
<data>{props.min}</data>
<data>{props.max}</data>
</div>
<data>{props.rain}</data>
</div>
);
And here in render method in WeatherLocation component I loop through data coming from state and give props the WeatherCard component:
const WeatherCards = this.state.reports.map( report => {
return(
<WeatherCard
key={report.id}
{...report}
clicked={() => this.handleCardClick(event)}
/>
);
});
And this is the handleCardClick that I added for it just for testing:
handleCardClick = event => {
// const { reports , selectedCardInfo , activeCard } = this.state;
const selectedDate = document.getElementById(event.target.id);
console.log(event.target.id);
}
I don't want to use anchor tag as I don't need href. The click works fine by itself. But because I need to get the id of the parent which is the div with the class of weatherCard. At the moment when I click on other elements inside the card I cannot get the id because they are not the parent. The reason I need its id is when I get data with from the API I need a unique value for each card so that when you click on the card the data for that card will be shown in the other component which is the WeatherDetails component. But for now I need to be able to somehow choose that selected card and pull out the state for that unique card. Could someone help me out? Thanks.
You just need to pass the Parent component ID to your onClick function in Weather Card.
Here is your WeatherCard - Component
const WeatherCard = (props) => (
<div id={props.date} className="weatherCard" onClick={event => props.clicked(event, props.id)}>
<h2 className="cardDate">{props.date}</h2>
<h4>{props.forcast}</h4>
<div className="minmaxDeg">
<data>{props.min}</data>
<data>{props.max}</data>
</div>
<data>{props.rain}</data>
</div>
);
You can see that I have added props.id to your onClick function and with help of event now you can access that id from the parent component.
Now here is your Parent Component- WeatherCards
const WeatherCards = this.state.reports.map( (report, i) => {
return(
<WeatherCard
key={report.id}
id={i}
{...report}
clicked={this.handleCardClick}
/>
);
});
You can see in the code I am passing index number as id to your child component.
So this will give you an id (for now it's an index number) of the card in your onClick handler.
and Finally, here is your on click handler.
handleCardClick = (event, weatherCardID) => {
console.log(weatherCardID)
}
As of now, I am using the index as id if you want to use a unique identifier, you can change that easily.
General JavaScript solution is to differentiate the elements and .stopPropogation after you've captured the event you are targeting. A nested unordered list, <ul>would be an example. Tag the containing <li> with an .opened class upon rendering/displaying each level of nesting, tag those <li> elements accordingly, e.g. a dataset attribute such as data-make, then data-model, then data-option. You then attach and fire event listeners on the different level <li>'s.
Thank you #RutulPatel. I made your answer as the answer. But I changed your code a bit as I got your point so I wrote an answer as it is long. I think we might not need to change the WeatherCard at all and I don't pass event or any logic there. so it will be intact:
const WeatherCard = (props) => (
<div id={props.date} className="weatherCard" onClick={event => props.clicked(event, props.id)}>
<h2 className="cardDate">{props.date}</h2>
<h4>{props.forcast}</h4>
<div className="minmaxDeg">
<data>{props.min}</data>
<data>{props.max}</data>
</div>
<data>{props.rain}</data>
</div>
);
But I use your tip changing my weatherCards array to look like this:
const weatherCards = this.state.reports.map( report => {
return(
<WeatherCard
key={report.id}
id={report.date}
{...report}
clicked={() => this.handleCardClick(event, report.date)}
/>
);
});
So I use the report.date which is a unique value as my id. Also I don't pass event as a parameter to the arrow function I just pass it with the report.date to the handler:
clicked={() => this.handleCardClick(event, report.date)}
And the handler will be the same as you did:
handleCardClick = (event, weatherCardID) => {
console.log(weatherCardID)
}
I might even remove event later on from both if there was no need fo that.
Thank you again.
I am starting my adventure with React so it is a hard time for me, however I prepared such pen for you to test. Here is a portion of code:
class App extends React.Component {
constructor() {
super();
this.state = {
settings: true,
next: false,
};
}
toggler(abc) {
console.log(">>", abc)
this.setState({
next: !this.state.next
/* {abc}: this.state.{abc} */
})
console.log(this.state.next)
}
render() {
return (
<div className="kalreg">
<MyButton name='settings' isActive={this.state.settings} type="settings" toggle={this.toggler.bind(this)}/>
<MyButton name='settings2' isActive={this.state.settings} type="settings" toggle={this.toggler.bind(this)}/>
<MyButton name='next' isActive={this.state.next} type="next" toggle={this.toggler.bind(this)}/>
</div>)
}
}
class MyButton extends React.Component {
constructor(props) {
super(props);
}
onChangeName(){
console.log(this.props.type)
if ( this.props.isActive ) { console.log("this one is active"); } else { console.log("ouch! it is not active, ignoring!"); return;}
this.props.toggle(this.props.type);
}
render () {
if ( this.props.isActive ) {
return ( <div className="button notVisible" onClick={this.onChangeName.bind(this)}>{this.props.name}</div>)
} else {
return ( <div className="button visible" onClick={this.onChangeName.bind(this)}>{this.props.name}</div>)
}
}
}
ReactDOM.render(<App />, document.getElementById("app"));
What I am trying to achieve is that when i press one of "settings" buttons (yellow) the "next" button becomes unclickable (green). There is a toggle function that every time I click settings button it turns on and off "next" button.
It works quite good, however it is just a draft of bigger project and i want to automate it a little bit.
As you can see I create my <MyButton> with both "isActive" and "type" props. But isActive holds what's inside this.state.settings while type is "settings". Instead of using two variables it would be great to pass only type of button to its component and component, depending on its type would check its parent's this.state.{type}. I used {type} because i would like to check it dynamically. Is that possible?
If so - how to do it?
My first attempt is to pass type from <MyButton> to <App> via toggler function. I named the variable "abc". I commented the way I wanted to do it because it doesn't work:
{abc}: !this.state.{abc}
Any idea to solve this problem would be more than appreciated.
Kalreg.
It is somewhat unclear what you are trying to achieve here. If you want to wire the state dynamically based on type, as you wrote in code: {abc}: !this.state.{abc} each button would toggle itself, not the next button. In this case your syntax is a little incorrect, it will work if you write it like:
[abc]: !this.state[abc]
However as I said, in your example, this makes the settings button change the state for this.state.settings disabling itself instead of the next button.
Another note would be, that if it is not necessary for the MyButton component to know its own type for other reasons, it is unnecessary to pass it as a prop and than make the component pass it back as an argument (this.props.toggle(this.props.type);). You can simply define the toggle function in the parent as:
toggle={() => this.toggler("settings")}
without passing type as a prop.
So basically we want to have the settings and settings2 buttons, and when we click on them, they toggle the state of the next button by making it un-clickable (green).
So if that is our goal, then
we don't need an isActive prop for the settings button. (Because it's always going to be active no matter what)
We also don't need to have a toggle prop on the Next button. (Because clicking the next button isn't supposed to toggle anything)
Instead of having two variables in the state why not just have one and then use that to determine the isActive prop of the next button?
The component would look like this:
constructor() {
super();
this.state = {
nextIsActive: false,
};
}
toggler() {
this.setState({
nextIsActive: !this.state.nextIsActive
})
console.log(this.state);
}
render() {
const {nextIsActive} = this.state
return (
<div className="kalreg">
<MyButton name='settings' isActive={true} type="settings" toggle={this.toggler.bind(this)}/>
<MyButton name='settings2' isActive={true} type="settings" toggle={this.toggler.bind(this)}/>
<MyButton name='next' isActive={nextIsActive}/>
</div>
)
}
That way you don't have to have 2 state properties that you have to dynamically update because it adds more complexity to your application.
You can see the finished product here: Codepen
I want to implement 'min-character-length' feature in react material-ui autocomplete component.
Below is the code .
constructor(props) {
super(props);
this.state = {
// based on this value, trying to maintain autocomplete's menu state open/close
shouldOpenList: false,
};
}
// Method in-built
onUpdateInput(searchText, dataSource, params) {
if( searchText && searchText.length >= 3) {
this.setState({
shouldOpenList: true
})
}
}
//component props
<AutoComplete
hintText={props.placeholder}
dataSource={ props.data }
dataSourceConfig={ {text: props.text, value: props.value} }
className="fabric-autocomplete form-control"
disableFocusRipple={false}
filter={filter}
onNewRequest={ this.onNewRequest.bind(this) }
onUpdateInput={ this.onUpdateInput.bind(this) }
open={this.state.shouldOpenList} // state's value used to show menu
/>
What I understand so far is function onUpdateInput() getting fired on typing each time and it is explicitly showing menu. Props 'open' is not able to deal with state 'shouldOpenList' value.
How do i achieve min-character-length feature for this component ?
thanks for help in advance.
Maybe you can try something like popoverProps={{style: {display: 'none'}}} and change that with state.
In the source of AutoComplete it keeps the bool open in it's state. Your open prop will only be set to the state on componentDidMount and in componentWillReceiveProps. In componentWillReceiveProps it checks for this.props.open !== nextProps.open.
So it checks for false !== false in this case, which does not trigger the setState. I dont really understand why they added this property since it seems a bit useless. Maybe only to open it on the initial render.
The internal handleChange of AutoComplete which calls onUpdateInput will set the components state to open every time a character is added. Completely ignoring your open property.
EDIT:
This solution works better
<AutoComplete
popoverProps={{
open: this.state.shouldOpenList
}}
hintText={props.placeholder}
dataSource={ props.data }
dataSourceConfig={ {text: props.text, value: props.value} }
className="fabric-autocomplete form-control"
disableFocusRipple={false}
filter={filter}
onNewRequest={ this.onNewRequest.bind(this) }
onUpdateInput={ this.onUpdateInput.bind(this) }
/>
But you will also need to set open to false if the length is less than 3.