Home component only loads on refresh (not on initial load), race condition? - javascript

I am having a situation where, when loading the page for the first time, the images that should be loaded inside Home.js aren't loaded, until refresh is pushed on the browser (Chrome). This is happening in development server and live production server, and sometimes one must hit refresh multiple times before images show up.
Please note that I am new to all web development as well as React as most of my experience is in Android development.
I'm thinking it's something to do with the way I load the images onto the Home component. With logging I found that the images were loaded before the page was loaded (used componentDidMount()), so I'm looking for any solutions available.
Thanks and here's the code...
Main.js:
import React from 'react';
import { withRouter, Route, NavLink, HashRouter } from 'react-router-dom';
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome';
import { faInstagram, faGithub, faFacebook } from '#fortawesome/fontawesome-free-brands';
import 'bootstrap/dist/css/bootstrap.css';
import Favicon from 'react-favicon';
import Home from './Home';
import Contact from './Contact';
import socialMediaLinks from './utilities/Utils';
class Main extends React.Component {
constructor() {
super();
this.state = {
screenWidth: null,
isMobile: false,
};
this.handleResize = this.handleResize.bind(this);
}
componentDidMount() {
window.addEventListener('resize', this.handleResize.bind(this));
this.handleResize();
}
componentWillUnmount() {
window.removeEventListener('resize', this.handleResize);
}
handleResize() {
this.setState({ screenWidth: window.innerWidth });
console.log(`width is ${this.state.screenWidth}`);
}
render() {
return (
<HashRouter>
<div>
<Favicon url='./favicon.ico' />
{/* Navigation */}
<nav className='navbar fixed-top bg-dark flex-md-nowrap'>
{/* Social Media */}
<a className='social-media-link' href={socialMediaLinks['instagram']}><FontAwesomeIcon icon={faInstagram} size='lg' /></a>
<a className='social-media-link' href={socialMediaLinks['github']}><FontAwesomeIcon icon={faGithub} size='lg' /></a>
<a className='social-media-link' href={socialMediaLinks['facebook']}><FontAwesomeIcon icon={faFacebook} size='lg' /></a>
<ul className="header">
<li className='nav-option'><NavLink exact to='/'>Home</NavLink></li>
<li className='nav-option'><NavLink to='/contact'>About/Contact</NavLink></li>
</ul>
</nav>
{/* Main Page */}
<div className='content container-fluid' align='center'>
<div className='row'>
<Route exact path='/' component={withRouter(Home)} />
<Route path='/contact' component={withRouter(Contact)} />
</div>
<footer>Created by me :)</footer>
</div>
</div>
</HashRouter>
);
}
}
export default Main;
Home.js:
import React from 'react';
import 'bootstrap/dist/css/bootstrap.css';
import './index.css';
var imageList = [];
function importAll(r) {
const keys = r.keys();
let images = {};
for (var k in keys) {
images[keys[k].replace('./', '')] = r(keys[k]);
}
return images;
}
const images = importAll(require.context('./images/resized/', false, /\.(png|jpe?g|svg)$/));
for (var image in images) {
var newImage = new Image(images[image], images[image], null);
newImage.name = images[image];
newImage.src = images[image];
imageList.push(newImage);
}
class Home extends React.Component {
render() {
let images = imageList.map(image => {
if (image.naturalHeight > image.naturalWidth) { // portrait
return <img className='portrait-img' src={image.src} alt=''/>
} else if (image.naturalHeight < image.naturalWidth) { // landscape
return <img className='landscape-img' src={image.src} alt=''/>
}
});
return (
<div>
{images}
</div>
);
}
}
export default Home;
index.css:
body {
background-color: #FFF;
padding: 20px;
margin: 0;
font-family: Helvetica;
}
h1 {
color: #111;
font-weight: bold;
padding-top: 2px;
}
ul.header li {
display: inline;
list-style-type: none;
margin: 0;
}
ul.header {
background-color: #111;
padding: 0;
}
ul.header li a {
color: #FFF;
font-weight: bold;
text-decoration: none;
padding: 20px;
display: inline-block;
}
.content {
background-color: #FFF;
padding: 20px;
}
.content h2 {
padding: 0;
margin: 0;
}
.content li {
margin-bottom: 10px;
}
.active {
background-color: #0099FF;
}
.portrait-img {
max-width: 55%;
height: auto;
padding: 5px;
}
.landscape-img {
max-width: 75%;
height: auto;
padding: 5px;
}
.navbar {
max-height: 110px;
background-color: #FFCC00 !important;
}
.content {
padding-top: 80px;
}
footer {
background-color: #FFF;
font-size: 7pt;
position: relative;
margin: auto;
float: left;
}
.social-media-link {
color: #111;
}
::-webkit-scrollbar {
display: none;
}
#media only screen and (max-width: 500px) {
h1 {
font-size: 18pt;
}
.navbar {
max-height: 120px;
}
.content {
padding-top: 104px;
}
.header {
font-size: 10pt;
}
.portrait-img {
max-width: 100%;
height: auto;
padding: 2px;
}
.landscape-img {
max-width: 100%;
height: auto;
padding: 2px;
}
footer {
font-size: 5pt;
}
.social-media-link {
padding: 5px;
}
}
index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.6.1/css/font-awesome.min.css">
<link rel='icon' href='../src/favicon.ico' type='image/x-icon' />
</head>
<body>
<div id="root"></div>
</body>
</html>
index.js:
import React from 'react';
import ReactDOM from 'react-dom';
import Main from './Main';
import './index.css';
ReactDOM.render(
<Main />,
document.getElementById('root'),
);
Edit:
revised Home.js below to use a Promise. The idea here is, a Promise to render the images will alter this.state.images and that value will be used in the HTML in the return of the Component's render(). This does not fix the problem, but, it does result in a list of imgs logged instead of a list of undefined as before.
import React from 'react';
import 'bootstrap/dist/css/bootstrap.css';
import './index.css';
import { resolve } from 'q';
var imageList = [];
function importAll(r) {
const keys = r.keys();
let images = {};
for (var k in keys) {
images[keys[k].replace('./', '')] = r(keys[k]);
}
return images;
}
const images = importAll(require.context('./images/resized/', false, /\.(png|jpe?g|svg)$/));
for (var image in images) {
var newImage = new Image(images[image], images[image], null);
newImage.name = images[image];
newImage.src = images[image];
imageList.push(newImage);
}
class Home extends React.Component {
constructor(props) {
super();
this.state = {
images: null,
};
}
componentDidMount() {
let promise = this.getImages();
promise.then(result => {
let images = result.map(image => {
if (image.naturalHeight > image.naturalWidth) { // portrait
return <img className='portrait-img' src={image.src} alt='' />
} else if (image.naturalHeight < image.naturalWidth) { // landscape
return <img className='landscape-img' src={image.src} alt='' />
}
});
this.setState({ images: images });
}, function(error) {
this.setState({ images: error });
});
}
getImages() {
let promise = new Promise((resolve, reject) => {
let imageList = [];
const images = importAll(require.context('./images/resized/', false, /\.(png|jpe?g|svg)$/));
for (var image in images) {
var newImage = new Image(images[image], images[image], null);
newImage.name = images[image];
newImage.src = images[image];
imageList.push(newImage);
}
if (imageList.length > 0) {
console.log(imageList);
resolve(imageList);
} else {
console.log('shit');
reject(Error('oh shit'));
}
});
return promise;
}
render() {
return (
<div>
{this.state.images}
</div>
);
}
}
export default Home;
This results in:
after hard reload (ctrl+F5):
images = [undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined]
after refresh (ctrl+R):
images = [{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}] -- all with typeof: Symbol(react.element), type: "img".

