TypeError: intlProvider.getChildContext is not a function - jestjs

I'm using injectIntl in my react functional component to achieve the localization.
I'm using enzyme/jest to do the unit test. I've copied the intl-test-helper file, but got error:
" TypeError: intlProvider.getChildContext is not a function "
I've tried other suggestions from stackflow:
1. remove mock file --- which I don't have mock file
2. use const { IntlProvider } = jest.requireActual("react-intl"); to force it use the actual one , not mock --- not working.
the react component is: WarningModal.jsx:
import { FormattedMessage, injectIntl } from "react-intl";
.......
const WarningModal = ({
.........
...props
}) => {
........
export default injectIntl(WarningModal);
the intlTestHelper.js file is :
* Components using the react-intl module require access to the intl context.
* This is not available when mounting single components in Enzyme.
* These helper functions aim to address that and wrap a valid,
* English-locale intl context around them.
*/
import React from "react";
import { IntlProvider, intlShape } from "react-intl";
import { mount, shallow } from "enzyme"; // eslint-disable-line import/no-extraneous-dependencies
/** Create the IntlProvider to retrieve context for wrapping around. */
function createIntlContext(messages, locale) {
const { IntlProvider } = jest.requireActual("react-intl");
const intlProvider = new IntlProvider({ messages, locale }, {});
const { intl } = intlProvider.getChildContext();
return intl;
}
/** When using React-Intl `injectIntl` on components, props.intl is required. */
function nodeWithIntlProp(node, messages = {}, locale = "en") {
return React.cloneElement(node, {
intl: createIntlContext(messages, locale)
});
}
/**
* Create a shadow renderer that wraps a node with Intl provider context.
* #param {ReactComponent} node - Any React Component
* #param {Object} context
* #param {Object} messages - A map with keys (id) and messages (value)
* #param {string} locale - Locale string
*/
export function shallowWithIntl(
node,
{ context } = {},
messages = {},
locale = "en"
) {
return shallow(nodeWithIntlProp(node), {
context: Object.assign({}, context, {
intl: createIntlContext(messages, locale)
})
});
}
/**
* Mount the node with Intl provider context.
* #param {Component} node - Any React Component
* #param {Object} context
* #param {Object} messages - A map with keys (id) and messages (value)
* #param {string} locale - Locale string
*/
export function mountWithIntl(
node,
{ context, childContextTypes } = {},
messages = {},
locale = "en"
) {
return mount(nodeWithIntlProp(node), {
context: Object.assign({}, context, {
intl: createIntlContext(messages, locale)
}),
childContextTypes: Object.assign({}, { intl: intlShape }, childContextTypes)
});
}
here how I use it to test:
import React from "react";
import { _WM as WarningModal } from "../components/WarningModal";
// import { shallow } from "enzyme";
import { mountWithIntl } from "../utils/intlTestHelper.js";
describe("<WarningModal />", () => {
const props = {
discardChanges: jest.fn(),
saveChanges: jest.fn(),
closeWarningModal: jest.fn(),
intl: { formatMessage: jest.fn() }
};
it("should have heading", () => {
const wrapper = mountWithIntl(<WarningModal {...props} />);
expect(wrapper.find(".confirm-title")).toBeTruthy();
});
});
error:
● <WarningModal /> › should have heading
TypeError: intlProvider.getChildContext is not a function
14 | const { IntlProvider } = jest.requireActual("react-intl");
15 | const intlProvider = new IntlProvider({ messages, locale }, {});
> 16 | const { intl } = intlProvider.getChildContext();
| ^
17 | return intl;
18 | }
19 |
at getChildContext (src/utils/intlTestHelper.js:16:33)
at createIntlContext (src/utils/intlTestHelper.js:23:11)
at nodeWithIntlProp (src/utils/intlTestHelper.js:60:16)
at Object.<anonymous> (src/tests/WarningModal.spec.js:29:21)
please shine some lights on this. Thank you.

