I have a div whose content changes. Whenever the content changes, the div should animate its width. I tried the following and it does not work. Any pointers will be helpful.
class App extends React.Component {
constructor() {
super();
this.state = {
text: 'Hello world'
}
}
changeText() {
this.setState({
text: 'Very long text stretching across screen'
});
}
render() {
return <div>
<div className="text">{this.state.text}</div>
<button onClick={this.changeText.bind(this)}>Change</button>
</div>;
}
}
ReactDOM.render( < App / > , document.getElementById('root'));
.text {
display: inline-block;
background-color: cyan;
transition: width 2000s;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
I could do it like this:
class App extends React.Component {
constructor() {
super();
this.state = {
text: 'Hello world'
}
this.myRef = React.createRef();
}
changeText() {
this.setState({
text: 'Very long text stretching across screen'
});
this.myRef.current.style.width = '250px';
}
render() {
return <div>
<div ref={this.myRef} className="text">{this.state.text}</div>
<button onClick={this.changeText.bind(this)}>Change</button>
</div>;
}
}
ReactDOM.render( < App / > , document.getElementById('root'));
.text {
display: inline-block;
background-color: cyan;
overflow: hidden;
height: 18px;
width: 80px;
transition: width 1.5s;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
You can do this with your current code by specifying initial and final width and setting the style class dynamically based on button click.Please find code below:
https://codesandbox.io/s/fervent-dawn-32bfl
Also note that you have given the transition as "2000s" which is 2000 seconds :-)
Note: Marudhupandiyan's comment in your original question also provides a solution where the width is determined dynamically, which is a much more optimal solution if you don't know the width beforehand. You can integrate that logic into the code I have provided above, if required.
Related
I have the following React test app:
class MemoTestApp extends React.Component {
constructor(props) {
super(props)
this.state = {
showOverlay: false,
}
}
render() {
return (
<div>
<div>
<MemoComponent str="Hello World" />
</div>
<div>
<input type="button" onClick={() => this.setState({showOverlay: true})} value="Show Overlay"/>
</div>
{this.state.showOverlay && (
<div className="overlay">
<h2>Overlay</h2>
<MemoComponent str="Hello World" />
</div>
)}
</div>
)
}
}
const Component = (props) => {
console.info('render ' + props.str);
return <div>{props.str}</div>;
}
const MemoComponent = React.memo(Component);
ReactDOM.render(<MemoTestApp />, document.querySelector("#app"))
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
position: relative;
min-height: 200px;
}
.overlay {
position: absolute;
padding: 20px;
left: 0;
top: 0;
right: 0;
bottom: 0;
background-color: white;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="app"></div>
As you can see, there is a memoized functional component which is rendered twice with the same props. The first rendering takes place immediately, the second one after the user presses the button.
However, the component really is rendered twice, as you can see in the console. React.memo prevents the second rendering of the first instance of the component, but the second instance seems to "now know" that this component has already been rendered with the given props.
Is there a way to make Memoization "global", i.e. so that rendered outputs are shared between different instances of the component?
What is the reason that React.memo is not global by default?
Short answer: Components are reusable, this is by design.
They may have their own state, for example a counter. Or they have side effects, e.g. own intervals, custom logic depending on the DOM nodes.
For that reason, they have to be separate "instances" depending where they live on the DOM (parent node, index or key), and are separately rendered. The result is then memoized per component "instance".
I used the code by #WitVault to display an overlay when my button 'add data' is clicked; It works fine but the overlay loads whenever the page is loaded. I have to close the overlay to see my main content.
I want the overlay to appear only when the button is clicked and not when the page loads. Here's my code:
class registration extends Component{
constructor(props) {
super(props);
this.state = {
style : {
width : "100%"
}
};
this.openNav = this.openNav.bind(this);
this.closeNav = this.closeNav.bind(this);
}
componentDidMount() {
document.addEventListener("click", this.closeNav);
}
componentWillUnmount() {
document.removeEventListener("click", this.closeNav);
}
openNav() {
const style = { width : "100%" };
this.setState({ style });
document.body.style.backgroundColor = "rgba(0,0,0,0.4)";
document.addEventListener("click", this.closeNav);
}
closeNav() {
document.removeEventListener("click", this.closeNav);
const style = { width : 0 };
this.setState({ style });
document.body.style.backgroundColor = "#F3F3F3";
}
render(){
return(
<div class="encloser" id="test1">
<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>
<div class="addbuttondiv">
<button class="addbutton" onClick={this.openNav}>Add Data</button>
<div ref="snav" className = "overlay" style={this.state.style}>
<div className = "sidenav-container">
<div className = "text-center">
<h2>Form</h2>
<p>This is a sample input form</p>
</div>
<a href = "javascript:void(0)"
className = "closebtn"
onClick = {this.closeNav}
>
×
</a>
</div>
</div>
</div>
</div>
//some html content
</div>
);
}
}
export default registration;
CSS:
/* The Overlay (background) */
.overlay {
/* Height & width depends on how you want to reveal the overlay (see JS below) */
height: 100%;
width: 0;
position: fixed; /* Stay in place */
z-index: 1; /* Sit on top */
left: 0;
top: 0;
background-color: rgb(0,0,0); /* Black fallback color */
background-color: rgba(0,0,0, 0.9); /* Black w/opacity */
overflow-x: hidden; /* Disable horizontal scroll */
transition: 0.5s; /* 0.5 second transition effect to slide in or slide down the overlay (height or width, depending on reveal) */
}
.overlay-content {
position: relative;
top: 25%; /* 25% from the top */
width: 100%; /* 100% width */
text-align: center; /* Centered text/links */
margin-top: 30px; /* 30px top margin to avoid conflict with the close button on smaller screens */
}
.overlay .closebtn {
position: absolute;
top: 20px;
right: 45px;
font-size: 60px;
}
/* When the height of the screen is less than 450 pixels,
change the font-size of the links and position the close button again, so they don't overlap */
#media screen and (max-height: 450px) {
.overlay a {font-size: 20px}
.overlay .closebtn {
font-size: 40px;
top: 15px;
right: 35px;
}
}
You have to set width as "0" in constructor like below:
class registration extends Component{
constructor(props) {
super(props);
this.state = {
style : {
width : "0"
}
};
this.openNav = this.openNav.bind(this);
this.closeNav = this.closeNav.bind(this);
}
componentDidMount() {
document.addEventListener("click", this.closeNav);
}
componentWillUnmount() {
document.removeEventListener("click", this.closeNav);
}
openNav() {
const style = { width : "100%" };
this.setState({ style });
document.body.style.backgroundColor = "rgba(0,0,0,0.4)";
document.addEventListener("click", this.closeNav);
}
closeNav() {
document.removeEventListener("click", this.closeNav);
const style = { width : 0 };
this.setState({ style });
document.body.style.backgroundColor = "#F3F3F3";
}
render(){
return(
<div class="encloser" id="test1">
<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>
<div class="addbuttondiv">
<button class="addbutton" onClick={this.openNav}>Add Data</button>
<div ref="snav" className = "overlay" style={this.state.style}>
<div className = "sidenav-container">
<div className = "text-center">
<h2>Form</h2>
<p>This is a sample input form</p>
</div>
<a href = "javascript:void(0)"
className = "closebtn"
onClick = {this.closeNav}
>
×
</a>
</div>
</div>
</div>
</div>
//some html content
</div>
);
}
}
export default registration;
I'm trying to implement the solution provided in this answer.
In the second step, I need to get the size of a div defined in my component in my component's componentDidMount. Many threads on StackOverflow have proposed using refs for this purpose. However, I have difficulty understanding how to implement it. Would you please give me a piece of code as an example to learn how to get the element's size after it is mounted in componentDidMount?
You can create a ref with createRef and store it in an instance variable which you pass to the ref prop of the element you want a reference to.
This ref will then have a reference to the node in the current property after the component has mounted.
Example
class App extends React.Component {
ref = React.createRef();
componentDidMount() {
const { current } = this.ref;
console.log(`${current.offsetWidth}, ${current.offsetHeight}`);
}
render() {
return <div ref={this.ref} style={{ width: 200, height: 200 }} />;
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://unpkg.com/react#16/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.production.min.js"></script>
<div id="root"></div>
By creating a ref you have access to all element's properties like offsetWidth.
https://developer.mozilla.org/en-US/docs/Web/API/Element
class TodoApp extends React.Component {
constructor(props) {
super(props)
this.state = {
items: [
{ text: "Learn JavaScript" },
{ text: "Learn React" },
{ text: "Play around in JSFiddle" },
{ text: "Build something awesome" }
]
}
this.myRef = React.createRef();
}
componentDidMount() {
console.log(this.myRef.current.offsetWidth);
}
render() {
return (
<div ref={this.myRef}>
{this.state.items.map((item, key) => (
<div className="rootContainer" key={key}>
<div className="item">{item.text}</div>
</div>
))}
</div>
)
}
}
ReactDOM.render(<TodoApp />, document.querySelector("#app"))
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
}
.rootContainer {
display: inline-block;
}
.item {
display: inline-block;
width: auto;
height: 100px;
background: red;
border: 1px solid green;
}
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="app"></div>
I'm trying to dynamically render font awesome icon in myself written checkbox component. When I'm trying to update state of a with font awesome icon after clicking on it it is not updating. I've tried to move render to separate function and tried to use react-fontawesome but nothing helps. The state is updating but font awesome icons are the same svg code in html.
...
state = {
checked: this.props.checked
}
toggleCheck = () => {
this.setState({ checked: !this.state.checked });
};
render () {
const iconUnchecked = 'far fa-square';
const iconChecked = 'fas fa-check-square';
const iconClass = this.state.checked ? iconChecked : iconUnchecked;
return (
<span onClick={this.toggleCheck}>
<i className={iconClass} />
</span>
);
}
As I understood the font awesome js manipulates DOM and React manipulates virtual DOM. When font awesome js doing its own stuff React can't rerender it after state change. Im still on React 15 and maybe it's not the issue in React 16. I just found a solution for me to put every with font awesome in a div with unique key. This way React see that div must change because key was changed.
Try that one https://jsfiddle.net/n5u2wwjg/30533/
For me seems to work
Check exactly jsfiddle it works, but not a snippet. Snippet is just to satisfy editor.
class TodoApp extends React.Component {
constructor(props) {
super(props);
this.state = {
checked: this.props.checked
}
this.toggleCheck = this.toggleCheck.bind(this);
}
toggleCheck() {
this.setState({ checked: !this.state.checked });
}
render() {
const iconUnchecked = 'far fa-square';
const iconChecked = 'fas fa-check-square';
let iconClass = this.state.checked ? iconChecked : iconUnchecked;
return (
<span onClick={this.toggleCheck}>
<i className={iconClass} />
</span>
)
}
}
ReactDOM.render(<TodoApp />, document.querySelector("#app"))
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
}
li {
margin: 8px 0;
}
h2 {
font-weight: bold;
margin-bottom: 15px;
}
.done {
color: rgba(0, 0, 0, 0.3);
text-decoration: line-through;
}
input {
margin-right: 5px;
}
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.0.10/css/all.css" integrity="sha384-+d0P83n9kaQMCwj8F4RJB66tzIwOKmrdb46+porD/OvrJ+37WqIM7UoBtwHO6Nlg" crossorigin="anonymous">
<div id="app"></div>
I too faced the same trouble. As 200Ok mentioned it correctly, svg--inline-fa is not a virtual DOM so it never gets updated. The best way to solve the problem is to wrap the font awesome elements that would contain the decisive classes.
I have an outer parent <div /> container that contains a <Table /> element. I'd like to be able to scroll the <Table /> on the onWheel event of the parent <div /> element, even if the mouse is not currently over the <Table />.
I have a ref to the table element and an event handler listening for the onWheel event but I haven't been able to figure out how to forward that event down to the table.
Because I guess that you want to scroll the table body, you can try with this.
class Table extends React.Component {
constructor() {
super();
this.callback = this.callback.bind(this);
this.body = null;
}
callback(ev) {
this.body.scrollTop += ev.deltaY;
}
render() {
return <div onWheel={this.callback}>
<table>
<tbody ref={c => this.body = c}>
{[1, 2, 3, 4, 5, 6,].map(i => <tr>
<td>{i}</td>
</tr>)}
</tbody>
</table>
</div>;
}
}
ReactDOM.render(<Table />, document.getElementById('root'));
tbody {
display: block;
height: 3rem;
overflow: scroll;
}
tr:nth-child(2n+1) {
background-color: #ddf;
}
tr:nth-child(2n) {
background-color: #eef;
}
table {
margin: auto;
border-collapse: collapse;
}
td {
width: 5rem;
text-align: center;
font-family: sans-serif;
color: #00f;
}
div {
width: 100%;
display: block;
text-align: center;
background-color: #99f;
}
<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>
I made a codePen illustrating a scroll redirection
This will listen wheelEvent on a parent <div>(the one with a red background), disable the default scrolling behavior (evt.preventDefault()) then set the scrollTop position of another <div>.
Here's the component code :
class RedirectScroll extends React.Component {
parentOnScroll = (evt) => {
evt.preventDefault();
const scrollTo= (evt.deltaY) + this.box.scrollTop;
this.box.scrollTop = scrollTo;
}
render() {
return (
<div className="parent" onWheel={this.parentOnScroll}> // Listen scrolls on this div
<div className="scrollablebox" ref={(box) => this.box = box}>
// Some content
</div>
</div>
);
}
}
I hope this is what you're looking for.