Skip to content

Commit

Permalink
feat(react): upgrade redux toolkit version and generated slice code (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
jdpearce authored Apr 17, 2020
1 parent b245d12 commit 54d06f0
Show file tree
Hide file tree
Showing 8 changed files with 209 additions and 117 deletions.
18 changes: 17 additions & 1 deletion e2e/react.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import {
renameFile,
runCLI,
runCLIAsync,
supportUi,
uniq,
updateFile,
workspaceConfigName
Expand Down Expand Up @@ -220,6 +219,23 @@ forEachCli(currentCLIName => {
expect(libTestResults.stderr).toContain('Test Suites: 1 passed, 1 total');
}, 120000);

it('should be able to add a redux slice', async () => {
ensureProject();
const appName = uniq('app');
const libName = uniq('lib');

runCLI(`g @nrwl/react:app ${appName} --no-interactive`);
runCLI(`g @nrwl/react:redux lemon --project=${appName}`);
runCLI(`g @nrwl/react:lib ${libName} --no-interactive`);
runCLI(`g @nrwl/react:redux orange --project=${libName}`);

const appTestResults = await runCLIAsync(`test ${appName}`);
expect(appTestResults.stderr).toContain('Test Suites: 2 passed, 2 total');

const libTestResults = await runCLIAsync(`test ${libName}`);
expect(libTestResults.stderr).toContain('Test Suites: 2 passed, 2 total');
}, 120000);

it('should be able to use JSX', async () => {
ensureProject();
const appName = uniq('app');
Expand Down
11 changes: 9 additions & 2 deletions e2e/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,10 +237,14 @@ export function copyMissingPackages(): void {

'react',
'react-dom',
'react-redux',
'react-router-dom',
'@reduxjs',
'@reduxjs/toolkit',
'styled-components',
'@types/react',
'@types/react-dom',
'@types/react-redux',
'@types/react-router-dom',
'@testing-library',

Expand Down Expand Up @@ -342,8 +346,11 @@ export function copyMissingPackages(): void {
}

function copyNodeModule(name: string) {
execSync(`rm -rf ${tmpProjPath('node_modules/' + name)}`);
execSync(`cp -a node_modules/${name} ${tmpProjPath('node_modules/' + name)}`);
const source = `node_modules/${name}`;
const destination = tmpProjPath(source);

execSync(`rm -rf ${destination}`);
execSync(`cp -a ${source} ${destination}`);
}

export function runCommandAsync(
Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
"@ngrx/store": "9.0.0",
"@ngrx/store-devtools": "9.0.0",
"@ngtools/webpack": "~9.1.0",
"@reduxjs/toolkit": "1.3.2",
"@rollup/plugin-commonjs": "11.0.2",
"@rollup/plugin-image": "2.0.4",
"@rollup/plugin-node-resolve": "7.1.1",
Expand All @@ -93,6 +94,7 @@
"@types/prettier": "^1.10.0",
"@types/react": "16.9.17",
"@types/react-dom": "16.9.4",
"@types/react-redux": "7.1.5",
"@types/react-router-dom": "5.1.3",
"@types/webpack": "^4.4.24",
"@types/yargs": "^11.0.0",
Expand Down Expand Up @@ -189,6 +191,7 @@
"raw-loader": "3.1.0",
"react": "16.10.2",
"react-dom": "16.10.2",
"react-redux": "7.1.3",
"react-router-dom": "5.1.2",
"regenerator-runtime": "0.13.3",
"release-it": "^7.4.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,40 +1,53 @@
import {
<%= propertyName %>Reducer,
get<%= className %>Start,
get<%= className %>Failure,
get<%= className %>Success
} from './<%= fileName %>.slice';
import { <%= propertyName %>Actions, <%= propertyName %>Adapter, <%= propertyName %>Reducer } from './<%= fileName %>.slice';

describe('<%= propertyName %> reducer', () => {
it('should handle initial state', () => {
expect(<%= propertyName %>Reducer(undefined, { type: '' })).toMatchObject({
entities: []
const expected = <%= propertyName %>Adapter.getInitialState({
loadingStatus: 'not loaded',
error: null
});

expect(<%= propertyName %>Reducer(undefined, { type: '' })).toEqual(expected);
});

it('should handle get <%= propertyName %> actions', () => {
let state = <%= propertyName %>Reducer(undefined, get<%= className %>Start());
it('should handle fetch<%= className %>s', () => {
let state = <%= propertyName %>Reducer(
undefined,
<%= propertyName %>Actions.fetch<%= className %>s.pending(null, null)
);

expect(state).toEqual({
loaded: false,
error: null,
entities: []
});
expect(state).toEqual(
expect.objectContaining({
loadingStatus: 'loading',
error: null,
entities: {}
})
);

state = <%= propertyName %>Reducer(state, get<%= className %>Success([{ id: 1 }]));
state = <%= propertyName %>Reducer(
state,
<%= propertyName %>Actions.fetch<%= className %>s.fulfilled([{ id: 1 }], null, null)
);

expect(state).toEqual({
loaded: true,
error: null,
entities: [{ id: 1 }]
});
expect(state).toEqual(
expect.objectContaining({
loadingStatus: 'loaded',
error: null,
entities: { 1: { id: 1 } }
})
);

state = <%= propertyName %>Reducer(state, get<%= className %>Failure('Uh oh'));
state = <%= propertyName %>Reducer(
state,
<%= propertyName %>Actions.fetch<%= className %>s.rejected(new Error('Uh oh'), null, null)
);

expect(state).toEqual({
loaded: true,
error: 'Uh oh',
entities: [{ id: 1 }]
});
expect(state).toEqual(
expect.objectContaining({
loadingStatus: 'error',
error: 'Uh oh',
entities: { 1: { id: 1 } }
})
);
});
});
Original file line number Diff line number Diff line change
@@ -1,46 +1,68 @@
import { createSlice, createSelector, Action, PayloadAction } from '@reduxjs/toolkit';
import { ThunkAction } from 'redux-thunk';
import {
createAsyncThunk,
createEntityAdapter,
createSlice,
EntityState,
PayloadAction
} from '@reduxjs/toolkit';

export const <%= constantName %>_FEATURE_KEY = '<%= propertyName %>';

/*
* Change this from `any` if there is a more specific error type.
*/
export type <%= className %>Error = any;

/*
* Update these interfaces according to your requirements.
*/
export interface <%= className %>Entity {
id: number;
}

export interface <%= className %>State {
entities: <%= className %>Entity[];
loaded: boolean;
error: <%= className %>Error;
export interface <%= className %>State extends EntityState<<%= className %>Entity> {
loadingStatus: 'not loaded' | 'loading' | 'loaded' | 'error';
error: string;
}

export const initial<%= className %>State: <%= className %>State = {
entities: [],
loaded: false,
export const <%= propertyName %>Adapter = createEntityAdapter<<%= className %>Entity>();

/**
* Export an effect using createAsyncThunk from
* the Redux Toolkit: https://redux-toolkit.js.org/api/createAsyncThunk
*/
export const fetch<%= className %>s = createAsyncThunk(
'<%= propertyName %>/fetchStatus',
async (_, thunkAPI) => {
/**
* Replace this with your custom fetch call.
* For example, `return myApi.get<%= className %>s()`;
* Right now we just return an empty array.
*/
return Promise.resolve([]);
}
);

export const initial<%= className %>State: <%= className %>State = <%= propertyName %>Adapter.getInitialState({
loadingStatus: 'not loaded',
error: null
};
});

export const <%= propertyName %>Slice = createSlice({
name: <%= constantName %>_FEATURE_KEY,
initialState: initial<%= className %>State as <%= className %>State,
initialState: initial<%= className %>State,
reducers: {
get<%= className %>Start: (state, action: PayloadAction<undefined>) => {
state.loaded = false;
},
get<%= className %>Success: (state, action: PayloadAction<<%= className %>Entity[]>) => {
state.loaded = true;
state.entities = action.payload;
},
get<%= className %>Failure: (state, action: PayloadAction<<%= className %>Error>) => {
state.error = action.payload;
}
add<%= className %>: <%= propertyName %>Adapter.addOne,
remove<%= className %>: <%= propertyName %>Adapter.removeOne
// ...
},
extraReducers: builder => {
builder.addCase(fetch<%= className %>s.pending, (state: <%= className %>State) => {
state.loadingStatus = 'loading';
});
builder.addCase(fetch<%= className %>s.fulfilled, (state: <%= className %>State, action: PayloadAction<<%= className %>Entity[]>) => {
<%= propertyName %>Adapter.addMany(state, action.payload);
state.loadingStatus = 'loaded';
});
builder.addCase(fetch<%= className %>s.rejected, (state: <%= className %>State, action) => {
state.loadingStatus = 'error';
state.error = action.error.message;
});
}
});

Expand All @@ -55,16 +77,15 @@ export const <%= propertyName %>Reducer = <%= propertyName %>Slice.reducer;
* e.g.
* ```
* const dispatch = useDispatch();
* dispatch(get<%= className %>Success([{ id: 1 }]));
* dispatch(<%= propertyName %>Actions.add<%= className %>([{ id: 1 }]));
* ```
*
* See: https://react-redux.js.org/next/api/hooks#usedispatch
*/
export const {
get<%= className %>Start,
get<%= className %>Success,
get<%= className %>Failure
} = <%= propertyName %>Slice.actions;
export const <%= propertyName %>Actions = {
...<%= propertyName %>Slice.actions,
fetch<%= className %>s
};

/*
* Export selectors to query state. For use with the `useSelector` hook.
Expand All @@ -76,42 +97,7 @@ export const {
*
* See: https://react-redux.js.org/next/api/hooks#useselector
*/
export const get<%= className %>State = (rootState: any): <%= className %>State =>
rootState[<%= constantName %>_FEATURE_KEY];

export const select<%= className %>Entities = createSelector(
get<%= className %>State,
s => s.entities
);

export const select<%= className %>Loaded = createSelector(
get<%= className %>State,
s => s.loaded
);

export const select<%= className %>Error = createSelector(
get<%= className %>State,
s => s.error
);

/*
* Export default effect, handled by redux-thunk.
* You can replace this with your own effects solution.
*/
export const fetch<%= className %> = (): ThunkAction<
void,
any,
null,
Action<string>
> => async dispatch => {
try {
dispatch(get<%= className %>Start());
// Replace this with your custom fetch call.
// For example, `const data = await myApi.get<%= className %>`;
// Right now we just load an empty array.
const data = [];
dispatch(get<%= className %>Success(data));
} catch (err) {
dispatch(get<%= className %>Failure(err));
}
};
export const <%= propertyName %>Selectors = {
get<%= className %>State: (rootState: unknown): <%= className %>State => rootState[<%= constantName %>_FEATURE_KEY],
...<%= propertyName %>Adapter.getSelectors()
};
27 changes: 13 additions & 14 deletions packages/react/src/schematics/redux/redux.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,4 @@
import * as ts from 'typescript';
import { join, Path, strings } from '@angular-devkit/core';
import '@nrwl/tao/src/compat/compat';
import {
addDepsToPackageJson,
addGlobal,
getProjectConfig,
insert,
readJsonInTree
} from '@nrwl/workspace/src/utils/ast-utils';
import {
apply,
chain,
Expand All @@ -20,17 +11,25 @@ import {
Tree,
url
} from '@angular-devkit/schematics';

import { NormalizedSchema, Schema } from './schema';
import '@nrwl/tao/src/compat/compat';
import { formatFiles, getWorkspace, names, toFileName } from '@nrwl/workspace';
import {
addDepsToPackageJson,
addGlobal,
getProjectConfig,
insert,
readJsonInTree
} from '@nrwl/workspace/src/utils/ast-utils';
import { toJS } from '@nrwl/workspace/src/utils/rules/to-js';
import * as path from 'path';
import * as ts from 'typescript';
import { addReduxStoreToMain, updateReduxStore } from '../../utils/ast-utils';
import {
typesReactReduxVersion,
reactReduxVersion,
reduxjsToolkitVersion
reduxjsToolkitVersion,
typesReactReduxVersion
} from '../../utils/versions';
import { toJS } from '@nrwl/workspace/src/utils/rules/to-js';
import { NormalizedSchema, Schema } from './schema';

export default function(schema: any): Rule {
return async (host: Tree, context: SchematicContext) => {
Expand Down
2 changes: 1 addition & 1 deletion packages/react/src/utils/versions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export const typesReactRouterDomVersion = '5.1.3';

export const testingLibraryReactVersion = '9.4.0';

export const reduxjsToolkitVersion = '1.1.0';
export const reduxjsToolkitVersion = '1.3.2';
export const reactReduxVersion = '7.1.3';
export const typesReactReduxVersion = '7.1.5';

Expand Down
Loading

0 comments on commit 54d06f0

Please sign in to comment.