Ngrx unit test jasmine reducer how to compare state - jasmine

I've done this simple test
// Mocks
const loginResponseData: LoginResponseDto = {
auth: { id: 1, username: 'me', roles: [] },
token: {
token: 'abc',
expirationEpochSeconds: 12345
}
};
describe('AuthReducer', () => {
describe('loginSuccess', () => {
it('should show loginResponseData state', () => {
const createAction = loginSuccess({ payload: loginResponseData });
const result = reducer(initialState, createAction);
console.log('AUTH', result);
// How Can I test this?
//expect(result).toEqual(loginResponseData);
});
});
});
export const initialState: State = {
error: null,
loading: false,
registered: false,
payload: null
};
const authReducer = createReducer(
initialState,
on(AuthActions.loginSuccess, (state, { payload }) => {
return {
...state,
error: null,
loading: false,
payload
};
})
);
How I can test result with loginResponseData?

result of a reducer is a new state.
You need to share code of your reducer for the right answer. Or to share what console.log outputs.
Because in your question the code is correct
describe('AuthReducer', () => {
describe('loginSuccess', () => {
it('should show loginResponseData state', () => {
const actionPayload: LoginResponseDto = {
auth: { id: 1, username: 'me', roles: [] },
token: {
token: 'abc',
expirationEpochSeconds: 12345
}
};
// for the test it's fine to have an empty object
const initialState: any = {
};
// check what should be changed
const expectedState = {
payload: {
auth: { id: 1, username: 'me', roles: [] },
token: {
token: 'abc',
expirationEpochSeconds: 12345
},
},
error: null,
loading: false,
};
const createAction = loginSuccess({ payload: loginResponseData });
// returns updated state we should compare against expected one.
const actualState = reducer(initialState, createAction);
// assertions
expect(actualState).toEqual(expectedState);
});
});
});

Related

How to call Redux-toolkit-query Manually on button click

i am using Redux-toolkit-query to fetch data from server. Now i want to call my query on button click,not automatically.I tried this but it's not working.
const { data, refetch } = useGetBuisnessAreasQuery({
enable: false,
refetchOnWindowFocus: false,
manual: true,
refetchOnReconnect: false,
});
You have to use the lazy query hook:
const [ trigger, { data } ] = api.endpoints.getBuisnessAreas.useLazyQuery()
const onClick = () => {
trigger()
}
This is how I did it, it's only cleaner:
in feature/service.ts:
export const authenticationApi = createApi({
reducerPath: 'myApi',
baseQuery: fetchBaseQuery({ baseUrl: baseUrl }),
endpoints: builder => ({
attemptLogin: builder.query({
query: (credentials) => ({
url: '/',
body: JSON.stringify(body)
})
})
})
})
export const { useLazyAttemptLoginQuery } = authenticationApi
and using it:
const [getAuthenticated, { data, isLoading, error }] = useLazyAttemptLoginQuery()

How to use RTK Query in createSlice?

I want to process the data that I get from the request in the slice.
Because not all slices are async (but work with the same data), transformResponse is not suitable.
Is there anything you can suggest?
My code example:
Some RTK Query
export const currencyApi = createApi({
reducerPath: 'currencyApi',
baseQuery: fetchBaseQuery({ baseUrl: 'https://api.apilayer.com/exchangerates_data' }),
endpoints: (build) => ({
fetchCurrencyRates: build.query<IApiResponse, string>({
query: (currency) => ({
url: '/latest',
params: {
base: currency
},
headers: {
apikey: *SomeApiKey*
}
})
})
})
})
Slice where I want to use data from RTK requests
const initialState: ICurrencyState = {
currencyRates: {},
availableCurrencyOptions: [],
fromCurrency: '',
toCurrency: '',
exchangeRate: 0,
error: null
}
export const currencySlice = createSlice({
name: 'currency',
initialState,
reducers: {
//
}
})
Use Hooks in Components
You can send the received data to the slice via useEffect. Something like this:
const { data } = useFetchCurrencyRatesQuery();
useEffect(() => {
if (data !== undefined) {
dispatch(...)
}
}, [data])

