I have a situation where I have something like this:
<DropDown>
<p>Select an option:</p>
<button onClick={() => console.log("opt 1")}>Option 1</button>
<button onClick={() => console.log("opt 1")}>Option 2</button>
</DropDown>
The DropDown component is one that I wrote, that renders this.props.children in a drop-down fashion. The DropDown has an onClick call that makes it close.
DropDown looks something like this (simplified):
class DropDown extends Component {
state = {
open: false
};
render() {
return (
<div className={`drop-down ${this.state.open ? "open" : "closed"}`}>
<div className="closed-version">
<div className="header" onClick={this.open}>
<div className="header-contents">Click here to select an option</div>
</div>
</div>
<div className="open-version">
<div className="content" onClick={this.closeWithoutSelection}>
{this.props.children}
</div>
</div>
</div>
);
}
open = () => {
this.setState({ open: true }, () => {
if (this.props.onOpen) {
this.props.onOpen();
}
});
};
closeWithoutSelection = () => {
this.setState({ open: false }, () => {
if (this.props.onCloseWithoutSelection) {
this.props.onCloseWithoutSelection();
}
});
};
}
The issue I'm running into is that I want to do something different to the DropDown whether it was closed selecting an option or not. How do I go about doing that?
In your DropDown component you have some state and you are probably rendering your children like this:
this.props.children
Instead you can use render props to pass state, methods or anything else down to your children without having to handle it outside of the DropDown component at the parent level.
class DropDown extends Component {
// constructor / state, methods...
yourSpecialMethod(item) {
// do your special thing here
}
render() {
// pass state or methods down to children!
return this.props.children(yourSpecialMethod)
}
}
Then modify your render slightly:
<DropDown>
{handleSpecialMethod =>
<>
<p>Select an option:</p>
<button onClick={() => handleSpecialMethod("opt 1")}>Option 1</button>
<button onClick={() => handleSpecialMethod("opt 1")}>Option 2</button>
</>
}
</DropDown>
UPDATED
Accordingly #pupeno (and he is right), the dropdown logic should be within the dropdown itself. However, we should pass a callback function in order to deal with the chosen data.
class DropDown extends React.Component {
state = {
open: false,
};
toggleDropdown = (e) => {
this.setState({
open: !this.state.open,
});
const value = e.target.getAttribute('value');
if ( value !== "null") {
this.props.selectItem(value);
}
};
render() {
const { selectItem } = this.props;
const { open } = this.state;
return (
<div className={open ? "drop-down open" : "drop-down"}>
<div onClick={this.toggleDropdown} value="null">Select</div>
<div onClick={this.toggleDropdown} value="1">Item 1</div>
<div onClick={this.toggleDropdown} value="2">Item 2</div>
<div onClick={this.toggleDropdown} value="3">Item 3</div>
</div>
);
}
};
class App extends React.Component {
requestItem = (item) => {
alert(`Request item ${item}`);
};
render() {
return (
<div>
<DropDown selectItem={this.requestItem}/>
</div>
);
}
}
ReactDOM.render(
<App />,
document.querySelector('#app')
);
.drop-down {
border: 1px solid black;
width: 180px;
height: 30px;
overflow: hidden;
transition: height 0.3s ease;
}
.open {
height: 120px;
}
.drop-down > div {
padding: 5px;
box-sizing: border-box;
height: 30px;
border-bottom: 1px solid black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="app"></div>
You could use an onChange function and call the parent function that is passed down as a prop. This will "affect the parent" as you asked.
this.props.onChange() or something similar.
Related
The code below illustrates a normal drop down list. To indicate a drop down list, I use a down arrow with
arrow_drop_down
This arrow remains static for me in any state of the list (open or closed). However, I would like that when clicking on the list, the arrow changes to
arrow_drop_up
.
Those. so that with two different states of the list, there would be two different arrows.
export default function FilterStatusCode() {
const [values, setValues] = React.useState([]);
const [isExpanded, setIsExpanded] = useState(false);
const toggleExpand = () => {
setIsExpanded(!isExpanded);
};
return <>
<div className="item-toggle-statuscode" onClick={toggleExpand}>
<h6>Status Code</h6>
<span class="material-icons">
arrow_drop_down
</span>
</div>
{ isExpanded &&
<div>
<TagInput
inputProps={{ placeholder: 'Add status code...' }}
values={values}
onChange={(values) => {
setValues(values)}}>
</TagInput>
</div>
}
</>;
}
try
<div className="item-toggle-statuscode" onClick={toggleExpand}>
<h6>Status Code</h6>
<span class="material-icons">
{ isExpanded ? arrow_drop_up : arrow_drop_down }
</span>
</div>
You can choose which arrow you use depending on the current state:
// If the list is open show the `up` arrow
// otherwise show the `down` arrow
<span className={open ? "up" : "down"}></span>
I had to improvise in this example and used unicode in the class names.
const { useState } = React;
function Example() {
return (
<div>
<Item />
<Item />
</div>
);
}
function Item() {
const [ input, setInput ] = useState('');
const [ open, setOpen ] = useState(false);
function handleChange(e) {
setInput(e.target.value);
}
function handleOpen() {
setOpen(!open);
}
function handleClick() {
console.log(input);
}
return (
<div className="item">
<div onClick={handleOpen} className="heading">
<span>Status code</span>
<span className={open ? "up" : "down"}></span>
</div>
{open && (
<div>
<input
type="text"
onChange={handleChange}
value={input}
/>
<button
type="button"
onClick={handleClick}
>Submit
</button>
</div>
)}
</div>
);
}
ReactDOM.render(
<Example />,
document.getElementById('react')
);
.down:after { content: '\25BC'; }
.up:after { content: '\25B2'; }
.heading:hover { cursor: pointer; color: red; }
.item { margin-bottom: 1em; }
<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>
Additional documentation
Conditional (ternary) operator
I create a function when I click outside of the sidebar it will hide it and I also have a button that toggles show and hide the sidebar. But when I combined both of them together, the button did not work properly, it only show the sidebar but can't close it, only when I click outside it will close the sidebar
Click OutSide to close function:
const ref = useRef(null);
useEffect(() => {
document.addEventListener("mousedown", Clickout);
return () => {
document.removeEventListener("mousedown", Clickout);
};
}, []);
const Clickout = (eve) => {
if (ref.current && !ref.current.contains(eve.target)) {
setShow(false);
}
};
My Return:
return (
<header>
<div className="head">
<div className="logo">
<img src={logo} alt="logo" />
</div>
<button
className="burger"
onClick={() => {
setShow(!showMenu);
console.log("here");
}}
>
<div className={`${showMenu ? "change" : ""} bur1 `}></div>
<div className={`${showMenu ? "change" : ""} bur2 `}></div>
<div className={`${showMenu ? "change" : ""} bur3 `}></div>
</button>
</div>
<nav className={showMenu ? "active" : ""} ref={ref}>
<ul>
{navItem.map((item) => {
const { id, url, text } = item;
return (
<li key={id}>
<a href={url}>{text}</a>
</li>
);
})}
</ul>
</nav>
</header>
);
};
Nav bar CSS:
nav {
position: fixed;
right: -100%;
top: 0;
width: 60%;
height: 100vh;
text-align: center;
padding-top: 15vh;
transition: 0.8s ease;
background-color: blue;
}
nav.active {
right: 0;
transition: 0.5s;
}
Thank you.
you can use another state for manage button onclick when menu is open:
const [disableBtn, setDisableBtn] = useState(false);
and in Clickout function manage it:
const Clickout = (eve) => {
if (showMenu && ref.current && !ref.current.contains(eve.target)) {
setShow(false);
setDisableBtn(true)
} else {
setDisableBtn(false)
}
};
and in button for onclick use condition:
if (!disableBtn) setShow(true);
Updating state this way setShow(!showMenu) does not immediately update the state.Rather it schedules the update(You can read the docs). When your setState depends on your previous state (in this case showMenu depends on previous state) use this technique: (prev) => setState(!prev) instead. So, simply updating your onClick will solve the issue.
<button className="burger"
onClick={() => {
(prevShowMenu) => setShow(!prevShowMenu)
}}>
(Let me know in the comments if this was helpful)
I'm trying to make it so when you click on the dropdown arrow the settings dropdown will appear.
When I currently press an arrow dropdown, all the settings dropdown open that are within the array loop.
This is the function that renders the loop:
viewPublishedPages() {
const pages = this.state.pages;
return (
<div>
{pages.map((val, i) => {
let dropdown = 'none';
return (
<div className="block" key={i}>
<div className="columns">
<div className="column is-10">
<p>PUBLISHED</p>
<h2>{val.title}</h2>
</div>
<div className="column">
<div className="settings">
<div className="arrow__container">
<div className="arrow" onClick={this.showSettings.bind(this, i)} />
</div>
{
this.state.settingPanel
?
<ClickOutside onClickOutside={::this.hide}>
<div className="arrow__dropdown">
<Link href={{pathname: '/admin/edit-page', query: {title: val.title}}}>
<a className="arrow__dropdown__link">Edit</a>
</Link>
<button
className="arrow__dropdown__delete"
onClick={() => this.handleDelete(i)}>Delete</button>
</div>
</ClickOutside>
: null
}
</div>
</div>
</div>
</div>
);
})}
</div>
);
}
Notice: <div className="arrow" onClick={this.showSettings.bind(this, i)} />
This is the state:
static dataStruc () {
return {
loading: true,
settingPanel: false,
pages: [],
};
}
Your are currently saving a boolean value to settingPanel and therefore all dropdowns open upon click.
My suggestion is replace settingPanel from boolean to the respective page id. In case you don't have page ids, then store the current page index on it.
That makes it easier to render the dropdown so you have access/control to the selected one and later render its settings:
showSettings(index) {
this.setState({
settingPanel: index,
})
}
And then in viewPublishedPages:
{this.state.settingPanel === i &&
<ClickOutside onClickOutside={::this.hide}>
..
</ClickOutside>}
I wrote a sample code so you get the idea.
class App extends React.Component {
constructor() {
super()
this.state = {
pages: [
{ title: 'Home' },
{ title: 'Contact' },
{ title: 'Page' }
],
settingPanel: -1,
}
this.showSettings = this.showSettings.bind(this)
}
showSettings(index) {
this.setState({
settingPanel: this.state.settingPanel === index ? -1 : index,
})
}
render() {
const { pages, settingPanel } = this.state
return (
<div>
{pages.map((page, index) =>
<div key={index} className="page">
<div onClick={this.showSettings.bind(this, index)}>
{page.title}
</div>
{settingPanel === index &&
<div className="settings">
<div>Setting 1</div>
<div>Setting 2</div>
<div>Setting 3</div>
</div>
}
</div>
)}
</div>
)
}
}
ReactDOM.render(
<App />,
document.getElementById('root')
)
.page {
background-color: cyan;
margin-top: 10px;
padding: 10px;
}
.settings {
background-color: black;
color: white;
}
<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="root"></div>
I'm using CSS transform to animate an AwesomeFont icon when I click it
.animate {
-webkit-animation-duration: 400ms;
-webkit-animation-name: animation
}
AFAIK, to trigger the animation I need to add the above class to an element after it's being rendered. So I do this in React
class Class extends Component {
state = {
likes: 5,
play: false
}
handleClick = () => {
this.setState((p) => { return {play: p.play ? false : true}; })
}
render() {
<a onClick={this.handleClick} href="#" />
<Icon className={this.state.play ? 'animate' : ''} name="thumbs-up" />
{this.state.likes}
</a>
}
}
which works pretty well if I'm just toggling the states with each click, but now I want to trigger the animation on every click
...
handleClick = () => {
this.setState((p) => { return {play: p.play ? false : true}; })
}
render() {
<a onClick={this.handleClick} onMouseUp={() => this.setState(()=>{return {play:false}; })} href="#" />
<Icon className={this.state.play ? 'animate' : ''} name="thumbs-up" />
{this.state.likes}
</a>
}
}
It's not pretty, but I just want to illustrate a point, and I don't think it is working as it should because clicking on the number part of the link repeatedly doesn't trigger the animation but clicking the Icon does.
I tried adding the class onMouseDown and removing it onMouseUp but React just batches up the setStates, resulting in nothing. Anyone with any ideas how should I approach this problem?
Please try to change a tag to div tag. The click and other mouse events work fine on div and it's child elements. See an example:
<div id='wrapper' onClick='wrapperClick()' onMouseUp='wrapperMouseUp()'>
This is parent
<div id='child'>
I am child
</div>
</div>
#wrapper {
width:100px;
height:100px;
border:1px solid black;
}
#child {
padding-top:10px;
border:1px solid red;
}
function wrapperClick() {
alert('you clicked me')
}
function wrapperMouseUp() {
alert('Mouse Up')
}
Just note that mouse up fires before click event.
handleMouseUp = () => {
setCounter()
}
handleClick = () => {
setCounter()
}
setCounter() {
this.setState((p) => { return {play: p.play ? false : true}; })
}
render() {
<div onClick={this.handleClick} onMouseUp={this.handleMouseUp} href="#" />
<Icon className={this.state.play ? 'animate' : ''} name="thumbs-up" />
{this.state.likes}
</div>
}
}
I use onBlur to close a dropdown, but I also want to handle a click handler of an li which is render within, setState won't work here, the behavior is broken when user try to open the dropdown again, try it here:
http://jsfiddle.net/ur1rbcrz
My code:
toggleDropdown = () => {
this.setState({
openDropdown: !this.state.openDropdown
})
}
render() {
return (
<div>
<div tabIndex="0" onFocus={this.toggleDropdown} onBlur={this.toggleDropdown}>
MyList
<ul className={this.state.openDropdown ? 'show' : 'hide'}>
<li>abc</li>
<li>123</li>
<li onClick={()=> this.setState({openDropdown:false})}>xyz</li> {/* not working */}
</ul>
</div>
</div>
);
}
Your code is not working because, even though you click li, a div container with onBlur event still is focused.
We add to your list container ref, after that we can call .blur(). We use it in your onClick li event handler.
this.dropDownList.blur()
See working example jsfiddle.
Or run this snippet:
class Hello extends React.Component {
constructor() {
super()
this.state = {
isDropdownVisible: false
}
this.toggleDropdown = this.toggleDropdown.bind(this);
}
toggleDropdown() {
this.setState({
isDropdownVisible: !this.state.isDropdownVisible
})
}
render() {
return (
<div>
<div
tabIndex="0"
ref={c => this.dropDownList = c}
onFocus={this.toggleDropdown}
onBlur={this.toggleDropdown}>
MyList
<ul
className={this.state.isDropdownVisible ? 'show' : 'hide'}>
<li>abc</li>
<li>123</li>
<li onClick={() => this.dropDownList.blur()}>xyz</li> {/* not working */}
</ul>
</div>
</div>
);
}
}
ReactDOM.render(
<Hello initialName="World"/>,
document.getElementById('container')
);
.hide {
display: none
}
.show {
display: block !important;
}
div:focus {
border: 1px solid #000;
}
div:focus {
outline: none;
}
<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="container">
<!-- This element's contents will be replaced with your component. -->
</div>
i added onClick event to your div and it worked, your code becomes:
render() {
return (
<div>
<div tabIndex="0" onClick={() => this.setState({openDropdown: !this.state.openDropdown})} onFocus={this.toggleDropdown} onBlur={this.toggleDropdown}>
MyList
<ul className={this.state.openDropdown ? 'show' : 'hide'}>
<li>abc</li>
<li>123</li>
<li onClick={()=> this.setState({openDropdown:false})}>xyz</li> {/* not working */}
</ul>
</div>
</div>
);
}
OnBlur is a React Synthetic event and can be used in two ways:
To trigger something:
const {useState} = React;
const Example = ({title}) => {
const [field, setField] = useState("");
return (
<div>
<p>{title}</p>
<p>Uppercase on blur</p>
<input type="text"
value={field}
onChange={e=>setField(e.target.value)}
//LOOK HERE !
onBlur={e=>setField(e.target.value.toUpperCase())}
/>
</div>
);
};
// Render it
ReactDOM.render(
<Example title="OnBlur triggering:" />,
document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="react"></div>
Be triggered by something
const {
useState,
} = React;
const Example = ({
title
}) => {
const [field, setField] = useState("");
return ( <
div >
<
p > {
title
} < /p> <
p > Remove focus by pressing enter < /p> <
input type = "text"
value = {
field
}
onChange = {
e => setField(e.target.value)
}
//LOOK HERE !
onBlur = {
e => setField(e.target.value.toUpperCase())
}
onKeyPress = {
e => (e.key === 'Enter' ? setField(e.target.value.toLowerCase()) || e.target.blur() : null)
}
/> < /
div >
);
};
// Render it
ReactDOM.render( <
Example title = "OnBlur triggered:" / > ,
document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="react"></div>
So to programmatically cause onBlur to trigger in react is necessary add an event to watch your change.
More info:
React SyntheticEvents