console.log is not guaranteed to be synchronous, so you can't depend on it for order of execution. Some engines are, some aren't.
I wouldn't rely on require.context either, particularly if you're using create-react-app. See: https://github.com/facebook/create-react-app/issues/517
In general, if your project is already set up for ES modules and you find yourself needing require, that's a bit of a code smell IMO. The whole point of ES modules is to make scopes static, not dynamic, so that the compiler can infer what to throw away when minifying. The directory structure doesn't actually exist anymore at runtime; it's all been minified into one big file, either on the fly by the webpack dev server, or as part of the build for production.
Are the files too numerous to import directly into a module? You could have a separate module for the imageList that handles the loading and exports it, then just import and use directly in your component(s):
// imageList.js
import foo from './images/foo.jpg'
import bar from './images/bar.jpg'
import baz from './images/baz.jpg'
import blah from './images/blah.jpg'
export default {
foo,
bar,
baz,
blah,
}
You can then have a test to make sure none get missed in the future (NOTE, tests run in node directly with the file structure, not the webpack-ed final procuct):
// __tests__/imagesList.test.js
import imageList from '../imageList'
import fs from 'fs'
import path from 'path'
const dir = path.join(__dirname, '..', 'images')
it('has all the images', () => {
const imageFiles = fs.readdirSync(dir).filter((file) => {
return path.extname(file).match(/\.(png|jpe?g|svg)$/)
})
expect(Object.keys(imageList)).toEqual(imageFiles)
})
If there really are too many, or they will change often, I generally like to automate the code creation on the command line. Below is quick n dirty, but should be close to what you need, depending on paths:
#!/usr/bin/env node
const { readdirSync, writeFileSync } = require('fs')
const { extname, join } = require('path')
const relPath = (...paths) => join(__dirname, ...paths)
const dir = relPath('src', 'images')
const printLoaderFile = () => {
const imageFiles = readdirSync(dir).filter((file) => {
return extname(file).match(/\.(png|jpe?g|svg)$/)
})
const output = []
imageFiles.forEach((file, index) => {
output.push(`import image${index} from '${dir}/${file}'`)
})
output.push('\nexport default {')
imageFiles.forEach((file, index) => {
output.push(` '${file}': image${index},`)
})
output.push('}\n')
writeFileSync(relPath('src', 'imagesList.js'), output.join('\n'))
}
printLoaderFile()