NextJS: `HYDRATION` action doesn't receive server payload when using `redux-observable`

Packages:
redux-observable#2.0.0-rc.2
rxjs latest
universal-rxjs-ajax dev branch
next-redux-wrapper latest
next.js latest
I have a simple Page with getStaticProps:
export const getStaticProps = wrapper.getStaticProps((store) => async (ctx) => {
store.dispatch({ type: 'ADD_DATA' });
// const response = await fetch('https://rickandmortyapi.com/api');
// const data = await response.json();
// store.dispatch({ type: 'SERVER_ACTION', payload: data.characters });
return {
props: {},
};
});
Action 'ADD_DATA' triggers action 'SERVER_ACTION':
export const AddDataEpic: Epic = (action$) =>
action$.pipe(
ofType('ADD_DATA'),
mergeMap((action) =>
request({ url: 'https://rickandmortyapi.com/api' }).pipe(
map((response) => {
return {
type: 'SERVER_ACTION',
payload: response.response.characters,
};
})
)
)
);
Inside the reducer in the case 'SERVER_ACTION': clause I receive the payload:
const server = (state: State = { data: null }, action: AnyAction) => {
switch (action.type) {
case HYDRATE: {
console.log('HYDRATE >', action.payload); // logs out "HYDRATE > { server: { data: null } }"
return {
...state,
...state.server,
...action.payload.server,
};
}
case 'SERVER_ACTION': {
console.log('SERVER_ACTION >', action.payload); // logs out "SERVER_ACTION > https://rickandmortyapi.com/api/character"
return {
...state,
...state.server,
data: action.payload,
};
}
default:
return state;
}
};
But the payload isn't passed to HYDRATE action:
console.log('HYDRATE >', action.payload); // logs out "HYDRATE > { server: { data: null } }"
If I dispatch the 'SERVER_ACTION' action from inside the getStaticProps:
export const getStaticProps = wrapper.getStaticProps((store) => async (ctx) => {
// store.dispatch({ type: 'ADD_DATA' });
const response = await fetch('https://rickandmortyapi.com/api');
const data = await response.json();
store.dispatch({ type: 'SERVER_ACTION', payload: data.characters });
return {
props: {},
};
});
The HYDRATE action inside the reducer receive the payload:
HYDRATE > { server: { data: 'https://rickandmortyapi.com/api/character' } }
I don't understand what's wrong with my code.
May it be a bug in one of the libraries? Or is it a mistake in my code?
If anyone has any suggestions, PLEASE
#PYTHON DEVELOPER999 It might be due to the latest update on next-redux-wrapper, there are few migration steps =>
https://github.com/kirill-konshin/next-redux-wrapper#upgrade-from-6x-to-7x

How to use remove mutation in Relay server?

