Redux - pass ownProps argument to selector - redux

I cannot pass variable ownProps from mapStateToProps to selector. My selector:
export const nameSelector = createSelector(
[
state => state.element.get('name')
],
(name) => !name.trim()
);
const mapStateToProps = (state, ownProps) => ({
disabledAfterSave: nameSelector(state)
});
And I need to have a selector:
export const nameSelector = createSelector(
[
state => state.element.get('name')
],
(name, ownProps) => !name.trim() && ownProps.showMessage
);
const mapStateToProps = (state, ownProps) => ({
disabledAfterSave: nameSelector(state, ownProps)
});
But now I get an error: ReferenceError: ownProps is not defined.
When I tried to pass it like below:
export const nameSelector = (ownProps) => createSelector(
[
state => state.element.get('name')
],
(name) => !name.trim() && ownProps.showMessage
);
the returned type is not bool but [Function selector].
How can I pass this argument to my selector?

Here's how to fix your selector and its usage:
export const nameSelector = createSelector(
[
state => state.element.get('name'),
(state, ownProps) => ownProps.showMessage
],
(name, showMessage) => !name.trim() && ownProps.showMessage
)
const mapStateToProps = (state, ownProps) => ({
disabledAfterSave: nameSelector(state, ownProps)
})
Here's a generic working example, in order to illustrate how you can pass props down to the selector:
const createSelector = Reselect.createSelector
// Selector: Get Active Users
const getUsers = createSelector([state => state.users], users => users.filter(u => u.active))
// Selector: Get Active Users by Country
// Here you can check how I pass the country prop
const getUsersByCountry = createSelector([getUsers, (state, props) => props.country],
(users, country) => users.filter(u => u.country === country))
// The state
const state = {
users: [
{ id: 1, name: 'Jordan', country: 'Bulgaria', active: true},
{ id: 2, name: 'Nadezhda', country: 'Bulgaria', active: true},
{ id: 3, name: 'Hristo', country: 'Bulgaria', active: false},
{ id: 4, name: 'Bobby', country: 'England', active: true},
{ id: 5, name: 'Kaloyan', country: 'Germany', active: true},
]
}
// Testing
console.log(getUsers(state))
console.log(getUsersByCountry(state, { country: 'Bulgaria' }))
<script src="https://cdnjs.cloudflare.com/ajax/libs/reselect/3.0.1/reselect.min.js"></script>

Related

How to mock useRoute() in vue testing library

I ran into a problem when running a component test that has a line inside it:
const route = useRoute(). I get an error: Cannot read properties of undefined (reading 'path'). Here is the test:
describe('Tariff card', () => {
const options = {
props: {
name: 'test',
tariffId: 2,
price: 3990,
maxCompanies: 1,
maxCampaigns: 5,
tariffNum: 2,
isFree: false,
duration: 30,
},
global: {
plugins: [createTestingPinia()],
},
}
it('render tariff card', async () => {
jest.mock('vue-router', () => ({
useRoute: jest.fn(() => ({ path: '/' }))
}))
render(TariffCard, options)
})
})

Next js Redux, Objects are not valid as a React child

Error: Objects are not valid as a React child (found: object with keys {_id, name}). If you meant to render a collection of children, use an array instead.
Tried to fix this for days and no result.
i have a model
import mongoose from 'mongoose'
const CategoriesSchema = new mongoose.Schema({
name: {
type: String,
required: true,
trim: true
},
parent: {
type: mongoose.Types.ObjectId,
ref: 'categories'
},
},
{
timestamps: true
})
let Dataset = mongoose.models.categories || mongoose.model('categories', CategoriesSchema)
export default Dataset
and i have getCategories like this
[getCategories ]
const getCategories = async (req, res) => {
try {
const categories = await Categories.find().populate("parent", "name");
res.json({ categories });
}
catch (err)
{
return res.status(500).json({ err: err.message });
}
};
in my Globale state i have
export const DataContext = createContext()
export const DataProvider = ({children}) => {
const initialState = {
notify: {}, auth: {}, cart: [], modal: [], orders: [], users: [], categories: []
}
const [state, dispatch] = useReducer(reducers, initialState)
useEffect(() => {
getData('categories').then(res => {
if(res.err)
return dispatch({type: 'NOTIFY', payload: {error: res.err}})
dispatch({ type: 'ADD_CATEGORIES', payload: res.categories })
})
},[])
return(
<DataContext.Provider value={{state, dispatch}}>
{children}
</DataContext.Provider>
)
}
when i call categories throw:exception
when i change dispatch in Globale state like :
dispatch({ type: 'ADD_CATEGORIES', payload: [] })
i get no elements in array :

