Handling dropdown item select in React Bootstrap - javascript

I'm iterating through a dict and displaying the keys in a react-bootstrap dropdown menu.
This is my React dropdown Component,
class Dropdown extends React.Component
{
constructor(props)
{
super(props);
this.onTargetSelect = this.onTargetSelect.bind(this);
}
onTargetSelect(target)
{
console.log("Outputting from here: ", target);
document.getElementById("dropdown-title").textContent(target);
this.props.passTargetToParent(target);
}
render()
{
return(
<SplitButton title="Select Target" id="dropdown-target">
{Object.keys(dict).map(key => <MenuItem key={dict[key]}
href={`#${dict[key]}`}
onSelect={this.onTargetSelect(dict[key])}>{key}</MenuItem>)}
</SplitButton>);
}
}
There are two things that I'm trying to do here, which are proving difficult because of my limited knowledge of Javascript.
This displays all the values associated with the respective keys on load. That's not the behavior I want. I only want to console log the selected Item in the dropdown.
Second I want to change the title displayed currently as Select Target to the selected item in the dropdown.

Change to this:
onSelect={() => this.onTargetSelect(dict[key])}
...what you have above is executing this.onTargetSelect() immediately during the render process, and thus once for each item in your list. You need to supply a handler (function) for onSelect, and that handler should call this.onTargetSelect() inside of it. The "() =>" syntax is creating a new function.
To change the title, you should not use the DOM as your are doing. In React, you modify state or props to cause a re-rendering of your component. Full code below using .state/.setState() (not tested, just copy/pasted from yours and tweaked):
class Dropdown extends React.Component {
constructor(props) {
super(props);
this.onTargetSelect = this.onTargetSelect.bind(this);
this.state = { title: 'Select Target' };
}
onTargetSelect(target) {
this.setState({ title: target });
this.props.passTargetToParent(target);
}
render() {
return (
<SplitButton title={this.state.title} id="dropdown-target">
{Object.keys(dict).map(key => <MenuItem key={dict[key]} href={`#${dict[key]}`} onSelect={() => this.onTargetSelect(dict[key]) }>{key}</MenuItem>) }
</SplitButton>
);
}
}

Related

React from mapped components open dialog

So my application is growing, and I am starting to wonder, what is the best approach in my case, to open dialog from mapped component.
So currently, I have events array of object, which i map and create new components.
EventFeed.js
events.map(event => <EventCard key={event._id} event={event}/>)
What i want to do from each eventcard on "More info" button click call dialog and pass event id to that dialog, my current aproach looks like this:
SingleEventCard
class RecipeReviewCard extends React.Component {
constructor(props) {
super(props);
this.state = {
expanded: false,
isgoing: this.props.isgoing,
showModal1: false
};
this.handleClick = this.handleClick.bind(this);
}
handleClick(event) { // switch the value of the showModal state
this.setState({
showModal1: !this.state.showModal1
});
}
getComponent = () => {
if (this.state.showModal1) { // show the modal if state showModal is true
return <EventView eventid={this.props.event._id} />;
} else {
return null;
}
}
render() {
return {
<Button onClick={this.handleClick}>More info</Button>
{this.getComponent()}
}
}
In render i have another views which I wont write(since no reason bunch of inputs and displays), the problem is that when I open dialog it messes up my event card style. Also I need to click two times next time in order to open. Can someone can offer more generic approach, where I can render that dialog in other component or maybe something different?

(react) Listening to dropdown/select value changes triggered by changed data list propagated as props

I am using a simple HTML select dropdown and controlling it with react (==> controlled component). Everything fine so far. The Problem is - the select options are updated via an async ajax call every few seconds and empty at the beginning. The selects data list is propagated via props.
So, the select data list changes, the selected options list changes - but no change is fired (afaik by design of react).
I have found a working way to listen for these changes by listening to "componentDidUpdate" and firing a onChange "by hand" by reading out the value of the select as reference - but this seems very "un react-ish" (code below). Does anyone know the "react" way to do this?
Full code:
class Dropdown extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.dropDown = React.createRef();
}
handleChange(event) {
if (this.props.onChange) this.props.onChange(event.target.value);
}
componentDidUpdate(prevProps) {
if (this.props.options.length != prevProps.options.length) {
if (this.props.onChange) this.props.onChange(this.dropDown.current.value);
} else {
for (let i = 0; i < this.props.options.length; i++) {
if (this.props.options.value != prevProps.options.value) {
if (this.props.onChange) this.props.onChange(this.dropDown.current.value);
return;
}
}
}
}
render() {
const optionList = this.props.options.map(option => <option value={option.value} key={option.value}>{option.name}</option>);
return <select value={this.props.value} onChange={this.handleChange} ref={this.dropDown}>{optionList}</select>;
}
}
props.options start as empty list. Some parent node holds this list as a state and updates it every few seconds with a ajax request.
Sandbox code: https://codesandbox.io/s/6l927kpx13
You should pass props to state.
state = {
options: this.props.options,
}
render method:
render() {
const optionList = this.state.options.map((option, index) => (
<option key={index} value={option.price}>{option.price}</option>
));
return (
<select>{optionList}</select>
);
}
listener for props changes:
componentDidUpdate(prevProps) {
if (this.props.options[0].price !== prevProps.options[0].price) {
this.setState({
options: this.props.options,
});
}
}
Try this codesandbox https://codesandbox.io/s/pjky3r4z60
React handles it's updates by looking at a component's props and state. The way you've implemented it now is mostly correct, whenever you call setState(), a re-render is triggered.
However, the onChange event you're looking for is not whenever your options are dynamically updated, but this event gets triggered whenever your user selects a different option. This has nothing to do with React.
See the answer provided by Rizal Ibnu if you want to check for updates in a more efficient manner.
However, I would add some updates to your code, it could be shorter:
class Dropdown extends React.Component {
constructor(props) {
super(props);
}
// You can 'bind' this also with an arrow function
handleChange = event => {
if (this.props.onChange) this.props.onChange(event.target.value);
};
componentDidUpdate(prevProps) {
if (this.props.options.length != prevProps.options.length) {
if (this.props.onChange) this.props.onChange(this.dropDown.current.value);
} else {
this.props.options.forEach(() => {
if (this.props.options.value != prevProps.options.value) {
if (this.props.onChange)
this.props.onChange(this.dropDown.current.value);
return;
}
})
}
}
}
render() {
return (
<select
value={this.props.value}
onChange={this.handleChange}
// Consider using callback refs
ref={dropdown => (this.dropDown = dropDown)}
>
// Pure preference, I like mapping a list inline
{this.props.options.map(option => (
<option value={option.value} key={option.value}>
{option.name}
</option>
))}
</select>
);
}
}
I would look again at the this.props.onChange method from your parent, I don't think it should be undefined.