I work with an express graphql server, prepared for react-relay.
Queries and createPost mutation works correctly in graphiql interface.
There is a problem with removePost mutation.
Trying to use it, I get this responce:
"Cast to ObjectId failed for value \"{ id: '5db0026a76376e0f7c82d431'
}\" at path \"_id\" for model \"Post\".
Tell me please, what's wrong with removePost mutation. Thanks!
Post.js:
const mongoose = require('mongoose');
mongoose.Promise = global.Promise;
mongoose.connect("mongodb://localhost/relay-project", {
useNewUrlParser: true,
useUnifiedTopology: true
});
const Schema = mongoose.Schema;
const postSchema = new Schema({
title: String,
content: String
});
var PostModel = mongoose.model("Post", postSchema);
module.exports = {
getPosts: () => {
return PostModel.find().sort({_id: -1});
},
getPost: id => {
return PostModel.findOne({ _id: id });
},
createPost: post => {
return PostModel(post).save();
},
removePost: id => {
return PostModel.findByIdAndRemove(id);
}
};
Mutation.js:
const {
GraphQLObjectType,
GraphQLNonNull,
GraphQLString,
GraphQLID
} = require('graphql');
const {mutationWithClientMutationId} = require('graphql-relay');
const {Post} = require('./Post');
const PostModel = require('../model/Post');
const CreatePostMutation = mutationWithClientMutationId({
name: "CreatePost",
inputFields: {
title: {type: new GraphQLNonNull(GraphQLString)},
content: {type: new GraphQLNonNull(GraphQLString)}
},
outputFields: {
post: {
type: Post
}
},
mutateAndGetPayload: args => {
return new Promise((resolve,reject)=>{
PostModel.createPost({
title: args.title,
content: args.content
})
.then(post=>resolve({post}))
.catch(reject);
});
}
});
const RemovePostMutation = mutationWithClientMutationId({
name: "RemovePost",
inputFields: {
id: {type: GraphQLID}
},
outputFields: {
post: {
type: Post
}
},
mutateAndGetPayload: args => {
return new Promise((resolve,reject)=>{
PostModel.removePost({
id: args.id
})
.then(post=>resolve({post}))
.catch(reject);
});
}
});
const Mutation = new GraphQLObjectType({
name: "Mutation",
description: "kjhkjhkjhkjh",
fields: {
createPost: CreatePostMutation,
removePost: RemovePostMutation
}
});
module.exports = Mutation;
you have to convert your id to object id as mongodb save
i guess use below code for id
const toBase64 = (str: string) => {
return new Buffer(str.toString()).toString('base64')
}
const fromBase64 = (str: string) => {
return Buffer.from(str, 'base64').toString('ascii')
}
The working mutation is:
const RemovePostMutation = mutationWithClientMutationId({
name: "RemovePost",
inputFields: {
id: { type: new GraphQLNonNull(GraphQLString) },
},
outputFields: {
deleted: { type: GraphQLBoolean },
deletedId: { type: GraphQLString }
},
mutateAndGetPayload: async ({ id }, { viewer }) =>{
const { id: productId } = fromGlobalId(id);
const result = await PostModel.removePost(productId);
return { deletedId: id, deleted: true };
}
});
Cheers, Kiten

How to write an action that updates Redux's store?