Revising Home.js in the following way resulted in the images loading fast and without refreshing:
import React from 'react';
import 'bootstrap/dist/css/bootstrap.css';
import './index.css';
var imageList = [];
function importAll(r) {
const keys = r.keys();
let images = {};
for (var k in keys) {
images[keys[k].replace('./', '')] = r(keys[k]);
}
return images;
}
const images = importAll(require.context('./images/resized/', false, /\.(png|jpe?g|svg)$/));
for (var image in images) {
var newImage = new Image(images[image], images[image], null);
newImage.name = images[image];
newImage.src = images[image];
imageList.push(newImage);
}
class Home extends React.Component {
constructor() {
super();
this.state = {
images: [],
}
}
componentDidMount() {
this.setState({ images: imageList });
}
render() {
let imagesFinal = [];
for (var image in this.state.images) {
console.log(this.state.images[image].src);
if (this.state.images[image].naturalHeight > this.state.images[image].naturalWidth) {}
imagesFinal.push(<img src={this.state.images[image].src} className='landscape-img' alt=''></img>);
}
return <div>{imagesFinal}</div>;
}
}
export default Home;

Related

How can I make this scroll To Top upon re-rendering while keeping the headers reactive functionality?

This is alot of code but it is the minimal way that I could think of reproducing my problem.
view in sandbox: https://codesandbox.io/s/silly-kare-j0kmz
I would like the header bar to hide upon scrolling. The problem is everytime I click on a box to go to another route, or click header to come back to the home route, the scroll bar stays where it was before. That is everytime I move from route to route, the scrollbar does not move to the top.
I was able to fix this problem with the scrollToTop code, But in order to make it scroll to the top, I need to remove "overflow-y" from App.css, which stops my header from reacting onScroll.
I came to realize this is because window is perhaps a BOM object and only targets the browser window itself, not div class which I have assigned onScroll to.
So it seems I can do either OR, but not both functions together. I would like both to happen, The scrolToTop on location change AND to keeep the Header Reacting to the onScroll method. How can I do that?
App.js ---------------------------------------------------
Provides routing to First.js and Second.js. Also has the onScroll method. That is when you scroll up, the header appears, and when you scroll up the header disapears. Along with some routing to 2 simple components.
import './App.css';
import {useState, useEffect, useRef} from 'react';
import { Route, Routes} from 'react-router-dom';
import Second from "./Second/Second";
import First from "./First/First";
import Header from './Header/Header';
import ScrollToTop from './ScrollToTop/ScrollToTop'
function App() {
const prevScrollY = useRef(0);
const [goingUp, setGoingUp] = useState(true);
const [HeaderisVisible, setHeaderIsVisible] = useState(0);
const onScroll = (e) => {
const currentScrollY = e.target.scrollTop;
if (prevScrollY.current < currentScrollY && goingUp) {
setGoingUp(false);
}
if (prevScrollY.current > currentScrollY && !goingUp) {
setGoingUp(true);
}
prevScrollY.current = currentScrollY;
console.log(goingUp, currentScrollY);
};
return (
<div className="App" onScroll = {onScroll}>
<ScrollToTop/>
<Routes>
<Route path = '/' exact element = {<First GoingUp = {goingUp}/>}/>
<Route path = '/second' element = {<Second GoingUp = {goingUp}/>} />
<Route path = '*'>
NOT FOUND
</Route>
</Routes>
</div>
);
}
export default App;
Header.js -------------------------------------------------
Header takes props from the state initialized in App.js containing a true or flase variable. and uses that in a conditional to either show or hide the header. Also on clicking the header you go back to the home page.
import './Header.css';
import {useState, useEffect} from 'react';
import {Link} from 'react-router-dom';
function Header(props) {
const [HeaderisVisible, setHeaderIsVisible] = useState(0);
useEffect(() => {
if(props.GoingUp == true){
setHeaderIsVisible(0);
}else{
setHeaderIsVisible(-199);
}
}, [props.GoingUp]);
return (
<Link to = '/'><div className = "Header"
style = {{
top: `${HeaderisVisible}px`
}}>
</div> </Link>
);
}
export default Header;
First.js --------------------------------------------------
First is a simple component that just displays some divs. Each black div will route the the second page.
import './First.css';
import {Link} from 'react-router-dom';
import Header from '../Header/Header';
function First(props) {
return (
<div className="First">
<Header GoingUp = {props.GoingUp}/>
<Link to = '/second'><div className = "entity"></div></Link>
<Link to = '/second'><div className = "entity"></div></Link>
<Link to = '/second'><div className = "entity"></div></Link>
<Link to = '/second'><div className = "entity"></div></Link>
<Link to = '/second'><div className = "entity"></div></Link>
</div>
);
}
export default First;
Second.js -------------------------------------------------
Second is a simple component that just displays some red divs.
import './Second.css';
import { Route, Routes, Link} from 'react-router-dom';
import Header from '../Header/Header';
function Second(props) {
return (
<div className="Second">
<Header GoingUp = {props.GoingUp}/>
<div className = "entity2"></div>
<div className = "entity2"></div>
<div className = "entity2"></div>
<div className = "entity2"></div>
<div className = "entity2"></div>
</div>
);
}
export default Second;
ScrollToTop.js --------------------------------------------
Gets the location via the url search path, and scrolls to the top of the page on every search.
import { useEffect } from "react";
import { useLocation } from "react-router-dom";
export default function ScrollToTop(props) {
const { pathname } = useLocation();
useEffect(() => {
window.scrollTo(0, 0);
}, [pathname]);
return null;
}
First.css
-----------------------------------
.entity{
height: 200px;
margin: auto;
width: 200px;
border: 2px solid black;
background-color: black;
margin-top: 200px;
}
Second.css
-------------------------------------
.Second{
background-color: lightgreen;
}
.entity2{
height: 200px;
margin: auto;
width: 200px;
border: 2px solid black;
background-color: red;
margin-top: 200px;
}
Header.css
------------------------------------
.Header{
background-color: brown;
height: 200px;
position: fixed;
width: calc(100% - 17px);
}
App.css
-------------------------------------
html{
margin: 0;
}
body{
margin: 0;
}
.App{
overflow-y: auto;
background-color: lightblue;
height: 100vh;
}
I was able to solve my problem by replacing window.scrollTo(...) with the following: props.refProp.current.scrollTop = 0; inside of scrollToTop function.
export default function ScrollToTop(props) {
const { pathname } = useLocation();
useEffect(() => {
props.refProp.current.scrollTop = 0;
}, [pathname]);
return null;
}