Change the state when clicking outside a component in React

I have a dropdown as is shown in the following image:
When I click the folder icon it opens and closes because showingProjectSelector property in the state that is set to false.
constructor (props) {
super(props)
const { organization, owner, ownerAvatar } = props
this.state = {
owner,
ownerAvatar,
showingProjectSelector: false
}
}
When I click the icon, it opens and closes properly.
<i
onClick={() => this.setState({ showingProjectSelector: !this.state.showingProjectSelector })}
className='fa fa-folder-open'>
</i>
But what I'm trying to do is to close the dropdown when I click outside it. How can I do this without using any library?
This is the entire component: https://jsbin.com/cunakejufa/edit?js,output
You could try leveraging onBlur:
<i onClick={...} onBlur={() => this.setState({showingProjectSelector: false})}/>
I faced same issue with you. Solved after reading this:
Detect click outside React component
Please try:
You should use a High Order Component to wrap the component that you would like to listen for clicks outside it.
This component example has only one prop: "onClickedOutside" that receives a function.
ClickedOutside.js
import React, { Component } from "react";
export default class ClickedOutside extends Component {
componentDidMount() {
document.addEventListener("mousedown", this.handleClickOutside);
}
componentWillUnmount() {
document.removeEventListener("mousedown", this.handleClickOutside);
}
handleClickOutside = event => {
// IF exists the Ref of the wrapped component AND his dom children doesnt have the clicked component
if (this.wrapperRef && !this.wrapperRef.contains(event.target)) {
// A props callback for the ClikedClickedOutside
this.props.onClickedOutside();
}
};
render() {
// In this piece of code I'm trying to get to the first not functional component
// Because it wouldn't work if use a functional component (like <Fade/> from react-reveal)
let firstNotFunctionalComponent = this.props.children;
while (typeof firstNotFunctionalComponent.type === "function") {
firstNotFunctionalComponent = firstNotFunctionalComponent.props.children;
}
// Here I'm cloning the element because I have to pass a new prop, the "reference"
const children = React.cloneElement(firstNotFunctionalComponent, {
ref: node => {
this.wrapperRef = node;
},
// Keeping all the old props with the new element
...firstNotFunctionalComponent.props
});
return <React.Fragment>{children}</React.Fragment>;
}
}
If you want to use a tiny component (466 Byte gzipped) that already exists for this functionality then you can check out this library react-outclick.
The good thing about the library is that it also lets you detect clicks outside of a component and inside of another. It also supports detecting other types of events.
Using the library you can have something like this inside your component.
import OnOutsiceClick from 'react-outclick';
class MyComp extends Component {
render() {
return (
<OnOutsiceClick
onOutsideClick={() => this.setState({showingProjectSelector: false})}>
<Dropdown />
</OnOutsiceClick>
);
}
}
Wrapper component - i.e. the one that wrapps all other components
create onClick event that runs a function handleClick.
handleClick function checks ID of the clicked event.
When ID matches it does something, otherwise it does something else.
const handleClick = (e) => {
if(e.target.id === 'selectTypeDropDown'){
setShowDropDown(true)
} else {
setShowDropDown(false);
}
}
So I have a dropdown menu that appears ONLY when you click on the dropdown menu, otherwise it hides it.

