At the moment I am trying to do a website on cruise ships using React in my spare time.
I have a working version on my Reviews branch, here https://github.com/RobertWSON/Personal-ship-project/tree/reviews.
However I am wanting to change how the Cruise Lines Page is displayed.
I would like to have Cruise Line Headings across the page.
When a Cruise Line Heading is clicked it expands to show a List of Ships for that Cruise Line and if you click again, it collapses to show just the Cruise Line Heading.
At the moment I am a bit confused, as to how I can make this work and I have not got it working just yet.
I have been working on this, on a different branch called robs-shipslist-under-cruiselines: here https://github.com/RobertWSON/Personal-ship-project/tree/robs-shipslist-under-cruiselines .
I have components called CruiseListHeader.jsx and ListofShips.jsx.
Just wondering if anyone can give me any advice on whether it's possible to do a ternary operator for this handleClick, that I have in my CruiseListHeader component?
It seems to me that the code inside my handleClick function is the code that causes the errors.
I think my state for opening and closing the ShipsList, so that's OpenshipsList and CloseshipsList, needs to be handled better.
How can I better deal with this?
Does anyone have any ideas that may help me solve this problem and make it work.
The following code is from my CruiseListHeader component
import React from 'react'
import {getCruiseLines } from '../api/api';
class CruiseListHeader extends React.Component {
constructor(props) {
super(props)
//setting intial state for cruise heading and shipsList and initialize cruiseHeaders as an empty array
this.state = {
cruiseHeaders: [],
shipsList: {isOpen:false}
}
//binding methods for Cruise Line Headers and Handle Click Function
this.setUpCruiseLines = this.setUpCruiseLines.bind(this),
this.handleClick = this.handleClick.bind(this)
}
componentDidMount() {
console.log('cdm')
this.setUpCruiseLines()
}
setUpCruiseLines() {
console.log('getcruiselines')
getCruiseLines()
.then(res => {
this.setState({
cruiseHeaders: res
})
})
}
/* There will be Headings for all the Cruise Lines.
When a Cruise Line Heading is clicked, it goes to ListofShips Component and the Ships List opens up for that Heading.
When user clicks on a Cruise Line Heading, when a Ships List is open, the Ships List Collapses.*/
handleClick(event) {
// Maybe do a ternary operator here before open and close functions
this.state.shipsList === isOpen ? OpenShipsList : CloseshipsList
OpenshipsList(event) {
this.setState = {shipsList: {isOpen:true}}
return
<div>
<ListofShips/>
</div>
}
CloseshipsList(event) {
this.setState = {shipsList: {isOpen: false}}
render()
}
}
// This renders at the start when the page loads and also when you close a list
render() {
return (
<React.Fragment>
<h3><button onClick = {this.handleClick}>{ship.cruise_line}</button></h3>
</React.Fragment>
)
}
}
export default CruiseListHeader
At the moment, when I do a yarn dev I am getting the following error
ERROR in ./client/components/CruiseListHeader.jsx Module build failed:
SyntaxError: Unexpected token, expected ; (42:29)
I would like to get rid of this error and display the page like I have described above.
As a beginning, to set isOpen correctly on the state, modify the onClick function handler as this:
handleClick(event) {
// this handleClick function should only handle the `isOpen` value in the state.
// Any renders supposibly to be made on the `render` method instead.
this.setState(prevState => ({
shipsList: {
isOpen: !prevState.shipsList.isOpen, //will reverse the prevState of isOpen.
}
}));
}
Now, Going to your render, we can handle the way you renderthe component that depends on the this.state.shipsList.isOpen this way:
render() {
//destructive declaration for isOpen from inside the shipsList in the state.
const { shipsList: { isOpen } } = this.state;
return (
<React.Fragment>
<h3>
<button onClick={this.handleClick}>
{ship.cruise_line}
</button>
</h3>
{
// Usually modals are shown at the bottom of the render return.
// it's better to use tenary `val ? component : null` rather than: (val && component)
// React accepts a component, or a null as return value, the second will return false if val was false.
isOpen ? <OpenShipsList /> : null
}
</React.Fragment>
)
}
PS: Please follow the comments inside the code above of each line, they should be enough illustrating what happened, if something was ambiguos, just let me know.
Hard to tell with the indentations, but is this.state.shipsList === isOpen ? OpenShipsList : CloseshipsList supposed to really be this.state.shipsList.isOpen ? OpenShipsList() : CloseshipsList();? Note that isOpen is a property of state.shipsList, and then the parens to invoke the calls to open/close the list, and also the semi-colon to end the line.
I think you probably really want your handleClick to simply toggle the open state and then use that state value to selectively render the list.
const handleClick = event => this.setState(
prevState => ({ shipsList: {isOpen: !prevState.shipsList.isOpen} })
);
render() {
const { shipsList: { isOpen } } = this.state;
return (
<Fragment>
{isOpen && <ListofShips />}
<h3>
<button onClick={this.handleClick}>{ship.cruise_line}</button>
</h3>
</Fragment>
)
}
Related
I am making a react app that has a navbar as pictured above. The navbar is a component called "TopButtonsBar". Rendered inside this TopButtonsBar component are a component for every button picture above. These components from left to right are InfoButton, NotificationsButton, and so on. Each of the button components manage their own state which dictates whether a dialog box of a given button should be shown or not. Here is what the buttons component look like individually, using the InfoButton component as an example.
export default class InfoButton extends React.Component{
constructor(props){
super(props);
this.state = {
isDialogueOpen:false,
isHoveringOver:false
};
this.handleOpenDialogue = this.handleOpenDialogue.bind(this);
this.handleHoverOver = this.handleHoverOver.bind(this);
}
**handleOpenDialogue = (e) => {
e.preventDefault();
this.setState((prevState) => ({
isDialogueOpen:!prevState.isDialogueOpen
}));
}**
handleHoverOver = (e) => {
e.preventDefault();
if(!this.state.isDialogueOpen){
this.setState((prevState) => ({
isHoveringOver:!prevState.isHoveringOver
}));
}
}
render(){
return(
<div className="navbar-button">
<img
onMouseOver={this.handleHoverOver}
onMouseLeave={this.handleHoverOver}
onClick={this.handleOpenDialogue}
src={this.state.isHoveringOver?infoButtonHovered:infoButtonNotHovered} alt="Info"
/>
{this.state.isHoveringOver && !this.state.isDialogueOpen && <InfoRollover />}
**{this.state.isDialogueOpen && <InfoDialogue />}**
</div>
)
}
}
The important bits are * enclosed by asterisks *. This logic works fine on a buttons individual level. What I am trying to do is the following: If, as picture above, the message notifications button is selected, if I click on the on the info button, I would like the message notifications button to close, simultaneously as the info button opens. However I have been unsuccessful in conceptualizing how I should re-configure the state. Should the TopButtonsBar component hold the information on the state if any of the buttons are closed? If so, how would I go about re-approaching how the buttons open (and if an individual button component should control that state or not). Also, I am not using any state manager such as Redux, Hooks, etc.
Thanks!
One way to solve this is to have the flags (as in isDialogueOpen) for all the child components (InfoButton, NotificationButton and so on) in the parent component's state (TopButtonsBar).
TopButtonsBar.js
I would start off with adding a few constants identifying each dialogue boxes. After that, we can declare a state, which would point to the diaogue box which is open.
Just follow along the comments in the code below to understand better.
// adding some constants here
const INFO_BUTTON = 'INFO_BUTTON';
const NOTIFICATION_BUTTON = 'NOTIFICATION_BUTTON';
export default class TopButtonsBar extends React.Component {
constructor(props) {
super(props);
this.state = {
...
// adding this state to point out which dialogue is open
selectedDialogue: null
}
}
handleOpenDialogue = (e, selectedDialogue) => {
e.preventDefault();
if (selectedDialogue === this.state.selectedDialogue) {
// close dialogue if already open
this.setState({selectedDialogue: null});
} else {
// else open this dialogue
this.setState({selectedDialogue});
}
}
....
render() {
return (
....
<InfoButton
isDialogueOpen={this.state.selectedDialogue === INFO_BUTTON}
handleOpenDialogue={(e) => handleOpenDialogue(e, INFO_BUTTON)}
...
/>
<NotificationButton
isDialogueOpen={this.state.selectedDialogue === NOTIFICATION_BUTTON}
handleOpenDialogue={(e) => handleOpenDialogue(e, NOTIFICATION_BUTTON)}
...
/>
)
}
}
InfoButton.js
Now that we are passing the state and its handling function from the TopButtonsBar component as props, we can call them directly in InfoButton and NotificationButton, without any related local states required.
export default class InfoButton extends React.Component{
constructor(props){
super(props);
this.state = {
// removing the state from here
isHoveringOver:false
};
this.handleHoverOver = this.handleHoverOver.bind(this);
}
// removing the handleOpenDialogue function here
...
render(){
return(
<div className="navbar-button">
<img
onMouseOver={this.handleHoverOver}
onMouseLeave={this.handleHoverOver}
// calling handleOpenDialogue from props
onClick={this.props.handleOpenDialogue}
...
/>
// using isDialogueOpen from props now
{this.state.isHoveringOver && !this.props.isDialogueOpen && <InfoRollover />}
{this.props.isDialogueOpen && <InfoDialogue />}
</div>
)
}
}
My react app is a multi-page form. It goes to next page after clicking 'Next'. Currently I have some text that should have a css class when current page is page 1, and when user goes to next page, the css class should be removed for that text (the text is still displayed for all pages).
My actual code is much larger so I'm only posting all the important parts(I think) that are required for this questions.
import ChildComponent from '....';
class Parent extends React.Component {
state = {
page: 1, //default start page
currentPageis1: true,
currentPageis2: false,
currentPageis3: false,
}
change = () => {
const = {page, currentPageis1} = this.state;
this.setState({
page: page + 1 //to go to next page
});
this.setState({
currentPageis1: !currentPageis1
});
}
showPage = () =>{
const {page, currentPageis1} = this.state;
if(page === 1)
return (<ChildComponent
change={this.change}
currentPageis1={currentPageis1}
/>)
}
render(){
return (
<p className={this.currentPageis1 ? '': 'some-css-class'}>Some Text</p>
<form>{this.showPage()}
)
}
}
class ChildComponent extends React.Component {
someFunction = e =>{
e.preventDefault();
this.props.change();
}
render(){
return (
<Button onClick={this.someFunction}>Next</Button>
)
}
}
Currently, when I click Next button, the currentPageis1 updates to false. I checked it using Firefox React extension. But it does not re-render the page. Which means "Some Text" still has the CSS class.
My guess is className={this.currentPageis1 ? '': 'css-class'} in Parent class is only being run once (when the page is first loaded). Do I have to use lifecycle method? How do I make react re-render everytime currentPageis1 is changed?
You are doing <p className={this.currentPageis1 ? '': 'some-css-class'}>Some Text</p>. In order to apply styles to only page 1, you should revert the values in your condition. When currentPageis1 is false '' value is picked up.
Also this.currentPageis1 is wrong. You should use state i.e. this.state.currentPageis1
Working demo
Like this
<p className={this.state.currentPageis1 ? "some-css-class" : ""}>
Some Text
</p>
To get your style to render, you'll need to add the props keyword.
Return Child component inside of Parent and pass the change method as
a prop
Also, updated your setState so you only call it once instead of twice
in the change method
class Parent extends React.Component {
state = {
page: 1, //default start page
currentPageis1: true,
currentPageis2: false,
currentPageis3: false,
}
change = () => {
const = {page, currentPageis1} = this.state;
this.setState({
...this.state,
page: page + 1,
currentPageis1: !currentPageis1
});
}
render(){
return (
<div>
<p className={this.props.currentPageis1 ? '': 'some-css-class'}>Some Text</p>
<Child change={this.change} />
</div>
)
}
}
My app has multiple Popover components, I know how to handle the state of one Popover component, using something like this:
class App extends Component {
constructor(props) {
super(props);
this.state = { pop_open: false };
}
handleProfileDropDown(e) {
e.preventDefault();
this.setState({
pop_open: !this.state.pop_open,
anchorEl: e.currentTarget,
});
}
handleRequestClose() {
this.setState({
pop_open: false,
});
};
render() {
return (
<div>
<button type="submit" onClick={this.handleProfileDropDown.bind(this)} >My Customized PopOver</button>
<Popover
open={this.state.pop_open}
anchorEl={this.state.anchorEl}
onRequestClose={this.handleRequestClose.bind(this)}
>
{"content"}
</Popover>
</div>
);
}
}
But for more than one Popover, I do not know how to do that, should I create a state for each Popover? Sorry for the question but I am new to the frontend world.
note: kindly do not use hooks in your answer.
An internal state is a good option when only the Component is going to modify it. It keeps the logic simple and inside the same block of code. On the other hand managing the state from outside of the Component lets other components read its values and modify them. This is a common approach when using Redux or Context, where there is a global app state. This state is meant for properties that several Components need to read/write to.
Which to use when is a design decision and depends on each situation. In my opinion each Component should handle its own state when possible. For example, when values are only going to be modified by it, or a children Component. Having an external state makes sense when multiple Components are going to read or modify it, or when the state values need to be passed several levels deep in the hierarchy.
In the example you propose I can see that the Popover is working with an internal state. This can work and you can use the Component several times and it will carry all the logic inside. If you rename the Components you can see more easily what I mean. I dont know exactly how the Component with the button works but this is to make the explanation clear:
class Popover extends Component {
constructor(props) {
super(props);
this.state = { is_open: false };
}
open = () => {
this.setState({
is_open: true
});
}
close = () => {
this.setState({
is_open: false
});
}
toggle = () => {
this.setState(prevState => ({
is_open: !prevState.is_open
}));
}
render() {
return (
<div>
<button onClick={this.toggle}>
Open
</button>
{this.state.is_open && <PopoverContent />}
</div>
);
}
}
If you need further explanation or something is not clear, let me know.
I would like to load the tab content only on the first time it becomes active, after that the content stays in the DOM
This is what I have
<Tabs defaultActiveKey={1} animation={false} id="my-tabs" mountOnEnter unmountOnExit>
<Tab eventKey={1}>
<div>content1</div>
</Tab>
<Tab eventKey={2}>
<div>content1</div>
</Tab>
</Tabs>
it works fine, but there is a lag between switching tabs, since the content I have is quite large and I would like to render it only once, on the first time the tab becomes active.
Is there a way to achieve that? I'm using react-bootstrap 0.30.10
UPDATE:
apparently mountOnEnter must be used with animation, otherwise it will not work as intended. I made the change and it works fine now
Old answer:
so I have come up with this wrapping component as follow
class TabsLazyLoad extends Component {
constructor(props) {
super(props);
this.state = this.getInitialState();
this.handleSelect = this.handleSelect.bind(this);
}
getInitialState() {
return {
key: this.props.key || this.props.defaultActiveKey,
rendered: [],
};
}
addRenderedTab(key) {
const newState = _.cloneDeep(this.state);
newState.rendered.push(key);
this.setState(newState);
}
handleSelect(key) {
this.setState({ key });
}
render() {
return (
<Tabs activeKey={this.state.key} onSelect={this.handleSelect} {...this.props}>
{_.map(this.props.children, (tabComponent) => {
if (_.includes(this.state.rendered, tabComponent.props.eventKey)) {
return tabComponent;
}
if (tabComponent.props.eventKey === this.state.key) {
this.addRenderedTab(this.state.key);
}
// if it's not rendered, return an empty tab
const emptyTab = _.cloneDeep(tabComponent);
emptyTab.props.children = null;
return emptyTab;
})}
</Tabs>
);
}
}
TabsLazyLoad.propTypes = Tabs.propTypes;
It seems to be working fine, but I reckon this is a bit hacky, but it's the best I can come up with for now.
It sounds like a good use case for the "Avoid Reconciliation" option that React provides.
Here's a link to the relevant section in the documentation.
Essentially, there's a lifecycle event called shouldComponentUpdate that defaults to true. When you change it to false, it tells React not to run the component through the standard Reconciliation process (i.e. the "diff" checks).
Like with any lifecycle method, you can create a conditional statement for it.
For a component that should be made completely static after its first render, this is really all you need:
class YourComponent extends React.Component {
...
shouldComponentUpdate() {
return false;
}
...
}
However, for a more general use case, you'd want to write a conditional statement based on the props and/or the state of the component:
class YourComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
// Your state
};
}
shouldComponentUpdate(nextProps, nextState) {
// A conditional statement to determine whether
// this component should check for updates or not
}
render () {
return (
<div>
{/* Your JSX*/}
</div>
)
}
I don't use React Boostrap but I guess it's based on the Component design,
example, the rendered content used TabIndex state. Take a closer look at this sample code:
renderActiveTabContent() {
const { children } = this.props
const { activeTabIndex } = this.state
if (children[activeTabIndex]) {
return children[activeTabIndex].props.children
}
}
So the content component render every time Tab state is indexed.
You could use https://github.com/reactjs/react-tabs for your solution other wise take a look of those codes to write a simple one, the Component is rendered once and show/hide state via display: style attribute.
Hope it's help.
I'm attempting to integrate or create a React version of https://github.com/kumailht/gridforms, to do so I need to normalize the height of the columns inside of the row. The original takes the height of the grid row and applies it to the children columns.
I had planned to get the height of the row and then map it to a property of the child, though from my attempts I'm thinking this might not be the ideal way or even possible?
Below is my current code.
GridRow = React.createClass({
render(){
const children = _.map(this.props.children, child => {
child.props.height = // somehow get row component height
return child
})
return (<div data-row-span={this.props.span} {...this.props}>
{children}
</div>)
}
})
GridCol = React.createClass({
render(){
return (<div data-field-span={this.props.span} style={{height:this.props.height}} {...this.props}>
{this.props.children}
</div>)
}
})
I tested setting the style this way and it will work, however getting the height isn't.
EDIT: Fiddle:
https://jsfiddle.net/4wm5bffn/2/
A bit late with the answer but technically you can get element hight this way:
var node = ReactDOM.findDOMNode(this.refs[ref-name]);
if (node){
var calculatedHeight = node.clientHeight;
}
According to current React docs, the preferred use of refs is to pass it a callback rather than a string to be accessed elsewhere in this.refs.
So to get the height of a div (within a React.Component class):
componentDidMount() {
this.setState({ elementHeight: this.divRef.clientHeight });
}
render() {
return <div ref={element => this.divRef = element}></div>
}
Or it works this way, though I don't know if this is advisable since we set state in the render method.
getHeight(element) {
if (element && !this.state.elementHeight) { // need to check that we haven't already set the height or we'll create an infinite render loop
this.setState({ elementHeight: element.clientHeight });
}
}
render() {
return <div ref={this.getHeight}></div>;
}
Reference: https://facebook.github.io/react/docs/more-about-refs.html
Don't know about anyone else but I always have to get it on the next tick to be sure of getting the correct height and width. Feels hacky but guessing it's to do with render cycle but I'll take it for now. onLayout may work better in certain use cases.
componentDidMount() {
setTimeout(() => {
let ref = this.refs.Container
console.log(ref.clientHeight)
console.log(ref.clientWidth)
}, 1)
}
Here is an example of using refs and clientWidth/clientHeight:
import React, { Component } from 'react';
import MyImageSrc from './../some-random-image.jpg'
class MyRandomImage extends Component {
componentDidMount(){
let { clientHeight, clientWidth } = this.refs.myImgContainer;
console.log(clientHeight, clientWidth);
}
render() {
return (
<div ref="myImgContainer">
<img src={MyImageSrc} alt="MyClickable" />
</div>
);
}
}
export default MyRandomImage;
Note: this appears to work for width reliably, but not height. Will edit if I find a fix...
My personal opinion is to try and avoid using static and measured sizes like this if you can avoid it because it can complicate the application unnecessarily. But sometimes you cannot get around it. Your component will need to be mounted before you can get a size from it.
General approach:
Give the element a ref
When the element is rendered, grab the ref and call .clientHeight and/or .clientWidth
Put the values on the state or pass with props
Render the element that needs the size from the state variables
In your case you want to grab the size of a column you can do something like:
GridRow = React.createClass({
render(){
const children = _.map(this.props.children, child => {
child.props.height = // somehow get row component height
return child
})
return (<div data-row-span={this.props.span} {...this.props}>
<GridCol onSizeChange={(size) => {
//Set it to state or whatever
console.log("sizeOfCol", size);
}} />
</div>)
}
})
GridCol = React.createClass({
componentDidMount(){
//Set stizes to the local state
this.setState({
colH: this.col.clientHeight,
colW: this.col.clientWidth
});
//Use a callback on the props to give parent the data
this.props.onSizeChange({colH: this.col.clientHeight, colW: this.col.clientWidth})
}
render(){
//Here you save a ref (col) on the class
return (<div ref={(col) => {this.col = col}} data-field-span={this.props.span} style={{height:this.props.height}} {...this.props}>
<.... >
</div>)
}
})
According this answer sizes of a component can be turned out having zero width or height inside componentDidMount event handler. So I'm seeing some ways to solve it.
Handle the event on top-level React component, and either recalculate the sizes there, or redraw the specific child component.
Set the load event handler on the componentDidMount to handle loading the cells into the react component to recalculate the proper sizes:
componentDidMount = () => {
this.$carousel = $(this.carousel)
window.addEventListener('load', this.componentLoaded)
}
Then in the componentLoaded method just do what you need to do.
A bit more late, but I have an approach which can be used without using the getElementById method. A class based component could be created and the sample code can be used.
constructor(props) {
super(props);
this.imageRef = React.createRef();
}
componentDidMount(){
this.imageRef.current.addEventListener("load", this.setSpans);
}
setSpans = () => {
//Here you get your image's height
console.log(this.imageRef.current.clientHeight);
};
render() {
const { description, urls } = this.props.image;
return (
<div>
<img ref={this.imageRef} alt={description} src={urls.regular} />
</div>
);
}
Above solutions are good. I thought I'd add my own that helped me solve this issue + others discussed in this question.
Since as others have said a timeout function is unpredictable and inline css with javascript variable dependencies (ex. style={{height: `calc(100vh - ${this.props.navHeight}px)`}}) can alter the height of elements after the componentDidMount method, there must be an update after all of the elements and inline javascript-computed css is executed.
I wasn't able to find very good information on which elements accept the onLoad attribute in React, but I knew the img element did. So I simply loaded a hidden image element at the bottom of my react component. I used the onLoad to update the heights of referenced components elsewhere to yield the correct results. I hope this helps someone else.
_setsectionheights = () => {
this.setState({
sectionHeights: [
this.first.clientHeight,
this.second.clientHeight,
this.third.clientHeight,
]
});
}
render() {
return (
<>
<section
ref={ (elem) => { this.first = elem } }
style={{height: `calc(100vh - ${this.props.navHeight}px)`}}
>
...
</section>
...
<img style={{display: "none"}} src={..} onLoad={this._setsectionheights}/>
</>
);
}
For the sake of being thorough, the issue is that when the componentDidMount method is executed, it only considers external css (speculation here). Therefore, my section elements (which are set to min-height: 400px in external css) each returned 400 when referenced with the clientHeight value. The img simply updates the section heights in the state once everything before it has loaded.
I'd rather do it in componentDidUpdate, but by making sure a condition is met to prevent an infinite loop:
componentDidUpdate(prevProps, prevState) {
const row = document.getElementById('yourId');
const height = row.clientHeight;
if (this.state.height !== height) {
this.setState({ height });
}
}