In later versions of react-intl getChildContext has been deprecated and may generate this error. You can use the following instead:
import { createIntl } from 'react-intl';
const intl = createIntl({ locale: "en",
messages: {
message1: "Hello world"
}
});

React-Intl has replaced IntlProvider.getChildContext, with the createIntl for testing purpose, while migrating V2 to V3.
We've removed IntlProvider.getChildContext for testing and now you can use createIntl to create a standalone intl object outside of React and use that for testing purposes. See Testing with React Intl for more details
Here is the Link
So the working code for this is
For resolving this error, you have to create custom shallow component. Like as
import { createIntl } from 'react-intl';
const LocalLanguage = {
french:{},
arabic:{},
english:{}
}
const lang = getCurrentLanguage('en', LocalLanguage);
const intl = createIntl({ locale: 'en', lang }, {});
export const shallowWithIntl = (node) => {
return shallow(nodeWithIntlProp(node), { context: { intl } });
}
If this not helps, then you can define the following function, in your helper file.
const messages = require('./Lang/en.json') // en.json
const defaultLocale = 'en'
const locale = defaultLocale
export const intl = (component) => {
return (
<IntlProvider
locale={locale}
messages={messages}
>
{React.cloneElement(component)}
</IntlProvider>
);
}
And use it in your test files as below
const wrapper = mount(intl(<MobileRechargeComponent />));

Related

How to upload files to AWS S3 from NestJS ( Suggestion )