React: How do I make Child components in an Array re-render upon prop change?

I have a MainComponent which has several buttons with different texts. Upon button click the first time for any of them, it'll create a new window component. There is a flag that prevents it from creating more Windows unless this flag changes for some reason (I didn't need to include it here). Assume the code can create multiple Window components and add it to the list and render it successfully, which it does but if the mode is changed, click the buttons again won't create more components.
I render this array of objects by creating an state array and state flag and add new Window component to that array everytime a new one is created and render that array.
My problem is that when new Window create flag is off (flagObj !== null), I click any of the buttons which pass in differing values to the current windows, and I want the values to update. But the values DO NOT update. Each window.body prop accesses this.state.currentWindowData which is what I'm changing everytime a button gets clicked in this mode, and using dev tool I see that the state gets changed accordingly.
For some reason, it is not propagating down to the children Window components in the list. What is the problem?
MainComponent
constructor(props) {
super(props)
this.state = {renderWindow: false, windowArray: []}
this.manage= this.manage.bind(this)
}
openWindow() {
const newWindow = <Window key={shortid.generate()} body={this.state.currentWindowData}/>;
this.setState({
renderWindow: true,
windowArray: [newWindow, ...this.state.windowArray]
})
}
modify(val) {
this.setState({currentWindowData: val});
}
manageWindow(val) {
if (flagObj === null) {
this.setState({currentWindowData: val}, () => {
this.openWindow();
});
} else {
this.modifyWindow();
}
}
render() {
return (
<div>
<button onClick = {() => this.manage("text1")}/>
<button onClick = {() => this.manage("text2")}/>
<button onClick = {() => this.manage("text3")}/>
{this.state.renderWindow ? this.state.windowArray : null}
</div>
)
}
This is the window component. I render this.props.body (the state data passed in) and also do this.props.bodyData (set from componentWillReceiveProps), but neither update.
Window
constructor(props) {
super(props);
}
componentWillReceiveProps(nextProps) {
this.setState({bodyData: nextProps.body});
}
render() {
return (
<div>
{this.props.body}
{this.state.bodyData}
</div>
);
}

Why does ReactJS handle radio `checked` attr differently from other attrs?

