React state toggle render not persisting - javascript

I am rendering my component when a link in my footer is clicked, however the element is almost immediately re-rendered away.
class Footer extends Component {
constructor(props) {
super(props);
this.state = {
privacyVisibile: false
};
this.togglePrivacyVisible = this.togglePrivacyVisible.bind(this)
}
togglePrivacyVisible = () => {
const { privacyVisibile } = this.state;
this.setState({ privacyVisibile : !privacyVisibile })
}
render() {
return (
<div>
{this.state.privacyVisibile && <Privacy />}
<ul className="footer-menu">
<li>About</li>
<li onClick= {this.togglePrivacyVisible}>Privacy</li>
</ul>
</div>
)
}
}
export default Footer;
If I move the state rendering from above my UL to below it it doesn't render at all when I attempt to toggle.
I've seen suggestions to restart my app but that seems to have no effect, any other ideas what may be happening here?

When the link is clicked the browser navigates away from the page and attempts to load the privacy.html page. Try using a button instead of a link, no href:
<button onClick={this.togglePrivacyVisible}>Privacy</button>

You could prevent the default action of a click on an anchor tag, by using Event#preventDefault. Like:
togglePrivacyVisible = (event) => {
event.preventDefault();
const { privacyVisibile } = this.state;
this.setState({ privacyVisibile : !privacyVisibile })
}

I don’t think you need to use arrow function syntax for your function AND bind(this) in your constructor. Not sure if that causes odd behavior but you should use one or the other.

Related

Render unique divs for each hovered element

