Passing methods as props from embeded component to be called by parent - javascript

I'm building my first React app and am seemingly way over my head. Anyway, I'm trying to take a component like this:
export default class Timer extends Component {
constructor(props) {
super(props)
this.state = { clock: 0, time: '' }
}
componentDidMount() {
this.play()
}
componentWillUnmount() {
this.pause()
}
pause() {
if (interval) {
clearInterval(interval)
interval = null
}
}
render() {
return (
<div className="react-timer" pause={this.pause.bind(this)}>
<h3 className="seconds"> {this.state.time} {this.props.prefix}</h3>
<br />
</div>
)
}
}
Timer.propTypes = {
options: PropTypes.object
}
and access it's state and pause functions in another component that is it's parent because the timer is embedded in the other component.
Other component:
class Level1 extends Component {
constructor(props) {
super(props);
this.state = {x: 0, y: 0};
}
render () {
...
return (
<div>
<div ref="elem" onMouseMove={this._onMouseMove.bind(this)} id="gameBoard">
<img id="waldo1" src={require('../images/waldo1(1).jpg')} alt="waldo"/>
<h2> {x} , {y}</h2>
</div>
<button onClick={this.refs.mytimer.pause()}>Pause</button>
<Timer ref="mytimer" options={OPTIONS}/> <-- Here Timer
</div>
) // return
} // render
} //component
For example, I'm going to write a function like this:
var isWaldoFound = function (x , y ) {
if (true) {
Timer.pause()
hashHistory.push({ '/result' + this.Timer.state})
} else {
..Whatever..
}
}
I've tried using refs and props but when I log anything it's undefined. When I log timer it shows me the Timer but when I log Timer.pause or Timer.pause() it says it is undefined.
How can I go about doing this?

React is all about your UI being a function of your state; i.e., React is all about state. So, instead of doing things imperatively, you change your state and the components "react" to the change in state.
You might want to read:
https://zhenyong.github.io/react/docs/more-about-refs.html
and
https://facebook.github.io/react/docs/refs-and-the-dom.html
In your case, you might pass the "current state" as a prop to the Timer, and then in componentWillRecieveProps() in the Timer, check the new "current state" against the current one, and if there's a change in state, then have the timer transition itself to the new "current state", rather than trying to imperatively tell the timer to transition to a new state.
So, what you're trying to do is not really the "React" way, but you should still be able to make it work...
First, I'd recommend using a callback ref instead of a string ref, and ideally the callback is a class method on the outer component instead of a fat-arrow function so that a new function isn't generated with each render. Use the ref to capture the Timer instance on first render, store that instance as an instance variable of your Level1 component, and then use the instance later when the pause button is clicked.
Something like this maybe (untested of course):
class Level1 extends Component {
constructor(props) {
super(props);
this.captureTimer.bind(this)
this.onPauseTimerButtonClicked.bind(this)
this.state = {x: 0, y: 0};
}
captureTimer(ref) {
this.timer = ref
}
onPauseTimerButtonClicked() {
this.timer.pause()
}
render () {
...
return (
<div>
<div ref="elem" onMouseMove={this._onMouseMove.bind(this)} id="gameBoard">
<img id="waldo1" src={require('../images/waldo1(1).jpg')} alt="waldo"/>
<h2> {x} , {y}</h2>
</div>
<button onClick={this.onPauseTimerButtonClicked}>Pause</button>
<Timer ref={timer => this.captureTimer(timer)} options={OPTIONS}/> <-- Here Timer
</div>
) // return
} // render
} //component

The reason it's not working is probably because the pause function is not bound to the class.
Try binding it in the constructor of your component:
constructor(props) {
super(props)
this.state = { clock: 0, time: '' }
this.pause = this.pause.bind(this);
}
Try this pen: https://codepen.io/CarlosEME/pen/xgmYXZ

Assuming isWaldoFound is a method of Level1.. If so , replace :
Timer.pause()
By :
this.refs.mytimer.pause();

Related

Why these function runs without binding....?

When a button is clicked {this.increment} is invoked. Why {this.increment} runs without binding {this.increment.bind(this)}
export default class App extends Component {
constructor(props) {
super(props)
this.state = {
count: 0
}
}
increment=()=>{
this.setState({ count: this.state.count + 2 })
}
render() {
return (
<>
<div id='1'>{this.state.count}</div>
<button onClick={this.increment}>Increment </button>
</>
)
}
}
I think you are confused about why binding is required. It is not required because the function will not be able to run without it. You are passing an event handler to the onClick and it will run when button is clicked.
When this piece of code is run : this.increment, the this is an instance of the class and hence the component. But the assignment: onClick={this.increment}, does something like the below:
class Animal {
constructor(name) {
this.name = name;
}
speak() { console.log(`${JSON.stringify(this)}`);
}
}
let meth = ani.speak;
meth();
The context is lost. And this becomes undefined. So even if increment runs, this.setState will throw an error. And that is why binding is required.
In the constructor add this statement:
this.increment = this.increment.bind(this)
Now your increment is assigned a new function(a new value), where the value of this (context) is the class instance.