tl;dr React refuses to honor checked={checkThisOption} on inputs, even though it honors data-ischecked={checkThisOption} perfectly on the same set of inputs.
I haven't made this work on jsfiddle, but I have reproduced the issue using this code.
the long version
I've got a simple ReactJS component that presents a list of radio buttons to the user. The user is supposed to be able to pick a radio and then push a button to confirm their choice.
Here's the component def (note: I'm using ES6 & webpack):
import React from 'react';
class Widget extends React.Component {
constructor(props) {
super(props);
this.state = {
currentValue: null // tracks currently-selected choice, by its value
};
}
onClickOptionRadio = (event) => {
this.setState({
currentValue: String(event.currentTarget.value)
});
}
onConfirm = (event) => {
if(!this.props.onChange) return;
this.props.onChange(this.state.currentValue);
};
render() {
let choices = this.props.choices;
let currentValue = this.state.currentValue;
return (
<div className="Widget">
<ol className="choices">
{
choices.map((choice, i) => {
// decide whether to mark radio as checked:
// - if no current choice, check first radios
// - otherwise, check radio matching current choice
let noCurrentChoice = (currentValue === null);
let drawingFirstChoice = (i === 0);
let thisChoiceIsSelected = (String(choice.value) === currentValue);
let checkThisOption = thisChoiceIsSelected || (noCurrentChoice && drawingFirstChoice);
return (
<li key={i}>
<input type="radio" name="choices"
value={choice.value}
onChange={this.onClickOptionRadio}
checked={checkThisOption?'checked':''}
data-ischecked={checkThisOption}
/>
<label>{choice.label}</label>
{' '}
{checkThisOption ? 'CHECKED' : ''}
</li>
);
})
}
</ol>
<button onClick={this.onConfirm}>Confirm choice</button>
</div>
);
}
}
export default Widget;
Here's the owning component:
import React from 'react';
import Widget from 'components/widget';
class Owner extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
let choices = [
{ value: 10, label: 'First' },
{ value: 20, label: 'Second' },
{ value: 30, label: 'Third' }
];
return (
<div className="Owner">
<Widget
choices={choices}
/>
</div>
);
}
}
export default Owner;
Here's a gif of it in action:
Note several things from the video:
the logic clearly works for checking the first radio on initial render
the other radios don't become checked when the user clicks on them
however, the logic clearly works for identifying which item is selected, as indicated by margin-right: 2rem on the radio that ought to be checked
the text indicating which option has been chosen is accurate throughout
when I click a radio, the componentWillUpdate method fires only for the Widget itself; none of its ancestors update
I think this demo proves that this isn't a case of the Widget instance being replaced by a different instance whose state is empty. The fact that the current selection is accurately reflected by a data- attr on the input, as well as plain text, shows that the state is persisting as desired. I am certain this unwanted behavior is by design, and I want to know how to work around the bizarre, exceptional logic that React applies to the form-related properties of controlled inputs.
Why do I think the current behavior is wrong? I don't want the owning component to know about each radio click -- the owner should bind to Widget's onChange method to be notified once a final choice is made.
This is a simplified example. The real component is more complicated, but the principle is the same: just as a date-picking component may have lots of internal state that the owning component is unaware of (like what time scale to show, which year, month, or week to display, etc.), so too does this component have some interesting internal state that owning components have no business managing.
As far as I can tell, I've done this exactly correctly. The component publishes its important state updates via onChange(event, newValue), which owning components should bind to. I think it's quite clear that React is deciding to not update the checked attr on these inputs, even though it's clearly capable of updating other attrs on the same elements in response to the same user actions.
Note that the owner isn't currently listening for the onChange, but that shouldn't matter: the child component should be able to manage its own internal state even when the owner isn't listening. And I reject the assertion that the radio state can't be accurate simply because Owner isn't providing a currentValue via props: Widget is plainly managing and rendering its state without that prop. React must be doing something special to prevent checked from being handled according to the rules that apply to every other element and attribute. This is an exception, and I think it's a bad one.
Finally, note that this problem only seems to occur when this component is beneath a certain comp-tree depth. When it is the only component in a Flux "page" or a Redux "container," it works great. When it's nested more deeply, it fails as I've described. I haven't yet worked out a concise way of showing that.
Any advice is appreciated. As far as I can tell, here React is violating its own stated rules, and I expect this behavior to frustrate building other stateful components that are built around vanilla inputs.
Edit: I corrected the generated names for the radios, and updated the demo to reflect it. Apologies to anyone who started chasing that stuff down.
I've edited it to not use class properties, and are not able to reproduce:
Code:
class Widget extends React.Component {
constructor(props) {
super(props);
this.state = {
currentValue: null // tracks currently-selected choice, by its value
};
this.onClickOptionRadio = this.onClickOptionRadio.bind(this)
this.onConfirm = this.onConfirm.bind(this)
}
onClickOptionRadio (event) {
this.setState({
currentValue: String(event.currentTarget.value)
});
}
onConfirm (event) {
if(!this.props.onChange) return;
this.props.onChange(this.state.currentValue);
};
render() {
let choices = this.props.choices;
let currentValue = this.state.currentValue;
return (
<div className="Widget">
<ol className="choices">
{
choices.map((choice, i) => {
// decide whether to mark radio as checked:
// - if no current choice, check first radios
// - otherwise, check radio matching current choice
let noCurrentChoice = (currentValue === null);
let drawingFirstChoice = (i === 0);
let thisChoiceIsSelected = (String(choice.value) === currentValue);
let checkThisOption = thisChoiceIsSelected || (noCurrentChoice && drawingFirstChoice);
return (
<li key={i}>
<input type="radio" name="choices"
value={choice.value}
onChange={this.onClickOptionRadio}
checked={checkThisOption?'checked':''}
data-ischecked={checkThisOption}
/>
<label>{choice.label}</label>
{' '}
{checkThisOption ? 'CHECKED' : ''}
</li>
);
})
}
</ol>
<button onClick={this.onConfirm}>Confirm choice</button>
</div>
);
}
}
class Owner extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
let choices = [
{ value: 10, label: 'First' },
{ value: 20, label: 'Second' },
{ value: 30, label: 'Third' }
];
return (
<div className="Owner">
<Widget
choices={choices}
/>
</div>
);
}
}
ReactDOM.render(
<Owner />,
document.getElementById('container')
);
My browser is Chrome 47. Here is the jsfiddle.

Categories

Resources