Separate on() function outside of createReducer() function in ngrx - ngrx

Is it possible to separate out the on() parameters from the createReducer() function.
For example, instead of this;
const yourInformationReducer = createReducer<IYourInformationState>(
initYourInformationState,
on(YourInformationStoreAction, (state, { payload }) => ({
...state,
...payload
}))
);
Define a constant for the on() function;
const onYourInformationStoreAction = on<IYourInformationState>(
YourInformationStoreAction,
(
state: IYourInformationState,
{ payload }: { payload: IYourInformationFormModel }
) => ({
...state,
...payload
})
);
Then reference it in the createReducer;
const yourInformationReducer = createReducer<IYourInformationState>(
initYourInformationState,
onYourInformationStoreAction
);
The problem I have is that when I set the type for the payload object in the separated out function, it gives a typescript error that I don't know how to deal with...
Error:(16, 2) TS2345: Argument of type '(state: IYourInformationState,
{ payload }: { payload: IYourInformationFormModel; }) => { name:
string; email: string; contactNumber: string; validityStatus:
ValidityStatus; }' is not assignable to parameter of type
'ActionCreator> |
OnReducer>]>'. Type '(state:
IYourInformationState, { payload }: { payload:
IYourInformationFormModel; }) => { name: string; email: string;
contactNumber: string; validityStatus: ValidityStatus; }' is not
assignable to type 'ActionCreator>'.
Property 'type' is missing in type '(state: IYourInformationState, { payload }: { payload: IYourInformationFormModel; }) => { name:
string; email: string; contactNumber: string; validityStatus:
ValidityStatus; }' but required in type 'TypedAction'.

Figured it out...
function onYourInformationInitAction() {
return on(YourInformationInitAction, (state: IYourInformationState) => ({
...state
}));
}
function onYourInformationStoreAction() {
return on(
YourInformationStoreAction,
(state: IYourInformationState, { payload }) => ({
...state,
...payload
})
);
}
function onYourInformationValidityStatus() {
return on(
YourInformationUpdateValidityStatusAction,
(state: IYourInformationState, { validityStatus }) => ({
...state,
validityStatus
})
);
}
const yourInformationReducer = createReducer<IYourInformationState>(
initYourInformationState,
onYourInformationInitAction(),
onYourInformationStoreAction(),
onYourInformationValidityStatus()
);

Related

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
});
})))
))

get the same state for different requests

i have this action:
export const connectToServer = (url, config, method) => {
return (dispatch) => {
dispatch({type: CONNECTION_START});
axios({
method: method,
url: url,
data: config
})
.then((response) => {
dispatch({type: CONNECTION_LOADING_SUCCESS, payload: response.data});
})
.catch((error) => {
dispatch({type: CONNECTION_LOADING_ERROR, payload: error.response.data});
})
}
};
And 2 identical reducers:
const initialState = {
data: null,
isLoading: false,
error: null
};
export const connectToServerReducer = (state = initialState, action) => {
switch (action.type) {
case CONNECTION_START :
return {...state, isLoading: true};
case CONNECTION_LOADING_SUCCESS :
return {...state, isLoading: false, data: action.payload, error: null};
case CONNECTION_LOADING_ERROR:
return {...state, isLoading: false, data: null, error: action.payload};
default :
return state
}
};
export const differentUrlConnectToServerReducerTest = (state = initialState, action) => {
switch (action.type) {
case CONNECTION_START :
return {...state, isLoading: true};
case CONNECTION_LOADING_SUCCESS :
return {...state, isLoading: false, data: action.payload, error: null};
case CONNECTION_LOADING_ERROR:
return {...state, isLoading: false, data: null, error: action.payload};
default :
return state
}
};
My store looks like this:
const rootReducer = combineReducers({
allUsersData: connectToServerReducer,
testData: differentUrlConnectToServerReducerTest
});
const configureStore = () => createStore(rootReducer, applyMiddleware(thunk));
export default configureStore
Then i use redux hooks to get a state with data in my components
const allUsersData = useSelector(state => state.allUsersData);
const testData = useSelector(state => state.testData);
const dispatch = useDispatch();
Finally i dispatch them
dispatch(connectToServer(`${BASE_URL}user/allUsersWithPets`, null, 'get'));
dispatch(connectToServer(`${BASE_URL}fakeUrl`, null, 'get'));
I receive a correct data in allUsersData, but also i receive it in testData but i should receive in testData an initial state(empty object), because url is a fake
Where am i wrong?
You need to separate the reducers, use different initial states for example:
connectToServer.js
connectToServerTest.js
Or you can try to add the test object to the initial state of connectToServerReducer.(not a good solution though)
const initialState = {
data: null,
testData: null,
isLoading: false,
error: null
};
Remember that arrays affections won't assign values but addresses, so the "data" array is the same array in both the connectToServerReducer and connectToServerReducerTest.
Second problem, you are calling the same action name in both reducers, this causes them not only to share the same variable from the previous problem I told you, but they share the same value assigned to them as well.
Just change them to:
CONNECTION_TEST_LOADING_SUCCESS
CONNECTION_TEST_LOADING_ERROR
CONNECTION_TEST_START
PS:
instead of using:
export const connectToServer = (url, config, method) => {
return (dispatch) => {
...
}
}
Use:
export const connectToServer = (url, config, method) => (dispatch) => {
...
}

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.

Action creators handling axios get.request with state access for param

