How to pass callback from one child component to another - javascript

I have a main component, App, which has two child components, Player, and VideoList, where Player is a wrapper around react-player, heavily based off of the react-player demo.
Player has a method renderLoadButton() which creates a button that loads a particular video when clicked. I would like to have several of these buttons inside of my VideoList component.
I am attempting to pass the renderLoadButton() function up into the parent component, and then down into the VideoList component where I can call it.
Here is the code for render() function of the parent component. Both my <Player/> and <VideoList/> components instantiated here.
I get the following error on the line mentioned in the comment.
TypeError: Cannot read property 'renderLoadButton' of undefined
render() {
const dragHandlers = {onStart: this.onStart, onStop: this.onStop};
const {deltaPosition, controlledPosition} = this.state;
return (
<div className="App">
<div className="fullscreen">
<Draggable handle="strong" bounds={'body'}{...dragHandlers}>
<div style={{position: 'absolute', bottom: '30%', right: '50%'}} className="video-box no-cursor">
<Player ref={instance=>{this.player = instance}} title="VIDEO" url='https://streamable.com/nfec3'/>
</div>
</Draggable>
<Draggable handle="strong" bounds={'body'}{...dragHandlers}>
<div>
{/*Error on the following line*/}
<VideoList callback = {(x,y)=> this.player.renderLoadButton(x,y)}/>
</div>
</Draggable>
</div>
<div className="App-footer">
<img src={vinyl} className="App-logo" alt="logo" />
<h1>Radio</h1>
</div>
</div>
);
}

As per the code you provided you are doing it right i have created similar working model as yours it is working fine:
https://codesandbox.io/s/6y5p9woqq3
You can add your code to sandbox so that we will able to figure out what is the problem.
Edit
The Problem with your code is not index.js but is in VideoList.js as per your minimal code
VideoList.js:
import React, { Component } from "react";
class VideoList extends Component {
render() {
console.log("dd");
return this.props.callback('www.something.com','BUTTON');
}
}
export default VideoList;
Here you are trying to return a prop which contains a function not the original jsx for better clarity try console logging like this
console.log("dd",this.props.callback)
which shows a object returning your this.player.renderLoadButton function. so when you are trying to return it which returns just a function which cannot be rendered it is causing errors.
So if you have to pass that function which returns jsx don't use ref.Create a new obj or instance of Player class and extract the function from it and then pass it as prop to the videoList and the call it in render return.
so your App component should look like:
class App extends Component {
render() {
const obj = new Player
const func = obj.renderLoadButton
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<Player title="VIDEO" url='https://streamable.com/nfec3'/>
<VideoList func={func} />
</div>
);
}
}
then your VideoList looks like:
class VideoList extends Component {
render() {
console.log("dd");
return (
<div>
{ this.props.func('www.something.com','BUTTON') }
</div>
)
}
}
export default VideoList;
here is working code :https://codesandbox.io/s/jpqnxwyyy
Edit 2:
i don't think it is possible that way. one thing you can do is use the same jsx every where and use the another function as props every where to call again. like this: https://codesandbox.io/s/7zwyl0yp3j

When use this.METHOD_NAME you must initial method!
constructor(props){
super(props);
this.renderLoadButton = this.renderLoadButton.bind(this);
}
renderLoadButton(x,y){
console.log(x,y); // for example
}
render(){
return(
...
<VideoList callback={this.renderLoadButton}/>
...
)
}
If you want to use static methods of other class, first import class then use static methods like this:
import Player from 'PLAYER_FILE_LOCATION';
.
.
.
.
.
render(){
return(
...
<VideoList callback={Player.renderLoadButton}/>
...
)
}

Related

How to pass the 'onClick' function to child component in React

