How to get an element By ID inside React.Fragment? - javascript

I am trying to catch an element using an ID in React, but I could not.
render() {
//Looping through all menus
let menuOptions = this.props.menuLists.map(menuList => {
return (
<li className="nav-item active" key={menuList.name}>
<a className="nav-link" href={menuList.anchorLink}>
{menuList.name}
</a>
</li>
);
});
return (
<React.Fragment>
<div id="animSec">
<canvas id="myCanvas" />
</div>
</React.Fragment>
);
}
I want to call the myCanvas ID.
I tried by this.refs, but it's sent me undefined. I also tried react-dom:
ReactDOM.findDOMNode(this.refs.myCanvas);
but get nothing. I call findDOMNode on the constructor first and I tried componentDidMount but get nothing.

You can use Callback Refs to retrieve the ID:
class YourComponent extends React.Component {
constructor(props) {
super(props)
this.canvas = null
}
componentDidMount() {
console.log(this.canvas.id) // gives you "myCanvas"
}
render() {
return (
<React.Fragment>
<div id="animSec">
<canvas id="myCanvas" ref={c => {this.canvas = c}}></canvas>
</div>
</React.Fragment>
)
}
}
alternatively, for React v16.3+, you can use createRef():
constructor(props) {
super(props)
this.canvas = React.createRef()
}
componentDidMount() {
console.log(this.canvas.current.id) // gives you "myCanvas"
}
render() {
return (
<React.Fragment>
<div id="animSec">
<canvas id="myCanvas" ref={this.canvas}></canvas>
</div>
</React.Fragment>
)
}

If you set up a ref in your constructor as this.canvasRef = React.createRef()
and apply it to your canvas as
<React.Fragment>
<div id="animSec">
<canvas ref={this.canvasRef}></canvas>
</div>
</React.Fragment>
You should be able to access the element directly. You can console log or check your dev tools to view the ref value. And (to my best knowledge), it's best practice to use Refs compared to querySelectors in React. You might also check out this post to see if you can work around with a method on canvas.

You can try using document.getElementById('myCanvas') in a custom function or any life cycle method to target the desired element.
Note that this doesn't work if you're doing SSR with a framework like next.js because there is no document object in the server.

Related

more efficiency way for performance in React Component Mounting

I've made many Modals in React.
I found 2 ways of making Modal.
The first one is like this
class Modal extends React.Component {
componentDidMount(){ console.log('DidMount') };
componentDidUpdate(){ console.log('DidUpdate') };
componentWillUnmount(){ console.log('WillUnmount') };
render(){
return (
<React.Fragment>
<div className="overlay" />
<div className="Modal>
This is Modal.
</div>
</React.Fragment>
)
}
}
class App extends React.Component {
state = {
isModalOpen: false
}
toggleModal = () => this.setState({ isModalOpen: !this.state.isModalOpen })
render(){
return (
<div className="App">
<button onClick={this.toggleModal}>Toggle</button>
{ this.state.isModalOpen ? <Modal /> : null }
</div>
)
}
}
This one repeats componentDidMount()&componentWillUnmount() when the state changed.
Let's see the other one.
class Modal extends React.Component {
componentDidMount(){ console.log('DidMount') };
componentDidUpdate(){ console.log('DidUpdate') };
componentWillUnmount(){ console.log('WillUnmount') };
render(){
return (
<React.Fragment>
{ props.isOpen ? <div className="overlay" /> : null }
{ props.isOpen ? <div className="Modal">This is Modal</div> : null }
</React.Fragment>
)
}
}
class App extends React.Component {
state = {
isModalOpen: false
}
toggleModal = () => this.setState({ isModalOpen: !this.state.isModalOpen })
render(){
return (
<div className="App">
<button onClick={this.toggleModal}>Toggle</button>
<Modal isOpen={this.state.isModalOpen} />
</div>
)
}
}
This one would not call componentWillUnmount().
It would call componentDidUpdate() when the state changed.
I wonder which one is a better way for the performance or something else.
Thank you in advance.
React.Fragment is a little bit fast and uses less memory because it doesn't need to create an extra DOM node.
With that being said unless your application is big and complex I wouldn't worry about it. I'm not exactly sure what wrapping the modal div in a React.Fragment is achieving.
You question is a bit confusing but I will attempt to clarify a few things.
Regarding your comment: This one would not call componentWillUnmount(). It will not call the the cWU() method because you are always rendering it by using this <Modal isOpen={this.state.isModalOpen} /> within your render. Now wether it appears or not based on your isOpen prop it's a different issue. On the other hand if you had something like {isThisPropertyTrue ? <Modal isOpen={this.state.isModalOpen} /> : null}, and your isThisPropertyTrue was toggling from true to false, then you would notice the console.log in your unmount hook.
This method { this.state.isModalOpen ? <Modal /> : null } has a better performance since the modal is render upon request.

Load the component when all data has ready

