How to silence "Can't perform a React state update on an unmounted component" in react-hooks-testing-library - react-testing-library

I have a react context that performs async actions after the context has been unmounted. It seems to me like this would not be a problem, since the context never gets unmounted in my real application. I don't want to add an unnecessary cleanup function to my context to test it. But then I get this annoying logs in my tests. Is there a solution to this?
const pendingFetch = {}
export default function ExampleProvider(props) {
const [examples, setExamples] = useState({})
const addExampleToContext = async (exampleId) => {
if (examples[exampleId] || pendingFetch[exampleId]) {
return
}
pendingFetch[exampleId] = true
const example = await fetchExample(exampleId) // this is mocked to take 10ms to resolve
setExamples(previousExamples => {
pendingFetch[exampleId] = false
return {...previousExamples, [exampleId]: example}
}
}
const context = {
examples,
addExampleToContext
}
return <ExampleContext.Provider value={context}>{props.children}</ExampleContext.Provider>
}

Related

VSCode extension development unable to use provideInlineCompletionItems

I've tried so many variations of this from code I found on github using the provideInlineCompletionItems function but cannot seem to get it to work. Is there something I am doing wrong?
const vscode = require('vscode');
function activate(context) {
const provider = {
provideInlineCompletionItems: async (document, position, context, token) => {
const txt = 'hi'
return [
{
text: txt,
insertText: txt,
range:new vscode.Range(position.translate(0, txt.length), position)
}
]
},
};
vscode.languages.registerInlineCompletionItemProvider({pattern: "**"}, provider);
}
exports.activate = activate;
function deactivate() {}
module.exports = {
activate,
deactivate
};
Even with https://github.com/microsoft/vscode/issues/125663 and "editor.inlineSuggest.enabled": true, set to true it doesn't work. I know inline suggestions works since I have github copilot, I just can't seem to get it to work. Copilot is also disabled so they don't interfere
I rollbacked a version of VSCode and it works now. this is the debug code I used.
// The module 'vscode' contains the VS Code extensibility API
// Import the module and reference it with the alias vscode in your code below
const vscode = require('vscode');
// This method is called when your extension is activated
// Your extension is activated the very first time the command is executed
/**
* #param {vscode.ExtensionContext} context
*/
function activate(context) {
// Use the console to output diagnostic information (console.log) and errors (console.error)
// This line of code will only be executed once when your extension is activated
console.log('Congratulations, your extension "seven" is now active!');
// The command has been defined in the package.json file
// Now provide the implementation of the command with registerCommand
// The commandId parameter must match the command field in package.json
let disposable = vscode.commands.registerCommand('seven.helloWorld', function () {
// The code you place here will be executed every time your command is executed
// Display a message box to the user
vscode.window.showInformationMessage('Hello World from seven!');
});
const provider = {
provideInlineCompletionItems: async (document, position, context, token) => {
const line = document.lineAt(position.line);
console.log("420")
if (line.text.startsWith('if')) {
console.log("421")
let range = line.range;
if (line.text.indexOf(";") !== -1) {
console.log("423")
range = new vscode.Range(range.start, range.end.with(undefined, line.text.indexOf(";") + 1));
}
console.log("424")
return [{ text: 'if (hello) {\n};', insertText: "if (hello)", range }];
}
}
};
vscode.languages.registerInlineCompletionItemProvider({ pattern: '**' }, provider);
context.subscriptions.push(disposable);
}
// This method is called when your extension is deactivated
function deactivate() {}
module.exports = {
activate,
deactivate
}

Redux toolkit createSlice not working as expected (state is not modified thought action seem to be fired)

I am new to Redux toolkit. I have a working app in which would like to implement it in place of existing "regular" reducer.
import { createSlice, PayloadAction } from "#reduxjs/toolkit";
import { SelectedMinifig } from "types";
const initialState = {} as SelectedMinifig;
const selectedMinifigSlice = createSlice({
name: "selectedMinifigX",
initialState,
reducers: {
setSelectedMinifigX(state, action: PayloadAction<SelectedMinifig>) {
state = action.payload;
console.log("state and action payload from slice", state, action.payload);
},
},
});
export default selectedMinifigSlice.reducer;
export const { setSelectedMinifigX } = selectedMinifigSlice.actions;
Please note that in the code I use postfix "X" to differentiate new names from existing ones.
From the above slice, exports are consumed like this:
import selectedMinifigReducer from "reduxware/reducers/selectedMinifigSlice";
import { partsApi } from "../api/partsApi";
const rootReducer = combineReducers({
fetch: fetchReducer,
selected: selectedReducer,
teasers: teasersReducer,
selectedMinifigX: selectedMinifigReducer,
[partsApi.reducerPath]: partsApi.reducer,
});
Above I consume reducer, and with two files below I consume action (the latest file is my usual workaround not to useDispatch in components directly):
index.ts:
export { setSelectedMinifigX } from "reduxware/reducers/selectedMinifigSlice";
useDispatchAction.ts
import { useDispatch } from "react-redux";
import { bindActionCreators } from "redux";
import { actionCreators } from "reduxware";
const useDispatchAction = () => {
const dispatch = useDispatch();
return bindActionCreators(actionCreators, dispatch);
};
export default useDispatchAction;
The actions are fired like this (the new action is setSelectedMinifigX(selected), the old is setSelectedMinifig(selected), both with the same argument) :
onClick={e => {
e.stopPropagation();
setSelectedMinifig(selected);
setSelectedMinifigX(selected);
history(Paths.order);
}}
And in the moment of firing action, I really receive in console comment "state and action payload from slice " with expected content. That is why I claim action is actually fired.
The problem is that when I reach for state it is still empty object like initial state.
I have a component that is linked with state like below:
const mapStateToProps = (state: RootStateType) => ({
selectedMinifig: state.selected.selectedMinifig,
selectedMinifigX: state.selectedMinifigX,
});
and within this component, selectedMinifigX is an empty object. What is wrong here?
Hmm some of this looks a little foreign / extra to me.
Your reducer looks correct. Inside of your React component try to do something like this:
const SomeComponent = () => {
const dispatch = useDispatch()
const someHandlerFn = (e) => {
e.stopPropagation();
dispatch(setSelectedMinifigX(selected))
history(Paths.order);
}
return <button onClick={someHandlerFn}>Test Me</button>
}
I'm not sure if bindActionCreators is still valid redux. Was something I use to do before redux toolkit when using class based components. You should see your reducer fire inside of your reducer file.
The useDispatchAction.ts seems like extra stuff you don't need.

Using Zxing Library with Jetpack compose

I am trying to implement qr scanner using zxing library. For this, i have added a button on screen, and on click of it, i am launching scanner as below
Button(
onClick = {
val intentIntegrator = IntentIntegrator(context)
intentIntegrator.setPrompt(QrScanLabel)
intentIntegrator.setOrientationLocked(true)
intentIntegrator.initiateScan()
},
modifier = Modifier
.fillMaxWidth()
) {
Text(
text = QrScanLabel
)
}
but, it launches an intent, which expects onActivityResult method to get back the results. And Jetpack compose uses rememberLauncherForActivityResult like below
val intentLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartIntentSenderForResult()
) {
if (it.resultCode != RESULT_OK) {
return#rememberLauncherForActivityResult
}
...
}
but how do we integrate both things together here?
I make a provisional solution with same library:
Gradle dependencies:
implementation('com.journeyapps:zxing-android-embedded:4.1.0') { transitive = false }
implementation 'com.google.zxing:core:3.4.0'
My new Screen with jetpack compose and camera capture, that works for my app:
#Composable
fun AdminClubMembershipScanScreen(navController: NavHostController) {
val context = LocalContext.current
var scanFlag by remember {
mutableStateOf(false)
}
val compoundBarcodeView = remember {
CompoundBarcodeView(context).apply {
val capture = CaptureManager(context as Activity, this)
capture.initializeFromIntent(context.intent, null)
this.setStatusText("")
capture.decode()
this.decodeContinuous { result ->
if(scanFlag){
return#decodeContinuous
}
scanFlag = true
result.text?.let { barCodeOrQr->
//Do something and when you finish this something
//put scanFlag = false to scan another item
scanFlag = false
}
//If you don't put this scanFlag = false, it will never work again.
//you can put a delay over 2 seconds and then scanFlag = false to prevent multiple scanning
}
}
}
AndroidView(
modifier = Modifier,
factory = { compoundBarcodeView },
)
}
Since zxing-android-embedded:4.3.0 there is a ScanContract, which can be used directly from Compose:
val scanLauncher = rememberLauncherForActivityResult(
contract = ScanContract(),
onResult = { result -> Log.i(TAG, "scanned code: ${result.contents}") }
)
Button(onClick = { scanLauncher.launch(ScanOptions()) }) {
Text(text = "Scan barcode")
}
Addendum to the accepted answer
This answer dives into the issues commented on by #Bharat Kumar and #Jose Pose S
in the accepted answer.
I basically just implemented the accepted answer in my code and then added the following code just after the defining compundBarCodeView
DisposableEffect(key1 = "someKey" ){
compoundBarcodeView.resume()
onDispose {
compoundBarcodeView.pause()
}
}
this makes sure the scanner is only active while it is in the foreground and unbourdens our device.
TL;DR
In escence even after you scan a QR code successfully and leave the scanner screen, the barcodeview will "haunt" you by continuing to scan from the backstack. which you usually dont want. And even if you use a boolean flag to prevent the scanner from doing anything after the focus has switched away from the scanner it will still burden your processor and slow down your UI since there is still a process constantly decrypting hi-res images in the background.
I have a problem, I've the same code as you, but i don't know why it's showing me a black screen
Code AddProduct
#ExperimentalPermissionsApi
#Composable
fun AddProduct(
navController: NavController
) {
val context = LocalContext.current
var scanFlag by remember {
mutableStateOf(false)
}
val compoundBarcodeView = remember {
CompoundBarcodeView(context).apply {
val capture = CaptureManager(context as Activity, this)
capture.initializeFromIntent(context.intent, null)
this.setStatusText("")
capture.decode()
this.decodeContinuous { result ->
if(scanFlag){
return#decodeContinuous
}
scanFlag = true
result.text?.let { barCodeOrQr->
//Do something
}
scanFlag = false
}
}
}
AndroidView(
modifier = Modifier.fillMaxSize(),
factory = { compoundBarcodeView },
)
}

