I am new to React and programming in general and have a question regarding componentWillReceiveProps method. Inside the LikesComponent class there is updateLikes method that does ReactDOM.render(<LikesComponent likes={this.props.likes+1} />, document.getElementById("app")). I thought that every time <ComponentName /> is called a new instance is going to be created, that's why I was very surprised when I saw a message logged by componentWillReceiveProps method. Instead, I was expecting to see messages logged by getDefaultProps, getInitialState and render methods. So, doesn't calling <ComponentName /> always create a new instance of that class? Could you please elaborate on when a new instance is created and when the instance is just updated? I am quite confused now. Thanks in advance.
body {
padding: 40px;
font-family: "helvetica neue", sans-serif;
}
.container {
width: 600px;
margin: auto;
color: black;
padding: 20px;
text-align: center;
}
.container h1 {
margin: 0;
padding: 20px;
font-size: 36px;
}
.container .btn {
border: 0;
padding: 15px;
margin: 10px;
width: 20%;
font-size: 15px;
outline: none;
border-radius: 3px;
color: #FFF;
font-weight: bold;
}
.btn.blue-btn {
background-color: #55acee;
box-shadow: 0px 5px 0px 0px #3C93D5;
}
.btn.blue-btn:hover {
background-color: #6FC6FF;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>React tutorial</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.7/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.7/react-dom.min.js"></script>
</head>
<body>
<div id="app"></div>
<script type="text/babel">
var LikesComponent = React.createClass({
updateLikes: function() {
ReactDOM.render(
<LikesComponent likes={this.props.likes+1}/>,
document.getElementById("app")
)
},
getDefaultProps: function() {
console.log("getDefaultProps");
return({
likes: 0
})
},
getInitialState: function() {
console.log("getInitialState");
return({
popular: false
})
},
componentWillReceiveProps: function(nextProps) {
console.log(nextProps);
console.log("Componentwillreceiveprops");
this.setState({
popular: nextProps.likes >= 10
})
},
render: function() {
console.log("Component Rendered");
return (
<div className="container">
<h1>{this.state.popular ? "I'm popular" : null}</h1>
<button className="btn blue-btn" onClick={this.updateLikes}>Likes: {this.props.likes}</button>
</div>
)
}
});
ReactDOM.render(
<LikesComponent />,
document.getElementById("app")
);
</script>
</body>
</html>
All <LikesComponent likes={5} /> does it create an object that means "we want to render a LikesComponent instance here with these props (likes=5)".
React will compare this new set of things to render with the set of things it has already rendered and think "hey I am already rendering a LikesComponent with (likes=4)".
Since the component type is the same and it is in the same place in the hierarchy, React will just update the instance with the new prop values instead of destroying the old instance and creating a new instance.
So basically:
New instances are created (mounted) when a component is rendered in a location in the hierarchy where an instance of that component currently does not exist.
Instances are reused when a component is rendered in a location in the hierarchy where an instance of the component already exists.
Instances are destroyed (unmounted) when, on a new render, a component of that type is not rendered to the location where the instance exists.
Related
I'm new to the site and React.
I built a project in React that works, constantly working.
I wanted to upload it to firebase, that's why I did npm run build, I could not upload it to firebase, and now npm start does not work either.
It drives me crazy, this is the error I get:
Error: Target container is not a DOM element.
3 | import App from './App';
4 | import * as serviceWorker from './serviceWorker';
5 |
> 6 | ReactDOM.render(<App />, document.getElementById('root'));
7 |
8 | // If you want your app to work offline and load faster, you can change
9 | // unregister() to register() below. Note this comes with some pitfalls.
I think I accidentally did something wrong with npm run build, firebase init, or firebase deploy. This is probably something really small
the public/index.html:
I did npm run build - maybe I changed it
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Welcome to Firebase Hosting</title>
<!-- update the version number as needed -->
<script defer src="/__/firebase/8.2.10/firebase-app.js"></script>
<!-- include only the Firebase features as you need -->
<script defer src="/__/firebase/8.2.10/firebase-auth.js"></script>
<script defer src="/__/firebase/8.2.10/firebase-database.js"></script>
<script defer src="/__/firebase/8.2.10/firebase-firestore.js"></script>
<script defer src="/__/firebase/8.2.10/firebase-functions.js"></script>
<script defer src="/__/firebase/8.2.10/firebase-messaging.js"></script>
<script defer src="/__/firebase/8.2.10/firebase-storage.js"></script>
<script defer src="/__/firebase/8.2.10/firebase-analytics.js"></script>
<script defer src="/__/firebase/8.2.10/firebase-remote-config.js"></script>
<script defer src="/__/firebase/8.2.10/firebase-performance.js"></script>
<!--
initialize the SDK after all desired features are loaded, set useEmulator to false
to avoid connecting the SDK to running emulators.
-->
<script defer src="/__/firebase/init.js?useEmulator=true"></script>
<style media="screen">
body { background: #ECEFF1; color: rgba(0,0,0,0.87); font-family: Roboto, Helvetica, Arial, sans-serif; margin: 0; padding: 0; }
#message { background: white; max-width: 360px; margin: 100px auto 16px; padding: 32px 24px; border-radius: 3px; }
#message h2 { color: #ffa100; font-weight: bold; font-size: 16px; margin: 0 0 8px; }
#message h1 { font-size: 22px; font-weight: 300; color: rgba(0,0,0,0.6); margin: 0 0 16px;}
#message p { line-height: 140%; margin: 16px 0 24px; font-size: 14px; }
#message a { display: block; text-align: center; background: #039be5; text-transform: uppercase; text-decoration: none; color: white; padding: 16px; border-radius: 4px; }
#message, #message a { box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24); }
#load { color: rgba(0,0,0,0.4); text-align: center; font-size: 13px; }
#media (max-width: 600px) {
body, #message { margin-top: 0; background: white; box-shadow: none; }
body { border-top: 16px solid #ffa100; }
}
</style>
</head>
<body>
<div id="message">
<h2>Welcome</h2>
<h1>Firebase Hosting Setup Complete</h1>
<p>You're seeing this because you've successfully setup Firebase Hosting. Now it's time to go build something extraordinary!</p>
<a target="_blank" href="https://firebase.google.com/docs/hosting/">Open Hosting Documentation</a>
</div>
<p id="load">Firebase SDK Loadingβ¦</p>
<script>
document.addEventListener('DOMContentLoaded', function() {
const loadEl = document.querySelector('#load');
// // π₯π₯π₯π₯π₯π₯π₯π₯π₯π₯π₯π₯π₯π₯π₯π₯π₯π₯π₯π₯π₯π₯π₯π₯π₯π₯π₯π₯π₯π₯π₯
// // The Firebase SDK is initialized and available here!
//
// firebase.auth().onAuthStateChanged(user => { });
// firebase.database().ref('/path/to/ref').on('value', snapshot => { });
// firebase.firestore().doc('/foo/bar').get().then(() => { });
// firebase.functions().httpsCallable('yourFunction')().then(() => { });
// firebase.messaging().requestPermission().then(() => { });
// firebase.storage().ref('/path/to/ref').getDownloadURL().then(() => { });
// firebase.analytics(); // call to activate
// firebase.analytics().logEvent('tutorial_completed');
// firebase.performance(); // call to activate
//
// // π₯π₯π₯π₯π₯π₯π₯π₯π₯π₯π₯π₯π₯π₯π₯π₯π₯π₯π₯π₯π₯π₯π₯π₯π₯π₯π₯π₯π₯π₯π₯
try {
let app = firebase.app();
let features = [
'auth',
'database',
'firestore',
'functions',
'messaging',
'storage',
'analytics',
'remoteConfig',
'performance',
].filter(feature => typeof app[feature] === 'function');
loadEl.textContent = `Firebase SDK loaded with ${features.join(', ')}`;
} catch (e) {
console.error(e);
loadEl.textContent = 'Error loading the Firebase SDK, check the console.';
}
});
</script>
</body>
</html>
I did not touch anything, everything worked great until I did npm run build.
The mistake I made was to run npm run build in public.
I switched to the previous index I had.
Firebase init should be done on the build folder and not on the public folder
You need to add a <div id="root"></div> inside your public/index.html. This is the DOM element where the React initial component is mounted with ReactDOM.render(<App />, document.getElementById('root'));.
As an alternative, you can create that directly from the JavaScript code, but the above solution is the usual and easiest one.
I'm creating a range slider and this is how it works: i've added onMouseDown event to the slider - in the function, i've attached onMouseMove and onMouseUp events to document - that's it! Simple. It works perfect on codepen.io but not locally.
I expected to see the log "down" in the console, then a lot of "move" and finally get the "up" message.
But the events showing up at the same time after release the mouse button.
console log:
Where could be the problem? How to fix if?
My project is created with create-react-app. I ran it in Google Chrome and Opera.
That's my code:
class Box extends React.Component {
downHandler = (e) => {
console.log('down');
document.addEventListener('mousemove', this.moveHandler);
document.addEventListener('mouseup', this.upHandler);
}
moveHandler = (e) => {
console.log('move');
}
upHandler = (e) => {
console.log('up');
document.removeEventListener('mousemove', this.moveHandler);
document.removeEventListener('mouseup', this.upHandler);
}
render() {
return (
<div className="container">
<div className="circle"
onMouseDown={this.downHandler}></div>
</div>
)
}
}
ReactDOM.render(<Box />, document.getElementById('root'));
.container{
width: 400px;
height: 7px;
background-color: #abc4f1;
position: relative;
margin-top: 120px;
}
.circle{
width: 20px;
height: 20px;
border-radius: 50%;
background-color: #eee;
box-shadow: 0 0 2px 2px rgba(0, 0, 0, .2);
position: absolute;
top: -7px;
left: calc(50% - 10px);
}
<div id="root"></div>
<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>
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'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>
Im trying to create a toggleable menu, but for some reason the hidden attribute won't work. It won't work for either value so I don't think its a data binding problem.
I'm using this method in other parts of my project and in other javascript liberies and frameworks it never gets any more complex, so I can't see what i'm doing wrong.
Any ideas?
<link rel="import" href="../../bower_components/polymer/polymer.html">
<link rel="import" href="../../bower_components/polymerfire/firebase-auth.html">
<link rel="import" href="../../bower_components/paper-menu/paper-menu.html">
<link rel="import" href="../../bower_components/paper-item/paper-item.html">
<dom-module id="user-account-menu">
<template>
<style>
img {
width: 72px;
height: 72px;
}
paper-menu {
position: absolute;
right: 15px;
width: 200px;
background: #A3A3A3;
}
</style>
<firebase-auth
id="auth"
signed-in="{{signedIn}}"
user="{{user}}">
</firebase-auth>
<!-- start the account dropdon -->
<div>
<img src="{{computePhotoURL()}}">
<paper-menu hidden$="{{show}}">
<paper-item>This is a menu item</paper-item>
<paper-item>[[show]]</paper-item>
</paper-menu>
</div>
</template>
<script>
Polymer({
is: 'user-account-menu',
properties: {
show: {
type: Boolean,
value: true
}
},
computePhotoURL: function() {
// get the users photo, if one doesn't exist,
// get the default user avatar
var photo;
try {
photo = this.user.photoURL;
return photo;
} catch(err) {
return 'https://developers.google.com/experts/img/user/user-default.png';
}
},
});
</script>
</dom-module>
update (css of paper-menu from dom):
element.style {
}
<style>β¦</style>
paper-menu {
position: absolute;
right: 15px;
width: 200px;
background: #A3A3A3;
}
<style>β¦</style>
:host {
display: block;
padding: 8px 0;
background: #ffffff;
color: #212121;
Another option is just to add this to your styles:
<style>
[hidden] {
display: none !important;
}
</style>
The display: block setting of the paper-menu component breaks the hidden functionality.
Using the hidden attribute is considered bad practice anyway because exactly this issue you just run into. It conflicts with the display setting.
I'd suggest using
<template dom-if="..."
or
add/remove a hidden class and a CSS rule .hidden { display: none; } (this also works in IE9 which doesn't recognize the hidden attribute.
try removing the ? from the hidden?="{{show}}"