Document null when testing with Testing Library - react-testing-library

I'm trying to test a single component in my react app and getting the following error:
TypeError: Cannot read property 'clientWidth' of null
20 |
21 | resize = () => {
> 22 | const contentWidth = document.getElementById('root').clientWidth;
Here's the test:
import React from 'react'
import { MemoryRouter } from 'react-router-dom'
import { render, screen } from '#testing-library/react';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import reducer from '../store/reducer';
import Navigation from '../App/layout/AdminLayout/Navigation'
const store = createStore(reducer);
describe('Menu', () => {
it('Estate Planning points to classic estate planning page', () => {
render(
<MemoryRouter>
<Provider store={store}>
<Navigation />
</Provider>
</MemoryRouter>
);
screen.debug()
})
})
I tried with defining the container render(..., { container: document.body}), but got the same error. I'm not sure what I'm missing.

You are getting this error because your document does not contain an element with id "root" when running a test with react-testing-library. This is likely because react-testing-library renders your component to a different container than ReactDOM (which is used to run your application).
The Problem
Let's look at a typical React application setup.
<!-- public/index.html -->
<html>
<body>
<div id="root"></div>
</body>
</html>
// src/index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./components/App";
ReactDOM.render(<App />, document.querySelector("#root"));
// src/components/App.js
import React from "react";
const App = () => <div>Hello World!</div>;
export default App;
As we can see, index.js looks for an HTML element with an id of "root" and renders our App component inside that element. We can confirm that is happening by inspecting the element in your browser's developer tools:
<html>
<body>
<div id="root">
<div>Hello World!</div>
</div>
</body>
</html>
Now, let's write a simple test, and see what the DOM looks like.
// src/components/App.test.js
import React from "react";
import { render, screen } from "#testing-library/react";
import App from "./App";
test("renders the component", () => {
render(<App />);
screen.debug();
});
screen.debug() prints the DOM for debugging purposes. We can see that it does not include an element with id "root":
<body>
<div>
<div>
Hello World!
</div>
</div>
</body>
So the DOM container is slightly different for your test vs. runtime.
The Solution
One way to fix this problem is to refactor your code not to be dependent on the "root" element. Another option is to tell react-testing-library to render your component in a custom container. Here is an example of what that might look like:
// src/components/App.test.js
import React from "react";
import { render, screen } from "#testing-library/react";
import App from "./App";
test("renders the component in a specific container", () => {
const container = document.createElement("div");
container.id = "root";
render(<App />, { container: document.body.appendChild(container) });
screen.debug();
});
Now, we can see an element with id of "root" in the DOM, so your component should be able to find that element.
<body>
<div id="root">
<div>
Hello World!
</div>
</div>
</body>

Related

How to make Post request with Axios (MERN Stack)

I am new to REACT and the MERN Stack and try to understand everything. But sometimes, it seems that the simplest things do not want to get into my head.
I hope that, one day, I will understand it all. Until then it still seems a long way away. Anyway. I am starting with a simple MERN app. All users should be displayed on the start page. On a separate "page" there should be a create users form. For now, the users are displayed on the home screen but when I switch back from the "create users page" they dissappeared. Furthermore the input form do not work (validation error). When checking get and posts requests from my backend with Thunder Client everthing works, so I suppose, this might be something to do with the frontend. Sorry for my wording. I am a programming newbie.
I hope it is somewhat understandable. What am I doing wrong? I would be so happy, if anyone could help. Thank you!
client/src/App.js
import React from "react";
import { Routes, Route } from "react-router-dom";
import AllUsers from "./pages/AllUsers";
import Navigation from "./components/Navigation";
import CreateUser from "./pages/CreateUser";
import "./App.css";
function App() {
return (
<div className="App">
<Navigation />
<Routes>
<Route path="/" element={<AllUsers />} />
<Route path="create-User" element={<CreateUser />} />
</Routes>
</div>
);
}
export default App;
client/src/components/Navigation.js
import React from "react";
import { Link } from "react-router-dom";
const Navigation = () => {
return (
<header className="bg-background border-t-0 shadow-none">
<nav className="bg-navigation bg-opacity-40 rounded-t-xs flex justify-around h-12 p-3 ">
<Link to="/create-user">Create User</Link>
<Link to="/">
<img id="workshop-icon" src="../assets/home.svg" alt="home button" />
</Link>
</nav>
</header>
);
};
export default Navigation;
client/src/AllUsers.js
import React from "react";
import { useState, useEffect } from "react";
import Axios from "axios";
const AllUsers = () => {
const [listOfUsers, setListOfUsers] = useState([]);
useEffect(() => {
Axios.get("http://localhost:3001/getUsers").then((response) => {
setListOfUsers(response.data);
});
}, []);
return (
<div className="App">
<div className="usersDisplay">
{listOfUsers.map((user) => {
return (
<div key={user._id}>
<h1>Name: {user.name}</h1>
<h1>Age: {user.age}</h1>
<h1>Username: {user.username}</h1>
</div>
);
})}
</div>
</div>
);
};
export default AllUsers;
client/src/createUser.js
import React from "react";
import { useState } from "react";
import Axios from "axios";
const CreateUser = () => {
const [listOfUsers, setListOfUsers] = useState([]);
const [name, setName] = useState("");
const [age, setAge] = useState(0);
const [username, setUsername] = useState("");
Axios.post("http://localhost:3001/createUser", {
name,
age,
username,
}).then((response) => {
setListOfUsers([
...listOfUsers,
{
name,
age,
username,
},
]);
});
return (
<div className="input">
<div>
<input
type="text"
placeholder="Name..."
onChange={(event) => {
setName(event.target.value);
}}
/>
<input
type="number"
placeholder="Age..."
onChange={(event) => {
setAge(event.target.value);
}}
/>
<input
type="text"
placeholder="Username..."
onChange={(event) => {
setUsername(event.target.value);
}}
/>
<button onClick={CreateUser}> Create User </button>
</div>
</div>
);
};
export default CreateUser;

How I can use my jquery plugin in Next.js?

i'm developing web app using Next.js and I'd like to use jQuery in Next.js.
But I can't import jquery plugin.
please check my code and Help me.
import React from 'react';
import Document, { Head, Main, NextScript } from 'next/document';
import { ServerStyleSheet } from 'styled-components';
export default class MyDocument extends Document {
static getInitialProps({ renderPage }) {
const sheet = new ServerStyleSheet();
const page = renderPage(App => props => sheet.collectStyles(<App {...props} />));
const styleTags = sheet.getStyleElement();
return { ...page, styleTags };
}
render() {
return (
<html lang="en">
<Head>{this.props.styleTags}
<link
href="//cdnjs.cloudflare.com/ajax/libs/KaTeX/0.9.0/katex.min.css"
rel="stylesheet"
/>
<script src="../static/js/jquery.main.js" async/>
</Head>
<body>
<Main />
<div id="modal" />
<NextScript />
</body>
</html>
);
}
}
One typical issue you will get into if you do jQuery is, After importing the jQuery plugins they will register for onClick events, and by that time you can't guarantee that the actual DOM Element is ready, elements may get created later and may get refreshed.
One way of solving the issues is call the jQuery code which does the initialization in componentDidMount() and also in componentDidUpdate() so that you can make sure the DOM elements are present before the jQuery plugin register them.

Error while loading a basic react-leaflet map

I am new to react-leaflet.
I'm working on a Flask React project and I'm trying to load a react-leaflet map.
I'm getting the below error :
Uncaught TypeError: Cannot use 'in' operator to search for 'default' in undefined
at react-leaflet.js:7
at React__default (react-leaflet.js:4)
at react-leaflet.js:5
Versions :
react-leaflet : v1.9.1
leaflet.css : v1.3.1
react-leaflet.js : v1.9.1
I have added the necessary routes for rendering the Appbody.
But I'm unable to figure out what is wrong.
import React, {Component} from 'react'
import {Map, Marker, Popup, TileLayer} from 'react-leaflet'
const position = [51.505, -0.09]
class MainMap extends Component {
render() {
return (
<div>
<Map center={position} zoom={13}>
<TileLayer
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
attribution="© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors"
/>
<Marker position={position}>
<Popup>
<span>A pretty CSS3 popup.<br/>Easily customizable.</span>
</Popup>
</Marker>
</Map>
</div>
);
}
}
export default MainMap;
import React, {Component} from 'react';
import MainMap from "./map";
class AppBody extends Component {
render() {
return (
<div>
<MainMap/>
</div>
);
}
}
export default AppBody;
.leaflet-container {
position: absolute;
height: 100%;
width: 100%;
}
I'm currently using react-leaflet and I never had this kind of error, but I can see something wrong in the code (I don't know if those resolve the problem).
Firstly have you imported the leaflet.css file? You need to import this in the index.js or in the current file
import "leaflet/dist/leaflet.css";
You have also to add a ref props in the map component like this
<Map ref = "map" {...otherprops} >
I think the only problem in your code is surrounding the Map element by div tags.
So if you remove them and make the Map element at the top, then the problem should be solved.
TL;DR
Do not include the react-leaflet CDN in your index.html if you have installed it with npm or yarn.
Well, after much probing around with the problem myself I think you may have set up things incorrectly in the <head> of your index.html, this is how I had it set up before with the error happening:
<link rel="stylesheet" href="https://unpkg.com/leaflet#1.7.1/dist/leaflet.css"
integrity="sha512-xodZBNTC5n17Xt2atTPuE1HxjVMSvLVW9ocqUKLsCC5CXdbqCmblAshOMAS6/keqq/sMZMZ19scR4PsZChSR7A=="
crossorigin=""/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-leaflet/2.0.0/react-leaflet.js"></script>
and solving the issue:
<link rel="stylesheet" href="https://unpkg.com/leaflet#1.7.1/dist/leaflet.css"
integrity="sha512-xodZBNTC5n17Xt2atTPuE1HxjVMSvLVW9ocqUKLsCC5CXdbqCmblAshOMAS6/keqq/sMZMZ19scR4PsZChSR7A=="
crossorigin=""/>
Notice the difference? If you have installed react-leaflet with npm or yarn, you'll see that you don't need to add the leaflet script in your <head>. What was happening before is that the react-leaflet script was loading before the webpacked js files and it therefore could not find React. The react-leaflet source code that threw the error looked like this:
var React__default = 'default' in React ? React['default'] : React;
And obviously, if there was no React object in the first place, there will be an error when trying to find the 'default' key.
Hope this helps someone some day!