NGRX EFFECTS Type 'Observable<unknown>' is not assignable to type 'EffectResult<Action>'

what do i do wrong?
fetchEmail$ = createEffect(() => this.actions$.pipe(
ofType(RDX_EMAIL_CONFIRM_FETCH),
switchMap(ac => axios.post(axiosInstance.post('/api/email-confirm/check-email', {
email: ac.payload.email
}).then(res => {
return {
type: RDX_EMAIL_CONFIRM_FETCH_SUCCESS,
};
}).catch(err => {
return {
type: RDX_EMAIL_CONFIRM_FETCH_ERROR
};
})))
))
would noah like to call some api and return an action based on that?
unfortunatley has the following error
Type 'Observable<unknown>' is not assignable to type 'EffectResult<Action>'.
Type 'Observable<unknown>' is not assignable to type 'Observable<Action>'.
Type 'unknown' is not assignable to type 'Action'
here's my reducer maby the problem occurs here
Is it the actions payload definition?
import { createAction, createReducer, on, props } from '#ngrx/store';
import { tassign } from 'tassign';
export const RDX_EMAIL_CONFIRM_FETCH = 'RDX_EMAIL_CONFIRM_FETCH';
export const RDX_EMAIL_CONFIRM_FETCH_SUCCESS = 'RDX_EMAIL_CONFIRM_FETCH_SUCCESS';
export const RDX_EMAIL_CONFIRM_FETCH_ERROR = 'RDX_EMAIL_CONFIRM_FETCH_ERROR';
export const rdxEmailConfirmFetch = createAction(
RDX_EMAIL_CONFIRM_FETCH,
props<{email: string}>()
);
export const rdxEmailConfirmFetchSuccess = createAction(RDX_EMAIL_CONFIRM_FETCH_SUCCESS);
export const rdxEmailConfirmFetchError = createAction(RDX_EMAIL_CONFIRM_FETCH_ERROR);
const initialState = {
isFetch: false
}
export const emailConfirmReducer = createReducer(
initialState,
on(rdxEmailConfirmFetch, (state) => tassign(state, {
isFetch: true
})),
on(rdxEmailConfirmFetchSuccess, (state) => tassign(state, {
isFetch: false
})),
on(rdxEmailConfirmFetchError, (state) => tassign(state, {
isFetch: false
}))
)
You have to use RxJS of operator to convert it to observable, just like below.
fetchEmail$ = createEffect(() => this.actions$.pipe(
ofType(RDX_EMAIL_CONFIRM_FETCH),
switchMap(ac => axios.post(axiosInstance.post('/api/email-confirm/check-email', {
email: ac.payload.email
}).then(res => {
return of({
type: RDX_EMAIL_CONFIRM_FETCH_SUCCESS,
});
}).catch(err => {
return of({
type: RDX_EMAIL_CONFIRM_FETCH_ERROR
});
})))
))

Redux and Firebase