How do I add a "save" button to the gtk filechooser dialog?

I have a Gjs app that will need to save files. I can open the file chooser dialog just fine from my menu, and I have added a "save" and "cancel" button, but I can't get the "save" button to trigger anything.
I know I'm supposed to pass it a response_id, but I'm not sure what that's supposed to look like nor what I'm supposed to do with it afterwards.
I read that part here:
https://www.roojs.com/seed/gir-1.2-gtk-3.0/gjs/Gtk.FileChooserDialog.html#expand
let actionSaveAs = new Gio.SimpleAction ({ name: 'saveAs' });
actionSaveAs.connect('activate', () => {
const saver = new Gtk.FileChooserDialog({title:'Select a destination'});
saver.set_action(Gtk.FileChooserAction.SAVE);
saver.add_button('save', 'GTK_RESPONSE_ACCEPT');
saver.add_button('cancel', 'GTK_RESPONSE_CANCEL');
const res = saver.run();
if (res) {
print(res);
const filename = saver.get_filename();
print(filename);
}
saver.destroy();
});
APP.add_action(actionSaveAs);
I can catch res and fire the associated little logging action when I close the dialog, but both the "save" and "cancel" buttons just close the dialog without doing or saying anything.
My question is, what are GTK_RESPONSE_ACCEPT and GTK_RESPONSE_CANCEL supposed to be (look like) in GJS and how do I use them?
In GJS enums like GTK_RESPONSE_* are numbers and effectively look like this:
// imagine this is the Gtk import
const Gtk = {
ResponseType: {
NONE: -1,
REJECT: -2,
ACCEPT: -3,
DELETE_EVENT: -4,
...
}
};
// access like so
let response_id = -3;
if (response_id === Gtk.ResponseType.ACCEPT) {
log(true);
}
There's a bit more information here about that.
let saver = new Gtk.FileChooserDialog({
title:'Select a destination',
// you had the enum usage correct here
action: Gtk.FileChooserAction.SAVE
});
// Really the response code doesn't matter much, since you're
// deciding what to do with it. You could pass number literals
// like 1, 2 or 3. Probably this was not working because you were
// passing a string as a response id.
saver.add_button('Cancel', Gtk.ResponseType.CANCEL);
saver.add_button('Save', Gtk.ResponseType.OK);
// run() is handy, but be aware that it will block the current (only)
// thread until it returns, so I usually prefer to connect to the
// GtkDialog::response signal and use GtkWidget.show()
saver.connect('response', (dialog, response_id) => {
if (response_id === Gtk.ResponseType.OK) {
// outputs "-5"
print(response_id);
// NOTE: we're using #dialog instead of 'saver' in the callback to
// avoid a possible cyclic reference which could prevent the dialog
// from being garbage collected.
let filename = dialog.get_filename();
// here's where you do your stuff with the filename. You might consider
// wrapping this whole thing in a re-usable Promise. Then you could call
// `resolve(filename)` or maybe `resolve(null)` if the response_id
// was not Gtk.ResponseType.OK. You could then `await` the result to get
// the same functionality as run() but allow other code to execute while
// you wait for the user.
print(filename);
// Also note, you actually have to do the writing yourself, such as
// with a GFile. GtkFileChooserDialog is really just for getting a
// file path from the user
let file = Gio.File.new_for_path(filename);
file.replace_contents_bytes_async(
// of course you actually need bytes to write, since GActions
// have no way to return a value, unless you're passing all the
// data through as a parameter, it might not be the best option
new GLib.Bytes('file contents to write to disk'),
null,
false,
Gio.FileCreateFlags.REPLACE_DESTINATION,
null,
// "shadowing" variable with the same name is another way
// to prevent cyclic references in callbacks.
(file, res) => {
try {
file.replace_contents_finish(res);
} catch (e) {
logError(e);
}
}
);
}
// destroy the dialog regardless of the response when we're done.
dialog.destroy();
});
// for bonus points, here's how you'd implement a simple preview widget ;)
saver.preview_widget = new Gtk.Image();
saver.preview_widget_active = false;
this.connect('update-preview', (dialog) => {
try {
// you'll have to import GdkPixbuf to use this
let pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(
dialog.get_preview_filename(),
dialog.get_scale_factor() * 128,
-1
);
dialog.preview_widget.pixbuf = pixbuf;
dialog.preview_widget.visible = true;
dialog.preview_widget_active = true;
// if there's some kind of error or the file isn't an image
// we'll just hide the preview widget
} catch (e) {
dialog.preview_widget.visible = false;
dialog.preview_widget_active = false;
}
});
// this is how we'll show the dialog to the user
saver.show();

