Consider the following code in a "helper" module, which should not contains hooks.
//helpers/helpers.ts
import { useState } from 'react';
export function testLower() {
const [a, setAt] = useState('');
}
export function TestLower() {
const [a, setAt] = useState('');
}
The Eslint Plugin is able to detect, that the code in the lower-case function (testLower) contains a hook and throws a linting error.
But in the TestLower upper-case function - there is no error. This is because it "could" be a component identified by the UpperCase name, I would assume. In this case the hooks plugin does not have a chance of detecting the violation.
Since this slipped our code-review before - I would like to know:
Is there a way to disallow hooks with Eslint in a certain directory or module file in a code base?
I tend to favor good documentation, communication and team consensus on these topics over scripted handcuffs. If I absolutely need to reach this point then I'd try using a custom script that would run along eslint on your build process and/or pipelines.
/*
* noReactHooksAllowed.js
*
* usage
* node ./noReactHooksAllowed.js path/to/dir
*/
const fs = require('fs');
const dir = process.argv[2];
fs.readdirSync(dir).forEach(filename => {
const file = dir + '/' + filename;
console.log("Checking:", file);
try {
const sourceCode = fs.readFileSync(file, 'utf8');
if (/(useState|useEffect)/.test(sourceCode)) {
console.error("You shall not pass!");
console.error("Please remove react hooks from", file);
process.exit(1);
}
} catch (err) {
console.error(err);
}
});
console.log("No naughty hooks used here, carry on :)");
process.exit(0);
You can call it from your npm scripts and if it fails, the process.exit(1) signal should stop the whole build. This is a simple example but you can change the script to include more custom checks, have better performance with async calls, check just one file, etc.
Related
So I sort of found a fix for this, but I want to understand what's happening and why.
The background is that I'm testing some classes, each class has a field for the result of a hook for reasons that aren't relevant here. All that happens with the hook in the constructor of each class is that it gets assigned to the member variable.
In order to test each of these classes, I had originally mocked out a few things so that the hook behaved just like a normal function, and that worked fine. But then I discovered the renderHook feature of react testing library and figured it was probably better to use a mature tool instead.
The hook depends on a react context, so I followed the instructions and created this helper function to get my hook
import { mockContext } from './mockContext';
import { renderHook } from '#testing-library/react';
import { OAuthRequests } from '../lib/core/models/auth/OAuthRequests';
import { useOAuth } from '../lib/core/hooks/useOAuth';
import { ClientConfig } from '../lib/core/models/auth/RedditConfig';
export const mockClient: ClientConfig = { clientId: '', clientSecret: '' };
export const mockOAuth = () => {
const wrapper = mockContext();
const {
result: { current: requests },
} = renderHook<OAuthRequests, ClientConfig>(() => useOAuth(mockClient), { wrapper });
return requests;
};
The problem came when I refactored these test suites, all of which were passing. ONLY those suites which were subclasses of one particular superclass were failing. The message with the failure was
TypeError: Class extends value undefined is not a constructor or null
mockOAuth works just fine in every test suite except the ones that extend a certain abstract superclass. I get no runtime errors in my actual app using any of these classes, and like I said, before refactoring to use renderHook it was also all working fine with passing tests. Also, the error seemed to happen at the import stage, not at construction of any objects.
So, after a lot of debugging and trial and error, I discovered that if I placed my import of mockOAuth at the top of the file above the class imports, it works, but if it's below them, it breaks.
It's great that I have a working solution, but I really want to understand why this is happening in case it is a canary for some very obscure bug in my code that won't pop up easily.
Also, I'm tagging the hooks testing library as well here, but I am using react testing library 13.4.0 and react 18.1.0.
I've got some horrendous code that has to be added to our project that you can see below. It basically checks if react-router exists in webpack and then tells our component what environment it is on.
let Link;
/* eslint-disable -- We have to use __non_webpack_require__ to handle the case when consumer has no react router (which is our external dependency) */
try {
// Link = __non_webpack_require__ && __non_webpack_require__('react-router/lib/Link');
if (__non_webpack_require__) {
Link = __non_webpack_require__('react-router/lib/Link'); // for Webpack
} else {
// Below line causes issue
Link = import('react-router/lib/Link').then(module => module); // for Rollup
}
} catch (e) { }
/* eslint-enable */
So how my project is setup is this is a repo that is a small part of a bigger one where react-router exists and we don't want to install react-router on our side as it causes other bugs. Because of this I get the below error when I go to build the bundle from rollup.
I've tried to ignore the dependency by using the skip property, the 'rollup-plugin-ignore' dependecy and also the externals property in webpack and none of these seem to work.
Examples:
skip: ['react-router/lib/Link'], // Default: []
externals: {
...nodeExternals(),
'react-router': 'react-router',
},
and following the example given here:
https://www.npmjs.com/package/rollup-plugin-ignore
I'm usimg electron-react biolerplate.electron's main.js file placed in public folder.I want to add another electron file to common constants,functions.when I try to use that file it throws above error.
I want to keep main.js short .that's why I'm using another file
/public/renderer/command-executor.js
export const executeCommandWithOutput=(command)=>{
const { exec } = require('child_process');
console.log(command);
return new Promise((resolve, reject) => {
console.log('inside promise');
exec(command, (err, stdout, stderr) => {
console.log('inside execsync');
if (err) {
console.log(err);
resolve(err);
} else if (stderr) {
console.log(stderr)
resolve(stderr);
} else {
resolve(stdout);
}
});
});
}
public/main.js
const commandExecutor=require('./renderer/command-executor');
electron.ipcMain.on('launch-App',async(event,args)=>{
commandExecutor.executeCommandWithOutput(`powershell -Command "& {Start-Process -Verb runas '${playLink}'}"`);
});
The issue is that you're trying to mix the ES5 require statement with the ES6 syntax for export. The two are incompatible. You either have to use one or the other.
Assuming your ES6 implementation with babel works fine, you should use the import statement like this:
// exporting like you are at the moment (called a named export):
export const executeCommandWithOutput = (command) =>{
...
}
// importing like so:
import { executeCommandWithOutput } from './renderer/command-executor';
But if you're using require in public/main.js, your export statement should look something like this:
exports.executeCommandWithOutput = executeCommandWithOutput
and your require will remain the same.
Here's an article to help you get a better understanding of what's happening, how the export functionality works in ES5 and what you can achieve with it: https://www.sitepoint.com/understanding-module-exports-exports-node-js/
Likewise with the ES6 syntax:
https://alligator.io/js/modules-es6/
I have many console.log in my code.
As we know those logs slow down app a lot, so at the end of development I need to delete all of them, but of course I don't remember all the places where I have it. How can I use some wrapper for console.log which I can use, so that I could turn on or turn off all the console logs in one place? If my approach is not very good, advise me some libraries, tools, ways of doing what I need...
You can do this in the following two ways:
if(!__DEV__) {
console = {};
console.log = () => {};
console.error = () => {};
}
a better approach would be to use babel plugin transform-remove-console by
creating .babelrc file, and setting up babel transpiler.
example setup:
{
"presets": ["react-native"],
"env": {
"production": {
"plugins": ["transform-remove-console"]
}
}
}
source: https://facebook.github.io/react-native/docs/performance.html#using-consolelog-statements
Use this: https://github.com/babel/minify/tree/master/packages/babel-plugin-transform-remove-console
or you can creat a function in utils like this:
export const showLog = (tag, log) => {
console.log(tag + ' : ' + log);
};
and use showLog anywhere in your project:
import { showLog } from '../utils/utils';
showLog('VideoPlayer', response)
At the end, I've chosen the method described here - https://levelup.gitconnected.com/step-up-your-console-messaging-game-in-your-react-app-42eee17659ec
I like it best of all.
Upd: As Chmac mentioned (thanks), the link is dead. Archive link here
I am working on a React app created with create-react-app. I was having trouble creating a web worker in it so I posted a question here on SO: Creating a web worker inside React
I've found a solution, as written in the post above, to load a worker without ejecting the app and messing with the Webpack config. This is the code, from the post above:
// worker.js
const workercode = () => {
self.onmessage = function(e) {
console.log('Message received from main script');
const workerResult = 'Received from main: ' + (e.data);
console.log('Posting message back to main script');
self.postMessage(workerResult);
}
};
let code = workercode.toString();
code = code.substring(code.indexOf("{")+1, code.lastIndexOf("}"));
const blob = new Blob([code], {type: "application/javascript"});
const worker_script = URL.createObjectURL(blob);
module.exports = worker_script;
and in the file that uses the worker:
import worker_script from './worker';
const myWorker = new Worker(worker_script);
myWorker.onmessage = (m) => {
console.log("msg from worker: ", m.data);
};
myWorker.postMessage('im from main');
It works, however, I cannot seem to get importScripts to work. Even if I do this (outside onmessage or inside onmessage):
if (typeof importScripts === 'function') {
importScripts('myscript.js');
}
In that case, the if statement turns out to be true, but then fails on the actual import with the same error message 'importScripts' is not defined as if the if statement is a false positive, which doesn't sound right. I'd say this is a context issue and that the worker probably isn't loading properly (although it seems to work), but it's just a guess.
Any ideas what's happening here?
importScripts in a worker created from Blob works fine, at least in 2021 (react 17.0.2, react-scripts 4.0.3, Chrome 92). The imported script URL must be absolute because worker was created from Blob.
The original issue might have been a bug in webpack or the transpilation might have changed the code in a weird way.
const workercode = () => {
importScripts("https://example.com/extra.js");
console.log(self.extraValue); // 10
self.onmessage = function(e) {
console.log('Message received from main script');
...
}
};
// extra.js
self.extraValue = 10;
Looks like this is still broken in 2022 - Seems there is a regression coming down the dev pipeline (at least in Android WebView and possibly some dev/canary chrome verions.)
https://bugs.chromium.org/p/chromium/issues/detail?id=1078821