I am having the following problem setting up my redux store in a react app with preloadedState: I am relying on data from an async function. I tried to solve it with top level await, but I couldn't enable it via the webpack.config.js file (how to enable top level await in reactjs).
my first question is if react (^18.2.0) supports top level await?
my second question is if there is a workaround to my problem?
async function preloadState() {
const selectionState = await window.browser.storage.local.get("selectionState");
const externalTimings = await window.browser.storage.local.get("externalTimings");
return {
'selectionState': selectionState,
'externalTimings': externalTimings
}
}
export const store = configureStore({
preloadedState: preloadState(),
reducer: {
selectionState: selectionStateSliceReducer,
externalTimings: externalTimingsReducer,
},
})
Related
I am trying to call an async function but I am getting an error
getUsersList(db).then is not a function
this is my code
async function getUsersList(db) {
const userCol = collection(db, 'Users');
const userSnapshot = await getDocs(userCol);
const tempUserList = userSnapshot.docs.map(doc => doc.data());
return tempUserList;
}
function App() {
const app = initializeApp(firebaseConfig);
const db = getFirestore(app);
const auth = getAuth(app);
var currentUser = auth.currentUser;
if(currentUser != null){
getUsersList(db).then((value) => {
console.log(value);
});
I also tried using await getUsersList but got the following error
Unexpected reserved word 'await'
So what I assume you're trying to do here const userSnapshot = await getDocs(userCol); is fetch some data that is then going to be used in your react component to render something (Maybe there's a fetch request in getDocs ?)
There is no return in your react component (but that's not what's causing your issue).
As it is it won't work using await since App() isn't an async function BUT you can't make it an async function since this is a standard react component.
What do you want to happen whilst waiting for the data to be fetched (= whilst your promise is pending) ? If you're happy to display nothing, why not just remove await before getDocs() ?
For more on this topic :
React: async and await not working with fetch
React Hooks: how to wait for the data to be fetched before rendering
Before top-level await becomes a thing, loading secrets asynchronously from AWS Secrets Manager upon startup is a bit of a pain. I'm wondering if anyone has a better solution than what I currently have.
Upon starting up my Node.JS server I'm loading all secrets from AWS Secrets manager and setting them in config files where I have a mix of hardcoded variables and secrets. Here's an example:
In aws.js
import AWS from 'aws-sdk';
const region = "eu-north-1";
AWS.config.setPromisesDependency();
const client = new AWS.SecretsManager({
region
});
export const getSecret = async(secretName) => {
const data = await client.getSecretValue({SecretId: secretName}).promise();
return data.SecretString;
}
Then in sendgridConfig.js
import { getSecret } from "./aws";
export default async() => {
const secret = JSON.parse(await getSecret("sendgridSecret"));
return {
APIKey: secret.sendgridKey,
fromEmail: "some#email.com",
toEmail: "some#email.com"
}
}
Then in some file where the config is used:
import { sendgridConfig } from "./sendgridConfig";
const myFunc = () => {
const sendgridConf = await sendgridConfig();
... do stuff with config ...
}
This works okay in async functions, but what if I'd like to use the same setup in non-async functions where I use my hardcoded variables? Then the secrets haven't been fetched yet, and I can't use them. Also I have to always await the secrets. IMO a good solution in the future could be top level await, where upon booting the server, the server will await the secrets from AWS before proceeding. I guess I could find a way to block the main thread and set the secrets, but that feels kind of hacky.
Does anyone have a better solution?
So I ended up doing the following. First I'm setting the non-async config variables in an exported object literal. Then I'm assigning values to the object literal in the sendgridConfigAsync IIFE (doesn't have to be an IFEE). That way I don't have to await the config promise. As long as the app awaits the IIFE on startup, the keys will be assigned before being accessed.
In sendgridConfig.js
import { getSecret } from "./aws";
export const sendgridConfig = {
emailFrom: process.env.sendgridFromEmail,
emailTo: process.env.sendgridToEmail
}
export const sendgridConfigAsync = (async() => {
const secret = JSON.parse(await getSecret("Sendgrid-dev"));
sendgridConfig.sendgridKey = secret.key;
})()
Then in the main config file _index.js where I import all the config files.
import { sendgridConfigAsync } from "./sendgrid";
import { twilioConfigAsync } from "./twilio";
import { appConfigAsync } from "./app";
export const setAsyncConfig = async() => {
await Promise.all([
appConfigAsync,
sendgridConfigAsync,
twilioConfigAsync
]);
}
Then in the main index.js file I'm awaiting the setAsyncConfig function first. I did also rebuild the app somewhat in order to control all function invocations and promise resolving in the desired order.
import { servicesConnect } from "../src/service/_index.js";
import { setAsyncConfig } from '$config';
import { asyncHandler } from "./middleware/async";
import { middleware } from "./middleware/_index";
import { initJobs } from "./jobs/_index"
import http from 'http';
async function startServer() {
await setAsyncConfig();
await servicesConnect();
await initJobs();
app.use(middleware);
app.server = http.createServer(app);
app.server.listen(appConfig.port);
console.log(`Started on port ${app.server.address().port}`);
}
asyncHandler(startServer());
Yup I have the same problem. Once you start with a promise, all dependencies down the line require await.
One solution is to do your awaits and only after that have all your downstream code run. Requires a slightly different software architecture.
E.g.
export const getSecretAndThenDoStuff = async(secretName) => {
const data = await client.getSecretValue({SecretId: secretName}).promise();
// instead of return data.SecretString;
runYourCodeThatNeedsSecret(data);
}
A more generic solution to the top-level await that I tend to use:
async function main() {
// Do whatever you want with await here
}
main();
Clean and simple.
I'm currently converting a vue application to use the NUXT framework. To add support for SSR to my application and have come across the following issue when trying to use asyncData with multiple data sources on a single page.
I'm trying to setup asyncData to get data from 2 separate data sources that are required for the page to work. Now the code works on the client-side when the Promise.all resolves it gets both sets of data. However, on the server-side the promises when console.log the promises are both pending and causes Nginx to timeout and give a 504 bad gateway error.
I have tried to get this to work use async/await and promise.all with no avail. see code samples for both below.
Import functions getData and getJsonFile are both using Axios and returning resolved promises with objects of data.
// Using async/await
export default {
async asyncData(context) {
const nameData = await getData('getInformationByNames', {
names: [context.params.name],
referingPage: `https://local.test.com${context.route.fullPath}`
});
const content = await getJsonFile(
`/data/pages/user/${context.params.id}`
);
return {
names: nameData,
content
};
}
}
// Using Promise.all
export default {
async asyncData(context) {
const [nameData, content] = await Promise.all([
getData('getInformationByNames', {
names: [context.params.name],
referingPage: `https://local.test.com${context.route.fullPath}`
}),
getJsonFile(`/data/pages/user/${context.params.id}`)
]);
return {
names: nameData,
content
};
}
}
// getJsonFile
import axios from 'axios';
import replaceStringTokens from '#/scripts/helpers/replaceStringTokens';
export default function getJsonFile(path, redirect = true) {
const jsonFilePath = `${path}.json`;
return axios.get(jsonFilePath).then((response) => {
if (typeof response.data === 'object') {
return replaceStringTokens(response.data);
}
return false;
});
}
// getData
import axios from 'axios';
import getUserDevice from '#/scripts/helpers/getUserDevice';
// require php-serialize node package to serialize the data like PHP would for the api endpoint.
const Serialize = require('php-serialize');
export default function getData(action, data) {
const dataApiAddress = '/api/getData.php';
const dataToPass = data || {};
// all actions available on the api will need to know the users device so add it to the data.
dataToPass.userDevice = getUserDevice();
// package the data like the api expects to receive it
const serializedAndEncodedData = encodeURIComponent(
Serialize.serialize(dataToPass)
);
const axiosParams = {
action,
data: serializedAndEncodedData
};
return axios
.get(dataApiAddress, {
params: axiosParams
})
.then((response) => {
return response.data.data;
})
.catch((error) => {
console.log(error);
return false;
});
}
I would expect that the promises resolve and both sets of data are returned an available for the page to use on both the client and server-side.
Is there a better way to get multiple sets of data or a way to debug the server-side so that I can see what is causing the promises to not resolve on the server?
Fixed the issue the problem was with some discrepancies in the data being queried on the API. The data in the database was using an uppercase letter at the start that must have been input incorrectly. So this was causing the promise to not resolve due to the API sending a query for the lowercase version and in turn causing Nginx to timeout.
I'm new to nuxt.js so I'm wondering what could be the best way to set up some data via REST api.
I have a store folder like this:
store
-posts.js
-categories.js
-index.js
I've tried to set the data with nuxtServerInit actions in the index.js:
export const actions = {
async nuxtServerInit({ dispatch }) {
await dispatch('categories/setCategories')
await dispatch('posts/loadPosts','all')
}
}
But doesn't works: actions are dispatched (on the server) but data are not set.
So I've tried with fetch but this method is called every time the page where I have to display posts is loaded. Even if, in the general layout, I do this:
<template>
<div>
<Header />
<keep-alive>
<nuxt/>
</keep-alive>
</div>
</template>
So my solution, for now, is to use fetch in this way,
In the page component:
async fetch({store}){
if(store.getters['posts/getPosts'].length === 0 && store.getters['categories/getCategories'].length === 0 ){
await store.dispatch('categories/setCategories')
await store.dispatch('posts/loadPosts','all')
}
}
Also, one thing I noted is that fetch seems not working on the root page component (pages/index.vue)
My solution seems works, but there is maybe another better way to set the data?
There's no out of the box solution for this as it's specific to your requirements/needs. My solution is very similar to yours but instead of checking the size of data array I introduced additional variable loaded in every store module. I only fetch data if loaded is false. This approach is more suitable in apps that have user generated content and require authentication. It will work optimally with SSR and client-side, and it won't try to fetch data on every page visit if user has no data.
You could also simplify your fetch method like this:
async fetch()
{
await this.$store.dispatch('posts/getOnce')
}
Now your posts.js store module will look something like this:
export const state = () => ({
list: [],
loaded: false
})
export const actions = {
async getOnce({ dispatch, state }) {
if (!state.loaded) {
dispatch('posts/get')
}
},
async get({ commit, state }) {
await this.$axios.get(`/posts`)
.then((res) => {
if (res.status === 200) {
commit('set', res.data.posts)
}
})
}
}
export const mutations = {
set(state, posts) {
state.list = posts
state.loaded = true
}
}
I'm currently building a learner React/Redux Application and I can not wrap my head around how to do dependency injection for services.
To be more specific: I have a BluetoothService (which abstracts a 3rd Party Library) to scan for and connect to other devices via bluetooth. This service gets utilized by the action creators, something like this:
deviceActionCreators.js:
const bluetoothService = require('./blueToothService')
function addDevice(device) {
return { type: 'ADD_DEVICE', device }
}
function startDeviceScan() {
return function (dispatch) {
// The Service invokes the given callback for each found device
bluetoothService.startDeviceSearch((device) => {
dispatch(addDevice(device));
});
}
}
module.exports = { addDevice, startDeviceScan };
(I am using the thunk-middleware)
My Problem however is: how to inject the service itself into the action-creator?
I don't want that hard-coded require (or importin ES6) as I don't think this is a good pattern - besides making testing so much harder. I also want to be able to use a mock-service while testing the app on my work station (which doesn't have bluetooth) - so depending on the environment i want another service with the same interface injected inside my action-creator. This is simply not possible with using a static import.
I already tried making the bluetoothService a parameter for the Method itself (startDeviceScan(bluetoothService){}) - effectively making the method itself pure - but that just moves the problem to the containers using the action. Every container would have to know about the service then and get an implementation of it injected (for example via props).
Plus when I want to use the action from within another action I end up with the same problem again.
The Goal:
I want to decide on bootstrapping time which implemenation to use in my app.
Is there a good way or best practice for doing this?
React-thunk supports passing an arbitrary object to a thunk using withExtraArgument. You can use this to dependency-inject a service object, e.g.:
const bluetoothService = require('./blueToothService');
const services = {
bluetoothService: bluetoothService
};
let store = createStore(reducers, {},
applyMiddleware(thunk.withExtraArgument(services))
);
Then the services are available to your thunk as a third argument:
function startDeviceScan() {
return function (dispatch, getstate, services) {
// ...
services.bluetoothService.startDeviceSearch((device) => {
dispatch(addDevice(device));
});
}
}
This is not as formal as using a dependency-injection decorator in Angular2 or creating a separate Redux middleware layer to pass services to thunks---it's just an "anything object" which is kind of ugly---but on the other hand it's fairly simple to implement.
You can use a redux middleware that will respond to an async action. In this way you can inject whatever service or mock you need in a single place, and the app will be free of any api implementation details:
// bluetoothAPI Middleware
import bluetoothService from 'bluetoothService';
export const DEVICE_SCAN = Symbol('DEVICE_SCAN'); // the symbol marks an action as belonging to this api
// actions creation helper for the middleware
const createAction = (type, payload) => ({
type,
payload
});
// This is the export that will be used in the applyMiddleware method
export default store => next => action => {
const blueToothAPI = action[DEVICE_SCAN];
if(blueToothAPI === undefined) {
return next(action);
}
const [ scanDeviceRequest, scanDeviceSuccess, scanDeviceFailure ] = blueToothAPI.actionTypes;
next(createAction(scanDeviceRequest)); // optional - use for waiting indication, such as spinner
return new Promise((resolve, reject) => // instead of promise you can do next(createAction(scanDeviceSuccess, device) in the success callback of the original method
bluetoothService.startDeviceSearch((device) => resolve(device), (error) = reject(error)) // I assume that you have a fail callback as well
.then((device) => next(createAction(scanDeviceSuccess, device))) // on success action dispatch
.catch((error) => next(createAction(scanDeviceFailure, error ))); // on error action dispatch
};
// Async Action Creator
export const startDeviceScan = (actionTypes) => ({
[DEVICE_SCAN]: {
actionTypes
}
});
// ACTION_TYPES
export const SCAN_DEVICE_REQUEST = 'SCAN_DEVICE_REQUEST';
export const SCAN_DEVICE_SUCCESS = 'SCAN_DEVICE_SUCCESS';
export const SCAN_DEVICE_FAILURE = 'SCAN_DEVICE_FAILURE';
// Action Creators - the actions will be created by the middleware, so no need for regular action creators
// Applying the bluetoothAPI middleware to the store
import { createStore, combineReducers, applyMiddleware } from 'redux'
import bluetoothAPI from './bluetoothAPI';
const store = createStore(
reducers,
applyMiddleware(bluetoothAPI);
);
// Usage
import { SCAN_DEVICE_REQUEST, SCAN_DEVICE_SUCCESS, SCAN_DEVICE_FAILURE } from 'ACTION_TYPES';
dispatch(startDeviceScan([SCAN_DEVICE_REQUEST, SCAN_DEVICE_SUCCESS, SCAN_DEVICE_FAILURE]));
You dispatch the startDeviceScan async action, with the action types that will be used in the creation of the relevant actions. The middleware identifies the action by the symbol DEVICE_SCAN. If the action doesn't contain the symbol, it dispatches it back to the store (next middleware / reducers).
If the symbol DEVICE_SCAN exists, the middleware extracts the action types, creates and dispatches a start action (for a loading spinner for example), makes the async request, and then creates and dispatches a success or failure action.
Also look at the real world redux middle example.
Can you wrap your action creators into their own service?
export function actionCreatorsService(bluetoothService) {
function addDevice(device) {
return { type: 'ADD_DEVICE', device }
}
function startDeviceScan() {
return function (dispatch) {
// The Service invokes the given callback for each found device
bluetoothService.startDeviceSearch((device) => {
dispatch(addDevice(device));
});
}
}
return {
addDevice,
startDeviceScan
};
}
Now, any clients of this service will need to provide an instance of the bluetoothService. In your actual src code:
const bluetoothService = require('./actual/bluetooth/service');
const actionCreators = require('./actionCreators')(bluetoothService);
And in your tests:
const mockBluetoothService = require('./mock/bluetooth/service');
const actionCreators = require('./actionCreators')(mockBluetoothService);
If you don't want to specify the bluetooth service every time you need to import the action creators, within the action creators module you can have a normal export (that uses the actual bluetooth service) and a mock export (that uses a mock service). Then the calling code might look like this:
const actionCreators = require('./actionCreators').actionCreators;
And your test code might look like this:
const actionCreators = require('./actionCreators').mockActionCreators;
I created a dependency-injecting middleware called redux-bubble-di for exactly that purpose. It can be used to inject an arbitrary number of dependencies into action creators.
You can install it by npm install --save redux-bubble-di or download it.
Your example using redux-bubble-di would look like this:
//import { DiContainer } from "bubble-di";
const { DiContainer } = require("bubble-di");
//import { createStore, applyMiddleware } from "redux";
const { createStore, applyMiddleware } = require("redux");
//import reduxBubbleDi from "redux-bubble-di";
const reduxBubbleDi = require("redux-bubble-di").default;
const bluetoothService = require('./blueToothService');
DiContainer.setContainer(new DiContainer());
DiContainer.getContainer().registerInstance("bluetoothService", bluetoothService);
const store = createStore(
state => state,
undefined,
applyMiddleware(reduxBubbleDi(DiContainer.getContainer())),
);
const startDeviceScan = {
bubble: (dispatch, bluetoothService) => {
bluetoothService.startDeviceSearch((device) => {
dispatch(addDevice(device));
});
},
dependencies: ["bluetoothService"],
};
// ...
store.dispatch(startDeviceScan);