Conditional Rendering not reliably setting state on different components - javascript

I have two navigation buttons (light version, and dark version) that I want to render on certain pages.
I tried setting the state in the constructor, and generating the link to the images based on the path of the page, but sometimes the wrong link to the image will generated. It seems as though it's getting the state based on the first page that was ever generated. For example, if "home" is supposed to have the light version of the button any other link I click will generate the light version of the logo, unless I refresh. If "about" is supposed to have the dark version of the logo, all other pages I click through will have the dark version, unless I refresh.
Why won't it generate properly while naturally clicking around and navigating through the different pages?
MenuButton.js
import React, { Component } from 'react';
export default class MenuButton extends Component {
constructor() {
super();
this.state = {
logo_url: ''
}
}
componentDidMount() {
let currentPath = window.location.pathname;
if (!currentPath.includes('about') && !currentPath.includes('news')
&& !currentPath.includes('work')) {
this.setState({ logo_url: `${require('../../assets/nav/logo-light.svg')}` });
} else {
this.setState({ logo_url: `${require('../../assets/nav/logo-dark.svg')}` });
}
}
render() {
return (
<div className="menu-btn--cntr">
<img src={this.state.logo_url} />
</div>
)
}
}

You don't need to use state and life cycle.
You can try something like below -
import React, { Component } from 'react';
export default class MenuButton extends Component {
constructor() {
super();
this.state = {
logo_url: ''
}
}
getButton() {
let currentPath = window.location.pathname;
let btnUrl = ''; // or set some default
if (!currentPath.includes('about') && !currentPath.includes('news')
&& !currentPath.includes('work')) {
// this.setState({ logo_url: `${require('../../assets/nav/logo-light.svg')}` });
btnUrl = `${require('../../assets/nav/logo-light.svg')}`;
} else {
// this.setState({ logo_url: `${require('../../assets/nav/logo-dark.svg')}` });
btnUrl = `${require('../../assets/nav/logo-light.svg')}`;
}
return btnUrl;
}
render() {
const btnUrl = this.getButton();
return (
<div className="menu-btn--cntr">
<img src={btnUrl} />
</div>
)
}
}

Related

Display a different components in a click of a button (react component class)

I created two dropdowns selection, input, and a button in my Addroom component and I want to display that component in my main page only in a click of the button - it will look like a page with a single button.
After the user will finish fill the details (at Addroom component) and clicking on the Create button I want to display the titles with the values that the user filled - Room component.
In the current situation, everything is just already on the main page..
the Addroom component with elements for fill out.
the Room Component with the titles that filled with the user details.
And this is what I want it to look like:
I hope the explanation is understandable - these are two buttons...
I have tried different methods to display the components separately but none of the methods work properly and just made a lot of mess that I couldn't understand in the code.
I tried things like boolean values and tried to create a button based on the specific condition, and also methods that based on the state value that contact operators with functions to update the state values based on the unique identifiers.
Is there a simple way to do this without ruin the algorithms that work?
I could really use a hand..
Thank you! and particularly thanks for your patience..
App.js
import React, { Component } from 'react'
import 'bootstrap/dist/css/bootstrap.min.css'
import './App.css';
import Addroom from './components/Addroom.js'
import Room from './components/Room.js'
export default class App extends Component {
state={
roomsList:[{room:''}],
roomTypeSelection:[{roomType:''}],
colorTypeSelection:[{colorType:''}],
isActive: false
}
handleShow = () => {
this.setState({isActive: true});
};
handleHide = () => {
this.setState({isActive: false});
};
create=(r)=> {
this.setState({roomsList:[...this.state.roomsList,{room:r}]})
}
getRoomTypeSelection=(rt)=> {
this.setState({roomTypeSelection:[...this.state.roomTypeSelection,{roomType:rt}]})
}
getColorTypeSelection=(ct)=> {
this.setState({colorTypeSelection:[...this.state.colorTypeSelection,{colorType:ct}]})
}
render() {
return (
<div>
<h1>My Smart House</h1>
{this.state.roomsList.map((element)=>{
return <Room r={element.room} rt={element.roomType} ct={element.colorType}/>
})}
<Addroom add={this.create} addRoomType={this.getRoomTypeSelection} addColorType={this.getColorTypeSelection}/>
</div>
)
}
}
Addroom.js
import React, { Component } from 'react'
export default class Addroom extends Component {
constructor(props) {
super(props)
this.state = {
backgroundColor:'white',
room:'',
roomNameInputColor:'white',
roomTypes:["kitchen", "bathroom", "bedroom"],
roomSelected: [''],
roomSel:'',
colorTypes:['red', 'green', 'blue', 'teal'],
colorSelected:[''],
colorSel:'',
};
}
addRoomName = (e) => {
const room = e.target.value;
let roomNameInputColor = 'white';
if (e.target.value.length >= 5) {
roomNameInputColor = 'green';
} else {
roomNameInputColor = 'red';
}
this.setState({ room, addRoomName: room, roomNameInputColor });
}
createRoom=()=> {
this.props.add(this.state.room);
}
createRoomType=()=> {
this.props.addRoomType(this.state.roomSelected);
}
createColorType=()=> {
this.props.addColorType(this.state.colorSelected);
}
setCategory = (roomSel) => {
this.setState({roomSelected : roomSel});
};
setColorCategory = (colorSel) => {
this.setState({colorSelected : colorSel});
};
render() {
return (
<div>
{/* //Select Room Type */}
<select onChange={(e) => this.setCategory(e.target.value)}>{this.state.roomTypes.map((type) =>
<option value={type}>{type}</option>
)}</select><br/>
{/* //Select Room Color */}
<select onChange={(e) => this.setColorCategory(e.target.value)}>{this.state.colorTypes.map((type) =>
<option value={type}>{type}</option>
)}
</select><br/>
<input onChange={this.addRoomName} style={{backgroundColor:this.state.roomNameInputColor}} placeholder='Name Your Room'/><br/>
<button onClick={() => {this.createRoom(); this.createRoomType(); this.createColorType();}}>Create</button>
</div>
)
}
}
Room.js
import React, { Component } from 'react'
export default class Room extends Component {
constructor(props) {
super(props)
this.state = {
}
}
render() {
return (
<div>
<h3>Room1: {this.props.r} </h3>
<h3>Room Type1: {this.props.rt}</h3>
<h3>Room Color1: {this.props.ct}</h3>
</div>
)
}
}
If I understand correctly, you only want to show the Addroom dropdowns and input when the user has clicked the Create button once, after which they can add a room. For that you can use a state to toggle what to display.
Ive made a quick sandbox example, please see if that works.
Note: dont forget to add a key when using map(), like this:
<select>
{items.map((value, key) => (
<option key={key} value={value}>{value}</div>
))}
</select>
Edit: improved the sandbox code

