I am building a web application using ReactJS and Meteor. I am using the method of structuring my application where components are split into separate Meteor packages.
For example. I have a component which renders a tabular menu (using the semantic-ui tab module) and initializes the tabs, then each tab is its own React component.
How would I be able to access the DOM in one component in another component.
Example:
Component = React.createClass({
componentDidMount() {
$('.menu .item').tab({
onVisible: () => {
// I need to call a function here, which is defined in OtherComponent,
// but this jQuery won't run in OtherComponent
}
})
},
render() {
return (
<div className="ui tabular menu">
<div className="active item" data-tab="tab-1">Tab 1</div>
<div className="item" data-tab="tab-2">Tab 2</div>
</div>
/* more code */
<div className="ui active tab" data-tab="tab-1"></div>
<div className="ui tab" data-tab="tab-2"></div>
)
}
}
OtherComponentInDifferentPackage = React.createClass({
componentDiMount() {
$('.menu .item').tab({
onVisible: () => {
// this won't work....
}
})
}
})
You'd need to have a parent component that holds state and can pass that as props to both components. In react you can also pass functions as props, in this case you could pass a function from the parent to the children that will trigger a state change in the parent. To change the state of parent you would call this.props.toggleVisible() from the child.
// Parent Component
toggleVisible() {
this.setState({
visible: !this.state.visible
});
}
getInitialState() {
return {
visible: false
}
}
render() {
return (
<div>
<Component
visible={this.state.visible}
toggleVisible={this.toggleVisible}
/>
<OtherComponentInDifferentPackage
visible={this.state.visible}
toggleVisible={this.toggleVisible}
/>
</div>
);
}
// Child Component
componentWillReceiveProps(nextProps) {
if (nextProps.visible !== this.props.visible) {
// do something
}
}
Related
I would like to make a draggable split panel for an editor. Its behavior is mainly like Console panel of CodeSandbox:
When we click on Console, the panel is expanded, and the arrow becomes ArrowDown for closing.
The border of the panel is dragabble.
When we click on Console on an expanded panel, the panel is closed, and the arrow becomes ArrowUp for expanding.
I have the following code (https://codesandbox.io/s/reset-forked-ydhy97?file=/src/App.js:0-927) by https://github.com/johnwalley/allotment. The problem is that the prop preferredSize does not change following this.state.toExpand.
Does anyone know why this does not work?
import React from "react";
import { Allotment } from "allotment";
import "allotment/dist/style.css";
import styles from "./App.module.css";
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
toExpand: true
};
}
render() {
return (
<div>
<div className={styles.container}>
<Allotment vertical>
<Allotment.Pane>Main Area</Allotment.Pane>
<Allotment.Pane preferredSize={this.state.toExpand ? "0%" : "50%"}>
<div
onClick={() => {
this.setState({ toExpand: !this.state.toExpand });
}}
>
Console
{this.state.toExpand ? "ArrowUp" : "ArrowDown"}
</div>
</Allotment.Pane>
</Allotment>
</div>
</div>
);
}
}
The problem is that the prop preferredSize does not change following this.state.toExpand.
This is not the problem, it does change, however, the documentation states:
Allotment will attempt to use this size when adding this pane (including on initial mount) as well as when a user double clicks a sash, or the reset method is called on the Allotment instance.
It is not configured to update when the prop is changed, however, if you double click on the border after setting it to ArrowDown, it will reset to 50%.
Instead, if you add a reference to the Allotment element by first initializing a reference in the constructor:
constructor(props) {
super(props);
this.allotment = React.createRef();
this.state = {
toExpand: true
};
}
And assigning it as a prop:
<Allotment vertical ref={this.allotment}>
Then you can add a callback to the setState for when you change the expand option that calls the reset function:
resetAllotment() {
if (this.allotment.current) {
this.allotment.current.reset();
}
}
// ...
this.setState({ toExpand: !this.state.toExpand }, () => this.resetAllotment());
Side-note, it appears that the Allotment component does not have time to process the new prop change, before reset is called in the setState callback... which is illogical to me, however, you can work around this by a hacky setTimeout of 0ms:
resetAllotment() {
setTimeout(() => this.allotment.current && this.allotment.current.reset(), 0);
}
I'm trying to integrate THEOPlayer in my project and I want to customize styles depending on certain events. For instance, I would love to hide the toolbar and show an overlay image when the video is paused.
They do expose some CSS classes that I can change manually but my question is, how do I change the values in CSS on a specific event. Since the player is imported as a single JSX element I don't know how to add custom classes to its specific parts. So I would like to know if there is another way.
Here is a component where an instance of Player is created:
class Player extends React.Component {
_player = null;
_el = React.createRef();
componentDidMount() {
const { source, onPlay, onPause } = this.props;
if (this._el.current) {
this._player = new window.THEOplayer.Player(this._el.current, {
libraryLocation:
"https://cdn.myth.theoplayer.com/7aff3fa6-f92e-45f9-a40e-1bce9911b073/",
});
this._player.source = source;
this._player.addEventListener("play", onPlay);
this._player.addEventListener("pause", onPause);
}
}
componentWillUnmount() {
if (this._player) {
this._player.destroy();
}
}
render() {
return (
<div
className={
"theoplayer-container video-js theoplayer-skin vjs-16-9 THEOplayer"
}
ref={this._el}
>
</div>
);
}
}
export default Player;
And that's a part of code where I want to change styles onPlay and onPause
<div className={"player-container"}>
<Player
source={source}
onPlay={() => {
console.log("playing");
}}
onPause={() => {
console.log("paused");
}}
/>
</div>
Use like this
state = {
play: false,
pause: true,
}
const playFn = () => {
this.setState = ({
play: true,
pause: false,
})
}
const pauseFn = () => {
this.setState = ({
play: false,
pause: true,
})
}
<div className={"player-container"}>
<Player
source={source}
onPlay={playFn}
onPause={pauseFn}
activatePlayClasses={play}
activatePauseClasses={pause}
bg={'https://example/example.jpg'}
/>
</div>
// on Player component
const { source, onPlay, onPause, activatePauseClasses, activatePlayClasses , bg} = this.props;
render() {
return (
<div
className={
`theoplayer-container video-js theoplayer-skin vjs-16-9 THEOplayer
${activatePauseClasses ? 'your pause class' : ''}
${activatePlayClasses ? 'your play class' : ''}`
}
style={{backgroundImage: `url(${bg})`}}
ref={this._el}
>
</div>
);
}
I have updated code
I am making an app that communicate with an api and fetch data,home page changes every day so i can't just add static components to it,
i need to create it according to the data that comes from the api.
i have a component for the home page called Home.vue
this component can have one or more Carousels depending on the data that i'am fetching.
i also have Carousel.vue which is responsible about displaying images and it had it's own props.
the question is :
How to add component to the dom from loop
this is Home.vue where i am making the loop :
<template>
<div>
<!--I Need The Loop right here-->
</div>
</template>
<script>
export default {
components: {},
data() {
return {
page_content: [],
widgets: [],
}
},
created() {
this.getHomeContent();
},
methods:
{
getHomeContent() {
window.axios.get(window.main_urls["home-content"]).then(response => {
this.page_content = JSON.parse(JSON.stringify(response.data));
console.log(this.page_content);
for (let index in this.page_content) {
switch (this.page_content[index].type) {
// if type is banner
case 'banner':
switch (this.page_content[index].display) {
// if display is carousel
case 'carousel':
console.log('carousel')
// end if display is carousel
this.widgets.push({
'type': 'Carousel',
'images': this.page_content[index].items,
})
}
// end if type is banner
}
}
});
}
}
}
</script>
and this is Carousel.vue which i need to be imported when needed with passing props :
<template>
<div>
<div >
<VueSlickCarousel>
<div v-for="image in images">
<img src="{{img}}">
</div>
</VueSlickCarousel>
</div>
</div>
</template>
<script>
import VueSlickCarousel from 'vue-slick-carousel'
import 'vue-slick-carousel/dist/vue-slick-carousel.css'
import 'vue-slick-carousel/dist/vue-slick-carousel-theme.css'
export default
{
components: {VueSlickCarousel},
name:'Carousel',
props:[
'images'
],
methods:
{
}
}
</script>
how to add Carousel.vue component to Home.vue dynamically some thing like:
if(data.display == 'carousel')
{
<carousel images="data.images"></carousel>
}
Import the component to your Home.vue :
import Carousel from './Carousel.vue'
export default {
components: {Carousel},
}
Then loop in your template:
<carousel v-for="(widget,index) in widgets" :key="index" :images="widget.images"/>
Best to use a widget.id rather than index for the key prop
This is the correct answer !
<template>
<div>
<template v-for="widget in widgets">
<div v-if="widget.type == 'carousel'" :key="widget.type">
<carousel
:images="widget.images"
:arrows ="widget.arrows"
:dots = "widget.dots"
>
</carousel>
</div>
</template>
</div>
</template>
<script>
import Carousel from './widgets/Carousel.vue'
export default {
components: {Carousel},
data() {
return {
page_content: [],
widgets: [],
}
},
created() {
this.getHomeContent();
},
methods:
{
getHomeContent() {
window.axios.get(window.main_urls["home-content"]).then(response => {
this.page_content = JSON.parse(JSON.stringify(response.data));
console.log(this.page_content);
for (let index in this.page_content) {
switch (this.page_content[index].type) {
// if type is banner
case 'banner':
switch (this.page_content[index].display) {
// if display is carousel
case 'carousel':
console.log('carousel')
// end if display is carousel
this.widgets.push({
'type': 'carousel',
'arrows':true,
'dots':true,
'images': this.page_content[index].items,
})
}
// end if type is banner
}
}
});
}
}
}
</script>
I am trying to use Video.js with create-next-app, and I am not able to load the video.js.css. this is what my component looks like.
import videojs from 'video.js'
import videoStyles from '../node_modules/video.js/dist/video-js.min.css'
export default class VideoPlayer extends React.Component {
componentDidMount() {
// instantiate Video.js
this.player = videojs(this.videoNode, this.props, function onPlayerReady() {
console.log('onPlayerReady', this)
});
}
// destroy player on unmount
componentWillUnmount() {
if (this.player) {
this.player.dispose()
}
}
// wrap the player in a div with a `data-vjs-player` attribute
// so videojs won't create additional wrapper in the DOM
// see https://github.com/videojs/video.js/pull/3856
render() {
return (
<div>
<div data-vjs-player>
<video ref={ node => this.videoNode = node } className="video-js"></video>
</div>
<style jsx>{videoStyles}</style>
</div>
)
}
}
I am using styled-jsx/css loader to load the external css file.
Look at the next.js github example. It should be enough to get you started.
Is there a way to make React Context type-safe with flow type?
For example :
Button.contextTypes = {
color: React.PropTypes.string
};
Unfortunately, it is inherently not possible because Context is not known at compile time (so I was told).
A bit of a workaround I use is pulling the the context from the consumer at the parent level, and then calling proptypes at the child...
Parent
//parent
class Parent extends component {
render(){
return (
<Consumer>{(context)=>{
const { color } = context
return(
<div>
<Button color={color} />
</div>
)}}</Consumer>
}
Child
//Button
...
Button.contextTypes = {
color: React.PropTypes.string
};
...