I'm using React with Redux.
In this example I have my class with mapStateToProps and mapDispatchToProps
class EnigmaPage extends Component {
constructor(props){
super(props);
}
componentDidMount() {
this.props.authCheckState();
}
readUserData() {
this.props.loadLevel(this.props.userId);
}
render(){
return (
<div className={classes.EnigmaPage}>
<div className={classes.Header}>
<div>
<LevelInfo
difficulty={this.props.level.difficulty}
level={this.props.level.level}
readUserData={this.readUserData()}
/>
</div>
</div>
</div>
)
}
}
const mapDispatchToProps = dispatch => {
return {
authCheckState: () => dispatch(actions.authCheckState()),
getLevel: () => dispatch(actions.getLevel()),
loadLevel:(id) => dispatch(actions.loadLevel(id))
};
}
const mapStateToProps = state => {
return {
userId:state.auth.user,
level:state.level.level
}
}
I wanna push to my component LevelInfo the values difficulty and level but these 2 data arrive from getLevel() that is an http request with delay.
The page loads before receiving all the data from the http call.
I'm looking a way to wait to load the component LevelInfo or reload the component when the data are all ready.
You need to tell your component that will wait for the data needed to render your Level component, so into your EnigmaPage component write as follow
render(){
const { level } = this.props;
if (!level) { return <LoadingComponentSpinner />; } // this will render only when level attr is not set, otherwise will render your `LevelInfo` component
return (
<div className={classes.EnigmaPage}>
<div className={classes.Header}>
<div>
<LevelInfo
difficulty={this.props.level.difficulty}
level={this.props.level.level}
readUserData={this.readUserData()}
/>
</div>
</div>
</div>
)
}
I hope it can help you.
We don't make our components wait, we let the initial rendering happens but render the target component with a conditional expression.
render() {
return (
<div className={classes.EnigmaPage}>
<div className={classes.Header}>
<div>
{this.props.level && (
<LevelInfo
difficulty={this.props.level.difficulty}
level={this.props.level.level}
readUserData={this.readUserData()}
/>
)}
</div>
</div>
</div>
);
}
So, here we are checking if this.props.level is defined. When it is undefined React does not render anything, after getting the data LevelInfo component is rendered. You can change conditional rendering logic. You can render a Loading component maybe instead of nothing. But, at the end of the day, you will render your component conditionally.
try to use conditional rendering:
isDataReady() {
if(this.props.level.difficulty && this.props.level.level)
return true;
return false;
}
render(){
return (
<div className={classes.EnigmaPage}>
<div className={classes.Header}>
<div>
{this.isDataReady() ? <LevelInfo
difficulty={this.props.level.difficulty}
level={this.props.level.level}
readUserData={this.readUserData()}
/>
: <div> </div>}
</div>
</div>
</div>
);
}
in case your data is not ready you can display anything you want, for simplicity here I just added an empty <div>.
Johuder Gonzalez has talked about the Spinner. The Material UI has a lot of examples, which is easy to apply. Please look the followings.
Material UI Progress

How to use MDCRipple.attachTo on multiple buttons in React Component

I have a simple React component that renders multiple buttons from an array in my props. I'm applying the ripple on DidMount, however, it's only attaching on the first button, the rest are being ignored. It looks like the attachTo only takes the first element. Is there another way to attach to all the buttons on didmount?
class NavBar extends Component {
constructor(props) {
super(props);
this.state = {
links
};
}
componentDidMount() {
MDCRipple.attachTo(document.querySelector('.mdc-button'));
}
render() {
return (
<section>
{this.state.links.map((link, i) => {
return (
<StyledLink key={i} to={link.url}>
<StyledButton className="mdc-button">
<StyledIcon className="material-icons">{link.icon}</StyledIcon>
<StyledTypography className="mdc-typography--caption">
{link.title}
</StyledTypography>
</StyledButton>
</StyledLink>
);
})}
</section>
);
}
}
Final markup
<a class="sc-iwsKbI bhaIR">
<button class="mdc-button sc-dnqmqq ksXmjj mdc-ripple-upgraded" style="--mdc-ripple-fg-size:57.599999999999994px; --mdc-ripple-fg-scale:2.1766951530355496; --mdc-ripple-fg-translate-start:-7.799999999999997px, 19.200000000000003px; --mdc-ripple-fg-translate-end:3.200000000000003px, 19.200000000000003px;">
...content
</button>
</a>
<a class="sc-iwsKbI bhaIR">
<button class="mdc-button sc-dnqmqq ksXmjj">
...content
</button>
</a>
Updated
I was able to find a way to use the attachTo with each button, but it still seems like there's a better way.
I changed by componentDidMount() to:
componentDidMount() {
this.state.links.forEach((link) => {
MDCRipple.attachTo(document.getElementById(`button-navbar-${link.id}`));
});
}
and then changed my render to
<StyledButton id={`button-navbar-${link.id}`} className="mdc-button">
Is there a way to do this without having to iterate through the array?
The react way to do this is to write component that injects the necessary logic.
class RippleButton extends Component {
const handleRef = elem => MDCRipple.attachTo(elem);
render() {
return (
<StyledButton {...this.props} ref={this.handleRef} />
);
}
}
Then render that component instead of your original StyledButton component and it will call the MDCRipple.attachTo() itself with its ref.
Depending on how the StyledButton is implemented you may need to use another prop to get the ref to the underlying DOM element. You did not provide enough of your code to exactly know this.