im working in my reutilizable code, and i have for you
a FileService to Upload files to AWS S3 with NestJS.
The if conditional that you see in the code about cloudPlatform variable
is because this is not the final code.
Eventually this service will be able to upload to many cloud platforms like
GCP, Azure, AWS and FileSystem.
import { Injectable, Logger, NotFoundException, InternalServerErrorException } from '#nestjs/common';
import { ConfigService } from '#nestjs/config';
import { S3 } from 'aws-sdk';
import * as moment from 'moment';
import { v4 as uuid } from 'uuid';
import { IAWS_S3 } from './interfaces/aws-s3.interface';
import { Response } from 'express';
#Injectable()
export class FileService {
/**
*
* #variable cloudPlatform is the tecnology to save files in the cloud
* #variable awsConfig is the Amazon Web Service S3 configuration
* #S3 is the S3 to instanciate your credentials to use Amazon Web Service
*/
private logger = new Logger('FileService');
private cloudPlatform: string = this.configService.get('uploadFilesSettings').fileUploadPlatform;
private awsConfig: IAWS_S3;
private s3: S3;
constructor(
private readonly configService: ConfigService,
) {
if ( this.cloudPlatform === 'AWS_S3' ) {
this.awsConfig = this.configService.get<IAWS_S3>('aws');
this.s3 = new S3( this.awsConfig );
}
}
/**
* * Function to upload files sent from the body
* #param files of the request body in an object
* #returns an object with properties that are an string array
* like this obj = { prop1: ['a.jpg','b.jpg'], prop2: ['test.gif'] ...prop n }
*/
async uploadFiles<T, U>( files: T ): Promise<U> {
let props = Object.keys(files);
let arrPromisesPending = await this.getArrayToUpload<T>( props, files );
let arrPromisesDone = await Promise.all(
arrPromisesPending.map( async( file: Express.Multer.File ) => {
return {
fieldname: file.fieldname,
...( await this.uploadFileTo( file, this.cloudPlatform )),
}
})
)
return await this.getUploadedFiles<U>( arrPromisesDone, props );
}
/**
* * Function to get an array of objects of the files sent from the body
* * to make a Promise.all()
* #param props all properties ( fieldname in the form-data body ) of the files object
* #param files of the request body in an object
* #returns an array of objects that every object is the file that will be uploaded
*/
async getArrayToUpload<T>( props: string[], files: T ): Promise<Object[]> {
let promises: Object[] = [];
props.map(( prop: string ) => {
files[prop].map(( file: Express.Multer.File ) => {
promises.push( file );
});
})
return promises;
}
/**
* * Function that upload the file to cloud platform
* #param file object to upload
* #param cloudPlatform site where files will be uploaded
* #returns the response of cloudPlatform
*/
async uploadFileTo( file: Express.Multer.File, cloudPlatform: string ): Promise<any> {
let type: string = file.originalname.split(".").pop();
let genericFilename: string = `${ uuid() }-${ moment().format().split('T')[0] }`;
if ( cloudPlatform === 'AWS_S3' ) {
try {
return await this.s3.upload({
Bucket: this.awsConfig.bucketName,
Key: `${ genericFilename }.${ type }`,
Body: file.buffer,
ContentType: file.mimetype,
}).promise();
} catch ( error ) {
this.logger.error( error );
throw new InternalServerErrorException('Unexpected error, check server logs');
}
}
this.logger.error('Set .env variable "FILE_UPLOAD_PLATFORM"');
throw new InternalServerErrorException('Unexpected error, check server logs');
}
/**
* * Function to format the names of the files uploaded to the cloud
* * platform and return them in an object with the corresponding
* * properties and each of these will have an array of strings
*
* #param arrPromisesDone an array of objects that were uploaded
* #param props that's means fieldnames of the files
* #returns
* ! Example of returns -----------------------------------------------------------
* ! {
* ! avatar: ['image1.png','image2.jpg', 'image3.jpeg', 'image4.jpg'],
* ! background: ['image1.png','image2.jpg', 'image3.jpeg', 'image4.jpg'],
* ! }
*/
async getUploadedFiles<T>( arrPromisesDone: Object[], props: string[] ): Promise<T> {
let uploadedFiles: any = {};
props.map(( prop: string ) => {
uploadedFiles[prop] = [];
arrPromisesDone.map(( file: any ) => {
if ( file.fieldname === prop ) {
uploadedFiles[prop].push( file.key );
}
});
});
return uploadedFiles;
}
/**
*
* #param imageName name of the image
* #param res #Res() to response from Express
* #returns the image obtained from the cloud platform to client
*/
async getFile( imageName: string, res: Response ) {
const params = { Bucket: this.awsConfig.bucketName, Key: imageName };
if ( this.cloudPlatform === 'AWS_S3' ) {
try {
const data = await this.s3.getObject( params ).promise();
res.contentType( data.ContentType );
return res.send( data.Body );
} catch ( error ) {
if ( error.statusCode === 404 )
throw new NotFoundException(`image ${ imageName } don't exist`);
this.logger.error( error );
throw new InternalServerErrorException('Unexpected error, check server logs');
}
}
this.logger.error('Set .env variable "FILE_UPLOAD_PLATFORM"');
throw new InternalServerErrorException('Unexpected error, check server logs');
}
}

next-i18next Jest Testing with useTranslation