minimum reproducible example: https://codesandbox.io/s/react-hover-example-tu1eu?file=/index.js
I currently have a new element being rendered when either of 2 other elements are hovered over. But i would like to render different things based upon which element is hovered.
In the example below and in the codepen, there are 2 hoverable divs that are rendered; when they are hovered over, it changes the state and another div is rendered. I would like for the HoverMe2 div to render text "hello2". Currently, whether i hover hoverme1 or 2, they both just render the text "hello".
import React, { Component } from "react";
import { render } from "react-dom";
class HoverExample extends Component {
constructor(props) {
super(props);
this.handleMouseHover = this.handleMouseHover.bind(this);
this.state = {
isHovering: false
};
}
handleMouseHover() {
this.setState(this.toggleHoverState);
}
toggleHoverState(state) {
return {
isHovering: !state.isHovering
};
}
render() {
return (
<div>
<div
onMouseEnter={this.handleMouseHover}
onMouseLeave={this.handleMouseHover}
>
Hover Me
</div>
<div
onMouseEnter={this.handleMouseHover}
onMouseLeave={this.handleMouseHover}
>
Hover Me2
</div>
{this.state.isHovering && <div>hello</div>}
</div>
);
}
}
render(<HoverExample />, document.getElementById("root"));
You need to keep the state of item which you have hovered that's for sure
const { Component, useState, useEffect } = React;
class HoverExample extends Component {
constructor(props) {
super(props);
this.handleMouseHover = this.handleMouseHover.bind(this);
this.state = {
isHovering: false,
values: ['hello', 'hello2'],
value: 'hello'
};
}
handleMouseHover({target: {dataset: {id}}}) {
this.setState(state => {
return {
...state,
isHovering: !state.isHovering,
value: state.values[id]
};
});
}
render() {
return (
<div>
<div
data-id="0"
onMouseEnter={this.handleMouseHover}
onMouseLeave={this.handleMouseHover}
>
Hover Me
</div>
<div
data-id="1"
onMouseEnter={this.handleMouseHover}
onMouseLeave={this.handleMouseHover}
>
Hover Me2
</div>
{this.state.isHovering && <div>{this.state.value}</div>}
</div>
);
}
}
ReactDOM.render(
<HoverExample />,
document.getElementById('root')
);
<script src="https://unpkg.com/react/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone#6/babel.min.js"></script>
<div id="root"></div>
You can pass the context text as shown in example. This is working code:
import React, { Component } from "react";
import { render } from "react-dom";
// Drive this using some configuration. You can set based on your requirement.
export const HOVER_Hello1 = "Hello1";
export const HOVER_Hello2 = "Hello2";
class HoverExample extends Component {
constructor(props) {
super(props);
this.handleMouseHover = this.handleMouseHover.bind(this);
this.state = {
isHovering: false,
contextText: ""
};
}
handleMouseHover = (e, currentText) => {
this.setState({
isHovering: !this.state.isHovering,
contextText: currentText
});
}
toggleHoverState(state) {
//
}
render() {
return (
<div>
<div
onMouseEnter={e => this.handleMouseHover(e, HOVER_Hello1)}
onMouseLeave={e => this.handleMouseHover(e, HOVER_Hello1)}
>
Hover Me
</div>
<div
onMouseEnter={e => this.handleMouseHover(e, HOVER_Hello2)}
onMouseLeave={e => this.handleMouseHover(e, HOVER_Hello2)}
>
Hover Me2
</div>
{this.state.isHovering && <div>{this.state.contextText}</div>}
</div>
);
}
}
export default HoverExample;
If the whole point is about linking dynamically messages to JSX-element you're hovering, you may store that binding (e.g. within an object).
Upon rendering, you simply pass some anchor (e.g. id property of corresponding object) within a custom attribute (data-*), so that later on you may retrieve that, look up for the matching object, put linked message into state and render the message.
Following is a quick demo:
const { Component } = React,
{ render } = ReactDOM,
rootNode = document.getElementById('root')
const data = [
{id:0, text: 'Hover me', message: 'Thanks for hovering'},
{id:1, text: 'Hover me too', message: 'Great job'}
]
class HoverableDivs extends Component {
state = {
messageToShow: null
}
enterHandler = ({target:{dataset:{id:recordId}}}) => {
const {message} = this.props.data.find(({id}) => id == recordId)
this.setState({messageToShow: message})
}
leaveHandler = () => this.setState({messageToShow: null})
render(){
return (
<div>
{
this.props.data.map(({text,id}) => (
<div
key={id}
data-id={id}
onMouseEnter={this.enterHandler}
onMouseLeave={this.leaveHandler}
>
{text}
</div>
))
}
{
this.state.messageToShow && <div>{this.state.messageToShow}</div>
}
</div>
)
}
}
render (
<HoverableDivs {...{data}} />,
rootNode
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script><div id="root"></div>
As #CevaComic pointed out, you can do this with CSS. But if you want to use React, for example, because your actual problem is more complex, here is the answer.
You will need a way to tell apart the two elements. It could be done with some neat tricks, like setting an unique id to each element, passing a custom argument, or something else.
But I would advise against "cool tricks" as it's more difficult to understand what is going on, and the code is more prone to errors. I think the best way it to use a dumb approach of unique functions for unique elements.
Each onMouseEnter and onMouseLeave has to be an unique function (e.g. handleMouseHover1 and handleMouseHover2), and each of those functions need to control unique state (for example, isHovering1 and isHovering2). Then you have to render the element you want based on the state. Of course, for a real-world code, you will probably want to use more descriptive names to make the code more comprehensible. The full code would look something like this.
class HoverExample extends Component {
state = {
isHovering1: false,
isHovering2: false
};
handleMouseHover1 = () => {
this.setState(({ isHovering1 }) => ({ isHovering1: !isHovering1 }));
};
handleMouseHover2 = () => {
this.setState(({ isHovering2 }) => ({ isHovering2: !isHovering2 }));
};
render() {
const { isHovering1, isHovering2 } = this.state;
return (
<div>
<div
onMouseEnter={this.handleMouseHover1}
onMouseLeave={this.handleMouseHover1}
>
Hover Me1
</div>
<div
onMouseEnter={this.handleMouseHover2}
onMouseLeave={this.handleMouseHover2}
>
Hover Me2
</div>
{isHovering1 && <div>hello1</div>}
{isHovering2 && <div>hello2</div>}
</div>
);
}
}
Also, updated example: https://codesandbox.io/s/react-hover-example-rc3h0
Note: I have also edited the code to add some syntax sugar which exists with newer ECMAScript versions. Instead of binding the function, you can use the arrow function format, e.g. fn = () => { ... }. The arrow function means the this context is automatically bound to the function, so you don't have to do it manually. Also, you don't have to initialize this.state inside the constructor, you can define it as a class instance property. With those two things together, you do not need the constructor at all, and it makes the code a bit cleaner.

using a simple component (dumb) to create a list of buttons and use parent method

I'm trying to create a simple dashboard. I'm just exploring some new ideas I have in react and it's been so long I'm running into a strange problem I can't seem to understand.
I have a very simple class:
export default class Dashboard extends React.Component {
constructor(){
super();
}
HandleClick = (e) => {
if (e.name === "createEvent") {
console.log('event clicked');
}
console.log(e.name);
}
render() {
return(
<div className="row">
<ButtonList onClick={this.HandleClick}/>
</div>
)
}
}
and then I have a simple function outside of the class that creates a button list:
function ButtonList(props) {
return (
<button name="createEvent" onClick={props.HandleClick}>Create Event</button>
)
}
the idea behind this was instead of having so much stuff inside one superclass I wanted to separate simple functionality, like a button or command list if you will, that opon clicking would eventually change the state of the navbar.
I'm not sure how I would return that values of the button, or aside from that pass a parameter into the button from a child prop.
For example instead of doing HandleClick = (e) => and actually look for a parameter, how would I pass that in the child function where it gets used (if there were many more buttons)?
This is what you should be doing instead:
On your parent component, you can use arrow functions to pass the parameters within handleClick. This will allow you to listen to the events on your child ButtonList component with the parameters passed onto the method.
In addition, if you want to access to name attribute of your button, you should be calling event.target.name, as name is part of the target property of the Event interface.
export default class Dashboard extends React.Component {
constructor(){
super();
}
handleClick = (e) => {
if (e.target.name === "createEvent") {
console.log('event clicked');
}
console.log(e.target.name);
}
render() {
return(
<div className="row">
<ButtonList onClick={(e) => this.handleClick(e)} />
</div>
)
}
}
And on your ButtonList functional component, you should pass the onClick event to the onClick props which was defined as part of the ButtonList component.
function ButtonList(props) {
const onClick = (e) => {
props.onClick(e);
};
return (
<button name="createEvent" onClick={(e) => onClick(e)}>Create Event</button>
)
}
I have created a demo over here.

componentWillReceiveProps in Vue

I am new pretty new to Vue, and coming from a rather React-y suburb. I am rebuilding my SideNav ("drawer") component from the latter. There, when one clicked the button (not being related to the navigation per se), it setStateed this.state.toggle that was tied to appropriate
class thePage extends React.Component {
...
this.handleToggleClick = this.handleToggleClick.bind(this);
this.state ={
toggleState: false
};
}
// Slide out buttons event handlers
handleToggleClick(){
this.setState({
toggleState: !this.state.toggleState
})
}
render() {
const button = <a href="#" onClick={this.handleToggleClick}>here</a>
const isOpenWithButton = this.state.toggleState;
return (
<div>
{button}
<SideNav logo="logo.png" isOpenWithButton={isOpenWithButton}>
. . .
</SideNav>
</div>
);
}
}
export default SideNavPage;
the SideNav looks as follows:
class SideNav extends React.Component {
constructor(props){
super(props);
this.state = {
isThere: false,
showOverlay: false,
}
this.handleOverlayClick = this.handleOverlayClick.bind(this);
}
componentWillReceiveProps(NextProps) {
if (this.props.isOpenWithButton !== NextProps.isOpenWithButton) {
this.setState({
isThere: true,
showOverlay: true
})
}
}
handleOverlayClick(){
this.setState({
isThere: false,
showOverlay: false
});
}
render() {
const {
tag: Tag,
...
isOpenWithButton,
} = this.props;
let isThere = this.state.isThere;
let showOverlay = this.state.showOverlay;
const overlay = <div class="overlay" onClick={this.handleOverlayClick}></div>
const sidenav = (
<Tag>
<ul>
{logo &&
<li>
<div className="logo-wrapper">
<a href={href}>
<img src={logo} className="img-fluid flex-center d-block"/>
</a>
</div>
</li>
}
{children}
</ul>
</Tag>
);
return (
<div>
{isThere && sidenav}
{showOverlay && overlay}
</div>
);
}
}
export default SideNav;
So, as you can see, clicking the button causes the isOpenWithButton props to change, and whenever it happens (componentWillReceiveProps), the sidenav with overlay appear.
I did some work on porting it to Vue, but as it lacks this lifecycle hook I am stuck with props. I have a following problem: clicking the button opens the overlay, but as you close it with clicking in the overlay, the Boolean prop sent by button does not change, what necessitates clicking the button twice if the sidenav has been already open. I know I must be missing a vital part in Vue logic, I just cannot grasp which.
Using .sync modifier
What you are looking for is called in vue a .sync modifier.
When a child component mutates a prop that has .sync, the value change will be reflected in the parent.
With this you can achive what you described:
clicking the button opens the overlay, but as you close it with clicking in the overlay, the Boolean prop sent by button does not change
Using a centralised store - (like vuex)
The same could also be achieved if you have a centralised state/store, in this case both of your components could rely on that state property.
See state management on Vue documentation:
Large applications can often grow in complexity, due to multiple pieces of state scattered across many components and the interactions between them
You could simple toogle the same property, for example:
$store.commit('overlayToggle');