React fetch img from slow api

I am sure that my question is obvious but I cannot find simple answer anywhere. I am not familiar with redux/flux so I don't know if I need to learn them to achieve my goal.
I get from my server urls to images I need to display on the component. I want to display loader till the image is fetched.
What is the best (and easiest) way to do that? Is necessary to use flux/redux?
May I use just fetch(image_URL).then... promise?
For now on just call url while rendering img html tag:
{this.props.data.images.map(img=>{
return(
<img src={img.url}/>
)})
how to manage async of this task? I already use apollo to fetch local db data. May I use apollo for fetching external data?
The easiest way is to define a loading flag and use it to determine if the loader should be rendered. It seems that your fetch logic somewhere else but the idea is the same.
class YourComponent() extends Component {
constructor() {
this.state = {
isLoading: false,
};
}
componentWillMount() {
this.setState({isLoading:true});
fetch('image_URL')
.then(res => {
this.setState({
images: res.images,
isLoading: false,
})
})
}
render() {
const { isLoading , images} = this.state;
if (isLoading) {
return <YourLoaderComponent />
}
return (
<div>
{images.map(img => <img src={img.url} />)}
</div>
);
}
}
You can make a use of onLoad react callback on the <img/> tag.
Tutorial:
Define React Component <LoadedComponent /> which will be a spinner.
Then you can define another React Component <ImageComponent /> which will have a default imageLoaded state set to false.
If imageLoaded is false, <ImageComponent/> will render img with width and height 0px.
The <img /> tag has onLoad binding to function imageLoaded() which then sets the imageLoaded state to true. When the state changes onLoad(when image finished loading) of <img/>, <ImageComponent/> automatically rerenders and it renders only <img/> with normal width and height.
import React from "react";
import { render } from "react-dom";
const LoaderComponent = () => (
<img
width="300"
height="300"
alt="Loading spinner"
src="http://blog.teamtreehouse.com/wp-content/uploads/2015/05/InternetSlowdown_Day.gif"
/>
);
const hiddenImageStyle = {
width: 0,
height: 0
};
class ImageComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
loaded: false
};
}
imageLoaded() {
this.setState({
loaded: true
});
}
render() {
if (this.state.loaded) {
return(
<div>
<img alt="Cat" src={this.props.url} width="300" height="300" />
</div>
)
}
return (
<div>
<img
alt="Cat"
src={this.props.url}
width="300"
height="300"
onLoad={() => this.imageLoaded()}
style={hiddenImageStyle}
/>
<LoaderComponent />
</div>
);
}
}
const imagesUrls = [
"https://c1.staticflickr.com/5/4200/34055408873_e9bf494e24_k.jpg",
"https://c1.staticflickr.com/5/4536/37705199575_ded3cf76df_c.jpg"
];
class App extends React.Component {
render() {
return (
<div>
{imagesUrls.map((url, index) => (
<ImageComponent key={index} url={url} />
))}
</div>
);
}
}
render(<App />, document.getElementById("root"));
Here you can see a working example: https://codesandbox.io/s/zwz9o84kn3
If you have a good Internet speed you will probably not notice the spinner. To see the spinner, the best is to open preview of the sandbox in a separate tab
and then open chrome dev tools and in the Network tab check disable cache and set a preset to Slow 3G
After refreshing you will notice loadining spinner, until the image will load

React interract with dom

How can I 'talk' with dom elements with react?
For example - I need to bind some actions with some js lib
Both approaches returns undefined for some reason
componentDidMount() {
const element1 = document.querySelector('.home-container')
const element2 = ReactDOM.findDOMNode(this);
// returns undefined, undefined
console.log(element1.length, element2.length);
}
render() {
return (
<div className="home-container">
...
</div>
)
}
But console.log(element1) returns html from render itself though
How can I work with them?
And what correct lifecycle method for this?
You use "refs":
<div ref={e => {this.div = el}} ...>...</div>
Once your component has rendered, with the above, its div property (you can use any name you want) will refer to the div element that was rendered.
Here's an example largely copied from the link above that focuses a text input when it's rendered:
class AutoFocusTextInput extends React.Component {
componentDidMount() {
this.textInput.focus();
}
render() {
return (
<input type="text"
ref={(input) => { this.textInput = input; }} />
);
}
}
ReactDOM.render(
<AutoFocusTextInput />,
document.getElementById("root")
);
<div id="root"></div>
<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>

Categories

Resources