I create a very generic Modal that can get different header, body and footer, but also different arguments for their Reactstrap components (I'm using Reactstrap to create the Modal but the question needn't be specific to solve a Reactstrap problem).
My GenericModal.js code looks like:
class GenericModal extends React.Component{
render(){
return(
<Reactstrap.Modal {...this.props.other} />
<Reactstrap.ModalHeader {...this.props.headerArgs}>{this.props.header}</Reactstrap.ModalHeader>
<Reactstrap.ModalBody {...this.props.bodyArgs}>{this.props.body}</Reactstrap.ModalBody>
<Reactstrap.ModalFooter {...this.props.footerArgs}>{this.props.footer}</Reactstrap.ModalFooter>
</Reactstrap.Modal>);
}
}
And so I call this class like this:
<GenericCard {...{other: other, headerArgs: headerArgs, bodyArgs: bodyArgs, footerArgs: footerArgs,
cardheader:header, cardbody:body, cardfooter:footer}} />
Now I know that this method works because I've tried it with className, for example:
const bodyArgs = {className: 'my-5'};
I want to also be able to pass an onClick function - but not merely the function (as we can see in this question), but the whole thing: onClick=foo().
I'm having a bit of a problem understanding how I can put the onClick method inside a json-style format like I did with className.
I can't write an anonymous function for the onClick inside const bodyArgs = {...}, and writing it as
const bodyArgs = {onClick: {foo}};
Provides an undefined foo. I also can't put this.foo because it's an unexpected syntax as well.
Any thoughts?
Welp, found the solution moments after I posted this.
Just didn't need the {} curly brackets.
const bodyArgs = {onClick: this.foo};
Does the job.
Thought I'd keep it here in case anyone stumbles into this issue.
This should work as you have explained but I cannot fully know without the whole example. Here is a working bit of code and a codesandbox link of what you are tying to do.
import React from "react";
import "./styles.css";
class ClickExecutor extends React.Component {
render() {
return (
<div>
<h4>Click Executor</h4>
<div>
<button onClick={() => this.props.bodyArgs.alert1()}>One</button>
<button onClick={() => this.props.bodyArgs.alert2()}>Two</button>
</div>
</div>
);
}
}
class GenericModal extends React.Component {
alert1 = () => {
alert("Alert 1");
};
alert2 = () => {
alert("Alert 2");
};
render() {
const bodyArgs = {
alert1: this.alert1,
alert2: this.alert2
};
return (
<div>
<h1>Generic Modal</h1>
<ClickExecutor
{...{
bodyArgs: bodyArgs,
otherProps: "other various properties ..."
}}
/>
</div>
);
}
}
export default function App() {
return (
<div className="App">
<GenericModal />
</div>
);
}
Working Demo LINK : https://codesandbox.io/s/smoosh-frost-rj1vb

In React JavaScript, How to call a class inside another class?

I began to learn JavaScript and React these days, I tried to draw some grids in a website and met a problem like this:
Everything works fine when I code like this:
export default class PathfindingVisualizer extends Component {
constructor(props) {
super(props);
this.state = {
nodes: [],
};
}
componentDidMount() {
const nodes = getInitialGrid();
this.setState({ nodes });
}
render() {
const { nodes } = this.state;
console.log(nodes);
return (
<>
<p style={{ fontSize: 40 }}>Visualize Algorithms</p>
<br />
<br />
<div className="node-container">{nodes}</div> // HERE WORKS FINE
</>
);
}
}
And the website turned out to be like this which is fine:
But when I changed the code like this:
render() {
const { nodes } = this.state;
console.log(nodes);
return (
<>
<p style={{ fontSize: 40 }}>Visualize Algorithms</p>
<br />
<br />
<NodeContainer>{nodes}</NodeContainer> // HERE
</>
);
}
}
The grids just disappear, and nothing in <body>:
Could anybody help me? I can't figure out why this is happening.
Class NodeContainer and Node are like this:
export default class NodeContainer extends Component {
render() {
return <div className="node-container"></div>;
}
}
export default class Node extends Component {
render() {
return <div className="node-item"></div>;
}
}
Hey, thank you guys for the answers:) this is my first time to ask a question here. I solved the problem by adding {this.props.xxxxx} as you said and it works.
Corrected codes as following:
...
<br />
<br />
<NodeContainer nodes={nodes}></NodeContainer> // HERE
</>
...
the NodeContainer class:
export default class NodeContainer extends Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
return <div className="node-container">{this.props.nodes}</div>; //HERE
}
}
I didn't use 'this.props.children', but will check out later. I skipped the basic tutorial so I didn't understand how to pass params to class, I checked this video to help myself quickly understand this:
https://www.youtube.com/watch?v=ICmMVfKjEuo&list=PLN3n1USn4xlntqksY83W3997mmQPrUmqM&index=5&t=0s
For this you need to call children inprops
export default class NodeContainer extends Component {
render() {
return <div className="node-container">{this.props.children}</div>;
}
}
I don't see where the Node class is being referenced so I'm not sure that's relevant.
Your issue is that the your passing the nodes component to the NodeContainer component, but not rendering it in NodeContainer. You should look into how props are passed to components - they appear as this.props.children on the component. Your code should look like this.
export default class NodeContainer extends Component {
render() {
return <div className="node-container">{this.props.children}</div>;
}
}
If you're wondering how nodes appears as this.props.children, it's because of how React treats components. You can achieve the same thing by passing it into children explicitly as a prop.
Dude, in reactJS, there's should be data to be pass from your Parent element to your Children element.
In your case to be able to show the data you want,
you need to pass your state from the <PathFindingVisualizer /> to your <NodeContainer />, which you have done it by using node as a children between <NodeContainer /> tag. And you forget the second step,
You need to access the data you have passed inside <NodeContainer /> class you made. How? just access it by using this.props.children.
here's the example.
export default class NodeContainer extends Component {
render() {
return <div className="node-container">{this.props.children}</div>
}
}
Problem solved.
as a reference see this. https://learn.co/lessons/react-this-props-children