Improving PWA Page Load

I have a PWA, which is essentially a book reader. As a result, it needs lots of data (viz. the book text) to operate. When analyzed by Lighthouse, it scores poorly on the Page Load Check.
My question is: What methods could I employ to improve the page load, while still ensuring offline functionality?
I could have a minimal start page (e.g., just display a 'Please wait, downloading text' message) and then dynamically download (via injected script tag or AJAX) the JSON data file. However, I'm not sure how I would subsequently ensure that the data is fetched from the cache.
Just wondering how others have handled this issue...
Since this question has gone tumbleweed, I decided to post the results of my attempts.
Based on Jake's article, I used the following script and Chrome DevTools to study service worker events:
'use strict';
let container = null;
let updateFound = false;
let newInstall = false;
window.onload = () => {
container = document.querySelector('.container');
let loading = document.createElement('div');
loading.classList.add('loading');
loading.innerHTML = 'Downloading application.<br>Please wait...';
container.appendChild(loading);
console.log(`window.onload: ${Date.now()}`);
swEvents();
};
let swEvents = () => {
if (navigator.serviceWorker) {
navigator.serviceWorker.ready.then(() => {
console.log(`sw.ready: ${Date.now()}`);
if (!updateFound) {
loadApp();
return;
}
newInstall = true;
console.log(`new install: ${Date.now()}`);
}).catch((error) => {
console.log(`sw.ready error: ${error.message}`);
});
}
navigator.serviceWorker.register('/sw.js').then((reg) => {
reg.onupdatefound = () => {
updateFound = true;
console.log(`reg.updatefound: ${Date.now()}`);
const newWorker = reg.installing;
newWorker.onstatechange = (event) => {
if (event.target.state === 'activated') {
console.log(`nw.activated: ${Date.now()}`);
if (newInstall) {
loadApp();
return;
}
refresh();
}
};
};
}).catch((error) => {
console.log(`reg.error: ${error.message}`);
});
};
let refresh = () => {
console.log(`refresh(): ${Date.now()}`);
// window.location.reload(true);
};
let loadApp = () => {
console.log(`loadApp(): ${Date.now()}`);
let child;
while (child = container.firstChild) {
container.removeChild(child);
}
let message = document.createComment('p');
message.textContent = 'Application loading';
container.appendChild(message);
let tag = document.createElement('script');
tag.src = './app.js';
document.body.appendChild(tag);
};
Along the way, I learned that once a service worker is registered, it immediately begins downloading all cached resources. I had assumed that resources were cached only after the page loaded them. I also found some definitive event patterns to indicate which lifecycle phase was occurring.
For a new install, the following events are logged in the above script:
window.onload -> reg.updatefound -> sw.ready -> nw.activated
For this case, when sw.ready fires, all resources have been cached. At this point, I can switch the app from the 'please wait' phase and dynamically load the cached resources and start the app.
For a simple page refresh, the following events are logged:
window.onload -> sw.ready
This will be the event sequence if the app has already been downloaded and no updates are available. At this point, I can again switch phase and start the app.
For a page refresh when the service worker script has been updated, the following events are logged:
window.onload -> sw.ready -> reg.updatefound -> nw.activated
In this case, when nw.activated fires, all cached resources have been updated. Another page refresh is required to actually load the changes. At this point, the user could be prompted to update. Or the app would update on its own the next time it was started.
By tracking these event patterns, it is easy to tell which lifecycle phase the service worker is in and take the appropriate action.