How to refer to Vue when using provide

I've been trying to pass down a value via a parent component to a child component without using props. I'm using provide for this on a parent component (not the highest parent). The value im passing down will be dynamically updated, so after reading the vue docs I have to do something like: Vue.computed(() => this.todos.length) but it throws an error because Vue is undefined. I seem to be unable to import Vue like this import Vue from 'vue' (or something similar). How can I make this work? To be honest, even when I try to pass down a static variable I get undefined in the (direct) child component, even when I use the exact same code as in the vue docs.
So I have 2 questions:
how to refer/import the Vue instance?
Is it possible to use provide on a direct parent component (which is not the root)?
I'm using Vue3
You can get the instance using getCurrentInstance but it's not needed for what you want to do:
import { getCurrentInstance } from 'vue';
setup() {
const internalInstance = getCurrentInstance();
}
You can provide a computed from any component. Import provide and computed in the parent and use them like:
Parent
import { provide, computed } from 'vue';
setup() {
...
const total = computed(() => x.value + y.value); // Some computed
provide('total', total);
}
Import inject in the child:
Child
import { inject } from 'vue';
setup() {
...
const total = inject('total');
}
Here's a demo (with just slightly different import syntax because the CDN doesn't use actual imports):
const { createApp, ref, computed, provide, inject } = Vue;
const app = createApp({});
// PARENT
app.component('parent', {
template: `
<div class="parent">
<child></child>
<button #click="x++">Increment</button> (from Parent)
</div>
`,
setup() {
const x = ref(5);
const y = ref(10);
const total = computed(() => x.value + y.value);
provide('total', total);
return {
x
}
}
});
// CHILD
app.component('child', {
template: `
<div class="child">
Total: {{ total }}
</div>
`,
setup() {
const total = inject('total');
return {
total
}
}
});
app.mount("#app");
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
}
.parent, .child { padding: 24px; }
.parent {
background: #dddddd;
}
.child {
margin: 6px 0;
background: #ddeeff;
}
<div id="app">
<parent></parent>
</div>
<script src="https://unpkg.com/vue#next"></script>