Passing props twice for a Higher Order Component?

I'm building a webpage and realized a common style shared by each component (same background, border, and title style). So I thought I should make an HOC which accepts the inner content of each component as well as a title, and returns an outer component which wraps this inner component and heading.
At first I ran into a lot of issues trying to get this to work, being new to React, but now it's finally working but I still don't understand how.
Here is my HOC
const BaseBlock = (WrappedComponent) => {
return class BaseBlock extends Component {
render () {
return (
<div className={styles['base-block']}>
<div className={styles['container']}>
<div className={styles['base-block-head']}>
{ this.props.title }
</div>
<div className={styles['base-block-body']}>
<WrappedComponent {...this.props} />
</div>
</div>
</div>
)
}
}
}
export default BaseBlock
This is the WrappedComponent:
const HighlightsBlock = (props) => {
return <ListsComponent items={props.items} />
}
export default BaseBlock(HighlightsBlock)
And this is the ListsComponent
const ListsComponent = (props) => {
if (props.items) {
return (
<ul className={styles['styled-list']}>
{props.items.map((item, idx) => {
return (
<li key={idx} className={styles['styled-list-item']}>{item}</li>
)
})}
</ul>
)
} else return (
<h3>No highlights</h3>
)
}
export default ListsComponent
And this is how I'm using the component in my app:
<HighlightsBlock items={this.getHighlights()} title='Highlights' />
Now, I can see the HighlightsBlock component receiving props twice (Once when I'm using it in my App with props, and once inside the HOC Baseblock as WrappedComponent ). If I remove props from either of these places it stops working. I don't understand how this is working.
When you render <HighlightsBlock items={this.getHighlights()} title='Highlights' /> you are actually rendering the component returned by HOC which in turn renders your actually HighlightsBlock component as <WrappedComponent {...this.props} />
You can think of HighlightsBlock component to be nested two level deep and hence you need to pass on the props to it, firstly as {...this.props} from within HOC and then receive it as props in functional component
This is because of this.getHighlights() in this line,
<HighlightsBlock items={this.getHighlights()} title='Highlights' />
Every time you pass props to child component this function is getting executed.
To solve this issue, maintain a state value in your parent component and set that value in getHighlights function like,
getHighlights(){
//you logic to get data
this.setState({items:data.items}); //considering `data` is object which has `items`
}
Now you can pass items like,
<HighlightsBlock items={this.state.items} title='Highlights' />

this.setState React bind issue

I am attempting to pass the changePage class method into a child component called SideBar. When the changePage method is then triggered by an onClick event in the child component I receive the following error:
Uncaught TypeError: this.SetState is not a function
From what I could find in other similar posts I need to bind the changePage method to this. I have done that but I still can't manage to get is to work.
I also saw many suggestions to use ES6 arrow functions for my methods but I get the exact same error message if I do.
I'm still quite new at web development and any help would be appreciated.
Parent Component called Main:
import React from 'react';
import Content from './Content';
import Sidebar from './Sidebar';
export default class Main extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedPage: 'home',
pages: ['home','about','skills','contact'],
};
this.changePage = this.changePage.bind(this);
}
changePage(page) {
console.log(page);
this.SetState({
selectedPage: page,
pages: ['home','about','skills','contact']
});
};
render() {
return (
<div>
<div id="sidebar" className="side-bar">
<Sidebar
changePage={this.changePage}
selectedPage={this.state.selectedPage}
pages={this.state.pages}
/>
</div>
<div id="main" className="main-content">
<Content
selectedPage={this.state.selectedPage}
/>
</div>
</div>
)
}
}
Child Component:
import React from 'react';
export default class Sidebar extends React.Component {
render() {
console.log("content props",this.props);
const buttons = this.props.pages.map(button =>
<span
className='nav-button'
id={button}
key={button}
onClick={() => this.props.changePage(button)}
>
<img src={`./app/images/${button}.svg`} />
</span>
);
return (
<div>
<span>
<img className='headshot' src='./app/images/headshot.jpg' />
</span>
<div className='nav-container'>
{buttons}
</div>
</div>
)
}
}
You have syntax error: this.SetState. Change it to this.setState.
The typo in setState was causing the error, but you may want consider a cleaner way to bind the parent this on the changePage prop. Your way works, but if you changed sidebar Component inclusion to:
<Sidebar
changePage={ (page) => this.changePage(page)}
selectedPage={this.state.selectedPage}
pages={this.state.pages}
/>
That may make it clearer what is going on (changePage prop is a function which takes one parameter and passes that parameter to instance method this.changePage), and removes the binding gymnastics in the constructor.

