Hide a component when another is hovered - reactjs

I'm using Gatsby for a static website.
My page is composed of two parts. Section 1 and Section 2.
I want to hide an image in Section 1, when a button is hovered in Section 2.
If I clean up a bit my .js, it looks like that :
<section>
<SomeText/>
<DefaultImage />
<ImageOne />
<ImageTwo />
</section>
<section>
<Button1/>
<Button2/>
</section>
What I want to achieve:
By default, <DefaultImage/> is shown.
If I hover <Button1>, I want to hide <DefaultImage/> and display <ImageOne/> instead.
Same goes for <Button2/>, which, when hovered, should hide <DefaultImage/> and display <ImageTwo/>.
I've read about onMouseEnter and onMouseLeave, and I think that the answer lies there but couldn't make it work for now.
Thank you for your ideas!
Maybe I can also pass a prop (like a css class) on the "to be hidden" component when the other is hovered
I managed to do it (check the accepted answer).
Here is my edited code:
class Parent extends Component {
state = {
isHoveringImage1: false
}
state = {
isNotHovering: false
}
state = {
isHoveringImage2: false
}
startHoverMasque = () => this.setState({ isHoveringMasque: true, isNotHovering: true})
stopHoverMasque = () => this.setState({ isHoveringMasque: false, isNotHovering: false })
startHoverMains = () => this.setState({ isHoveringMains: true, isNotHovering: true})
stopHoverMains = () => this.setState({ isHoveringMains: false, isNotHovering: false })
render() {
return (
<>
<Global
styles={globalStyles}/>
<section>
{
this.state.isNotHovering
? <ImageDefaultHidden />
: <ImageDefault/>
}
{
this.state.isHoveringImage1
? <Image1 />
: <ImageDefaultHidden />
}
{
this.state.isHoveringImage2
? <Image2 />
: <ImageDefaultHidden />
}
</section>
<section>
<Button1
onMouseEnter={ this.startHoverImage1}
onMouseLeave={ this.stopHoverImage1 }
>Bouton1</Button1>
<Button2
onMouseEnter={ this.startHoverImage2}
onMouseLeave={ this.stopHoverImage2 }
>Bouton 2</Button2>
</section>
</>
)
}
}
export default Parent```

You can annotate when the mouse enter and leaves the target Button in the state of your parent component:
class Parent extends Component {
state = {
isHovering: false
}
startHover = () => this.setState({ isHovering: true })
stopHover = () => this.setState({ isHovering: false })
render() {
return (
<>
<section>
<SomeText/>
{
this.state.isHovering
? <ImageOne />
: <DefaultImage />
}
<ImageTwo />
</section>
<section>
<Button1
onMouseEnter={ this.startHover }
onMouseLeave={ this.stopHover }
/>
<Button2/>
</section>
</>
)
}
}

The solution is to include the variable saying whether or not your image should be rendered in your parent component's state.
To set this variable, pass down a function to the component containing the button and bind it to the events you gave in your question : onMouseEnter and onMouseLeave.
Working example :
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
hideImage: false
}
}
toggleImage = hideImage => ev => {
this.setState({ hideImage })
}
render = () => {
return(
<div>
<ButtonComponent hovered={this.toggleImage}/>
<ImageComponent isHidden={this.state.hideImage}/>
</div>
)
}
}
const ButtonComponent = ({ hovered }) => <button onMouseEnter={hovered(true)} onMouseLeave={hovered(false)}>Hover me :)</button>
const ImageComponent = ({ isHidden }) => <img hidden={isHidden} src='https://reactjs.org/logo-og.png'/>
ReactDOM.render(<App />, document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.5.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.5.1/umd/react-dom.production.min.js"></script>
<div id='root'>

Related

React show/hide content

I'm trying to make a toggle content button with React. But I can only get it to open, not to close when I click on it again. Can someone please take a look and let me know what I need to change within the code to accomplish it?
Here's what I have so far:
class Test extends React.Component {
constructor(props) {
super(props)
this.state = {
activeLocation: 0,
}
}
changeActiveLocation = (activeLocation) => {
this.setState({
activeLocation: activeLocation,
});
}
render() {
const activeLocation = company.locations[this.state.activeLocation];
return (
{company.locations.map((location, index) => (
<div className="test-item">
<div className="test-item-container" onClick={() => {this.changeActiveLocation(index)}}>
<div className="test-item-header">
<h3>Text goes here!</h3>
<a><FontAwesomeIcon icon={(this.state.activeLocation === index) ? 'times' : 'chevron-right'} /></a>
</div>
</div>
</div>
))}
)
}
}
Thank you!
You're setting the active location to be the same location that you've clicked already so the this.state.activeLocation === index is always true. I would refactor the locations to their own component with an isOpen state value that gets updated when the location is clicked. So like the following:
// test class
class Test extends React.Component {
constructor(props) {
super(props)
this.state = {
activeLocation: 0,
}
}
changeActiveLocation = (activeLocation) => {
this.setState({
activeLocation: activeLocation,
});
}
render() {
const activeLocation = company.locations[this.state.activeLocation];
return (
{company.locations.map((location, index) => (
<LocationItem location={location} onClick={() => this.changeActiveLocation(index)} />
))}
)
}
}
// LocationItem
class LocationItem extends React.Component {
state = { isOpen: false };
handleClick = () => {
this.setState(prevState => { isOpen: !prevState.isOpen});
// call parent click to set new active location if that's still needed
if(this.props.onClick) this.props.onClick;
}
render() {
return <div className="test-item">
<div className="test-item-container" onClick={this.handleClick}>
<div className="test-item-header">
<h3>Text goes here!</h3>
<a><FontAwesomeIcon icon={(this.state.isOpen ? 'times' : 'chevron-right'} /></a>
</div>
</div>
</div>
}
}

why is my bind not working in constructor

I'm doing a very simple two button state. where if i click abutton, A component displays and if bbutton is clicked then component B. I'm mapping through array of items so that each of them have their own buttons state. Lets say if I click item 1's button B then I want only first Item B to show. Right now All of them gets triggered at once. I have bounded each of them in the constructor but still i'm unable to get only the once the once clicked to trigger and show the relevant component.
class Home extends Component {
constructor(props) {
super(props);
this.state = {
lists: []
showA: true,
showB: false
}
this.aButtonHandler = this.aButtonHandler.bind(this);
this.bButtonHandler = this.bButtonHandler.bind(this);
}
aButtonHandler = (e) => {
this.setState({
showA: true,
showB: false
})
}
bButtonHandler = (e) => {
this.setState({
showA: false,
showB: true
})
}
render(){
return (
<div>
{this.state.lists.map(detail =>
<li>{detail.id}</li>
<button onClick={(e) => this.aButtonHandler(e)} >see A</button>
<button onClick={(e) => this.bButtonHandler(e)} >see B</button>
{this.state.showA ?
<ComponentA /> : null
}
{this.state.showB ?
<ComponentB /> : null
}
)}
</div>
)
}
If you are using arrow functions no need to bind functions.
If you want to bind then change it to normal function like this.
aButtonHandler(e){...}
bButtonHandler(e){...}
If you want to use bind in constructor no need to use arrow function, just use regular functions and pass the function directly to onClick
aButtonHandler(e) { this.setState({ showA: true, showB: false }); }
bButtonHandler(e) { this.setState({ showA: false, showB: true }); }
render() {
return (
<div>
{this.state.lists.map(detail => (
<div>
<li>{detail.id}</li>
<button onClick={this.aButtonHandler}>see A</button>
<button onClick={this.bButtonHandler}>see B</button>
{this.state.showA ? <ComponentA /> : null}
{this.state.showA ? <ComponentB /> : null}
</div>
))}
</div>
);

Cannot control state properly

I have 3 components: App, Map and ListPlaces. In ListPlaces component, when a user types something in the input element, I want to change the state(markers's state) in App.js to show only related markers on the map.
Edit: When I edit my typo, the error was disappeared. However, I think the logic is still wrong. Because when I write something in the input element, markers array would be 0 immediately. And of course, all markers are disappeared.
More Explanation:
After componentDidMount, my markers array holds 7 items. And Map component takes this markers array and render markers on the map. However, I need to control my markers from ListPlaces component according to value of input element. So I put this: onChange={e => {this.updateQuery(e.target.value); changeMarkersHandler(e.target.value)}} in onChange attribute of input element. (Omit the this.updateQuery, for now, you can focus on only changeMarkersHandler).
This changeMarkersHandler runs changeMarkers function in App.js, but I don't know why my marker arrays would be 0 immediately while changeMarkers function is working.
Note: I am using react-google-maps and I've omitted some code blocks which aren't related to question.
App.js
class App extends Component {
constructor(props) {
super(props);
this.state = {
places: [],
markers: [],
markerID: -1,
newMarkers: []
};
this.changeMarkers = this.changeMarkers.bind(this);
}
componentDidMount() {
fetch("api_url")
.then(response => response.json())
.then(data => {
this.setState({
places: data.response.venues,
markers: data.response.venues
});
})
.catch(error => {
console.log("Someting went wrong ", error);
});
}
changeMarkers(value) {
const newMarkers = this.state.markers.filter(
place => place.name === value
);
this.setState({
newMarkers : newMarkers,
markers: newMarkers
})
}
render() {
return (
<div className="App">
<Map role="application"
places={this.state.places}
markers={this.state.markers}
openInfoHandler={this.openInfo}
closeInfoHandler={this.closeInfo}
markerID={this.state.markerID}
googleMapURL="url_here" />
<ListPlaces changeMarkersHandler={this.changeMarkers} />
</div>
);
}
}
ListPlaces.js
import React, { Component } from "react";
import escapeRegExp from "escape-string-regexp";
class ListPlaces extends Component {
state = {
searchQuery: ""
};
updateQuery = query => {
this.setState({ searchQuery: query});
};
render() {
const { toggleListHandler, locations, openInfoHandler, changeMarkersHandler} = this.props;
let showLocations;
if (this.state.searchQuery) {
const match = new RegExp(escapeRegExp(this.state.searchQuery), "i");
showLocations = locations.filter(location =>match.test(location.name));
} else {
showLocations = locations;
}
return (
<div>
<aside>
<h2>Restaurants</h2>
<nav>
<div className="search-area">
<input
className="search-input"
type="text"
placeholder="Search Restaurant"
value={this.state.searchQuery}
onChange={e => {this.updateQuery(e.target.value); changeMarkersHandler(e.target.value)}}
/>
</div>
<ul>
{showLocations.map(location => {
return (
<li
key={location.id}
onClick={e =>
openInfoHandler(e, location.id)
}
>
{location.name}
</li>
);
})}
</ul>
</nav>
<p>some text</p>
</aside>
<a
onClick={toggleListHandler}
id="nav-toggle"
className="position"
>
<span />
</a>
</div>
);
}
}
export default ListPlaces;
You have a typo in you constructor.
this.changeMarkers(this.changeMarkers.bind(this));
should be
this.changeMarkers = this.changeMarkers.bind(this);

conditional rendering of components based on state change

When a user enters a search item, if the data is available, then <Pictures /> is displayed. If the data is not present then <NoResultsFound /> is displayed.By default <NoResultsFound /> state is false and <Pictures /> is true because when the page loads the list of pictures are present. I tried to switch the state like this: this.setState({uisNoResultsFound: true}) and this.setState({uisPictures: false}) throws syntax error. I want this conditional rendering of the UI states within app.js. How to do this?
App.js:
class App extends Component {
constructor(props) {
super(props);
this.state = {
uisSearchBarItems: true,
uisNoResultsFound: false,
uisPictures: true,
dsPictures: []
};
}
componentDidMount() {
unsplash.search.collections("frog", 1, 60)
.then(toJson)
.then(json => {
this.setState({ dsPictures:json.results });
})
}
enteredDatahandler = (ctp) => {
unsplash.search.collections(ctp, 1, 60)
.then(toJson)
.then(json => {
this.setState({ dsPictures:json.results })
})
//******** conditional rendering ***********
if(this.state.dsPictures.length === 0){
return (
this.setState({uisNoResultsFound: true})
this.setState({uisPictures: false})
)
}
else{
this.setState({uisNoResultsFound: false})
this.setState({uisPictures: true})
}
//***********************************
}
render() {
return (
<div className="App">
<SearchBarItems ctpEnteredData={this.enteredDatahandler}/>
<NoResultsFound />
<Pictures ptcEnteredData={this.state.dsPictures}/>
</div>
);
}
}
export default App;
searchbaritems.js
class SearchBarItems extends Component {
enterKeyHandler = (event) => {
if (event.key === 'Enter'){
event.preventDefault();
this.props.ctpEnteredData(this.search.value)
}
}
render() {
return (
<div>
<form autoComplete="off" ref={(el) => this.myFormRef = el}>
<input
type="text"
name="search"
ref={input => this.search = input}
onKeyPress={this.enterKeyHandler}/>
</form>
</div>
)
}
}
Use a ternary expression inside your render method.
{this.state.dsPictures.length === 0 ? <NoResultsFound /> : <Pictures ptcEnteredData={this.state.dsPictures}/> }
In your render function you are returning both components, you need to either have if statements or you can do what #Barazu did - which is the most cleanest code.
Github gist: https://gist.github.com/tintinmovie/ed5b4782fa98c3482b561ea3243f98ea
render() {
if (this.state.uisNoResultsFound === true) {
return(
<div className="App">
<SearchBarItems ctpEnteredData={this.enteredDatahandler}/>
<NoResultsFound />
</div>
);
}
else if (this.state.uisPictures === true) {
return(
<div className="App">
<SearchBarItems ctpEnteredData={this.enteredDatahandler}/>
<Pictures ptcEnteredData={this.state.dsPictures}/>
</div>
);
}
}

React Passing state to sibling component and up to parent class

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>

Resources