How to create a ref for each instance of a component
I've extracted some code into it's own component. The component is a PlayWhenVisible animation component that plays/stops the animation depending on whether the element is in view.
I'm creating a ref inside the component constructor but since I'm getting some lag when using 2 instances of the component I'm wondering if I should create the refs outside the component and pass them in as props or whether there's a way to create a new instance for each compoenent instance.
import VisibilitySensor from "react-visibility-sensor";
class PlayWhenVisible extends React.Component {
constructor(props) {
super(props);
this.animation = React.createRef();
this.anim = null;
}
render() {
return (
<VisibilitySensor
scrollCheck
scrollThrottle={100}
intervalDelay={8000}
containment={this.props.containment}
onChange={this.onChange}
minTopValue={this.props.minTopValue}
partialVisibility={this.props.partialVisibility}
offset={this.props.offset}
>
{({ isVisible }) => {
isVisible ? this.anim.play() : this.anim && this.anim.stop();
return (
// <div style={style}>
<i ref={this.animation} id="animation" className={this.props.class} />
);
}}
</VisibilitySensor>
);
}
}
The issue was caused by the VisibilityChecker component which was overflowing the container and causing it to be erratic when firing.
Related
I have a need to use forwarded refs
const InfoBox = React.forwardRef((props, ref) => (
<div ref={ref} >
<Rings >
</Rings>
<Tagline />
</div>
));
I also happen already have the code written like this
class InfoBox extends React.Component {
constructor(props) {
super(props)
}
render () {
return (
<div >
<Rings />
<Tagline />
</div>
)
}
basically my InfoBox needs to be a Component because it holds some state, but I also want it to behave like an object that can receive refs from the parent and forward them down to the children (basically React.forwardRef)
After familiarizing myself with React.forwardRef, I can't figure out how to get it to work with my existing React components, which already have functionality attached to state.
do I need to separate the two objects, and wrap one within the other or is there a way I can achieve this in the same object?
the code that wraps Infobox looks like
class AppContainer extends React.Component {
constructor(props) {
super()
this.infobox_ref = React.createRef()
}
componentDidMount() {
// this.infobox_ref.current.innerHTML should return the inner HTML of the infobox
}
render() {
return (
<InfoBox ref={this.infobox_ref}>
)
}
am I using forwarded refs correctly?
In React, the ref prop is not forwarded by default. In order to get a reference in a child component, you have 2 options:
Using a function component wrapped in the forwardRef function. You have already done this:
const InfoBox = React.forwardRef((props, ref) => (
<div ref={ref} >
<Rings >
</Rings>
<Tagline />
</div>
));
Changing the name of the ref prop.
// Parent Component
class AppContainer extends React.Component {
constructor(props) {
super()
this.infobox_ref = React.createRef()
}
componentDidMount() {
// this.infobox_ref.current.innerHTML should return the inner HTML of the infobox
}
render() {
return (
<InfoBox infoboxRef={this.infobox_ref}>
)
}
}
// Child Component
class InfoBox extends React.Component {
render () {
return (
<div ref={this.props.infoboxRef}>
<Rings />
<Tagline />
</div>
)
}
}
Of course, you can also combine them, allowing you to still pass to the ref prop from the parent, but consuming the "fixed" prop in the child class component, as shown here by #tubu13:
class InfoBox extend React.Component{
render() {
return (
<div ref={this.props.infoboxRef}>
<Rings />
<Tagline />
</div>
)
}
}
export default React.forwardRef((props, ref) => <InfoBox {...props} infoboxRef={ref} />)
I expected this toggle to work but somehow the constructor of component <A/> is called only once. https://codesandbox.io/s/jvr720mz75
import React, { Component } from "react";
import ReactDOM from "react-dom";
class App extends Component {
state = { toggle: false };
render() {
const { toggle } = this.state;
return (
<div>
{toggle ? <A prop={"A"} /> : <A prop={"B"} />}
<button onClick={() => this.setState({ toggle: !toggle })}>
toggle
</button>
</div>
);
}
}
class A extends Component {
constructor(props) {
super(props);
console.log("INIT");
this.state = { content: props.prop };
}
render() {
const { content } = this.state;
return <div>{content}</div>;
}
}
ReactDOM.render(<App />, document.getElementById("root"));
I already found a workaround https://codesandbox.io/s/0qmnjow1jw.
<div style={{ display: toggle ? "none" : "block" }}>
<A prop={"A"} />
</div>
<div style={{ display: toggle ? "block" : "none" }}>
<A prop={"B"} />
</div>
I want to understand why the above code is not working
In react if you want to render same component multiple times and treat them as different then you need to provide them a unique key. Try the below code.
{toggle ? <A key="A" prop={"A"} /> : <A key="B" prop={"B"} />}
Since that ternary statement renders results in an <A> component in either case, when the <App>'s state updates and changes toggle, React sees that there is still an <A> in the same place as before, but with a different prop prop. When React re-renders it does so by making as few changes as possible. So since this is the same class of element in the same place, React doesn't need to create a new element when toggle changes, only update the props of that <A> element.
Essentially, the line
{toggle ? <A prop="A"/> : <A prop="B"/> }
is equivalent to
<A prop={ toggle ? "A" : "B" }/>
which perhaps more clearly does not need to create a new <A> component, only update the existing one.
The problem then becomes that you set the state.content of the <A> using props.prop in the constructor, so the state.content is never updated. The cleanest way to fix this would be to use props.prop in the render method of the <A> component instead of state.content. So your A class would look like this:
class A extends Component {
render() {
const { prop } = this.props;
return <div>{ prop }</div>;
}
}
If you must take the prop prop and use it in the <A> component's state, you can use componentDidUpdate. Here's an example:
class A extends Component {
constructor(props) {
super(props);
this.state = {content: props.prop};
}
componentDidUpdate(prevProps) {
if (prevProps.prop !== this.props.prop) {
this.setState({content: this.props.prop});
}
}
render() {
const { content } = this.state;
return <div>{ content }</div>
}
}
React will only call the constructor once. That's the expected outcome.
Looks like you're trying to update the state of the component A based on the props.
You could either use the prop directly or use the componentDidUpdate lifecycle method, as Henry suggested. Another way is using the static method getDerivedStateFromProps to update the state based on the prop passed.
static getDerivedStateFromProps(props, state) {
return ({
content: props.prop
});
}
I want to render BlackSpark when RedSpark is clicked, but I'm not sure how to change the state of a component in another component. I know how to set state in the component itself, but how do I affect another component when I click a different component?
class BlackSpark extends React.Component {
render() {
return (
<div className="black"></div>
);
}
}
class RedSpark extends React.Component {
render() {
return (
<div className="red"></div>
);
}
}
class App extends React.Component {
render() {
return (
<div>
<BlackSpark />
<RedSpark />
</div>
);
}
}
In React, there's a concept of component composition as you've already embraced -- it allows you to accomplish what you want by rendering children based on the parent's state, another key concept known as lifting state up. What this means, is if you have mutually dependent components, create a single parent which composes them, and have state in the parent control the presentation and logic of the children. With the parent App, you can keep your state inside App, and based on App's state, conditionally render whatever you want -- either BlackSpark or both. For example, using the logical && operator:
{condition && <Component />}
This will only render <Component> when condition is truthy, or else it will not render anything at all (except for when condition is 0). Applying it to this situation, try adding state to your App component to utilize conditional rendering.
There's another key concept you need to understand: component props. They are essentially inputs to a component, certain properties passed to the component to tell how it should behave -- like attributes on regular HTML elements such as input placeholders, URLs, and event handlers. For example:
<Component foo="bar" bar={3} />
This will pass the props foo and bar down to Component with the values "bar" and 3 respectively and are accessible through this.props. If you were to access this.props.foo inside the Component component it would give you "bar". If you pair this up with composition, you can accomplish what you want:
class Example extends React.Component {
constructor() {
super();
this.state = {
showHello: true
}
this.handleChange = this.handleChange.bind(this);
}
handleChange() {
this.setState(prevState => ({
showHello: !prevState.showHello
}));
}
render() {
return (
<div>
{this.state.showHello && <Child2 />}
This is a test.
<Child1 onClick={this.handleChange} />
</div>
);
}
}
class Child1 extends React.Component {
render() {
return <div onClick={this.props.onClick}>Click me!</div>
}
}
class Child2 extends React.Component {
render() {
return <div>Hello!</div>
}
}
ReactDOM.render(<Example />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
The above example lifts state up by having a parent compose the children and maintain the state. It then uses props to pass down an onClick handler to Child1, so that whenever Child1 is clicked, the state of the parent changes. Once the state of the parent changes, it will use conditional rendering to render <Child2> if the condition is truthy. Further reading at the React documentation and on the logical && operator.
I know how to set state in the component itself, but how do I affect another component when I click a different component?
The recommended way to do it would be to create a parent component that has the state. You'd then use that state to determine when to render the other child component.
I want to render BlackSpark when RedSpark is clicked, but I'm not sure how to change the state of a component in another component. Also, what if I want to hide BlackSpark when GreenSpark is clicked and GreenSpark is inside BlackSpark?
In this case, here's how you'd do it.
const GreenSpark = ({ onClick }) => (
<button className="green" onClick={onClick}>X</button>
)
const BlackSpark = ({ onClick }) => (
<div className="black">
<GreenSpark onClick={onClick} />
</div>
)
const RedSpark = ({ onClick }) => (
<div className="red" onClick={onClick}></div>
)
class Spark extends React.Component {
constructor(props) {
super(props)
this.state = {
showBlack: false
}
this.boundShowBlack = this.showBlack.bind(this)
this.boundHideBlack = this.hideBlack.bind(this)
}
showBlack() {
this.setState({ showBlack: true })
}
hideBlack() {
this.setState({ showBlack: false })
}
render() {
return (
<div>
<RedSpark onClick={this.boundShowBlack} />
{this.state.showBlack && <BlackSpark onClick={this.boundHideBlack} />}
</div>
)
}
}
How to change the value of props, how to setProps, suppose the value of this.props.contact.name is John, I want to change it to Johnny.
How can I do this?
For example:
changeValue(){
this.props.contact.name='Johnny'
}
You would change the prop in the parent component, as that is what holds the value of the prop itself. This would force a re-render of any child components that use the specific prop being changed. If you want to intercept the props as they're sent, you can use the lifecycle method componentWillReceiveProps.
I would suggest rather then change the props value you can pass the function into props and then change the parent component state so it will change the child component props like
your Parent Component should be
class SendData extends React.Component{
constructor(props) {
super(props);
this.state = {
images: [
'http://via.placeholder.com/350x150',
'http://via.placeholder.com/350x151'
],
currentImage: 0
};
this.fadeImage=this.fadeImage.bind(this);
}
fadeImage(e) {
e.preventDefault();
this.setState({currentImage: (this.state.currentImage + 1) % this.state.images.length})
}
render()
{
return(
<FadeImage images={this.state.images} currentImage={this.state.currentImage} fadeImage={this.fadeImage}/>
)
}
}
your Child Component should be like
class FadeImage extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div className="image">
<CSSTransitionGroup
transitionName="example"
transitionEnterTimeout={300}
transitionLeaveTimeout={300}
>
<section>
<button className="button" onClick={this.props.fadeImage.bind(this)}>Click!</button>
<img src={this.props.images[this.props.currentImage]}/></section>
</CSSTransitionGroup>
</div>
);
}
}
Please check working example here Demo
Props are immutable, that means you can not change them!
https://facebook.github.io/react/docs/components-and-props.html
If you want to save a new value build it as a state and use this.setState(...)
https://facebook.github.io/react/docs/state-and-lifecycle.html
I have two components one is app component and other one is sidebar component i have been using input field in side bar and i want to get the value of that input field in my app component on click how this could be possible ?
You can try lifting the state up.
Create a new component that will contain your two components. In that new component, create a function that you will pass as props inside the sidebar component.
class ContainerComponent extends React.Component {
constructor() {
super();
this.state = {
valueThatNeedsToBeShared: ''
}
}
handleChange(e) {
this.setState({valueThatNeedsToBeShared: e.target.value})
}
render() {
return (
<div>
<AppComponent value={this.state.valueThatNeedsToBeShared} />
<SidebarComponent handleChange={this.handleClick.bind(this)} value={this.state.valueThatNeedsToBeShared} />
</div>
)
}
}
const SidebarComponent = ({handleChange, value}) => <aside>
<input value={value} onChange={handleChange} />
</aside>
const AppComponent = ({value}) => <div>
value from sidebar: {value}
</div>
In pure react it is possible by adding callback from one component and use it into parent component to change their state and then send input value from parent's state to props of your second component