How to update state of a parent while using its another state without causing infinite updates?

Let's have components A and B.
Component B only shows the parent's state in contentForB in a different format and doesn't manipulate with it. It takes A's state as a prop, applies a function transform(content) and shows it, so whenever A's contentForB changes, the new content get transformed and updated in B.
The problem comes when A wants to use B's transformed content and use it somewhere else. I tried to implemented in a standard way, using state-updating function and passed it from A to B like this:
class A extends React.Component {
constructor(props) {
super(props);
this.state = {
contentForB: "",
transformedContent: ""
};
this.updateTransformedContent = this.updateTransformedContent.bind(this);
}
updateTransformedContent(newContent) {
this.setState = { transformedContent: newContent };
}
render() {
return (
...
<B content={ this.state.contentForB }
updateTransformedContent={ this.updateTransformedContent } />
<ComponentUsingTransformedContent transformedContent = { this.state.transformedContent } />
);
}
}
class B extends React.Component {
constructor(props) {
super(props);
this.transform = this.transform.bind(this);
}
transform(content) {
let newContent = ...; ​
​this.props.updateTransformedContent(newContent);
​return newContent;
​}
​render() {
​<Something value={this.transform(this.props.content)} />
​}
}
However, when A's state changes, B gets reinitialized, it then changes A's state by calling the updateTransformedContent which again causes B to get reinitialized, thus causing an infinite loop even though the updateTransformedContent changes the state object which isn't directly passed into B.
Any ideas how to deal with such situation properly?
You can use componentDidUpdate lifecycle method.
[codesandbox.io]
https://codesandbox.io/s/cool-leaf-92nt8?file=/src/App.js:457-475
class TestB extends Component {
constructor(props) {
super(props);
this.state = {
transformed: ""
};
this.transform = this.transform.bind(this);
}
componentDidUpdate(prevProps) {
if (this.props.content !== prevProps.content) {
this.transform(this.props.content);
}
}
transform(content) {
const transformed = `<h4>${this.props.content}</h4>`;
this.setState({ transformed: transformed });
this.props.updateTransformedContent(transformed);
return transformed;
}
render() {
return (
<div>
<h4>B Componenet:</h4>
<p>{this.state.transformed}</p>
</div>
);
}
}

Changing an Image on in time interval using react

