Pass reference of a component to another one in ReactJS - javascript

Before anyone press eagerly the close button, I already have looked the following question: ReactJS Two components communicating. My problem is exactly the third scenario developped in the current accepted answer.
I am using ReactJS to build something with two components. For HTML reasons (and presentation), i want my two components to be at two different places of the page.
For the moment, I have the following pattern, corresponding to scenario #2:
FooForm = React.createClass({
...
});
FooList = React.createClass({
...
});
FooManager = React.createClass({
...
render: function () {
return (
<div>
<FooForm ref="form" manager={this} />
<FooList ref="list" />
</div>
);
}
});
React.render(
<FooManager someProp={value} />,
document.getElementById('foo')
);
This gives something like:
<div id="foo">
<form>Form generated with the render of FooForm</form>
<ul>List generated with the render of FooList</ul>
</div>
However, i would like to have something like this:
<div id="fooform">
<form>Form generated with the render of FooForm</form>
</div>
<!-- Some HTML + other controls. Whatever I want in fact -->
<div>...</div>
<div id="foolist">
<ul>List generated with the render of FooList</ul>
</div>
The problem here is: how can I keep a reference in each component? Or at least the link Form -> List?
I tried to create the FooList before and pass the reference to the current manager, but I get the following warning/error:
Error: Invariant Violation: addComponentAsRefTo(...): Only a ReactOwner can have refs. This usually means that you're trying to add a ref to a component that doesn't have an owner (that is, was not created inside of another component's `render` method). Try rendering this component inside of a new top-level component which will hold the ref.
The documentation says you can attach events to link two components which do not have a parent-child relation. But I don't see how. Can someone give me some pointers?

The Less Simple Communication lesson from react-training has a good example of how you can move actions & state sideways to avoid having to create an explicit link between related components.
You don't need to jump into a full Flux implementation to get the benefit of this approach, but it's a good example to lead you up to Flux, should you eventually need it or something like it.
Note that this requires you to model the relationship between the components based on changing state rather than explicitly passing a reference to a component instance (as you're doing above) or a callback bound to the component managing the state.

This would be the perfect use-case for a Flux type architecture.
What you want is someone FooManager to be able to trigger state changes in both components. Or, in fact, having the different components trigger, through Actions, state changes in each other.
The Flux Todo-App Tutorial illustrates your use-case perfectly!
After this, then you'd have the choices of using Facebooks implementation of Flux or the other gazillion ones.
My personal favorite is Reflux

Related

react js trigger function from any other component

I have a component that basically loads a video in an overlay using JW Player (simplified example below).
import React, { Component } from 'react'
import ReactJWPlayer from 'react-jw-player'
class VideoPopup extends Component {
render() {
return (
<div id="video">
<ReactJWPlayer
playerId='video-player'
playerScript='https://content.jwplatform.com/libraries/vr6ybmGf.js'
file='path to video file'
/>
</div>
)
}
}
export default VideoPopup;
I would like the component to sit directly in the root of my app, but I need to be able to display it when called from ANY other component - this might be a child component, a child of a child, a sibling etc. etc. I was hoping to be able to call it and pass the video file reference simply like below:
<button onClick={somehow show video popup}>show video popup</button>
I understand how to do this easily if there is a direct parent-child relationship, but not if I want to place the link in a variety of different components; I hope someone can point me in the right direction.
If you want to get rid of the parent-children relationships when it comes to actions, use an event manager like Redux (react-redux). It is pretty standard an becomes necessary as your app grows anyway.
The principle is that wherever you place your link, it will fire an "action" on click, which is sent to a global dispatcher that other components listen to for changes.
Multiple ways to do this
You can define a function that controls the show/hide functionality
of the video player in the app component itself and pass it down as a
prop to all the components where the event can be fired.
Use Redux. This is the ideal choice. You just have to dispatch an action from anywhere in your app and the corresponding reducer will take care of the functionality.
Using a global function (not recommended).
Please comment if you need more explanation.
You can try to make global function and call it wherever you want.
showVideoPopup () {
ReactDOM.render(
<VideoPopup />,
document.getElementById('popupHolder')
);
}

How to make a React.js inline portal for a jQuery Datepicker

I've watched,
http://youtu.be/z5e7kWSHWTg?t=15m17s
and read,
https://github.com/ryanflorence/react-training/blob/gh-pages/lessons/05-wrapping-dom-libs.md
https://github.com/ryanflorence/react-training/tree/gh-pages/code/Dialog
http://jsbin.com/dutuqimawo/edit?js,output
How to create a React Modal(which is append to `<body>`) with transitions?
and I get the concept of the Portal, that you're tricking React into ceasing its rendering for one piece of the DOM, then continuing the rendering afterward, so you can tinker with that piece of the DOM without confusing React by making its virtual DOM get out of sync.
My problem is that the examples all address a Dialog that is rendered at the end of the page, but appears inline when you're reviewing your code. It's a cool trick for using a jQuery modal, but I need a jQuery datepicker whose div actually remains where I put it. (As an aside, I'm also curious about GetDOMNode's presence in the examples when it's deprecated? I suppose you use FindDOMNode, although you call it slightly differently, plus the documentation says "In most cases, use of this escape hatch is discouraged because it pierces the component abstraction", which makes me a little gunshy to use it.)
To isolate the jQuery datepicker from React, I originally created one React component to handle everything above the datepicker, and another to handle everything below the datepicker, and used event listeners in each component to listen for updates. However, I prefer the design of a single parent component that passes everything down to its children; it seems like a cleaner design.
I redesigned it with a single parent and it seems to work, but I have no idea if my portal is really isolated from React's virtual DOM or not; it's my first crack at a portal so I'm really muddling through. (I am using React-Bootstrap for my navbar and it works great; I just couldn't find an equivalent to jQuery's datepicker and I like how it looks and operates, which is why I'm sticking with it.)
Here's my top-level component (I removed the props/componentDidMount/etc for clarity). The <CalendarControl /> is going to be the portal:
var ReactApp = React.createClass({
render: function() {
return (
<div>
<BootstrapNavbar division={this.state.division} dps={this.state.dps} sections={this.state.sections} />
<div className="container">
<br />
<br />
<br />
<div className="row">
<div className="col-md-4" id="calendarPortal">
<CalendarControl />
</div>
<div className="col-md-8">
<h3>{this.state.dp}</h3>
<h4>{this.state.dpStartDate} - {this.state.dpEndDate}</h4>
</div>
</div>
<TimebookTableRecords timebookRecords={this.state.timebookRecs} />
</div>
</div>
);
}
});
Here's the code for my CalendarControl portal. When the CalendarControl mounts, I'm creating a new div calendarControl as a child of calendarPortal. I then use jQuery to create the datepicker on the calendarControl div.
var CalendarControl = React.createClass({
render: function () {
return null;
},
componentDidMount() {
var portalLocation = document.getElementById("calendarPortal");
var newElement = document.createElement('div');
newElement.id = "calendarControl";
portalLocation.appendChild(newElement);
},
componentWillUnmount() {
var portalLocation = document.getElementById("calendarPortal");
document.body.removeChild(portalLocation);
},
});
Here's the jQuery code that creates a datepicker on the calendarControl div:
$("#calendarControl").datepicker({
numberOfMonths: monthDiff,
defaultDate: dpStartDate,
showButtonPanel: false,
beforeShowDay: formatCalendarDays, //formatter function
onSelect: dateClicked //handles click on calendar date
The final product seems to work fine, and doesn't generate any "the DOM was unexpectedly mutated" errors like when you manipulate part of the DOM that's under React's purview. I can update the state of the parent and see the changes propagate down nicely, and use jQuery to update the calendar.
However, I just don't know if this is the correct approach? That is to say, have I achieved a true portal here? I used the Google Chrome React Developer Tools add-in to inspect the component hierarchy, and it does look like from React's perspective there's a null in the CalendarControl div:
Thanks for bearing with me in this lengthy post. I have to say that so far I'm really loving the React approach to web development; it's so radically different that it took a number of readings and tinkering just to understand its concepts, but now it seems so much more elegant than the ways I've done it in the past.
From my understanding of portals, you are doing this mostly correct. But if it had any other children, you would have to reconnect with react after the jquery stuff, but I assume that is not the case here.
The reason you are seeing a "null" inside calendar control is because you return a null in your CalendarControl render function.
Why don't you just change your render function in calendarControl to:
render: function () {
return (
<div id="calendarControl"></div>
)
and do all your funky jQuery rendering inside componentDidMount function?

Having trouble building a simple audio player in React using HTML5

I've recently started learning React and I'm trying to build a simple audio player. I'm currently using this example as a reference but it's built in one file
https://github.com/CezarLuiz0/react-cl-audio-player
The one I'm trying to make is done in a "React" way where the UI has reusable components but I'm having trouble separating my code into meaningful and working components. For example, if I try to move some of the rendering code from the parent component (AudioPlayer) into (PlayButton), the audio methods that is created on the mounting of the parent component suddenly becomes inaccessible to the child components.
Here is my code repo.
https://github.com/vincentchin/reactmusicplayer
It works now but I'd like to improve it. Also it'd be great if someone can point out huge flaws in this since I'm sure I've broken some rules or standards to coding in React.
You can access parent component's methods from a child component by passing the method as a prop, and then invoking it inside the child component.
For example (in the child component's render method):
<button onClick={this.props.methodFromTheParent}>Click me</button>
You can also pass arguments to these methods:
<button onClick={this.props.methodFromTheParent.bind(null, 'Hello')}>Click me</button>
Remember to pass in null instead of this as the first argument when binding values to a method belonging to a parent component.
I skimmed through your repo as well. You could clean up the AudioPlayer component a lot by putting the different elements into their own components.
The render method could look something like this:
render() {
return (
<div>
<PlayButton onClick={this.togglePlay} playing={this.state.playing} />
{!this.state.hidePlayer ?
(<Player
playerState={this.state}
togglePlay={this.togglePlay}
setProgress={this.setProgress}
...
/>) : null}
</div>
);
}
And then inside the newly-created Player component:
render() {
var pState = this.props.playerState; // Just to make this more readable
return (
<div className="player">
<PlayButton onClick={this.props.togglePlay} playing={pState.playing} />
<Timeline
currentTimeDisplay={pState.currentTimeDisplay}
setProgress={this.props.setProgress}
progress={pState.progress}
...
/>
<VolumeContainer
onMouseLeave={this.props.noShow}
setVolume={this.setVolume}
toggleMute={this.toggleMute}
...
/>
</div>
);
}
You can break the layout into as many nested components as is needed and makes sense.
Remember to actually add the onClick attribute inside the child components as well (<button onClick={this.props.onClick}>Play</button>).

How to not re-render React component's specific DOM elements?

Recently I started to refactor my Backbone web app with React and I'm trying to write interactive graph visualization component using react and sigma.js.
I roughly understood React's declarative paradigm and how it is implemented by render() method using jsx syntax.
But what gets me stumbled is a situation where I cannot define my React component declarativly.
It is because of the javascript-generated DOM elements, which only can be generated on componentDidMount() after the declarative DOM elements are rendered by render().
It makes me worried about both performance and buggy animations (my graph animates on instantiation time, which will be re-played on every render() calls in this situation)
My current code looks like:
...
render: function() {
return (
<div class="my-graph-visualization-component">
{/*
This div is defined declaratively, so won't be re-rendered
on every `change` events* unless `React`'s diff algorithm
think it needs to be re-rendered.
*/}
<div class="my-declarative-div">{this.props.text}</div>
{/*
This div will be filled by javascript after the `render()`
call. So it will be always cleared and re-rendered on every
`change` events.
*/}
<div class="graph-container MY-PROBLEM-DIV"></div>
</div>
);
},
componentDidMount: function() {
this.props.sigmaInstance.render('.graph-container', this.props.graph);
}
...
Is there any way to do something like
render: function() {
return (
<div class="my-graph-visualization-component">
<div class="my-declarative-div">{this.props.text}</div>
{/*
Any nice workaround to tell react not to re-render specific
DOM elements?
*/}
<div class="graph-container NO-RE-RENDER"></div>
</div>
);
},
so that my sigma.js graph component won't get re-instantiated with identical starting animation on every change on states?
Since it seems to be it is about handling non-declarative part of react components, any workarounds for this kind of problem will be appreciated.
The cleanest way is to define react sub-components and re-render what you really need instead of re-rendering the whole block
render: function() {
return (
<div class='myStaticContainerNotupdated'>
<SubComponentToUpdateOften/>
<MyGraph/>
</div>
)
}
The other solution could be to work on your graph and implement a singleton so your animation is only played once at the first render.
But really the easiest and cleanest thing I see is to create clean separate subcomponent and update them when needed. You never update the big container component just the subs one.
Hope it helps
You can use dangerouslySetInnerHTML. This basically tells React to stay away from it’s content and it wont evaluate/update it when doing it’s DOM diffing.

Re-rendering a ReactJS component hangs browser

I've been experimenting with creating a component based UI using ReactJS, versus my usual slapdash approach of a million global functions, variables and non-reusable markup. So far I really like React but I've hit a stumbling block.
Consider the following component layout
EventView
EventViewSidebar
EventViewList
EventViewListRow
EventViewDetail
In this layout, multiple occurrences of EventViewListRow are present for each unique key. Clicking an instance of EventViewListRow should update EventViewDetail with the details of that item.
This is the render function for the top level EventView component:
render: function () {
return (
<div className="event-view row-fluid">
<div className="event-view__sidebar col-md-4">
<EventViewSidebar projectId={this.state.projectId} />
</div>
<div className="event-view__content col-md-8" id="eventDetail">
</div>
</div>
);
}
And this is the EventViewDetail component
var EventViewDetail = React.createClass({
getInitialState: function () {
return { eventId: 0 };
},
render: function () {
if (this.state.eventId === 0) {
return (<h3>Nothing selected</h3>);
}
else {
return (
<div>
{this.state.eventId}
</div>
);
}
}
});
For the updating of EventViewDetail when a EventViewListRow is clicked, I have the following event handler defined in EventViewListRow
handleClick: function (event) {
event.preventDefault();
React.render(
React.createElement(EventViewDetail, { eventId: this.props.id }),
document.getElementById("eventDetail")
).setState({ eventId: this.props.id });
},
This all seems to be working fine (with the exception of the setState call above which I had to add otherwise clicking a different EventViewListRow didn't seem to have any effect - no doubt that's my first problem). The actual critical problem is that if I add default html to the eventDetail div defined in EventView then when I click the link in EventViewListRow, the following message is displayed in the console and the browser hangs.
Warning: React attempted to reuse markup in a container but the checksum was invalid. This generally means that you are using server rendering and the markup generated on the server was not what the client was expecting. React injected new markup to compensate which works but you have lost many of the benefits of server rendering. Instead, figure out why the markup being generated is different on the client or server:
(client) <h3 data-reactid=".0">Nothing selected
(server) <h3 data-reactid=".0.1.0">Select an even
Once the browser tab (Chrome 43) has hung, I have to terminate it using Task Manager.
Originally, I was calling an instance of the EventViewDetail directly, for example
<div className="event-view__content col-md-8" id="eventDetail">
<EventViewDetail />
</div>
but it also hangs if I just use vanilla HTML
<div className="event-view__content col-md-8" id="eventDetail">
<h3>Select an event to view</h3>
</div>
Clearly I'm doing something very wrong, but I'm somewhat unfamiliar with React so I don't know what that is. I read that I'm suppose to have state on the top level EventView component, but I don't have access to that and React doesn't seem to offer the ability to go back up the component chain. Unless you are supposed to pass the EventView instance as a property to each child component?
Oh, I should also add - I also tried removing the setState call from the EventViewListRow click handler in case that was the cause, but it had no effect.
Can anyone offer any advice on what it is I'm doing wrong. Should EventView have all the state for the child components, and if so, how do I reference the parent from a nested child component - do I have to pass the instance of EventView as a prop to every single child?
Sorry if these are idiot questions!
You should not call React.render in the handleClick function. Just call this.setState and React will automatically render again.

Categories

Resources