Testing libs...always fun. I am using next-i18next within my NextJS project. We are using the useTranslation hook with namespaces.
When I run my test there is a warning:
console.warn
react-i18next:: You will need to pass in an i18next instance by using initReactI18next
> 33 | const { t } = useTranslation(['common', 'account']);
| ^
I have tried the setup from the react-i18next test examples without success. I have tried this suggestion too.
as well as just trying to mock useTranslation without success.
Is there a more straightforward solution to avoid this warning? The test passes FWIW...
test('feature displays error', async () => {
const { findByTestId, findByRole } = render(
<I18nextProvider i18n={i18n}>
<InviteCollectEmails onSubmit={jest.fn()} />
</I18nextProvider>,
{
query: {
orgId: 666,
},
}
);
const submitBtn = await findByRole('button', {
name: 'account:organization.invite.copyLink',
});
fireEvent.click(submitBtn);
await findByTestId('loader');
const alert = await findByRole('alert');
within(alert).getByText('failed attempt');
});
Last, is there a way to have the translated plain text be the outcome, instead of the namespaced: account:account:organization.invite.copyLink?
Use the following snippet before the describe block OR in beforeEach() to mock the needful.
jest.mock("react-i18next", () => ({
useTranslation: () => ({ t: key => key }),
}));
Hope this helps. Peace.
use this for replace render function.
import { render, screen } from '#testing-library/react'
import DarkModeToggleBtn from '../../components/layout/DarkModeToggleBtn'
import { appWithTranslation } from 'next-i18next'
import { NextRouter } from 'next/router'
jest.mock('react-i18next', () => ({
I18nextProvider: jest.fn(),
__esmodule: true,
}))
const createProps = (locale = 'en', router: Partial<NextRouter> = {}) => ({
pageProps: {
_nextI18Next: {
initialLocale: locale,
userConfig: {
i18n: {
defaultLocale: 'en',
locales: ['en', 'fr'],
},
},
},
} as any,
router: {
locale: locale,
route: '/',
...router,
},
} as any)
const Component = appWithTranslation(() => <DarkModeToggleBtn />)
const defaultRenderProps = createProps()
const renderComponent = (props = defaultRenderProps) => render(
<Component {...props} />
)
describe('', () => {
it('', () => {
renderComponent()
expect(screen.getByRole("button")).toHaveTextContent("")
})
})
I used a little bit more sophisticated approach than mocking to ensure all the functions work the same both in testing and production environment.
First, I create a testing environment:
// testing/env.ts
import i18next, { i18n } from "i18next";
import JSDomEnvironment from "jest-environment-jsdom";
import { initReactI18next } from "react-i18next";
declare global {
var i18nInstance: i18n;
}
export default class extends JSDomEnvironment {
async setup() {
await super.setup();
/* The important part start */
const i18nInstance = i18next.createInstance();
await i18nInstance.use(initReactI18next).init({
lng: "cimode",
resources: {},
});
this.global.i18nInstance = i18nInstance;
/* The important part end */
}
}
I add this environment in jest.config.ts:
// jest.config.ts
export default {
// ...
testEnvironment: "testing/env.ts",
};
Sample component:
// component.tsx
import { useTranslation } from "next-i18next";
export const Component = () => {
const { t } = useTranslation();
return <div>{t('foo')}</div>
}
And later on I use it in tests:
// component.test.tsx
import { setI18n } from "react-i18next";
import { create, act, ReactTestRenderer } from "react-test-renderer";
import { Component } from "./component";
it("renders Component", () => {
/* The important part start */
setI18n(global.i18nInstance);
/* The important part end */
let root: ReactTestRenderer;
act(() => {
root = create(<Component />);
});
expect(root.toJSON()).toMatchSnapshot();
});
I figured out how to make the tests work with an instance of i18next using the renderHook function and the useTranslation hook from react-i18next based on the previous answers and some research.
This is the Home component I wanted to test:
import { useTranslation } from 'next-i18next';
const Home = () => {
const { t } = useTranslation("");
return (
<main>
<div>
<h1> {t("welcome", {ns: 'home'})}</h1>
</div>
</main>
)
};
export default Home;
First, we need to create a setup file for jest so we can start an i18n instance and import the translations to the configuration. test/setup.ts
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import homeES from '#/public/locales/es/home.json';
import homeEN from '#/public/locales/en/home.json';
i18n.use(initReactI18next).init({
lng: "es",
resources: {
en: {
home: homeEN,
},
es: {
home: homeES,
}
},
fallbackLng: "es",
debug: false,
});
export default i18n;
Then we add the setup file to our jest.config.js:
setupFilesAfterEnv: ["<rootDir>/test/setup.ts"]
Now we can try our tests using the I18nextProvider and the useTranslation hook:
import '#testing-library/jest-dom/extend-expect';
import { cleanup, render, renderHook } from '#testing-library/react';
import { act } from 'react-dom/test-utils';
import { I18nextProvider, useTranslation } from 'react-i18next';
import Home from '.';
describe("Index page", (): void => {
afterEach(cleanup);
it("should render properly in Spanish", (): void => {
const t = renderHook(() => useTranslation());
const component = render(
<I18nextProvider i18n={t.result.current.i18n}>
<Home / >
</I18nextProvider>
);
expect(component.getByText("Bienvenido a Pocky")).toBeInTheDocument();
});
it("should render properly in English", (): void => {
const t = renderHook(() => useTranslation());
act(() => {
t.result.current.i18n.changeLanguage("en");
});
const component = render(
<I18nextProvider i18n={t.result.current.i18n}>
<Home/>
</I18nextProvider>
);
expect(component.getByText("Welcome to Pocky")).toBeInTheDocument();
});
});
Here we used the I18nextProvider and send the i18n instance using the useTranslation hook. after that the translations were loaded without problems in the Home component.
We can also change the selected language running the changeLanguage() function and test the other translations.