How to use props or state to render or not render 1 out of these 5 forms on click?

I'm looking for advice on how to render one out of five forms in React on the click on one of their five related buttons. I've got it working actually for one form, but I'm pretty sure this is not how things should be done in React.
There is a sidebar with 5 buttons and a content area that displays some content. One of the forms should appear in the content area after a click on their respective buttons in the sidebar. Only one form should be displayed at a time. Both the sidebar and the content area are functions inside of a class called GraphArea that renders them.
AddNodes (one of five buttons in the sidebar):
const AddNodes = ({ showAdd }) => (
<li role="presentation" className="list-inline">
<a href="" className="nav-link" onClick={showAdd}>
<i className="fa fa-plus" aria-hidden="true"/> Add
</a>
</li>
);
export default AddNodes;
The problem is, there are five forms and buttons... There's no way one could reasonably continue like this.
I'm using Apollo link state. All 5 five forms won't be rendered by default, so they've got a default value of false. The values are written to the cache. Apollo queries and mutates a default value in the cache to true on click.
It would mean a lot to me if you could show me the right way here. I'm missing that 'click' of how things work in React and this would be a great oppertunity for it. This is the sidebar's and content area's parent full component:
GraphArea: (parent)
class GraphArea extends Component {
render() {
const { updateEditGraph, editGraph: { mode } } = this.props;
const showAdd = (e) => {
updateEditGraph(
{
variables: {
index: 'mode',
value: 'addNode'
}
});
e.preventDefault()
};
const showLink = (e) => {
updateEditGraph(
{
variables: {
index: 'mode',
value: 'addLink'
}
});
e.preventDefault()
};
return (
<div className="item">
<GraphSidebar showAdd={showAdd} showLink={showLink}/>
<GraphContent/>
</div>
)
}
}
export default compose(
graphql(updateEditGraph, {name: 'updateEditGraph'}),
graphql(getEditGraph, {
props: ({data: {editGraph}}) => ({
editGraph
})
})
)(GraphArea);
GraphContent:
class GraphContent extends Component {
render() {
let content;
if (this.props.editGraph.mode === 'addNode') {
content = <AddNodesForm/>
} else if (this.props.editGraph.mode === 'addLink') {
content = <LinkNodesForm/>
} else {
content = null;
}
return (
<div className="content">
{content}
<Graph/>
</div>
);
}
}
export default compose(
graphql(getEditGraph, {
props: ({data: {editGraph}}) => ({
editGraph
})
})
)(GraphContent);
GraphSidebar:
const GraphSidebar = ({ showAdd, showLink }) => (
<div className="avatars">
<ul>
<AddNodes showAdd={showAdd} />
<SequenceNodes />
<EditNodes />
<LinkNodes showLink={showLink} />
<DeleteNodes />
</ul>
</div>
);
I will answer what should happen in principle.
You say you have a top level component GraphArea that renders both the component for selecting a form, and the container of the forms:
<div>
<GraphSidebar onClickName={handleClick}/>
<GraphContent/>
</div>
GraphSidebar and GraphContent are related in such a way that GraphSidebar changes the state that should be presented on GraphContent, that is they relate to the same state. Because of that their common parent (GraphArea) should hold that state. You then pass the state to the GraphContent which contains the forms.
Make GraphArea a class:
class GraphArea extends React.Component {
constructor(){
super();
this.state = {formId: 1};
}
handleClick(formId) {
this.setState({fromId: formId});
}
render(){
return (
<div>
<GraphSidebar onClickName={handleClick}/>
<GraphContent formId={this.state.formId}/>
</div>
)
}
}
Also I think you say GraphContent doesn't directly render the forms but renders some other component that renders the form. You pass formId as property through the nested components all the way to the component that selects which form to render. For example if GraphContent renders Graph component that renders (selects) the forms you do like this:
<div className="content">
<Graph formId={this.props.formId}/>
</div>
or just
<div className="content">
<Graph formId={formId}/>
</div>
if GraphContent is not a class but a function like
GraphContent = ({formId}) => {
....
}
Let's say Graph is finally the component that renders (selects) the forms then you can use:
Graph = ({formId}) => {
if (formId === 1) {
return(<div>...code for form1</div>)
} else if (formId === 2) {
return(<div>...code for form2</div>)
} ...
}