I've struggled to implement react-redux-firebase and redux-firestore into my app after configuring the redux store (struggled with this too, even though redux-toolkit simplified some things). Is it possible that I can communicate with firebase without using those two packages above? If so, how do I use firebase in any of my slices? e.g., auth slice below.
import {createSlice, createAsyncThunk} from '#reduxjs/toolkit';
import firebase from 'firebase/app';
export const authSlice = createSlice({
name: 'authSlice',
initialState: {
currentUser: null,
isLoggedIn: false,
isLoading: false,
},
reducers: {
login: async (state, action) => {},
registerUser: (state, action) => {},
changeProfile: (state, action) => {},
logout: async (state, action) => {},
setCurrentUser: (state, action) => {},
},
});
// Action creators are generated for each case reducer function
export const {
login,
registerUser,
changeProfile,
logout,
setCurrentUser,
} = authSlice.actions;
export default authSlice.reducer;
This is the query in a separate file.
import firestore from '#react-native-firebase/firestore';
export const getPopularProducts = firestore()
.collection('POPULAR')
.orderBy('count', 'desc')
.limit(10)
.get()
.then(querySnapshot => {
const views = [];
querySnapshot.forEach(doc => {
views.push({
key: doc.id,
count: doc.data().count,
product: doc.data().product,
});
});
return views;
})
.catch(error => {
alert('Error getting popular products: ', error);
});
In the reducer/slice, import getPopularProducts.
import {createSlice, createAsyncThunk} from '#reduxjs/toolkit';
import {getPopularProducts} from './../../lib/fetchData';
// Initial states
const initialState = {
products: [],
mainList: [],
popular: [],
};
// Get popular products from firebase
export const fetchPopularProducts = createAsyncThunk(
'prodSlice/fetchPopularProducts',
async () => {
const data = getPopularProducts;
const {_W} = data;
if (_W !== null) {
return _W;
}
},
);
export const productSlice = createSlice({
name: 'prodSlice',
initialState,
reducers: {
fetchData: (state, action) => {
state.isLoading = true;
state.mainList = action.payload;
state.products = action.payload;
}
},
extraReducers: {
[fetchPopularProducts.fulfilled]: (state, action) => {
state.popular = action.payload;
},
},
});
// Action creators are generated for each case reducer function
export const {fetchData} = productSlice.actions;
export const selectProducts = state => state.prodSlice;
export default productSlice.reducer;
Then you dispatch fetchPopularProducts inside the useEffect hook. I cases where I needed a parameter for the query, I'd put the query inside createAsyncThunk.

Redux Toolkit: How to test actions with uid prepare callback

In the docs for testing incrementing todo ids, this assumes a predictable response.
In an example such as below, a unique id is generated.
How could this be tested?
This test passes, but I'm not sure if it's correct, shouldn't the id be defined based on what's in the prepare callback?
slice.js
add: {
reducer: (state, {payload}: PayloadAction<{id: string, item: Item}>) => {
state[payload.id] = payload.item
},
prepare: (item: Item) => ({
payload: {id: cuid(), item}
})
}
slice.test.js
it('should handle add', () => {
expect(
reducer(
{},
{
type: actions.add,
payload: {
id: 'id-here?',
item: {
other: 'properties...'
}
},
}
)
).toEqual({
'id-here?': {
other: 'properties...'
},
})
})
You can pull out the prepare function and also the reducer function into it's own constant and then test prepare in isolation:
todosSlice.js:
[...]
let nextTodoId = 0;
export const addTodoPrepare = (text) => {
return {
payload: {
text,
id: nextTodoId++
}
}
}
export const addTodoReducer = (state,
action) => {
const {id, text} = action.payload;
state.push({
id,
text,
completed: false
});
};
const todosSlice = createSlice({
name: 'todos',
initialState: [],
reducers: {
addTodo: {
prepare: addTodoPrepare,
reducer: addTodoReducer,
},
}
})
[...]
todosSlice.spec.js:
import todos, {addTodo, addTodoPrepare} from './todosSlice'
describe('addTodoPrepare',
() => {
it('should generate incrementing IDs',
() => {
const action1 = addTodoPrepare('a');
const action2 = addTodoPrepare('b');
expect(action1.payload).toEqual({
id: 0,
text: 'a'
})
expect(action2.payload).toEqual({
id: 1,
text: 'b'
})
})
})
describe('todos reducer',
() => {
[...]
})
For unit testing, NO, just test each reducer independently.
For integration testing and e2e testing, Yes.

Resources