I am trying to change the image displayed every 1 second the first image appears then switches to the alt display and does not continue switching the pictures
export default class Slideshow extends Component {
constructor(props) {
super(props);
this.getImageId = this.getImageId.bind(this);
this.switchImage = this.switchImage.bind(this);
this.init = this.init.bind(this);
this.state = {
currentImage: 0,
image: 0
};
}
getImageId() {
if(this.currentImage < 3) {
this.setState({
currentImage: this.state.currentImage +1
})
} else {
this.setState({
currentImage: 0
})
}
return this.currentImage;
}
switchImage() {
this.setState({
image: this.getImageId()
});
}
init() {
setInterval(this.switchImage, 1000)
}
render() {
const imagePath = [guy, girl, wash, swifer];
this.init();
return (
<div className="slideshow-container">
<img src={imagePath[this.state.image]} alt="cleaning images"/>
</div>
);
}
}
Pictures will switch every 1 seconds to the next picture in the array and go back to original after going through whole array
Try something like this instead: https://codesandbox.io/s/naughty-sara-q3m16
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
class App extends React.Component {
constructor(props) {
super(props);
this.switchImage = this.switchImage.bind(this);
this.state = {
currentImage: 0,
images: [
"https://images.unsplash.com/photo-1518791841217-8f162f1e1131?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&w=1000&q=80",
"https://img.purch.com/w/660/aHR0cDovL3d3dy5saXZlc2NpZW5jZS5jb20vaW1hZ2VzL2kvMDAwLzEwNC84MzAvb3JpZ2luYWwvc2h1dHRlcnN0b2NrXzExMTA1NzIxNTkuanBn",
"https://d17fnq9dkz9hgj.cloudfront.net/uploads/2012/11/152964589-welcome-home-new-cat-632x475.jpg",
"https://i.ytimg.com/vi/jpsGLsaZKS0/maxresdefault.jpg"
]
};
}
switchImage() {
if (this.state.currentImage < this.state.images.length - 1) {
this.setState({
currentImage: this.state.currentImage + 1
});
} else {
this.setState({
currentImage: 0
});
}
return this.currentImage;
}
componentDidMount() {
setInterval(this.switchImage, 1000);
}
render() {
return (
<div className="slideshow-container">
<img
src={this.state.images[this.state.currentImage]}
alt="cleaning images"
/>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
We can simplify your code by doing a couple of things:
Put the images-array in the state, so that we can iterate over
the image-paths and keep track of the current images index.
So now we can consolidate switchImage and getImageId into a
single function that serves the same purpose. We just check the
currentImage (index) against the length of the array.
React also has a life-cycle method called componentDidMount()
which executes logic right after a component is rendered the
first-time. I used this to replace the init() function. There is an issue with calling init() in render(). Every time a component re-renders, it executes the logic in render(), which means you would be creating a new setInterval() on every subsequent re-render. componentDidMount() only triggers a single time, making it perfect for defining intervals.
The main issue with your code is that you called init function with in render function, whenever state get update render executes as well, so init function call again and again each time render function execute
the solution is to set intervals in componentDidMount function
componentDidMount run only one time after component mount in the DOM, for help related to react life cycle function do visit the official documentation
https://reactjs.org/docs/react-component.html
also have a look this post image
https://reactjs.org/docs/react-component.html

ReactJS - Destroy old Component-Instance and create new

I've got a may confusing question because it does not fit standard-behaviour how react and the virtual dom works but i would like to know the answer anyway.
Imagine i've got a simple react-component which is called "Container".
The Container-component has a "div" inside of the render-method which contains another component called "ChildContainer". The "div" which surrounds the "ChildContainer" has the id "wrappingDiv".
Example:
render() {
<Container>
<div id="wrappingDiv">
<ChildContainer/>
</div>
</Container
}
How can i destroy the "ChildContainer"-component-instance and create a completly new one. Which mean the "ComponentWillUnmount" of the old instance is called and the "ComponentDidMount" of the new component is called.
I don't want the old component to update by changing the state or props.
I need this behaviour, because an external library from our partner-company got a libary which change the dom-items and in React i'll get a "Node not found" exception when i Update the component.
If you give the component a key, and change that key when re-rendering, the old component instance will unmount and the new one will mount:
render() {
++this.childKey;
return <Container>
<div id="wrappingDiv">
<ChildContainer key={this.childKey}/>
</div>
</Container>;
}
The child will have a new key each time, so React will assume it's part of a list and throw away the old one, creating the new one. Any state change in your component that causes it to re-render will force that unmount-and-recreated behavior on the child.
Live Example:
class Container extends React.Component {
render() {
return <div>{this.props.children}</div>;
}
}
class ChildContainer extends React.Component {
render() {
return <div>The child container</div>;
}
componentDidMount() {
console.log("componentDidMount");
}
componentWillUnmount() {
console.log("componentWillUnmount");
}
}
class Example extends React.Component {
constructor(...args) {
super(...args);
this.childKey = 0;
this.state = {
something: true
};
}
componentDidMount() {
let timer = setInterval(() => {
this.setState(({something}) => ({something: !something}));
}, 1000);
setTimeout(() => {
clearInterval(timer);
timer = 0;
}, 10000);
}
render() {
++this.childKey;
return <Container>
{this.state.something}
<div id="wrappingDiv">
<ChildContainer key={this.childKey}/>
</div>
</Container>;
}
}
ReactDOM.render(
<Example />,
document.getElementById("root")
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.4.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.4.2/umd/react-dom.production.min.js"></script>
Having said that, there may well be a better answer to your underlying issue with the plugin. But the above addresses the question actually asked... :-)
Using hooks, first create a state variable to hold the key:
const [childKey, setChildKey] = useState(1);
Then use the useEffect hook to update the key on render:
useEffect(() => {
setChildKey(prev => prev + 1);
});
Note: you probably want something in the array parameter in useEffect to only update the key if a certain state changes

How to pass parameter in a function on onClick event

I am trying to make a component which receiving a function as a props. i want to pass some value into function while calling it:
class Course extends Component {
render() {
return (
<div>
<div id="courses">
<p onClick={this.props.sumPrice}>{this.props.name}<b>{this.props.price}</b></p>
</div>
</div>
);
}
}
sumPrice is a function define in parent component and its need a value.
This is my sumPrice function and parent class constructor code:
constructor(props) {
super(props);
this.state = {
active: false,
total: 0
};
this.sumPrice = this.sumPrice.bind(this);
}
sumPrice(price) {
this.setState({ total: this.state.total + price });
}
Usually the closure, arrow function in render handles such situations exactly how it is needed:
<div id="courses">
<p
onClick={() => this.props.sumPrice(this.props.price)}
>
{ this.props.name }<b>{ this.props.price }</b>
</p>
</div>
While it works as expected, unfortunately it comes at cost ot performance penalty Why shouldn't JSX props use arrow functions or bind?. The impact does not have to be dramatic problem but should be generally avoided.
The optimal solution is to use the functions which are not recreated on each re-render, like class method:
class Course extends Component {
constructor(props) {
super(props)
this.onClick = this.onClick.bind(this)
}
onClick () {
const { sumPrice, price } = this.props
sumPrice(price)
}
render() {
return (
<div>
<div id="courses">
<p onClick={this.onClick}>{this.props.name}<b>{this.props.price}</b></p>
</div>
</div>
)
}
}
Avoiding performance issues.

Categories