Delay in Rendering a Component?

I'm attempting to make my own personal website, and trying to use React to do so. In the process, I intend to make each section a different React Component. My plan is to have the navbar at the top be able to select which component is currently "active", and actually gets rendered and shown. In addition, when switching to a new section, I would like the old component to have a "leaving" animation, and the new component to have an "entering" animation (these are done with react-motion). However, currently both the entering and leaving are done at the same time, because I'm changing the active state for both components at the same time. Is there any way to delay one component becomes active after another one becoming inactive?
The parent component that houses each section looks like so:
class Website extends React.Component {
constructor(props){
super(props)
this.state = {
homeActive: true,
aboutActive: false
}
homeActivator(){
this.setState({
homeActive: true,
aboutActive: false
})
}
aboutActivator(){
this.setState({
homeActive: false,
aboutActive: true
})
}
render(){
return (
<div>
<NavBar handleHome={this.homeActivator.bind(this)} handleAbout=
{this.aboutActivator.bind(this)}/>
<Home active={this.state.homeActive} />
<About active={this.state.aboutActive} />
</div>
}
And then one of the "sections" would look like so:
class Home extends React.Component{
render() {
let content = (
<div>
Home
</div>
)
if (!this.props.active){
return (
//Some jsx that results in the content leaving the page
)
}
return(
//Some jsx that results in the content entering the page
)
}
}
I did not have a ton of time to answer this, but came up with the best example I could. It's not an exact replica of what you are looking to do, but is very similar, so if you understand it, you will be able to figure out your problem quite easily.
To make things a little easier to understand, I am mimicking components with methods placed inside the React Class. Obviously in the real world, you would be importing your components from other files. I'm sure you'll understand what's going on.
export default class Example extends Component {
constructor(props) {
super(props)
this.state = {
c1: true,
c2: false
}
}
// Component One
renderc1() {
return (
<div>
I am component one
</div>
)
}
// Component Two
renderc2() {
return (
<div>
I am component two
</div>
)
}
changeComponents = () => {
this.setState({ c1: false })
setTimeout(() => {
this.setState({ c2: true })
}, 1500)
}
render() {
return (
<div className="example">
{this.state.c1 ? this.renderc1() : null}
{this.state.c2 ? this.renderc2() : null}
<button onClick={this.changeComponents}>Click me</button>
</div>
)
}
}
Clicking the button will fire off the changeComponents function, which will then immediately set the state of "c1" to false. A setTimeout after that ensures that component 2 will be delayed rendering to the screen.
Notice the arrow syntax, I used, which binds the this keyword to the class, so you don't have to worry about writing bind this everywhere.

Categories

Resources