Vue.js page transition fade effect with vue-router

How to achieve a fade effect page transition between vue-router defined pages (components)?
Wrap <router-view></router-view> with <transition name="fade"></transition> and add these styles:
.fade-enter-active, .fade-leave-active {
transition-property: opacity;
transition-duration: .25s;
}
.fade-enter-active {
transition-delay: .25s;
}
.fade-enter, .fade-leave-active {
opacity: 0
}
Detailed answer
Assuming you have created your application with vue-cli, e.g.:
vue init webpack fadetransition
cd fadetransition
npm install
Install the router:
npm i vue-router
If you are not developing your app using vue-cli, make sure to add the vue router the standard way:
<script src="/path/to/vue.js"></script>
<script src="/path/to/vue-router.js"></script>
You can use e.g.: https://unpkg.com/vue-router/dist/vue-router.js
The CLI has created a backbone application for you, which you can add components to.
1) Create page components
In Vue, components (UI elements) can be nested. A page in your app can be made with a regular Vue component that is considered as the root to other components in that page.
Go to src/ and create pages/ directory. These page-root components (individual pages) will be put in this directory, while the other components used in the actual pages can be put to the ready-made components/ directory.
Create two pages in files called src/pages/Page1.vue and src/pages/Page2.vue for starters. Their content will be (edit page numbers respectively):
<template>
<h1>Page 1</h1>
</template>
<script>
export default {
}
</script>
<style scoped>
</style>
2) Setup routing
Edit the generated src/main.js add the required imports:
import VueRouter from 'vue-router'
import Page1 from './pages/Page1'
import Page2 from './pages/Page2'
Add a global router usage:
Vue.use(VueRouter)
Add a router setup:
const router = new VueRouter({
routes: [
{ path: '/page1', component: Page1 },
{ path: '/page2', component: Page2 },
{ path: '/', redirect: '/page1' }
]
})
The last route just redirects the initial path / to /page1. Edit the app initiation:
new Vue({
router,
el: '#app',
render: h => h(App)
})
The whole src/main.js example is at the end of the answer.
3) Add a router view
Routing is set up by now, just a place where the page components will be rendered according to the router is missing. This is done by placing <router-view></router-view> somewhere in the templates, you will want to put it in the src/App.vue's <template> tag.
The whole src/App.vue example is at the end of the answer.
4) Add fade transition effect between page components
Wrap the <router-view></router-view> with a <transition name="fade"> element, e.g.:
<template>
<div id="app">
<transition name="fade">
<router-view></router-view>
</transition>
</div>
</template>
Vue will do the job here: it will create and insert appropriate CSS classes starting with the name specified through-out the effect's duration, e.g.: .fade-enter-active. Now define the effects in App.vue's section:
<style>
.fade-enter-active, .fade-leave-active {
transition-property: opacity;
transition-duration: .25s;
}
.fade-enter-active {
transition-delay: .25s;
}
.fade-enter, .fade-leave-active {
opacity: 0
}
</style>
That's it. If you run the app now, e.g. with npm run dev, it will automatically display Page 1 with a fade-in effect. If you rewrite the URL to /page2, it will switch the pages with fade-out and fade-in effects.
Check out the documentation on routing and transitions for more information.
5) Optional: Add links to pages.
You can add links to particular pages with the <router-link> component, e.g.:
<router-link to="/page1">Page 1</router-link>
<router-link to="/page2">Page 2</router-link>
This automatically gives the links a router-link-active class in case they are active, but you can also specify custom classes if you are using e.g. Bootstrap:
<router-link class="nav-link" active-class="active" to="/page1">Page 1</router-link>
<router-link class="nav-link" active-class="active" to="/page2">Page 2</router-link>
Files for reference
src/main.js:
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import VueRouter from 'vue-router'
import App from './App'
import Page1 from './pages/Page1'
import Page2 from './pages/Page2'
Vue.use(VueRouter)
const router = new VueRouter({
routes: [
{ path: '/page1', component: Page1 },
{ path: '/page2', component: Page2 },
{ path: '/', redirect: '/page1' }
]
})
/* eslint-disable no-new */
new Vue({
router,
el: '#app',
render: h => h(App)
})
src/App.vue:
<template>
<div id="app">
<router-link class="nav-link" active-class="active" to="/page1">Page 1</router-link>
<router-link class="nav-link" active-class="active" to="/page2">Page 2</router-link>
<transition name="fade">
<router-view></router-view>
</transition>
</div>
</template>
<script>
export default {
name: 'app'
}
</script>
<style>
.fade-enter-active, .fade-leave-active {
transition-property: opacity;
transition-duration: .25s;
}
.fade-enter-active {
transition-delay: .25s;
}
.fade-enter, .fade-leave-active {
opacity: 0
}
</style>
src/pages/Page1.vue:
<template>
<h1>Page 1</h1>
</template>
<script>
export default {
}
</script>
<style scoped>
</style>
src/pages/Page2.vue:
<template>
<h1>Page 2</h1>
</template>
<script>
export default {
}
</script>
<style scoped>
</style>
Plug and Play Solution
There is also a plug and play solution called vue-page-transition which offers you all sort of transitions. (fade, flip, zoom, overlay etc.)
1 - Install the npm package:
yarn add vue-page-transition
2 - register the plugin:
import Vue from 'vue'
import VuePageTransition from 'vue-page-transition'
Vue.use(VuePageTransition)
3 - wrap your router-view with the global animation:
<vue-page-transition name="fade-in-right">
<router-view/>
</vue-page-transition>
Learn more on GitHub:
https://github.com/Orlandster/vue-page-transition