Gatsby not loading useEffect function in production

I'm creating a website using Gatsby.js.
In my component, I'd created animation using Gsap, inside useEffect function.
While debugging, all works. In production the useEffect function not running, what follows to not showing animations.
What I should do?
Any ideas?
Thanks for answers!
My component:
import React, { useRef, useEffect } from "react"
import styled from "styled-components"
import gsap from "gsap"
import WhatEver from "../../../static/whatever.svg"
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome'
import { faArrowDown } from '#fortawesome/free-solid-svg-icons'
import scrollTo from 'gatsby-plugin-smoothscroll';
const HeaderWrapper = styled.header`
width: 100%;
height: 100vh;
min-height: 150px;
padding: 10px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: rgb(255, 216, 41);
`
const HeaderButton = styled.button`
display: block;
margin: 40px auto;
border: 2px solid #000000;
`
const HeaderComponent = () => {
const animWrapper = useRef(null)
useEffect(() => {
const [elements] = animWrapper.current.children
const what = elements.getElementById('What')
const ever = elements.getElementById('Ever')
const button = document.getElementById('header-button')
const icon = document.getElementById('header-icon')
const whatChildrens = what.children
const everChildrens = ever.children
const allChildrens = [...whatChildrens, ...everChildrens]
gsap.set([...allChildrens, button], { autoAlpha: 0 })
const timeLine = gsap.timeline({ defaults: { ease: 'power3.inOut' } })
timeLine
.to(whatChildrens, { autoAlpha: 1, duration: 0.75 })
.to(everChildrens, { autoAlpha: 1, stagger: 0.025 })
.to(button, { autoAlpha: 1 })
}, [])
return (
<HeaderWrapper className="header" id="main-header">
<div ref={animWrapper} id="header-logo-wrapper">
<WhatEver style={{width: '100%'}}/>
<HeaderButton id="header-button" onClick={() => scrollTo('#poznaj-nas')}>
<FontAwesomeIcon icon={faArrowDown} id="header-icon"/>
</HeaderButton>
</div>
</HeaderWrapper>
)
}
export default HeaderComponent
I think what is happening is that the gsap library is getting tree shaken out of the production build. I would either try adjusting your webpack settings to make sure that it is not removed, or include it like this instead:
const gsap = require("gsap")
Which in my experience has prevented libraries from being tree shaken out.
I was facing this issue and it got resolved just by following the steps:
If you are trying to open the html directly without any web server then useEffect won't be called.
Only way to solve this issue is by running a webserver and serve the html from the server.
I am using a mac system so run the python server using the command inside the public folder:
python -m SimpleHTTPServer 8000
then open it using localhost:8000

Assign styled-component CSS property based on props passed to component

