Cannot solve that one.
I'm creating navigation bar with a scroll-to functionality, ie. when pressed on the certain menu item the page is scrolled to the corresponding section. While this seems to be quite straightforward I don't know how to highlight in a different colour this certain menu item when window gets to the point of the section. F.ex. user presses 'contact', the page is scrolled to contact section, the contact menu item changes its colour to red. Thank you for any help!
Here's my code:
import React, { Component } from 'react';
import Container from './Container.jsx';
class Header extends Component {
constructor(props) {
super(props);
this.state = {
backgroundColor: 'rgba(34,34,34,0)',
};
}
componentDidMount() {
window.addEventListener('scroll', () => this.handleScroll());
}
handleScroll(e) {
const test = (pageYOffset > 900) ?
(this.setState({ backgroundColor: 'black' })) :
(this.setState({ backgroundColor: 'rgba(34,34,34,0)' }));
}
handleClick(e) {
e.preventDefault();
const elementOffsetTop = document.getElementById(e.target.innerText).offsetTop;
window.scrollTo(0, elementOffsetTop);
}
handleUp(e) {
e.preventDefault();
window.scrollTo(0, 0);
}
render() {
const menuItems = [
{ menuItem: 'O nas', link: 'About' },
{ menuItem: 'Księgowość', link: 'Ksiegowosc' },
{ menuItem: 'Kadre i płace', link: 'Kadre' },
{ menuItem: 'Doradztwo', link: 'Doradztwo' },
{ menuItem: 'Nieruchomości', link: 'nieruchomosci' },
{ menuItem: 'Kontakt', link: 'kontakt' }
];
const items = menuItems.map(item => {
const styles = {
linkStyle: {
textDecoration: 'none',
color: '#ffffff',
cursor: 'pointer'
},
textStyle: {
marginLeft: '1rem',
textTransform: 'uppercase'
}
};
const { linkStyle, textStyle } = styles;
return (
<a onClick={e => this.handleClick(e)} key={item.link} style={linkStyle}>
<p style={textStyle}> {item.menuItem} </p>
</a>
);
});
const styles = {
containerStyle: {
height: 70,
position: 'fixed',
top: 0,
width: '100%',
backgroundColor: this.state.backgroundColor,
zIndex: 20000,
},
headerStyle: {
height: 70,
},
navigationStyle: {
height: '100%',
color: 'white',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
fontSize: '0.9rem'
},
navigationItemsStyle: {
display: 'flex',
},
logoStyle: {
fontSize: '1.3rem',
cursor: 'pointer'
}
};
const { headerStyle,
containerStyle,
navigationStyle,
navigationItemsStyle,
logoStyle
} = styles;
return (
<div id="header" style={containerStyle}>
<header style={headerStyle} ref='header'>
<Container>
<div style={navigationStyle}>
<a onClick={e => this.handleUp(e)} style={logoStyle}>
<div>{this.props.text}</div>
</a>
<div style={navigationItemsStyle}> {items} </div>
</div>
</Container>
</header>
</div>
);
}
}
export default Header;
you can use react-intersection-observer to achieve that.
More info about that on react-intersection-observer
Related
I am making a basic drop down menu in React. I am new to React and have managed to get to where the menu opens on click and such. What I would like to do is change the background of each choice to a different color only when the mouse is hovered over that specific element.
Right now the issue is that every element is highlighted when one is hovered over. I understand this is because I am changing the state which effects every element, but I cant wrap my mind around how to implement it how I would like it. Perhaps there is a React concept(s) I am unaware of that would make this easier.
Here is my code so far:
App.jsx file
import React from "react";
import InnerDiv from './components/InnerDiv';
import CountiesList from "./components/countiesList";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
menuIsOpen: false,
height: '50px'
}
this.handleClick = this.handleClick.bind(this)
}
handleClick = () => {
if (this.state.menuIsOpen == false) {
this.setState(state => ({
menuIsOpen: !state.menuIsOpen,
height: '350px'
}))
} else {
this.setState(state => ({
menuIsOpen: !state.menuIsOpen,
height: '50px'
}))
}
}
render() {
const divStyle = {
height: this.state.height,
width: '300px',
border: '2px solid cornflowerblue',
borderRadius: '12px',
position: 'absolute',
top: '50px',
left: '50px',
cursor: 'pointer',
display: 'flex',
flexDirection: 'column',
justifyContent: 'flex-start',
alignItems: 'center',
transition: 'all 0.1s linear',
}
return (
<div onClick={this.handleClick} style={divStyle} className="App">
<InnerDiv/>
<CountiesList/>
</div>
);
}
}
export default App;
InnerDiv.jsx file
import React from "react";
const innerDivStyle = {
height: '50px',
width: '90%',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between'
}
const h1Style = {
fontSize: '20px',
WebkitTouchCallout: 'none',
WebkitUserSelect: 'none',
khtmlUserSelect: 'none',
MozUserSelect: 'none',
msUserSelect: 'none',
UserSelect: 'none'
}
const svgStyle = {
height: '30px'
}
class InnerDiv extends React.Component {
constructor(props) {
super(props);
}
render(){
return (
<div style={innerDivStyle} className="InnerDiv">
<h1 style={h1Style}>Select A Location</h1>
<svg style={svgStyle} xmlns="http://www.w3.org/2000/svg" id="Layer_1" data-name="Layer 1" viewBox="0 0 122.88 122.88"><title>round-line-bottom-arrow</title><path d="M122.85,61.45h0A61.39,61.39,0,0,0,61.45,0V0h0V0A61.38,61.38,0,0,0,0,61.43H0v0H0a61.35,61.35,0,0,0,61.4,61.38v0h0v0a61.34,61.34,0,0,0,61.38-61.4ZM61.44,91,33.92,60.47H51.5V39.2H71.38V60.47H89L61.44,91Zm48.28-29.54h0a48.36,48.36,0,0,1-48.27,48.29v0h0v0A48.35,48.35,0,0,1,13.14,61.47h0v0h0A48.27,48.27,0,0,1,61.41,13.14v0h0v0a48.3,48.3,0,0,1,48.27,48.3Z"/></svg>
</div>
)
}
}
export default InnerDiv;
countiesList.jsx file
import React from "react";
class CountiesList extends React.Component {
constructor(props) {
super(props);
this.state = {
counties: [
'Antrim',
'Armagh',
'Carlow',
'Cavan',
'Clare',
'Cork',
'Derry',
'Donegal',
'Down',
'Dublin',
'Fermanagh',
'Galway',
'Kerry',
'Kildare',
'Kilkenny',
'Laois',
'Leitrim',
'Limerick',
'Longford',
'Louth',
'Mayo',
'Meath',
'Monaghan',
'Offaly',
'Roscommon',
'Sligo',
'Tipperary',
'Tyrone',
'Waterford',
'Westmeath',
'Wexford',
'Wicklow'
],
backgroundColor: '#a9c4f5'
};
this.updateBackgroundColor = this.updateBackgroundColor.bind(this);
this.reverseBackgroundColor = this.reverseBackgroundColor.bind(this);
}
updateBackgroundColor() {
this.setState({backgroundColor: 'cornflowerblue'})
}
reverseBackgroundColor() {
this.setState({backgroundColor: '#a9c4f5'})
}
render() {
const ulStyle = {
listStyleType: 'none',
paddingInlineStart: 0,
margin: 0,
width: '100%',
height: '300px',
overflowY: 'scroll',
borderBottomLeftRadius: '12px'
}
const liItemContainer = {
height: '50px',
paddingLeft: '15px',
display: 'flex',
alignItems: 'center',
background: this.state.backgroundColor,
}
const liStyle = {
fontWeight: '700'
}
let countiesListItems = this.state.counties.map(county => {
return (
<div key={county} style={liItemContainer} onMouseEnter={this.updateBackgroundColor} onMouseOut={this.reverseBackgroundColor}>
<li style={liStyle}>{county}</li>
</div>
)
})
return (
<ul style={ulStyle}>
{countiesListItems}
</ul>
)
}
}
export default CountiesList;
Thank you for your help in advance
I am new to React. My issue is that I am trying to change the background color of a div that I dynamically render in React. To do this I learned about useRef() to single out the element.
If I do a console.log() in the onHover function, the log works in the browser, but the background color of the element does not change for some reason, even though I have implemented useRef how others have for this exact same reason.
Thank you in advance for your help, here is the code:
import React, { useRef } from "react";
const CountiesList = () => {
const counties = [
'Antrim',
'Armagh',
'Carlow',
'Cavan',
'Clare',
'Cork',
'Derry',
'Donegal',
'Down',
'Dublin',
'Fermanagh',
'Galway',
'Kerry',
'Kildare',
'Kilkenny',
'Laois',
'Leitrim',
'Limerick',
'Longford',
'Louth',
'Mayo',
'Meath',
'Monaghan',
'Offaly',
'Roscommon',
'Sligo',
'Tipperary',
'Tyrone',
'Waterford',
'Westmeath',
'Wexford',
'Wicklow'
]
const liItem = useRef(null)
const onHover = () => {
liItem.current.style.backgroundColor = 'cornflowerblue'
}
const ulStyle = {
listStyleType: 'none',
paddingInlineStart: 0,
margin: 0,
width: '100%',
height: '300px',
overflowY: 'scroll',
borderBottomLeftRadius: '12px'
}
const liItemContainer = {
height: '50px',
paddingLeft: '15px',
display: 'flex',
alignItems: 'center',
backgroundColor: '#a9c4f5'
}
const liStyle = {
fontWeight: '700'
}
let countiesListItems = counties.map(county => {
return (
<div ref={liItem} key={county} style={liItemContainer} onMouseOver={onHover}>
<li style={liStyle}>{county}</li>
</div>
)
})
return (
<ul style={ulStyle}>
{countiesListItems}
</ul>
)
}
export default CountiesList;
That's because you use a single ref on an array of elements, You need to use an array of template refs or just pass the target element by onMouseOver={(e) => onHover(e.target)} and change the style directly without the need to template refs
import React, { useRef } from "react";
const CountiesList = () => {
const counties = [
'Antrim',
'Armagh',
'Carlow',
'Cavan',
'Clare',
'Cork',
'Derry',
'Donegal',
'Down',
'Dublin',
'Fermanagh',
'Galway',
'Kerry',
'Kildare',
'Kilkenny',
'Laois',
'Leitrim',
'Limerick',
'Longford',
'Louth',
'Mayo',
'Meath',
'Monaghan',
'Offaly',
'Roscommon',
'Sligo',
'Tipperary',
'Tyrone',
'Waterford',
'Westmeath',
'Wexford',
'Wicklow'
]
const liItem = useRef(null)
const onHover = (element) => {
element.style.backgroundColor = 'cornflowerblue'
}
const ulStyle = {
listStyleType: 'none',
paddingInlineStart: 0,
margin: 0,
width: '100%',
height: '300px',
overflowY: 'scroll',
borderBottomLeftRadius: '12px'
}
const liItemContainer = {
height: '50px',
paddingLeft: '15px',
display: 'flex',
alignItems: 'center',
backgroundColor: '#a9c4f5'
}
const liStyle = {
fontWeight: '700'
}
let countiesListItems = counties.map(county => {
return (
<div ref={liItem} key={county} style={liItemContainer} onMouseOver={(e) => onHover(e.target)}>
<li style={liStyle}>{county}</li>
</div>
)
})
return (
<ul style={ulStyle}>
{countiesListItems}
</ul>
)
}
export default CountiesList;
If you really want to do your hover effect "in software" (i.e. not with CSS, which is much more preferred for a purely visual effect), then the React way is to use state, not refs.
const ulStyle = {
listStyleType: "none",
paddingInlineStart: 0,
margin: 0,
width: "100%",
height: "300px",
overflowY: "scroll",
borderBottomLeftRadius: "12px"
};
const liItemContainer = {
height: "50px",
paddingLeft: "15px",
display: "flex",
alignItems: "center"
};
const liStyle = {
fontWeight: "700"
};
const counties = [
"Antrim",
"Armagh",
"Carlow",
"Cavan",
// ...
];
const CountiesList = () => {
const [hoveredCounty, setHoveredCounty] = React.useState(null);
const onHover = (event) => {
setHoveredCounty(event.currentTarget.dataset.county);
};
return (
<ul style={ulStyle}>
{counties.map((county) => (
<div
data-county={county}
key={county}
style={{
...liItemContainer,
background: hoveredCounty === county ? "orange" : "#a9c4f5"
}}
onMouseOver={onHover}
>
<li style={liStyle}>{county}</li>
</div>
))}
</ul>
);
};
https://i.stack.imgur.com/bkGNA.png
I am getting error as shown in the image, when i trigger the ImageGallery component in my code. I am passing image items as props to ImageGallery and displaying Imagegallery on pop-up modal. But i am not able to display images on pop-up modal due to error i am getting as shown in the image
import React, {Component} from 'react'
import {Modal,Button} from '../../../components/elements'
import ImageGallery from 'react-image-gallery';
import "react-image-gallery/styles/css/image-gallery.css";
class ProfileImageGridModal extends Component {
constructor(props) {
super(props)
this.state = {
showGridModal: false,
images : [
{
original: 'https://cdn.pixabay.com/photo/2016/03/27/17/59/motorcycle-1283299_960_720.jpg',
thumbnail: 'https://cdn.pixabay.com/photo/2016/03/27/17/59/motorcycle-1283299_960_720.jpg',
},
{
original: 'https://cdn.pixabay.com/photo/2016/11/29/10/21/dirt-bike-1868996_960_720.jpg',
thumbnail: 'https://cdn.pixabay.com/photo/2016/11/29/10/21/dirt-bike-1868996_960_720.jpg',
},
{
original: 'https://cdn.pixabay.com/photo/2019/05/08/01/52/motorcycle-4187586_960_720.jpg',
thumbnail: 'https://cdn.pixabay.com/photo/2019/05/08/01/52/motorcycle-4187586_960_720.jpg',
},
{
original: 'https://cdn.pixabay.com/photo/2014/04/23/20/34/dirt-bike-330815_960_720.jpg',
thumbnail: 'https://cdn.pixabay.com/photo/2014/04/23/20/34/dirt-bike-330815_960_720.jpg',
},
{
original: 'https://cdn.pixabay.com/photo/2021/10/17/18/55/motorcycle-6719182_960_720.jpg',
thumbnail: 'https://cdn.pixabay.com/photo/2021/10/17/18/55/motorcycle-6719182_960_720.jpg',
},
{
original: 'https://cdn.pixabay.com/photo/2021/10/17/18/55/motorcycle-6719182_960_720.jpg',
thumbnail: 'https://cdn.pixabay.com/photo/2021/10/17/18/55/motorcycle-6719182_960_720.jpg',
},
{
original: 'https://cdn.pixabay.com/photo/2021/10/17/18/55/motorcycle-6719182_960_720.jpg',
thumbnail: 'https://cdn.pixabay.com/photo/2021/10/17/18/55/motorcycle-6719182_960_720.jpg',
},
{
original: 'https://cdn.pixabay.com/photo/2021/10/17/18/55/motorcycle-6719182_960_720.jpg',
thumbnail: 'https://cdn.pixabay.com/photo/2021/10/17/18/55/motorcycle-6719182_960_720.jpg',
},
{
original: 'https://cdn.pixabay.com/photo/2021/10/17/18/55/motorcycle-6719182_960_720.jpg',
thumbnail: 'https://cdn.pixabay.com/photo/2021/10/17/18/55/motorcycle-6719182_960_720.jpg',
},
],
renderRightNav : (onClick, disabled) => (
<RightNav onClick={onClick} disabled={disabled} />
)
,
renderLeftNav : (onClick, disabled) => (
<LeftNav onClick={onClick} disabled={disabled} />
)
}
}
render() {
const { isMobile, isReviewModal } = this.props
return (
<div className="see-more-wrapper">
{ !isReviewModal ?
<Button
className="see-more-gallery-btn"
onClick={() => {
document.body.classList.add('bike-profile-modal-open')
this.setState({ showGridModal: true })
}}
>
<i class="fa fa-th" aria-hidden="true"></i> See All 10 Photos
</Button>:
<div className="review-see-more">
<Button
className="review-see-more-gallery-btn"
onClick={() => {
document.body.classList.add('bike-profile-modal-open')
this.setState({ showGridModal: true })
}}
>
<i class="fa fa-th" aria-hidden="true"></i> <br/>See All <br/>10 Photos
</Button>
</div>
}
{ this.state.showGridModal &&
<div className="grid-gallery-modal-wrapper">
<Modal
className="my-modal-name"
showClose={true}
closeOnEsc={true}
closeOnBlur={true}
onClose={() => {
document.body.classList.remove('bike-profile-modal-open')
this.setState({ showGridModal: false })
}}
show={this.state.showGridModal}
size="fullscreen"
>
<ImageGallery
items={this.state.images}
renderRightNav = { this.state.renderRightNav}
renderLeftNav = { this.state.renderLeftNav}
/>
</Modal>
</div>
}
</div>
);
}
}
const RightNav = (props) => {
const settings = {
width: "3rem",
height: "3rem",
borderRadius: "50%",
background: "#AF985F",
fontStyle: "normal",
fontWeight: "bold",
fontSize: "24px",
lineHeight: "29px",
color:"#FFFFFF",
display: "flex",
alignItems: "center",
justifyContent: "center",
cursor: "pointer"
}
const { onClick } = props
return(
<button className="bikegallery-nextarrow-wrapper image-gallery-icon image-gallery-right-nav" onClick={onClick}>
<div classname="next-arrow">
<i class="fas fa-arrow-right" style={settings}></i>
</div>
</button>
)
}
const LeftNav = (props) => {
const settings = {
width: "3rem",
height: "3rem",
borderRadius: "50%",
background: "#AF985F",
fontStyle: "normal",
fontWeight: "bold",
fontSize: "24px",
lineHeight: "29px",
color:"#FFFFFF",
display: "flex",
alignItems: "center",
justifyContent: "center",
cursor: "pointer"
}
const { onClick } = props
return(
<button className="bikegallery-leftarrow-wrapper image-gallery-icon image-gallery-left-nav" onClick={onClick}>
<div classname="next-arrow">
<i class="fas fa-arrow-left" style={settings}></i>
</div>
</button>
)
}
export default ProfileImageGridModal
ProfileImageGridModal.defaultProps = {
showGridModal: false
}
You are using hooks wrongly. This is not React-image-Gallery error.
I have a React-Native flashcard app that boots with two tabs, a Home tab, and a New Deck tab. The Home tab is the default, and you can press or swipe over to the New Deck tab.
The Home tab displays all of the decks the user currently has saved.
On the New Deck tab, I have the user enter the title of their new deck and press a submit button. When that submit button is pressed, I re-navigate to the Home tab.
My issue is: How in the world do I trigger a re-render on the Home tab from a button press on the New Deck tab so the user can see the deck they just created?
I know I could use Redux to solve this issue, but no other part of the app is optimized in a "Redux" fashion, and I'd really like to not redesign the architecture of my app for the sole purpose of updating a single screen, mostly because this is the only instance where I would need this ability.
I've attempted to get around this by passing screenProps containing the this.forceUpdate method all the way from the StackNavigator component, but it didn't work. I also tried manually update the state of the App component to trigger a re-render, but the re-render never happened (although the state did update).
App.js
import React, { Component } from 'react'
import { Text, View } from 'react-native'
import AlphaHome from './Components/Home/AlphaHome'
import AlphaQuiz from './Components/Quiz/AlphaQuiz'
import AlphaNewUdaciDeck from './Components/NewUdaciDeck/AlphaNewUdaciDeck'
import AlphaNewUdaciCard from './Components/NewUdaciCard/AlphaNewUdaciCard'
import AlphaUdaciDeckDetails from './Components/UdaciDeckDetails/AlphaUdaciDeckDetails'
import { TabNavigator, StackNavigator } from 'react-navigation'
const Tabs = TabNavigator({
Home: {
screen: AlphaHome,
navigationOptions: {
tabBarLabel: 'Home',
},
},
NewDeck: {
screen: AlphaNewUdaciDeck,
navigationOptions: {
tabBarLabel: 'New Deck',
}
}
}, {
navigationOptions: {
header: null,
},
tabBarOptions: {
activeTintColor: 'white',
indicatorStyle: {
backgroundColor: 'white'
},
style: {
height: 50,
borderBottomColor: 'white',
backgroundColor: 'deepskyblue',
}
},
})
const Stack = StackNavigator({
Home: {
screen: Tabs,
},
AlphaNewUdaciDeck: {
screen: AlphaNewUdaciDeck,
navigationOptions: {
headerTintColor: 'white',
headerStyle: {
backgroundColor: 'deepskyblue'
}
}
},
AlphaNewUdaciCard: {
screen: AlphaNewUdaciCard,
navigationOptions: {
headerTintColor: 'white',
headerStyle: {
backgroundColor: 'deepskyblue'
}
}
},
AlphaUdaciDeckDetails: {
screen: AlphaUdaciDeckDetails,
navigationOptions: {
headerTintColor: 'white',
headerStyle: {
backgroundColor: 'deepskyblue'
}
}
},
})
export default class App extends Component {
render() {
return (
<Stack />
)
}
}
Home.js
import React, { Component } from 'react'
import { ScrollView, View, Text, StyleSheet, AsyncStorage, ActivityIndicator } from 'react-native'
import UdaciDeck from '../Reusable/UdaciDeck'
import { getAllData } from '../../utils/AsyncApi'
export default class HomeExistingUser extends Component {
state = {
decks: null,
}
componentDidMount() {
let decks = getAllData()
setTimeout(() => {
this.setState({
decks
})
}, 1000)
}
showDetails = (title, count) => {
this.props.navigation.navigate('AlphaUdaciDeckDetails', {title, count})
}
render() {
const {decks} = this.state
return (
decks
? <ScrollView contentContainerStyle={styles.container}>
{decks.map(s => <UdaciDeck key={s[1].title} name={s[1].title} count={s[1].questions.length} method={this.showDetails} />)}
</ScrollView>
: <View style={[styles.container, {flex: 1, justifyContent: 'center'}]}>
<ActivityIndicator size='large' color='white' />
</View>
)
}
}
const styles = StyleSheet.create({
container: {
minHeight: '100%',
backgroundColor: 'lightskyblue',
paddingTop: 20,
paddingBottom: 20,
alignItems: 'center',
},
})
NewDeck.js
import React, { Component } from 'react'
import { View, Text, TextInput, StyleSheet, AsyncStorage, TouchableNativeFeedback, Alert } from 'react-native'
import { addDeck } from '../../utils/AsyncApi'
// BUG: when adding a new deck (if HomeExistingUser is true) view doesn't update. Need to figure out a way to update on tab navigate back
export default class AlphaNewUdaciDeck extends Component {
state = {
input: '',
keys: null,
}
componentDidMount() {
AsyncStorage.getAllKeys()
.then(keys => this.setState({
keys
}))
}
handleSubmit = () => {
const {input, keys} = this.state
input.search(' ') > 0 || input.length < 1 || keys.filter(s => s === input).length > 0
? Alert.alert(`Please enter a valid name (${input.length < 1 || keys.filter(s => s === input).length > 0 ? `you can't save a deck with ${input.length < 1 ? 'no' : 'an already used'} name` : "no spaces"})`)
: addDeck(input)
;if(input.search(' ') < 0 || input.length > 0 || keys.filter(s => s === input).length < 1) {
this.props.navigation.goBack()
}
}
render() {
return (
<View style={[styles.container, styles.containerOne]}>
<View style={styles.containerTwo}>
<Text style={styles.text}>Name of the deck</Text>
<Text style={styles.text}>(Please no spaces)</Text>
<TextInput
autoFocus={true}
onChangeText={(input) => this.setState({
input
})}
selectionColor={'deepskyblue'}
underlineColorAndroid={'transparent'}
style={styles.input}
/>
<TouchableNativeFeedback onPress={this.handleSubmit}>
<View style={styles.btn}>
<Text style={styles.btnText}>Save Deck</Text>
</View>
</TouchableNativeFeedback>
</View>
</View>
)
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: 'lightskyblue',
},
containerOne: {
alignItems: 'center',
},
containerTwo: {
marginTop: 50,
},
text: {
color: 'white',
fontSize: 20,
},
input: {
backgroundColor: 'white',
height: 50,
width: 300,
marginTop: 15,
fontSize: 20,
paddingLeft: 5,
paddingRight: 5,
color: 'deepskyblue'
},
btn: {
backgroundColor: 'deepskyblue',
marginTop: 50,
padding: 20,
paddingLeft: 50,
paddingRight: 50,
justifyContent: 'center',
alignItems: 'center',
borderRadius: 5,
},
btnText: {
color: 'white',
},
})
You should check out react-navigation-is-focused-hoc at https://github.com/pmachowski/react-navigation-is-focused-hoc to solve the specific problem you mentioned.
You can also try
onNavigationStateChange(prevState, newState)
there is a sample at How can I tell if the screen is navigated to with ReactNavigation
I'm sure there is a way to DRY the code. All changeColor() is doing is changing the background color of the parent component .
import { Play } from "./play";
import { Hello } from "./hello";
import { styles } from "./styles";`
export class Buttons extends React.Component {
constructor(props) {
super(props);
this.state = {
color: styles.container
};
this.changeColor = this.changeColor.bind(this);
this.changeColor1 = this.changeColor1.bind(this);
this.changeColor2 = this.changeColor2.bind(this);
}
changeColor(newColor) {
this.setState({
color: styles.backPlay
});
}
changeColor1(newColor) {
this.setState({
color: styles.backTime
});
}
changeColor2(newColor) {
this.setState({
color: styles.backHello
});
}
render() {
return (
<div style={this.state.color}>
<Play onClick={this.changeColor} />
<Time onClick={this.changeColor1} />
<Hello onClick={this.changeColor2} />
</div>
);
}
}
Here is the styles page, which I also think can use a little DRY. Container, backPlay, backTime and backHello all represent the same container but a different background.
styles.js
export var styles = {
loc: {
padding: 25,
margin: 40,
fontWeight: "bold",
textAlign: "center"
},
green: {
color: "green",
background: "#59D13E"
},
red: {
color: "yellow",
background: "#A9A81D"
},
blue: {
color: "blue",
background: "#34BEE3"
},
container: {
display: "inline-block",
textAlign: "center",
marginTop: 50,
padding: 40
},
backPlay: {
display: "inline-block",
textAlign: "center",
background: "yellow",
marginTop: 50,
padding: 40
},
backTime: {
display: "inline-block",
textAlign: "center",
background: "blue",
marginTop: 50,
padding: 40
},
backHello: {
display: "inline-block",
textAlign: "center",
background: "green",
marginTop: 50,
padding: 40
},
mainCont: {
height: "100vh",
textAlign: "center",
background: "#FFA692"
}
};
UPDATE
I found a better way to DRY up this code. By using one button component and manipulating it's state. Let me know if there is even a better way to do this.
ButtonContainer.js
import React from 'react'
import Button from './Button'
export default class ButtonContainer extends React.Component {
state = {
colors: ['red', 'blue', 'green']
}
toggleClass = (color, id) => {
let colors = [...this.state.colors]
const newColors = colors.map((newColor, index) => {
if (id === index) {
const copyMap = { 0: 'red', 1: 'blue', 2: 'green' }
const copy = color === 'not' ? copyMap[index] : 'not'
return copy
} else {
return newColor
}
})
this.setState({ colors: newColors })
}
render() {
return (
<div className='button-container'>
{this.state.colors.map((color, index) =>
<Button
toggleClass={this.toggleClass}
key={index}
id={index}
name={color}
/>
)}
</div>
)
}
}
Button.js
import React from 'react'
const Button = (props) => (
<button
className={`button-component ${props.name}`}
onClick={() => props.toggleClass(props.name, props.id)}
>
{props.name}
</button>
)
export default Button
_button-container.scss
.button-container {
margin: 10rem auto;
text-align: center;
}
_button.scss
.button-component {
padding: 4rem;
margin: 0 2rem;
}
.red {
background: red;
}
.blue {
background: blue;
}
.green {
background: green;
}
.not {
background: none;
}
You can use .bind() to pre-bind arguments to a function before passing it down as a prop:
export class Buttons extends React.Component {
state = {
color: styles.container
};
changeColor = newColor => {
this.setState({
color: newColor
});
};
render() {
return (
<div style={this.state.color}>
<Play onClick={this.changeColor.bind(this, styles.backPlay)} />
<Time onClick={this.changeColor.bind(this, styles.backTime)} />
<Hello onClick={this.changeColor.bind(this, styles.backHello)} />
</div>
);
}
}
You can also remove your constructor and use fat arrow functions to autobind your methods to the component.
styles.js
const container = {
display: "inline-block",
textAlign: "center",
marginTop: 50,
padding: 40
};
export const styles = {
loc: {
padding: 25,
margin: 40,
fontWeight: "bold",
textAlign: "center"
},
green: {
color: "green",
background: "#59D13E"
},
red: {
color: "yellow",
background: "#A9A81D"
},
blue: {
color: "blue",
background: "#34BEE3"
},
container,
backPlay: {
...container,
background: "yellow"
},
backTime: {
...container,
background: "blue"
},
backHello: {
...container,
background: "green"
},
mainCont: {
height: "100vh",
textAlign: "center",
background: "#FFA692"
}
};
You can use the es6 spread operator to copy the contents of styles.container to each style, and then override the color property.
Since all of your color changing functions are very similar, you can pass in the name of the style you want to apply and use it inside the function, thus saving you the repetition.
changeColor(attr) {
this.setState({
color: styles[attr]
});
}
render() {
return (
<div style={this.state.color}>
<Play onClick={() => this.changeColor('backPlay')} />
<Time onClick={() => this.changeColor('backTime')} />
<Hello onClick={() => this.changeColor('backHello')} />
</div>
);
}