State Affecting Multiple Components in React with Baobab - javascript

I'm in the process of translating a 90% complete website produced in HTML, CSS and jQuery into a more forward thinking react "application". I have some pretty basic principals that I'm struggling to get to grips with - I really like react's JSX language but and struggling to deal with heavy UI manipulation that I've achieve in jQuery previously.
To get me used to the concepts I'm starting with one of the most simple interactions on my website - Hover over the menu button and bring out the side menu partially (e.g. 20px). My components are structured like so:
var App = React.createClass({
render: function() {
return (
<div id="reactWrap">
<MainNav ref="main-nav"/>
<SideNav projects={PROJECTS} ref="side-nav" />
<RouteHandler projects={PROJECTS} ref="content" />
</div>
)
}
});
MainNav includes the 'hamburger' button and the site title:
render: function() {
return (
<nav className="fixed-nav">
<div id="menu-button" onMouseEnter={this.teaseMenu} onMouseLeave={this.unteaseMenu} ref="menu-btn">
<span className="menu-line"></span>
</div>
<span className="site-title">Lorem Ipsum</span>
</nav>
)
}
When "#menu-button" is hovered over I'm changing the state tree that I have defined in Baobab inside "teaseMenu()" using "menuActions.isHovering()".
....
teaseMenu: function(e) {
menuActions.isHovering();
// other stuff done
},....
What I'm struggling with is that once the change has been affected in my stateTree, I'm unsure how to then give the knowledge of this change to all of the other elements that rely on it. For example, how does the SideNav which is totally unrelated to "MainNav" and it's child "#menu-button" become aware of this change and alter it's state accordingly? This would simply be solved in jQuery with something like the following:
globalVars.menuBtn.on({
mouseenter: function(e) {
var self = $(this);
var movePercentage = (-100 + (20 / globalVars.sideNavW * 100))/2;
if (!pageStatus.menuActive) {
globalVars.wrap.addClass('menu-hover');
globalVars.sideNav.css({
'left': movePercentage + '%'
});
menuBtnIn(e, self);
}
},....

Flux is a great way to do this, and I highly recommend you incorporate it in to your site as it'll make a lot of things easier. Have a read of this page for more information: https://facebook.github.io/flux/docs/overview.html
However you could also use state in your root App to affect change on MainNav whenever something occurs in SideNav.
Consider this change to your root app component:
var App = React.createClass({
getInitialState: function() {
return {
sideNavShowSomething: false
}
},
mainNavChangeHandler: function(sideNavShowSomething) {
this.setState({
sideNavShowSomething: sideNavShowSomething
})
},
render: function() {
return (
<div id="reactWrap">
<MainNav ref="main-nav" onChange={this.mainNavChangeHandler} />
<SideNav projects={PROJECTS} ref="side-nav" showSomething={this.state.sideNavShowSomething} />
<RouteHandler projects={PROJECTS} ref="content" />
</div>
)
}
});
The above is an example of how you can use the state of your root App to affect change on it's children. Your MainNav now takes a prop of onChange which is a function. This function is called by MainNav any time a change occurs that you want to notify your root App of. In this example case, the mainNavChangeHandler is executed with a boolean sideNavShowSomething variable. In your case you probably want to do something more complex ;)
So when you call this.props.onChange(true) in your MainNav then your root app will update it's state and SideNav will then receive this.props.showSomething as true wheras previously it was false. And by so doing you can affect change between child components by utilizing callbacks to the root App and handling new props to give children from them.

Related

Vue.JS - listen to click on component

I'm fairly new to Vue.JS and currently having an issue listening to a click event on a component.
JS:
Vue.component('photo-option', {
data: function () {
return {
count: 0
}
},
props: ['src'],
template: `
<img :src=src v-on:click="$emit('my-event')" />
`
});
HTML:
<photo-option :src=uri v-for='uri in aboutUsPhotos' v-on:my-event="foo" />
...where foo is a method on my main Vue instance.
The above is based on the Vue.JS docs for handling component events, and I can't see what I'm doing wrong. The alert doesn't fire, and there's no errors in the console.
Before I found those docs, I also tried simply adding v-on:click='...' to both the JS (i.e. the template) and the HTML, each with no success.
What am I doing wrong?
[EDIT]
This is happening because the code is picked up by a lightbox script and has its DOM position changed. So presumably the binding/event attachment is being lost.
Does Vue have any way of allowing for this, perhaps by 'reinitialising' itself on an element, or something?

Responding to a click on a Vue element programmatically

Essentially, I want my Vue instance to respond to a click on an uploaded thumbnail.
I'm using the FineUploader Vue package with the template layout per the docs (see end of the question). Upon uploading an image, a tree like this is outputted:
<Root>
<Gallery>
<Thumbnail>
</Gallery>
</Root>
Coming from a jQuery background I really have no idea about the 'correct' way to go about this given that the Thumbnail Template is defined by the package already, and so I'm not creating my own Thumbnail template. I know that I can access elements like this:
let thumb = this.$el.querySelector('.vue-fine-uploader-thumbnail');
And perhaps a listener
thumb.addEventListener('click', function() {
alert('I got clicked');
});
But dealing with the Vue instance being re-rendered etc. I'm not familiar with.
Vue Template:
<template>
<Gallery :uploader="uploader" />
</template>
<script>
import FineUploaderTraditional from 'fine-uploader-wrappers'
import Gallery from 'vue-fineuploader/gallery'
export default {
components: {
Gallery
},
data () {
const uploader = new FineUploaderTraditional({
options: {/*snip*/}
})
return {
uploader
}
}
}
</script>
In order to respond to click events you add the v-on:click (or it's short form: #click) to whatever tag you want.
If you have elements that are nested that respond to the click event you might experience that a click on a child triggers a parents click event. To prevent this you add #click.stop instead, so that it doesn't trigger the parents click.
So you would have something along the lines of:
<Gallery #click="myFunction" />

How independent can a component be before it becomes an anti-pattern?

I understand that React apps should follow the following principle:
Data flows down, events bubble up
However, I find it quite cumbersome to send events like mouse-clicks from child components all the way up to the root and then send the new state all the way back down. I understand this method makes sense if multiple components are affected by the new state - but consider the following example:
var PSMenu = React.createClass({
getInitialState: function() {
return {
isOpen: false
}
},
handleMenuClick: function(){
console.log("hello worldd");
this.setState({isOpen: !this.state.isOpen});
},
render: function() {
return (
<div className={this.state.isOpen ? "ps-menu ps-menu-open" : "ps-menu"}>
<div className="ps-menu-button" onClick={this.handleMenuClick}>
</div>
</div>
)
}
});
Here, I have a menu class that has a button sticking out. The button is a toggle for opening and closing the menu. Is it okay that I handle the onClick of the ps-menu-button div within the PSMenu component? The root component has no idea this occurred but it doesn't matter since it shouldn't care.
If this example does indeed follow the React paradigm, consider the following. What if the div ps-menu-button was not a regular div but a React component called PSMenuButton nested inside PSMenu. Can I have just the PSMenu and PSMenuButton talk between each other without the parent knowing?
Thanks for any insight.

Meteor and React: Correct way to manipulate elements on click?

I'm learning application development with Meteor and React and have run into a hurdle. I want users to be able to click on an element and have that change the class of another element. If I was creating a site without Meteor or React I would use a jQuery function like this:
$("#button").click(function(){
$("#target").removeClass("hidden");
});
I can't seem to figure out how to use jQuery in my React application (but copying the code into chrome web console works) so I started googling and found that it isn't recommended to use jQuery or to directly manipulate the DOM at all while using React. I don't understand much of how React's virtual DOM works at this stage.
So my question is: what is the correct way to replicate what this jQuery code does in a React application?
I recommend You to combine classnames (link) and state, so You can do it like this:
class Example extends React.Component {
constructor() {
super();
this.state = {
clicked: false
};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState({ clicked: true });
}
render() {
return (
<div className={classNames('foo', { hidden : this.state.clicked })}>
<button onClick={this.handleClick} >BUTTON</button>
</div>
);
}
}
ReactDOM.render(
<Example />,
document.getElementById('example')
);
If state clicked is false, class hidden is not active on a specific element.

Update React component when state changes, using a reference to itself

In a React component, I define how the component handles state change in a callback to setState(). This seems wrong / against flux convention. Where is the correct place to define how an element should behave given a new state?
I'm assuming it's in the render() method or the didMount() method.
In this situation, I need to call a JS method on the DOM element:
if (this.state.play) {
document.querySelector(".VideoPlayer-Video").play();
} else {
document.querySelect(".VideoPlayer-Video").pause();
}
I can't do this before the component has rendered. So how & where should this be done?
Example Details:
This is a very simple react component. It has a <video> element, and a button that pauses or plays it.
The state only has one attribute, "play", which is "true" if the video is playing or false if the video is paused.
When the "play" button is clicked, I flip the state, then do a DOM query to get the video element, and call a method on it.
This seems unconventional in React, since I am telling the component how to respond to a state change inside of a click handler. I would expect to define how the component responds to state change elsewhere, but I'm not sure where.
Where is the conventional place to tell the video element to play or not in response to state change? How can I reference the DOM element there?
Code:
var React = require('react');
module.exports = React.createClass({
getInitialState: function(){
return {
play: true
}
},
render: function(){
return <div className="VideoPlayer">
<video className="VideoPlayer-Video" src="/videos/my-video.mov"></video>
<button className="VideoPlayer-PlayButton" onClick={this.handlePlayButtonClick}>
{this.state.play ? "Pause" : "Play"}
</button>
</div>
},
handlePlayButtonClick: function(){
this.setState({
play: !this.state.play
}), function(){
var video = document.querySelector(".VideoPlayer-Video");
if (this.state.play) {
video.play();
} else {
video.pause();
}
}
}
});
Using componentDidUpdate seems appropriate.
1.Click
2. Change state -> trigger - rerender
3. Just after your component is mounted call the right function for video
componentDidUpdate: function() {
if (this.state.play) {
this.refs.MyVideo.play();
} else {
this.refs.MyVideo.pause();
}
}
If you need to control those function for the very first rendering use componentDidMount.
Simple and clean in my opinion.
Edit: I edited my code using ref, I think this is indeed the right way to go
https://facebook.github.io/react/docs/more-about-refs.html
Just place a ref in your video component:
<video ref="MyVideo"> </video>
Updated after mark's comment
Flux is good consideration for long term components, yes it's very big and requires some breaking changes, as François Richard noticed. But still worth it.
If you have a big component, then here's some tips:
You change state of that video component to playing, ONLY when video component receives $event, otherwise it doesn't make sense, as flux heavily uses nodejs eventEmitter, note video components must be explicitly separated, so when you expand your functionality it won't hurt your component.
Simple demo repo.
Methods performed on the DOM should be called within componentDidUpdate, as it is called once react has finished its updates to the DOM.

Categories

Resources