Passing React State Between Imported Components - javascript

I am trying to pass state from parent to child using React, however both components are imported and therefor the state variables of the parent component are not declared.
I have two components both exported from the same file. The first component is a wrapper for the second. This component has a useEffect function which find its height and width and set these values to hook state.
export const TooltipWrapper = ({ children, ariaLabel, ...props }) => {
const [width, setWidth] = React.useState(0);
const [height, setHeight] = React.useState(0);
const ref = React.useRef(null);
React.useEffect(() => {
if (ref.current && ref.current.getBoundingClientRect().width) {
setWidth(ref.current.getBoundingClientRect().width);
}
if (ref.current && ref.current.getBoundingClientRect().height) {
setHeight(ref.current.getBoundingClientRect().height);
}
});
return <TooltipDiv>{children}</TooltipDiv>;
The next component which is exported from the same file looks like this
export const Tooltip = ({
ariaLabel,
icon,
iconDescription,
text,
modifiers,
wrapperWidth,
}) => {
return (
<TooltipContainer
aria-label={ariaLabel}
width={wrapperWidth}
>
<TooltipArrow data-testid="tooltip-arrow" modifiers={modifiers} />
<TooltipLabel
aria-label={ariaLabel}
>
{text}
</TooltipLabel>
</TooltipContainer>
);
};
The component Tooltip is expecting a prop wrapperWidth. This is where I want to pass in the width hook value from the TooltipWrapper component.
Both components are imported into my App component
import React from "react";
import { GlobalStyle } from "./pattern-library/utils";
import { Tooltip, TooltipWrapper } from "./pattern-library/components/";
function App() {
return (
<div className="App">
<div style={{ padding: "2rem", position: "relative" }}>
<TooltipWrapper>
<button style={{ position: "relative" }}>click </button>
<Tooltip
modifiers={["right"]}
text="changing width"
wrapperWidth={width}
/>
</TooltipWrapper>
</div>
</div>
);
}
Here I am told that width is not defined, which I expect since I'm not declaring width in this file.
Does anyone have an idea of how I can access the width and height state value for the parent component within the App file?

Render Props could work:
Add a renderTooltip prop to <TooltipWrapper>:
<TooltipWrapper renderTooltip={({ width }) => <Tooltip ...existing wrapperWidth={width} />}>
<button style={{ position: 'relative' }}>click</button>
</TooltipWrapper>
NB. ...existing is just the other props you are using with Tooltip
And then update the return of <TooltipWrapper>:
return (
<TooltipDiv>
{children}
props.renderTooltip({ width });
</TooltipDiv>
);

Related

How to animate child prop changes in react?

I'm trying to create a component that takes a child prop and when this child prop changes I'd like the old component that was the child prop to animate out of screen and the new component to animate in. I attempted to do this using react-spring using the code below, you can run it here https://codesandbox.io/s/react-spring-issue-898-forked-ril7mc?file=/src/App.js.
What am I missing here? Shouldn't the component animate left when it unmounts and animate in from the right when it mounts?
/** #jsx jsx */
import { useState } from "react";
import { useTransition, animated } from "react-spring";
import { jsx } from "#emotion/core";
function FadeTransition({ children, onClick }) {
const [toggle, setToggle] = useState(true);
const transition = useTransition(toggle, {
from: {
opacity: 0,
transform: "translateX(100%)"
},
enter: {
opacity: 1,
transform: "translateX(0)"
},
leave: { transform: "translateX(-100%)" }
});
return (
<div>
{transition(
(props, item) =>
item && <animated.div style={props}>{children}</animated.div>
)}
<button
onClick={() => {
setToggle(!toggle);
onClick();
}}
style={{ marginTop: "2rem" }}
>
Change
</button>
</div>
);
}
export default function App() {
const [currentComponent, setCurrentComponent] = useState(0);
let components = [<div>Thing 1</div>, <div>Thing 2</div>];
const onClick = () => {
setCurrentComponent((currentComponent + 1) % 2);
};
return (
<div style={{ paddingTop: "2rem" }}>
<FadeTransition onClick={onClick}>
{components[currentComponent]}
</FadeTransition>
</div>
);
}
I didn't use rect-sprint as much,There is issue with toggle state so when thing 1 is rendered animation component (FadeTransition) is rendered with setting up toggle state to true.
Now when you click on change button we are setting up the toggle button to opposite of current state which will be false this time and without unmounting animation component (FadeTransition) it will update dom and so the item which is thing 2.
As we are passing toggle in useTransition and if it's values will be false then animation will not be performed.
So I have updated snippet of yours, Here is the working example codesandbox

I want to only a single component update on state change

I want to update only a single element when using setState in a function
import { useState } from "react";
export default function App(){
const [state, setState] = useState("foo");
return(
<Component1/>
<Component2/>
<Component3/>
);
}
I need some way of updating one some of those elements, but not all.
In functional components, you can wrap your component with React.memo. With this way, React will memorize the component structure and on next render, if the props still the same, React does not render again and use the memorized one. For more information, https://reactjs.org/docs/react-api.html#reactmemo
Basically wrap with React.memo. Below code, when state1 change, Component2's render count won't increase because its props stays same. But when state2 change, both of them will render.
export const Component2 = React.memo(({ state2 }) => {
const renderCount = useRef(0);
renderCount.current = renderCount.current + 1;
return (
<div style={{ margin: 10 }}>
Component2(with React.memo): <b>Rendered:</b> {renderCount.current} times,{" "}
<b>State2:</b> {state2}
</div>
);
});
export default function App() {
const [state1, setState1] = useState(1);
const [state2, setState2] = useState(1);
return (
<div className="App">
<div onClick={() => setState1((state) => state + 1)}>
Click to change state1
</div>
<div onClick={() => setState2((state) => state + 1)}>
Click to change state2
</div>
<Component1 state1={state1} />
<Component2 state2={state2} />
</div>
);
}
I created a sandbox for you to play. Click the buttons and see React.memo in action. https://codesandbox.io/s/xenodochial-sound-gug2x?file=/src/App.js:872-1753
Also, with Class Components, you can use PureComponents for the same purpose. https://reactjs.org/docs/react-api.html#reactpurecomponent

Passing props to makeStyles react

I have this component
const MyComponent = (props) => {
const classes = useStyles(props);
return (
<div
className={classes.divBackground}
backgroundImageLink={props.product?.image}
sx={{ position: "relative" }}
></div>
);
};
export default MyComponent;
I am trying to pass backgroundImage link in props and trying to put into makeStyles
export default makeStyles(props => ({
divBackground:{
background:`url("${props.backgroundImageLink}")`,
}
}));
But this does not works
& I am getting this warning in console
index.js:1 Warning: React does not recognize the `backgroundImage` prop on a DOM element. If you intentionally want it to appear in the DOM as a custom attribute, spell it as lowercase `backgroundimage` instead. If you accidentally passed it from a parent component, remove it from the DOM element.
You're not supposed to pass arbitrary attributes to the native elements (div in this case) because it doesn't do anything. The prop only works when passed in useStyles:
export default makeStyles({
divBackground: {
background: props => `url("${props.product?.image}")`,
}
});
Usage
const MyComponent = (props) => {
// you only need to pass the props here. useStyles will then use the
// prop.product.image to create the background property, generate a
// stylesheet and return the class name for you.
const classes = useStyles(props);
return (
<div
className={classes.divBackground}
// remove this line -----> backgroundImageLink={props.product?.image}
sx={{ position: "relative" }}
></div>
);
};
const MyComponent = (props) => {
const classes = useStyles(props)();
return (
<div
className={classes.divBackground}
backgroundImageLink={props.product?.image}
sx={{ position: "relative" }}
></div>
);
};
export default MyComponent;
then :
export default useStyles=(props)=>makeStyles(()=> ({
divBackground:{
background:`url("${props.backgroundImageLink}")`,
}
}));

How to set parent node style in React?

I try to set parent node background color like this, but does not work, why?
<div
onLoad="this.parentNode.style.background = 'yellow';"
>
Found here the inspiration: https://www.w3schools.com/jsref/prop_node_parentnode.asp
The main challange here is this: Make absolute positioned div expand parent div height
I have a parent div with position relative and a child div with position absolute, and I would set the parent height the same as the child.
This post above tells, it can be done only with Javascript, but there is no exact steps for it. Would you help? And I have React on top of it.
I thought if I can set color, I will able to set height also. Set color seemed a bit easier in first turn.
Why don't you use useRef hook to get the ref of the node?
const node = useRef(null);
...
<div ref={node} onLoad={() => {
node.current.parentNode.style.background = 'yellow';
}} />
Unless there's a really good reason to access the div directly, or as a ref, why not just use props?
Edit: I've included an example of how to use useRef to set the parent height using the child's calculated height.
import React, { useState, useEffect, useRef } from "react";
const ParentComponent = () => {
const [height, setHeight] = useState(null)
const node = useRef(null)
return (
<div
style={{
...(height ? { height: `${height}px` } : {}),
backgroundColor: 'yellow',
position: 'relative'
}}
>
<MyComponent setHeight={setHeight} node={node} />
</div>
)
}
const MyComponent = ({ setHeight, node }) => {
useEffect(() => {
const childHeight = node.current ? node.current.offsetHeight : 0
setHeight(childHeight), [node.current]
})
// sample parent updates when child updates
const [content, setContent] = useState(['child'])
useEffect(
() => setTimeout(() => setContent([...content, 'child']), 1000),
[content]
)
return (
<div style={{ position: 'absolute', top: 0, left: 0 }} ref={node}>
{content.map((item, i) => (
<div key={i}>{item + i}</div>
))}
</div>
)
}

In react, how to get noticed when children change?

I am making this class called Scrollable which enables scrolling if the width/height of the children elements exceeds a certain value. Here is the code.
import React, { Component } from 'react';
const INITIAL = 'initial';
class Scrollable extends Component {
render() {
let outter_styles = {
overflowX: (this.props.x? 'auto': INITIAL),
overflowY: (this.props.y? 'auto': INITIAL),
maxWidth: this.props.width || INITIAL,
maxHeight: this.props.height || INITIAL,
};
return (
<div ref={el => this.outterEl = el} style={outter_styles}>
<div ref={el => this.innerEl = el}>
{this.props.children}
</div>
</div>
);
}
};
export default Scrollable;
// To use: <Scrollable y><OtherComponent /></Scrollable>
This works great. Except now I wish to add one more functionality which makes the scrollable always scroll to the bottom. I have some idea of how to do it:
this.outterEl.scrollTop = this.innerEl.offsetHeight;
But this only need to be called when this.props.children height changes. Is there any idea on how to achieve this?
Thanks in advance.
I would recommend a package element-resize-detector. It is not React-specific but you can easily build a high-order component around it or integrate your Scrollable component with it.
Now I have an idea of solving this.
Since I am using react-redux. The problem is that I could not use lifecycle hooks on this Scrollable component since this.props.children might not necessarily be changed when the content is updated.
One way to achieve this is to make Scroll component aware of the corresponding values in the redux state. So that when that relevant value is updated, we can scroll down to the bottom.
Scrollable component:
import React, { Component } from 'react';
const INITIAL = 'initial';
class Scrollable extends Component {
componentWillUpdate(){
if(this.props.autoScroll){
// only auto scroll when the scroll is already at bottom.
this.autoScroll = this.outterEl.scrollHeight - this.outterEl.scrollTop - Number.parseInt(this.props.height) < 1;
}
}
componentDidUpdate(){
if(this.autoScroll) this.outterEl.scrollTop = this.outterEl.scrollHeight;
}
render() {
let styles = {
overflowX: (this.props.x? 'auto': INITIAL),
overflowY: (this.props.y? 'auto': INITIAL),
maxWidth: this.props.width || INITIAL,
maxHeight: this.props.height || INITIAL,
};
return (
<div ref={el => this.outterEl = el} style={styles}>
<div ref={el => this.innerEl = el}>
{this.props.children}
</div>
</div>
);
}
};
export default Scrollable;
Scrollable container:
import { connect } from 'react-redux';
import Scrollable from '../components/Scrollable';
const mapStateToProps = (state, ownProps) => Object.assign({
state: state[ownProps.autoScroll] || false
}, ownProps);
export default connect(mapStateToProps)(Scrollable)
With this, Scrollable's life cycle hooks will be called when the corresponding state changes.

Categories

Resources