How to stub nested dependecies with ts-sinon

I got a simple unit test with the following code:
my-pubsub.spec.ts
import * as tsSinon from 'ts-sinon';
import { myPubSubFunction } from './my-pubsub';
import * as sendEmail from './send-mail';
describe("Notifications PubSub tests", () => {
it("Should trigger audit", (done) => {
const today = new Date()
const data = {
( my data )
}
const spy = tsSinon.default.spy(sendEmail, "sendNotificationMessage")
const dataBuffer = Buffer.from(JSON.stringify(data))
// Call tested function and verify its behavior
myPubSubFunction(dataBuffer)
setTimeout(() => {
// check if spy was called
tsSinon.default.assert.calledOnce(spy)
done()
}, 100)
})
})
And my-pubsub.ts got a call to a function from send-mail with contains a function to set the Api key
import * as sgMail from '#sendgrid/mail';
sgMail.setApiKey(
"MyKey"
) // error in here
export function sendNotificationMessage(mailConfig: any) {
const defaultConfig = {
from: {
email: "noreply#mymail.com",
name: "my name",
},
template_id: "my template",
}
const msg = { ...defaultConfig, ...mailConfig }
return sgMail.send(msg)
}
However when running my tests I got the following error TypeError: sgMail.setApiKey is not a function
Edit: added a bit more code to the send-mail code.
Bellow you can find a bit more code about my-pubsub.ts
my-pubsub.ts
import * as admin from 'firebase-admin';
import * as functions from 'firebase-functions';
import moment = require('moment');
import { IModel, ModelType } from '../models/model.model';
import { sendNotificationMessage } from '../shared/send-mail';
const { PubSub } = require("#google-cloud/pubsub")
try {
admin.initializeApp()
} catch (e) {}
const db = admin.firestore()
const pubSubClient = new PubSub()
export const myPubSubTrigger = functions.pubsub
.topic("on-trigger")
.onPublish(async (message) => {
console.log("version 1")
const myMessage = Buffer.from(message.data, "base64").toString("utf-8")
const data: IModel = JSON.parse(myMessage)
( logic to create my object )
/**
* Send email
*/
const result: any = await sendNotificationMessage(myObject)
/**
* Check result
*/
if (result[0].statusCode === 202) {
await docRef.update({ emailSent: true })
}
( another publish to audit the action )
})
The problem is not with tests per se, but incorrect types definition of #sendgrid/mail:
// OK
import sgMail from "#sendgrid/mail";
import { default as sgMail2 } from "#sendgrid/mail";
console.log(sgMail === sgMail2);
sgMail.setApiKey("SG.key");
sgMail2.setApiKey("SG.key2");
// BROKEN
// type definition does not match runtime shape
import * as sgMailIncorrectlyTyped from "#sendgrid/mail";
console.log(sgMailIncorrectlyTyped, sgMailIncorrectlyTyped.setApiKey === undefined);
STACKBLITZ