I have two components TextField and Label.
The TextField is passing the prop req to the Label. I want to modify the styled-component based on the req prop being passed in. Here is my current code that is not working.
No errors are being reported to the console.
TextField.js
import React, {Component} from 'react';
import styled from 'styled-components';
import Label from '../Label/Label';
const Wrapper = styled.div`
display: flex;
flex-direction: column;
margin: 16px 8px 8px 8px;
`;
const Input = styled.input`
border-bottom: 1px solid rgba(0, 0, 0, .23);
&:focus {
border-bottom: 1px solid #2196f3;
}
`;
class TextField extends Component {
render() {
const {
label,
req = true,
} = this.props;
return (
<Wrapper>
<Label req={req} text={label}/>
<Input type={'textfield'}/>
</Wrapper>
);
}
}
export default TextField;
Label.js
import React, {Component} from 'react';
import styled from 'styled-components';
const LabelBase = styled.label`
color: rgba(0, 0, 0, .54);
font-size: 1rem;
line-height: 1;
&:after {
content: ${props => props.req ? '*' : ''};
}
`;
class Label extends Component {
render() {
const {
req,
text,
} = this.props;
return (
<LabelBase req={req}>{text}</LabelBase>
);
}
}
export default Label;
You say you want to style the component based on the ref prop, but it seems that you're using that prop as a boolean to add text, not styles so I just went with a simplified solution for that since psuedo-selectors like :after aren't supported in React's JS styles. There are other ways around that if need be, but I think you can just do the following. However, I've included a way to pass styles to the child component as well for your reference:
class Label extends React.Component {
render() {
const {
req,
text,
moreStyles
} = this.props;
const styles = {
"color": "rgba(0, 0, 0, .54)",
"fontSize": "1rem",
"lineHeight": 1
}
return (
<div style={{...styles, ...moreStyles}}>{text + (req ? '*' : '')}</div>
);
}
}
ReactDOM.render(<Label text="test" req="Yes" moreStyles={{"backgroundColor": "blue", "border": "1px solid black"}}/>, 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>

Testing DOM in Enzyme

Let's say I have a tiny component like this:
Button.js
import React from 'react';
import './Button.css';
export default class Button extends React.Component {
render() {
return (
<a href={ this.props.url } className={`button button-${ this.props.type }`}>
{ this.props.content }
</a>
);
}
}
And there's some super basic styling like this:
Button.css
.button {
color: white;
padding: 1em;
border-radius: 5px;
text-decoration: none;
}
.button-primary {
background-color: red;
}
.button-primary:hover {
background-color: darkred
}
.button-secondary {
background-color: aqua;
color: black;
}
.button-secondary:hover {
background-color: darkcyan;
color: white;
}
And let's say I want to write some tests for this:
Button.test.js
import React from 'react';
import Enzyme, {shallow, mount} from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
Enzyme.configure({adapter: new Adapter()});
import Button from './Button';
import './Button.css';
// Render buttons
const primaryButton = mount(
<Button
content="Primary button"
url="http://www.amazon.co.uk"
type="primary"
/>
);
const secondaryButton = mount(
<Button
content="Secondary button"
url="http://www.ebay.co.uk"
type="secondary"
/>
);
it('should exist', () => {
expect(primaryButton).toBeDefined();
expect(secondaryButton).toBeDefined();
});
it('should display text in the button', () => {
expect(primaryButton.text()).toEqual('Primary button');
});
it('should have the correct CSS classes', () => {
expect(primaryButton.find('.button').hasClass('button-primary')).toEqual(true);
expect(secondaryButton.find('.button').hasClass('button-secondary')).toEqual(true);
});
I've set this up using react-create-app and all the above works perfectly.
My question is: how do I test that what is getting rendered looks correct? For example, in this case I would want to make sure that the buttons have the correct background colours defined in the CSS file and that they have the correct border radius. This will prevent other developers accidentally overriding critical styling for example.
I was under the impression that Enzyme did this out of the box, but I cannot understand how to interrogate the virtual DOM which I assume is happening in the background? I thought that JSDOM was automatically running and I'm executing this from the CLI which is a Node environment.
I've tried this so far:
it('should have the correct background colours', () => {
const domNode = primaryButton.find('.button').at(0).getDOMNode();
const background = getComputedStyle(domNode).getPropertyValue('background');
expect(background).toBe('red');
});
But background is returned blank, in fact if I do console.log(getComputedStyle(domNode)) I get this returned which seems to be missing the styles:
console.log src/modules/Button/Button.test.js:42
CSSStyleDeclaration {
_values: {},
_importants: {},
_length: 0,
_onChange: [Function] }
The getDOMNode of an enzyme wrapper gets you the corresponding DOM node.
You can then use getComputedStyle to get the style of that DOM:
const renderedComponent = mount(<MyComponent /);
const domNode = renderedComponent.find('div').at(0).getDOMNode();
const background = getComputedStyle(domNode).getPropertyValue('background');
expect(background).toBe('red');

Categories

Resources