React - Passing props from Array to Div

I'm developing a more complex example of passing props from a component to another. In this case, it's the content of an array(when I click on that content) to a <div>.
You can find the code here: https://codesandbox.io/s/509j5npkwx
(Please check the code in the link above)
TextBox <div> component:
export class TextBox extends React.Component {
constructor(props) {
super(props);
this.state = {
content: "Select A Node To See Its Data Structure Here..."
};
this.changeContent = this.changeContent.bind(this);
}
changeContent(newContent) {
this.setState({
content: newContent
});
}
render() {
return (
<div className="padd_top">
<div className="content_box">{this.state.content}</div>
</div>
);
}
}
export default TextBox;
FileTree component:
export class FileTree extends React.Component {
constructor(props) {
super(props)
this.state = {
activeNode: null
}
this.setActiveNode = this.setActiveNode.bind(this)
}
setActiveNode(name) {
this.setState({ activeNode: name })
}
render() {
return (
<div className="padd_top">{
renderTree(
this.props.root || root,
this.setActiveNode,
this.state.activeNode
)
}
<TextBox />
</div>
)
}
}
I'm trying to do something like this, for further understanding: http://alexcurtis.github.io/react-treebeard/
I understood how to prepare the <div> to receive new information, by substituting the "Select A Node To See Its Data Structure Here..." when I click one of the elements belonging to the file tree.
I also understood how to prepare the file tree to pass content <div>, but in this case, I'm confused about where and how should I apply to the right component.
I'm new to React JS. If you have any tips for me about this issue, I'm very grateful.
Thank you.
I changed a bit the structure of my project, and now I'm looking forward to put <TextBox> and <FileTree> side by side.
More specifically, like this:
export class App extends React.Component {
render() {
return (
<div>
<div className="col-md-12">
<SearchEngine />
</div>
<div className="col-md-6">
<FileTree />
</div>
<div className="col-md-6">
<TextBox content={this.props.activeNode} />
</div>
</div>
);
}
}
I tought it wouldn't be different to pass props to <App>, but again I might be missing something, because it's not passing properly. What is missing here?
Thanks again.
I'm not sure if I understood your question.
Here is my fork: https://codesandbox.io/s/50pv75q8ll
Basically, you pass the activeNode to < TextBox />. Look at line 126 of index.js.
And then, in text_box.js use componentWillReceiveProps() to set the TextBox state with the content prop. Line 18 of text_box.js.

Categories