App Engine Node.js: how to link app logs and requests logs

I am using Node.js on App Engine Standard and Flexible.
In the logs viewer, is it possible to display application logs nested inside request logs?
Yes it is possible to correlate application logs and request logs. This is the end result in the Logs Viewer:
To achieve this you can either:
Use both the #google-cloud/trace-agent and #google-cloud/logging-bunyan modules in your application. When you do so, your logs are automatically annotated with the correct Trace ID (see docs for Bunyan).
Extract the trace ID from the request header
If you do not want to use the Trace module, you can extract the trace ID from the request header, use the following code to extract the traceId:
const traceHeader = req && req.headers ? req.headers['x-cloud-trace-context'] || '' : '';
const traceId = traceHeader ? traceHeader.split('/')[0] : '';
Then, you need to populate the trace attribute of your log entries. When using the Bunyan logger, use the following code:
logger.info({
'logging.googleapis.com/trace': `projects/${project}/traces/${traceId}`
}, 'your message');
I also faced the same issue sometime back and did some workaround to make it. But in the above-mentioned solution might not help in some use cases where you have don't req, res object reference.
So here the solution. it will group all the logs under the request log.
Also created -> NPM Module
File Name: correlate-logs.js
import bunyan from 'bunyan';
import { LOGGING_TRACE_KEY } from '#google-cloud/logging-bunyan';
import cls from 'cls-hooked';
import uuid from 'uuid/v1';
/**
* CreateLogger will return loggerContextMiddleware and log.
* Bind the loggerContextMiddleware on top to corelate other middleware logs. `app.use(loggerContextMiddleware);`
* then you can log like this anywhere `log.info('This is helpful to see corelated logs in nodejs.)` and it will show with-in reqeust log.
* #param {*} options
*/
export default function createLogger(projectId, bunyanLoggerOptions) {
if (!projectId || !bunyanLoggerOptions) throw new Error('Please pass the required fields projectId and bunyanLoggerOption');
const ns = cls.createNamespace(`logger/${uuid()}`); // To create unique namespace.
const logger = bunyan.createLogger(bunyanLoggerOptions);
/**
* Express Middleware to add request context to logger for corelating the logs in GCP.
* #param {*} req
* #param {*} res
* #param {*} next
*/
const loggerContextMiddleware = (req, res, next) => {
const traceHeader = (req && req.headers && req.headers['x-cloud-trace-context']) || '';
if (traceHeader) {
ns.bindEmitter(req);
ns.bindEmitter(res);
const traceId = traceHeader ? traceHeader.split('/')[0] : '';
const trace = `projects/${projectId}/traces/${traceId}`;
ns.run(() => {
ns.set('trace', trace);
next();
});
} else {
next();
}
};
/**
* Helper method to get the trace id from CLS hook.
*/
function getTrace() {
if (ns && ns.active) return ns.get('trace');
return '';
}
/**
* Simple wrapper to avoid pushing dev logs to cloud.
* #param {*} level
* #param {*} msg
*/
function printLog(level, ...msg) {
const trace = getTrace();
if (trace) { logger[level]({ [LOGGING_TRACE_KEY]: trace }, ...msg); } else { logger[level](...msg); }
}
/**
* Little wrapper to abstract the log level.
*/
const log = ['trace', 'debug', 'info', 'warn', 'error', 'fatal'].reduce((prev, curr) => ({ [curr]: (...msg) => printLog(curr, ...msg), ...prev }), {});
return { loggerContextMiddleware, log };
}
File Name: logger.js
import { LoggingBunyan } from '#google-cloud/logging-bunyan';
import createLogger from '../lib/corelate-logs';
import { getProjectId, ifDev } from './config';
// Creates a Bunyan Stackdriver Logging client
const loggingBunyan = new LoggingBunyan();
let loggerOption;
if (ifDev()) {
const bunyanDebugStream = require('bunyan-debug-stream'); // eslint-disable-line
loggerOption = {
name: 'my-service',
streams: [{
level: 'info',
type: 'raw',
stream: bunyanDebugStream({
forceColor: true,
}),
}],
serializers: bunyanDebugStream.serializers,
};
} else {
loggerOption = {
name: 'my-service',
level: 'info',
streams: [loggingBunyan.stream('info')],
};
}
const { loggerContextMiddleware, log } = createLogger(getProjectId() || 'dev', loggerOption);
export { loggerContextMiddleware, log };
Hope this helps somebody.