I'm trying to pass some value from a component to a action creators which is doing a get request with axios. I'm trying to follow this pattern from Dan Abramov :
export const SOME_ACTION = 'SOME_ACTION';
export function someAction() {
return (dispatch, getState) => {
const {items} = getState().otherReducer;
dispatch(anotherAction(items));
}
}
However I can't make it work. I think I have trouble on two level : my component and my action creator. Would be great to have some helps.
my component :
const timeR = ({
selectedTimeRange,
timeRange = [],
onTimeChange }) => {
return (
<div>
<div>
Filters:
<div>
Year:
<select
defaultValue={selectedTimeRange}
onChange={onTimeChange}>
<option value="all" >All</option>
{timeRange.map((y, i) =>
<option key={i} value={y}>{y}</option>
)}
</select>
</div>
</div>
</div>
);
}
function mapStateToProps(state) {
var range = ['30daysAgo', '15daysAgo', '7daysAgo'];
return {
selectedTimeRange: state.timeReducer.timerange[0],
timeRange: range
};
};
const mapDispachToProps = (dispatch) => {
return {
onTimeChange: (e) => {dispatch (onSetTimeRange(e.target.value));},
};
};
const TimeRange = connect(mapStateToProps, mapDispachToProps)(timeR);
export default TimeRange;
This component give me a dropdown menu. When selecting a timerange, for example '30daysAgo', it update my redux store state so I can access the value from my reducer.
Here is the action associated to my dropdown menu :
export function onSetTimeRange(timerange) {
return {
type: 'SET_TIME_RANGE',
timerange
}
}
and here is the action dealing with axios.get :
export const fetchgadata = () => (dispatch) => {
dispatch({
type: 'FETCH_DATA_REQUEST',
isFetching:true,
error:null
});
var VIEW_ID = "ga:80820965";
return axios.get("http://localhost:3000/gadata", {
params: {
id: VIEW_ID
}
}).then(response => {
dispatch({
type: 'FETCH_DATA_SUCCESS',
isFetching: false,
data: response.data.rows.map( ([x, y]) => ({ x, y }) )
});
})
.catch(err => {
dispatch({
type: 'FETCH_DATA_FAILURE',
isFetching:false,
error:err
});
console.error("Failure: ", err);
});
};
My question :
How do I bring these two actions together. At the end I would like to be able, when doing onChange on my drop-down menu, to call a action with the value selected from my menu as a param for my axios.get request.
I feel like I need to nest two actions creators. I've tried this but doesn't work ("fetchgadata" is read-only error in my terminal)
export const SET_TIME_RANGE = 'SET_TIME_RANGE';
export function onSetTimeRange() {
return (dispatch, getState) => {
const {VIEW_ID} = getState().timerange;
dispatch(fetchgadata = (VIEW_ID) => (dispatch) => {
dispatch({
type: 'FETCH_DATA_REQUEST',
isFetching:true,
error:null,
id:VIEW_ID,
});
});
return axios.get("http://localhost:3000/gadata", {
params: {
id: VIEW_ID
}
}).then(response => {
dispatch({
type: 'FETCH_DATA_SUCCESS',
isFetching: false,
data: response.data.rows.map( ([x, y]) => ({ x, y }) )
});
})
.catch(err => {
dispatch({
ype: 'FETCH_DATA_FAILURE',
isFetching:false,
error:err
});
console.error("Failure: ", err);
});
}
}
Edit:
reducers for API call :
const initialState = {data:null,isFetching: false,error:null};
export const gaData = (state = initialState, action)=>{
switch (action.type) {
case 'FETCH_DATA_REQUEST':
case 'FETCH_DATA_FAILURE':
return { ...state, isFetching: action.isFetching, error: action.error };
case 'FETCH_DATA_SUCCESS':
return Object.assign({}, state, {data: action.data, isFetching: action.isFetching,
error: null });
default:return state;
}
};
reducers for Drop-down :
const items = [{timerange: '30daysAgo'},{timerange: '15daysAgo'},{timerange: '7daysAgo'}]
const timeReducer = (state = {
timerange: items
}, action) => {
switch (action.type) {
case 'SET_TIME_RANGE':
console.log(state,action);
return {
...state,
timerange: action.timerange,
};
default:
return state;
}
}
I see a little typo in the catch of your axios.get request, it reads ype: FETCH_DATA_FAILURE. Otherwise, can you add in your reducer for me, I don't see it up there? If I understand correctly, you want one action to update two different pieces of state, in which case you would simply dispatch an action and add it to both reducers. Really it's best to just demonstrate:
//axios call
axios.get("some route", { some params } )
.then(response => {
dispatch({
type: UPDATE_TWO_THINGS,
payload: some_value
})
}) .... catch, etc
//reducer 1
import { UPDATE_TWO_THINGS } from 'types';
const INITIAL_STATE = { userInfo: '' };
export default function (state = INITIAL_STATE, action) {
switch(action.type) {
case UPDATE_TWO_THINGS:
return {...state, userInfo: payload };
}
return state;
}
//reducer 2
import { UPDATE_TWO_THINGS } from 'types';
const INITIAL_STATE = { businessInfo: '' };
export default function (state = INITIAL_STATE, action) {
switch(action.type) {
case UPDATE_TWO_THINGS:
return {...state, businessInfo: payload };
}
return state;
}
Hopefully this helps, but let me know if not, I'll do my best to get this working with you! Thanks for asking!

Resources