How to handle events using Electron + Vue (SPA)

I am having problems figuring out how to handle events when using Vue together with Electron. It may seem stupid, but I have spent time reading the docs, testing Vue instances and directives in the browser which works fine but the same principles won't work in my electron desktop app (this is so much different then Php OOP).
I use the electron-vue boilerplate, set it up, works like a charm. Created a template and a component (TopMenu), now I need to handle the click event of the menu buttons placed into my TopMenu component, but no matter how I try, I get:
[Vue warn]: Property or method "say" is not
defined on the instance but referenced during render. Make sure to
declare reactive data properties in the data option. (found in
component )
[Vue warn]: Handler for event "click" is undefined.
./LandingPageView/TopMenu.vue:
<template>
<div>
<button type="button" name="button" v-on:click="say">BUTTON</button>
</div>
</template>
main.js:
import Vue from 'vue'
import Electron from 'vue-electron'
import Resource from 'vue-resource'
import Router from 'vue-router'
import App from './App'
import routes from './routes'
import 'bootstrap/dist/css/bootstrap.min.css'
import 'bootstrap/dist/js/bootstrap.min.js'
Vue.use(Electron)
Vue.use(Resource)
Vue.use(Router)
Vue.config.debug = true
const router = new Router({
scrollBehavior: () => ({ y: 0 }),
routes
})
/* eslint-disable no-new */
new Vue({
methods: {
say: function () {
alert()
}
},
router,
...App
}).$mount('#app')
App.vue:
<style>
#import url(https://fonts.googleapis.com/css?family=Lato:400,700);
* {
margin: 0;
padding: 0;
}
html,
body { height: 100%; }
body {
align-items: center;
background:
radial-gradient(
ellipse at center,
rgba(255, 255, 255, 1) 0%,
rgba(229, 229, 229, .85) 100%
);
background-position: center;
font-family: Lato, Helvetica, sans-serif;
}
</style>
<template>
<div>
<router-view></router-view>
</div>
</template>
<script>
import store from 'src/vuex/store'
export default {
store
}
</script>
index.ejs:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<div id="app"></div>
<!-- webpack builds are automatically injected -->
</body>
</html>
Hello you need to put the methods in the same component that the template:
<template>
<div class="example" #click="say">say method</div>
</template>
<script>
export default {
methods: {
say () {
console.log('Hello world!')
}
}
}
</script>
Take a look in the vue documents: https://v2.vuejs.org/v2/guide/