ESM import not working with redux, Node 10.1.0

//index.mjs
import { createStore } from 'redux'
import todoApp from './reducers'
const store = createStore(todoApp)
import {
addTodo,
toggleTodo,
setVisibilityFilter,
VisibilityFilters
} from './actions'
// Log the initial state
console.log(store.getState())
// Every time the state changes, log it
// Note that subscribe() returns a function for unregistering the listener
const unsubscribe = store.subscribe(() =>
console.log(store.getState())
)
// Dispatch some actions
store.dispatch(addTodo('Learn about actions'))
store.dispatch(addTodo('Learn about reducers'))
store.dispatch(addTodo('Learn about store'))
store.dispatch(toggleTodo(0))
store.dispatch(toggleTodo(1))
store.dispatch(setVisibilityFilter(VisibilityFilters.SHOW_COMPLETED))
// Stop listening to state updates
unsubscribe()
//reducers.mjs
import { combineReducers } from 'redux'
import {
ADD_TODO,
TOGGLE_TODO,
SET_VISIBILITY_FILTER,
VisibilityFilters
} from './actions'
const { SHOW_ALL } = VisibilityFilter
function visibilityFilter(state = SHOW_ALL, action) {
switch (action.type) {
case SET_VISIBILITY_FILTER:
return action.filter
default:
return state
}
}
function todos(state = [], action) {
switch (action.type) {
case ADD_TODO:
return [
...state,
{
text: action.text,
completed: false
}
]
case TOGGLE_TODO:
return state.map((todo, index) => {
if (index === action.index) {
return Object.assign({}, todo, {
completed: !todo.completed
})
}
return todo
})
default:
return state
}
}
const todoApp = combineReducers({
visibilityFilter,
todos
})
export default todoApp
// actions.mjs
/*
* action types
*/
export const ADD_TODO = 'ADD_TODO'
export const TOGGLE_TODO = 'TOGGLE_TODO'
export const SET_VISIBILITY_FILTER = 'SET_VISIBILITY_FILTER'
/*
* other constants
*/
export const VisibilityFilters = {
SHOW_ALL: 'SHOW_ALL',
SHOW_COMPLETED: 'SHOW_COMPLETED',
SHOW_ACTIVE: 'SHOW_ACTIVE'
}
/*
* action creators
*/
export function addTodo(text) {
return { type: ADD_TODO, text }
}
export function toggleTodo(index) {
return { type: TOGGLE_TODO, index }
}
export function setVisibilityFilter(filter) {
return { type: SET_VISIBILITY_FILTER, filter }
}
Edit- added code
I am working through the Redux Basics tutorial from the Redux docs, and am having trouble getting import to work properly.
I currently have 3 files for my todo-list app:
actions.mjs
reducers.mjs
index.mjs
I have done an npm init -y, and npm install --save redux. I copy and pasted the source code directly from the Redux docs for all 3 files.
With the command: node --experimental-modules index, I get the error:
SyntaxError: The requested module 'redux' does not provide an export named 'combineReducers'
I would expect similar error messages for other named exports of Redux...
I have had success with refactoring back to CommonJS using require, module.exports, .js file extensions, and the command: node index.js

Resources