I have a web app where use React-Redux. There is React table (list) that I need to populate with data from database. I use WebApi on the server and automatically generated (by TypeWriter) web-api on the client. The key parts of code looks as following:
1) Routing:
<Route path="/Dictionary/:dictionaryName" component={Dictionary} />
2) State:
export type SingleDictionaryState = Readonly<{
singleDictionary: WebApi.SingleDictionary[];
}>;
export const initialState: SingleDictionaryState = {
singleDictionary: [],
};
3) Reducer:
export const reducer: Reducer<SingleDictionaryState> = (state: SingleDictionaryState = initialState, action: AllActions): SingleDictionaryState => {
switch (action.type) {
case getType(actions.setSingleDictionaryValue):
return { ...state, ...action.payload };
}
return state;
};
4) Actions:
const actionsBasic = {
setSingleDictionaryValue: createAction('singleDictionary/setSingleDictionaryValue', (singleDictionary: any) => singleDictionary),
};
const actionsAsync = {
getDictionaryByName: (dictionaryName: string) => {
const currentState = store.getState().singleDictionary;
WebApi.api.dictionaryQuery.getDictionary(capitalizeForApi(dictionaryName));
},
};
export const actions = Object.assign(actionsBasic, actionsAsync);
const returnsOfActions = Object.values(actionsBasic).map($call);
export type AllActions = typeof returnsOfActions[number];
5) Container:
const mapStateToProps = (state: AppState, ownProps: OwnProps): StateProps => ({
dictionaryType: state.singleDictionary,
});
const mapDispatchToProps = (dispatch: Dispatch<any>): DispatchProps => ({
onLoad: (dictionaryName: string) => {
Actions.singleDictionary.getDictionaryByName(dictionaryName);
},
});
export default withRouter(connect<StateProps, DispatchProps, OwnProps>(mapStateToProps, mapDispatchToProps)(DictionaryPage));
6) The client web-api:
class DictionaryQueryService {
getDictionary(name: string) {
const user = store.getState().oidc.user;
const headers = new Headers();
headers.append('Accept', 'application/json');
headers.append('Content-Type', 'application/json');
headers.append('Cache-Control', 'no-cache, no-store, must-revalidate');
headers.append('Pragma', 'no-cache');
headers.append('Expires', '0');
if (user) {
headers.append('Authorization', `Bearer ${user.access_token}`);
}
return () => {
return fetch(`api/dictionaries/${encodeURIComponent(name)}`, {
method: 'get',
headers,
})
.then(response => {
if (!response.ok) {
const traceId = response.headers.get("X-Trace-Id");
throw new ApiError(`${response.status} ${response.statusText}`, traceId);
}
return response.status == 204 ? null : response.json() as Promise<any[]>;
});
};
}
Actually, I'm not sure how to write my getDictionaryByName action.
Just my 2 cents. I use ES6 syntax, but Typescript would work as similar way.
actionTypes.js
export const RESET_DICTIONARIES = 'RESET_DICTIONARIES';
export const LOAD_DICTIONARIES_REQUEST = 'LOAD_DICTIONARIES_REQUEST';
export const LOAD_DICTIONARIES_REQUEST_SUCCESS = 'LOAD_DICTIONARIES_REQUEST_SUCCESS';
export const LOAD_DICTIONARIES_REQUEST_FAILURE = 'LOAD_DICTIONARIES_REQUEST_FAILURE';
dictionaryActions.js
/* Load Dictionariies*/
export function loadDictionariesRequestBegin() {
return {type: types.LOAD_DICTIONARIES_REQUEST};
}
export function loadDictionariesRequest(name) {
return function(dispatch) {
dispatch(loadDictionariesRequestBegin());
// eslint-disable-next-line no-undef
const request = new Request(`${YOUR_URL}/api/dictionaries/{name}`, {
method: 'get',
headers: new Headers({
'Content-type': 'application/json',
'Accept': 'application/json',
'Authorization': auth.getToken(),
})
});
return fetch(request)
.then(
response => {
if (!response.ok) {
dispatch(loadDictionariesRequestFailure(response.statusText));
throw new Error(response.statusText);
}
return response.json();
},
error => {
dispatch(loadDictionariesRequestFailure(error));
throw error;
})
.then(dictionaries=> {
if (dictionaries) {
dispatch(loadDictionariesRequestSuccess(dictionaries));
return dictionaries;
} else {
throw new Error('dictionaries NOT found in response');
}
});
};
}
export function loadDictionariesRequestSuccess(dictionaries) {
return {type: types.LOAD_DICTIONARIES_REQUEST_SUCCESS, dictionaries};
}
export function loadDictionariesRequestFailure(error) {
return {type: types.LOAD_DICTIONARIES_REQUEST_FAILURE, error};
}
dictionaryReducer.js
export default function dictionaryReducer(state = initialState.dictionaries, action) {
switch (action.type) {
case types.RESET_DICTIONARIES:
return {
...state,
loaded: false,
loading: false,
error: null,
};
/* load dictionaries*/
case types.LOAD_DICTIONARIES_REQUEST:
return {
...state,
error: null,
loaded: false,
loading: true
};
case types.LOAD_DICTIONARIES_REQUEST_SUCCESS:
return {
...state,
data: action.dictionaries,
error: null,
loaded: true,
loading: false
};
case types.LOAD_DICTIONARIES_REQUEST_FAILURE:
return {
...state,
loaded: false,
loading: false,
error: action.error
};
return state;
}
initialState.js
export default {
actions: {},
dictionaries: {
data: [],
loaded: false,
loading: false,
error: null,
},
}
dictionary client side API
this.props.actions
.loadDictionaryRequest(name)
.then(data => {
this.setState({ data: data, errorMessage: '' });
})
.then(() => {
this.props.actions.resetDictionaries();
})
.catch(error => {
...
});
Hope this may help.

Resources