How do I reveal my button only when on a specific component?

I want to show the Logout button on the same row of the title but only when the user has made it to Home component.
In other words, I don't want to show the logout button at all times, especially when the user's at the login screen. I want it to show on the same row of the title only when they've logged in successfully and they're in Home
How would I achieve this? My head hurts from trying to make this work :(
Below's what I've tried so far, among other things.
import React, {Component} from 'react';
import classes from './Title.css';
import LogoutButton from '../../containers/LogoutButton/LogoutButton';
import Home from '../../components/Home/Home';
class Title extends Component {
constructor(props) {
super(props);
this.state = {
show: false,
showLogoutButton: true
};
}
showButton() {
this.setState({show: true});
if(this.state.show) {
return <LogoutButton/>;
} else {
return null;
}
}
render() {
return(
<div>
{ this.state.showLogoutButton ? this.showButton : null }
<h1 className={classes.Title}>Pick Ups</h1>
</div>
);
}
}
export default Title;
You can try something like below. You don't need to deal with function and modifying states.
You can simply do like below
import classes from './Title.css';
import LogoutButton from '../../containers/LogoutButton/LogoutButton';
import Home from '../../components/Home/Home';
class Title extends Component {
constructor(props) {
super(props);
this.state = {
showLogoutButton: this.props.authenticated
};
}
render() {
const { showLogoutButton } = this.state;
return(
<div className="row" style={{"display" :"flex"}}>
{ showLogoutButton && <LogoutButton/>}
<h1 className={classes.Title}>Pick Ups</h1>
</div>
);
}
}
export default Title;
Note: When you modify state using setState the state value will be updated only after render so you can't directly check immediately modifying the value.

onClick not working inside the pop up opened via React portals

Below is the code that I have implemented:
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import {Link} from 'react-router'
import {Table, Column} from '../../Layout/components/Table'
import ReactDOM from 'react-dom'
// import ExternalPortal from '../../Sites/components/ExternalPortal'
export const TableName = 'ProjectDashboard'
class MyWindowPortal extends Component {
static propTypes = {
children: PropTypes.node,
closeWindowPortal: PropTypes.func
}
constructor (props) {
super(props)
this.containerEl = document.createElement('div') // STEP 1: create an empty div
this.externalWindow = null
}
componentDidMount () {
// STEP 3: open a new browser window and store a reference to it
this.externalWindow = window.open('', '', 'width=600,height=400')
// STEP 4: append the container <div> (that has props.children appended to it) to the body of the new window
this.externalWindow.document.body.appendChild(this.containerEl)
this.externalWindow.document.title = 'A React portal window'
// copyStyles(document, this.externalWindow.document)
// update the state in the parent component if the user closes the
// new window
this.externalWindow.addEventListener('beforeunload', () => {
this.props.closeWindowPortal()
})
}
componentWillUnmount () {
// This will fire when this.state.showWindowPortal in the parent component becomes false
// So we tidy up by just closing the window
this.externalWindow.close()
}
render () {
// STEP 2: append props.children to the container <div> that isn't mounted anywhere yet
return ReactDOM.createPortal(this.props.children, this.containerEl)
}
}
export default class ProjectTable extends Component {
constructor (props) {
super(props)
this.state={showWindowPortal:false}
this.toggleWindowPortal = this.toggleWindowPortal.bind(this)
this.closeWindowPortal = this.closeWindowPortal.bind(this)
}
static propTypes = {
data: PropTypes.object.isRequired,
loginId: PropTypes.string.isRequired
}
componentDidMount () {
window.addEventListener('beforeunload', () => {
this.closeWindowPortal()
})
}
toggleWindowPortal () {
this.setState({showWindowPortal: !this.state.showWindowPortal})
}
closeWindowPortal () {
this.setState({showWindowPortal: false})
}
render () {
return (
<div>
<div>
<p>This div is just for testing click here to see the portal</p>
{
this.state.showWindowPortal &&
(
<MyWindowPortal closeWindowPortal={this.closeWindowPortal}>
<button
onClick={() => this.closeWindowPortal()}
>
Close
</button>
</MyWindowPortal>
)
}
<button onClick={this.toggleWindowPortal}>
{this.state.showWindowPortal ? 'Close the' : 'Open a'} Portal
</button>
</div>
</div>
)
}
}
The button inside the portal doesn't trigger anything on click. It doesn't fire at all. Please note that MyWindowPortal opens a new window and the button gets rendered in that window. Tried testing it on Firefox and Chrome. I am not sure if I am doing anything wrong.
The solution has been posted here
https://github.com/facebook/react/issues/12355
The issue is that you're first creating a DOM node in one document, but then moving it to another one. However React has already bound the event handlers to the first document. React doesn't expect that you would move a DOM node between documents while something is rendered into it
To fix it, you can change your code so that you move the node before rendering something into in React
class Window extends React.Component {
constructor(props) {
super(props);
this.state = { win: null, el: null };
}
componentDidMount() {
let win = window.open('', '', 'width=600,height=400');
win.document.title = 'A React portal window';
let el = document.createElement('div');
win.document.body.appendChild(el);
this.setState({ win, el });
}
componentWillUnmount() {
this.state.win.close();
}
render() {
const { el } = this.state;
if (!el) {
return null;
}
return ReactDOM.createPortal(this.props.children, el);
}
}
I've faiced the same problems with onClick handlers but in my case I used component inside WindowPortal.
My workaround for the problem was usage of ref's and manual assignment of onclick event handlers to each of button elements I had inside my component.
Here is snippet example:
class SomeComponent extends Component {
constructor(props) {
super(props);
this.ref = (element) => {
element.querySelector('button.one').onclick = () => {
alert( 'One!' );
}
element.querySelector('button.two').onclick = () => {
alert( 'Two!' );
}
}
}
render() {
return <div className="some-component" ref={this.ref}>
<button type='button' className='one'>Click One</button>
<button type='button' className='two'>Click Two</button>
</div>;
}
}
Btw, I'm using redux and SomeComponent successfully connects to store and allows dispatching of actions.

How to link on click function to another component in React app?

I have a landing page that contains a logo. I'm trying to get this logo to trigger a change of of state value. The purpose of this is to change from the landing page to the home page on click. I have set it up so that the landing page clear in an determined time, but I want to do this on click. This is my splash.js file that contains the on click function as well as the logo and landing page:
import React, { Component } from 'react';
import Woods from './woods.jpeg';
import Logo1 from './whitestar.png';
export default class Splash extends Component {
constructor() {
super();
this.toggleShowHome = this.toggleShowHome.bind(this);
}
toggleShowHome(property){
this.setState((prevState)=>({[property]:!prevState[property]}))
}
render() {
return(
<div id='Splashwrapper'>
<img src={Woods}></img>
<img id='logoc' src={Logo1} onClick={()=>this.toggleShowHome('showSquareOne')}></img>
</div>
);
}
}
I want the on click function to change the value of splash to false in my App.js file:
import React, { Component } from 'react';
import Splash from './splash';
import Menu from 'components/Global/Menu';
export default class About extends Component {
constructor(){
super();
this.state = {
splash: true
}
}
componentDidMount() {
setTimeout (() => {
this.setState({splash: false});
}, 10000);
}
render() {
if (this.state.splash) {
return <Splash />
}
const { children } = this.props; // eslint-disable-line
return (
<div className='About'>
<Menu />
{ children }
</div>
);
}
}
How can I link the on click function to the App.js file and change the value of splash?
You should define your function toggleShowHome in app.is and pass it as a prop to your splash component. Then you could change your local state in app.js
To make sure I'm understanding, you're looking for the image on the Splash component to trigger a change in the About component?
You can pass a method to your Splash component (from About) that it can call when the image is pressed. So something like this:
render() {
if(this.state.splash) {
return <Splash onLogoClicked={this.logoClicked.bind(this)} />
}
(.......)
}
logoClicked(foo) {
< change state here >
}
And then in your Splash component:
<img id='logoc' src={Logo1} onClick={this.props.onLogoClicked}></img>
Not sure if I understood you well, but you can try this: to pass the on click function from parent (About) to child (Splash), something like this:
YOUR MAIN APP:
export default class About extends Component {
constructor(){
super();
this.state = {
splash: true
}
this.changeSplashState = this.changeSplashState.bind(this);
}
//componentDidMount() {
//setTimeout (() => {
//this.setState({splash: false});
//}, 10000);
//}
changeSplashState() {
this.setState({splash: false});
}
render() {
if (this.state.splash) {
return <Splash triggerClickOnParent={this.changeSplashState} />
}
const { children } = this.props; // eslint-disable-line
return (
<div className='About'>
<Menu />
{ children }
</div>
);
}
}
YOUR SPLASH COMPONENT:
export default class Splash extends Component {
constructor() {
super();
//this.toggleShowHome = this.toggleShowHome.bind(this);
}
toggleShowHome(property){
this.setState((prevState)=>({[property]:!prevState[property]}));
//IT'S UP TO YOU TO DECIDE SETTING TIMOUT OR NOT HERE
//setTimeout (() => {
this.props.triggerClickOnParent();
//}, 10000);
}
render() {
return(
<div id='Splashwrapper'>
<img src={Woods}></img>
<img id='logoc' src={Logo1} onClick={this.toggleShowHome.bind(this,'showSquareOne')}></img>
</div>
);
}
}
Feel free to post here some errors or explain to me more about what you need, but that is the way it should look like, a standard way to pass function as props from parent to child.
You can also read more about how to pass props from parent to child/grandchild/many-deeper-level-child (of course in react's way):
Force React container to refresh data
Re-initializing class on redirect

React show image if state is set to true

I'm trying to hide an image by default and only show it when element is hovered. I've been able to set the default state etc.. Only issue is creating an if statement that will show and hide the image.
This is the component:
import React from 'react';
import { Link } from 'react-router';
import Eyecon from '../../static/eye.svg';
class Item extends React.Component {
constructor(props) {
super(props);
this.displayName = 'Item';
this.handleHover = this.handleHover.bind(this);
this.state = {
hover: false
};
}
mouseOver() {
this.state.hover = true;
}
mouseOut() {
this.state.hover = false;
}
handleHover() {
console.log("hover");
}
render() {
const { item, i } = this.props;
return (
<div className="grid-box">
<img src={Eyecon}/>
</div>
)
}
}
export default Item;
I've tried a few things, but also want to see what the best practice is.
Thanks for your time
There are multiple ways, I would do it like this:
render() {
const { item, i } = this.props;
return (
<div className="grid-box">
{this.state.hover ? (
<img src={Eyecon} />
) : null}
</div>
)
}
But you could also abstract the image rendering into a separate function and not return anything when needed.
Sitenote: You shouldn't mutate the state directly. Use the this.setState() function. Otherwise the component will not be re-rendered.
Also, may I ask why you're not just using css :hover to achieve this behaviour?
I typically like to handle conditional displaying of content in helper functions, like so:
function renderImage() {
const { hover } = this.state;
if (hover) {
return (
<img src={Eyecon} />
);
}
}
Then, you can just call this function from render()
render() {
const { item, i } = this.props;
return (
<div className="grid-box">
{renderImage.call(this)}
</div>
)
}

Categories