Why I am getting the key name as my reducer name when using useSelector hook - user-interface

My Reducer
export default function saveContactsReducer(state = {}, { type, contacts }) {
switch (type) {
case types.SAVE_CONTACTS:
return {
...state,
contactsDetails: contacts,
currentUserName: contacts.personName
}
default:
return state;
}
}
My Action
import * as types from './actionTypes';
function saveContacts(contacts) {
return { type: types.SAVE_CONTACTS, contacts: contacts }
}
export default saveContacts;
My Action dispatch
dispatch(contactsAction(contactsDetails));
When I am using the useSelector hook from the react-redux
My state is like this
state.saveContactsReducer: {contacts}}
Instead I want state.contacts and don't want reducer name

Actually, the problem was the root reducer where I had passed all the reducers with their name. which I changed to the key which I wanted something like this.
Earliter it was like this
const rootReducer = combineReducers({
contactsReducer,
});
To fix changed the reducer name to the key which I wanted.
const rootReducer = combineReducers({
contacts,
});

Related

NestJS IntersectionType from #nestjs/swagger does not validate the combined class fields

In following up from this question, I am trying to ensure the validation remains and works. However, my combined class does not validate the included fields.
For instance, I have a basic AdminCodeDTO that sepcifies the AdminCode is required, has a valid value (1-999)
import { IsNumber, Min, Max, IsDefined } from '#nestjs/class-validator';
import { ApiProperty, ApiResponseProperty } from '#nestjs/swagger';
export class AdminCodeDTO {
#ApiProperty({
description: 'Sweda Administration Code used for time tracking that is not part of a mantis.',
})
#ApiResponseProperty({ example: 5 })
#IsDefined() #IsNumber() #Min(1) #Max(999) public AdminCode: number;
constructor(AdminCode?: number) {
this.AdminCode = AdminCode;
}
}
Testing this class works, and the validation will return the errors:
import { validate } from '#nestjs/class-validator';
import { ValidationError } from '#nestjs/common';
import { AdminCodeDTO } from './admin-code-dto';
describe('AdminCodeDto', () => {
let TestDTO: AdminCodeDTO;
beforeEach( () => {
TestDTO = new AdminCodeDTO(5);
});
it('should be defined', () => {
expect(TestDTO).toBeDefined();
});
it('should have the AdminCode value set', () => {
expect(TestDTO.AdminCode).toBe(5);
});
it('should allow creation with an empty constructor', () => {
expect(new AdminCodeDTO()).toBeDefined();
});
it('should generate the DTO errors', async () => {
const DTOValidCheck: AdminCodeDTO = new AdminCodeDTO();
const Errors: Array<ValidationError> = await validate(DTOValidCheck);
expect(Errors.length).toBe(1);
expect(Errors[0].constraints['isDefined']).toBe('AdminCode should not be null or undefined');
expect(Errors[0].constraints['isNumber']).toBe('AdminCode must be a number conforming to the specified constraints');
expect(Errors[0].constraints['max']).toBe('AdminCode must not be greater than 999');
expect(Errors[0].constraints['min']).toBe('AdminCode must not be less than 1');
});
});
To then build a simple DTO combining 2 fields to do the testing, I create a description DTO as well, to add that field for this simple example.
import { IsDefined, IsString, MaxLength, MinLength } from '#nestjs/class-validator';
import { ApiProperty, ApiResponseProperty } from '#nestjs/swagger';
export class DescriptionDTO {
#ApiProperty({
description: '',
minLength: 3,
maxLength: 20
})
#ApiResponseProperty({ example: 'Sick Day' })
#IsDefined() #IsString() #MaxLength(20) #MinLength(3) public Description: string;
constructor(Description?: string) {
this.Description = Description;
}
}
I then use the IntersectionType of #nestjs/swagger, to combine the AdminCodeDTO, with a new description field for the payload.
import { IsDefined, IsString, MaxLength, MinLength } from '#nestjs/class-validator';
import { ApiProperty, ApiResponseProperty, IntersectionType} from '#nestjs/swagger';
import { AdminCodeDTO } from './admin-code-dto';
export class AdmininstrationCodesDTO extends IntersectionType(
AdminCodeDTO,
DescriptionDTO
)
{
constructor(AdminCode?: number, Description?: string) {
this.AdminCode = AdminCode;
this.Description = Description;
}
My test however, while all the columns are defined, the validation does not work.
import { AdmininstrationCodesDTO } from './admininstration-codes-dto';
describe('AdmininstrationCodesDTO', () => {
let TestDTO: AdmininstrationCodesDTO;
beforeEach( () => {
TestDTO = new AdmininstrationCodesDTO(77, 'Test Admin Code');
})
it('should be defined', () => {
expect(TestDTO).toBeDefined();
});
it('should be defined when launched without parameters', () => {
expect(new AdmininstrationCodesDTO()).toBeDefined();
})
it.each([
['AdminCode', 77],
['Description', 'Test Admin Code'],
])('should have the proper field {%s} set to be %d', (FieldName, Expected) => {
expect(FieldName in TestDTO).toBe(true);
expect(TestDTO[FieldName]).toBe(Expected);
});
// This test fails as the validation settings are not enforced. Working on any of the DTOs directly though, the validation is confirmed.
it('should generate the DTO errors', async () => {
const TestDTO: AdmininstrationCodesDTO = new AdmininstrationCodesDTO();
const Errors: Array<ValidationError> = await validate(TestDTO, );
expect(Errors.length).toBe(8);
});
});
EDIT: This also causes a problem in my Swagger UI documentation, where this method now prevents my request schemas from showing the data. When I define my fields directly in the DTO (without IntersectionType) the fields show up in the request schema for Swagger. I have the CLI functions enabled in the project.json (NX monorepo).
As found out from your GitHub Issue (thank you for that by the way) you were using #nestjs/class-validator and #nestjs/class-transformer for the validator and transformer packages. #nestjs/mapped-types uses the original class-valdiator and class-transformer packages and these packages use an internal metadata storage device rather than the full Reflect API and metadata storage, so when Nest tried to copy over the metadata from class-validator there was none found because of the use of #nestjs/class-validator, which ended up in having no metadata present for the IntersectionType request

React component not receiving intermediate state when chaining actions in redux-saga

I have two actions TEST and TEST_DONE which both increment an id property in my redux state. I am using redux-saga to dispatch the second action TEST_DONE automatically whenever I dispatch the first action TEST from my component.
I expect the order of execution to go like this:
component renders with initial value of testState.id = 0
component dispatches TEST action
component re-renders with testState.id = 1
saga dispatches the TEST_DONE action
component re-renders with testState.id = 2
Instead my component only re-renders when testState.id is updated to 2. I can't see the 1 value in the getSnapshotBeforeUpdate function. It shows 0 as the previous prop.
Why does the prop jump from 0 to 2 without receiving 1 in between?
saga.js:
export function* TestSagaFunc() {
yield put({
type: actions.TEST_DONE
});
};
export default function* rootSaga() {
yield all([
yield takeEvery(actions.TEST, TestSagaFunc),
]);
};
action.js:
const actions = {
TEST: 'TEST',
TEST_DONE: 'TEST_DONE',
callTest: (id) => ({
type: actions.TEST,
payload: {
id
}
}),
};
export default actions;
reducer.js:
const initState = {
testState: {
id: 0
}
};
export default function TestReducers ( state=initState, { type, ...action}) {
switch(type) {
default:
return state;
case actions.TEST: {
const { id } = state.testState;
const nextId = id + 1;
return {
...state,
testState: {
...state.testState,
id: nextId
}
};
};
case actions.TEST_DONE: {
const { id } = state.testState;
const nextId = id + 1;
return {
...state,
testState: {
...state.testState,
id: nextId
}
};
}
};
};
console output from component getSnapshotBeforeUpdate
Summarizing my comments from the question:
The redux state is indeed being updated as you've seen, but a component is not guaranteed to render every intermediate state change based on the way react batches state changes. To test this you can try importing delay from redux-saga/effects and adding yield delay(1000); before calling yield put in TestSagaFunc so the two state updates don't get batched together.
This is just a trick to illustrate the effects of batching and almost certainly not what you want to do. If you need the intermediate state to be rendered you could dispatch TEST_DONE from the component being rendered with a useEffect (or componentDidUpdate) to ensure that the component went through one render cycle with the intermediate state. But there is no way to force your component to render intermediate reducer states that are batched together.

change local data object on change of global state

I am trying to edit and update using vuex. I have two components, one is form for entry/edit and another for viewing data. When I click on edit button of view component I want to populate the form component with the data from backend.
Note that I already have a data object on form component that is bind with form and used to store data. I want to change it on edit. I have no problem on getting data from backend.
I just can not change the local data object from state
This is my codes what I've tried
Form component:
import { mapState } from 'vuex'
export default {
data(){
return{
form:{
id:'',
name:'',
email:'',
phone:'',
address:''
}
}
},
computed:{
...mapState({
data (state) {
this.form=state.employee; //This is where I am stuck
}
}),
}
state:
export default{
state:{
employee:{}
},
getters:{}
},
mutations:{
setEmployee(state,employee){
state.employee=employee;
}
},
actions:{
fetchEmployee({commit},id){
axios.put('employee-edit/'+id)
.then(res=>res.data)
.then(employee=>{
commit('setEmployee',employee)
})
}
}
}
view component:
export default {
methods:{
editEmployee(id){
this.$store.dispatch('fetchEmployee',id);
}
}
}
Multiple issues/misunderstandings with your approach.
1) computed properties are "passive" and supposed to return a value "computed" from other values. Directly assigning to your local state is probably not what you want to do.
2) HTTP methods: In general the HTTP PUT method replaces the resource at the current URL with the resource contained within the request.
Please read up on http methods and how they are supposed to be used. You want GET
3) mapState is a helper method if you need multiple getters from vuex state store. I suggest you use this.$store.getters.myVariableInState for simple tasks like in your example.
What you probably want is more along these lines:
// store
getters:{
employee: state => state.employee
}
// component
computed: {
employee() {
return this.$store.getters.employee
}
}
If your actions was already dispatched earlier and the data is available in the store all you then need is
methods: {
editEmployee() {
this.form = this.employee
}
}
Since your question stated "change local data on state change", here is an approach for that:
watch your local computed property
watch: {
employee() {
// this.employee (the computed value) now refers to the new data
// and this method is triggered whenever state.employee changes
}
}

React-Redux re-render on dispatch inside HOC not working

I am busy with a little proof of concept where basically the requirement is to have the home page be a login screen when a user has not logged in yet, after which a component with the relevant content is shown instead when the state changes upon successful authentication.
I have to state upfront that I am very new to react and redux and am busy working through a tutorial to get my skills up. However, this tutorial is a bit basic in the sense that it doesn't deal with connecting with a server to get stuff done on it.
My first problem was to get props to be available in the context of the last then of a fetch as I was getting an error that this.props.dispatch was undefined. I used the old javascript trick around that and if I put a console.log in the final then, I can see it is no longer undefined and actually a function as expected.
The problem for me now is that nothing happens when dispatch is called. However, if I manually refresh the page it will display the AuthenticatedPartialPage component as expected because the localstorage got populated.
My understanding is that on dispatch being called, the conditional statement will be reavaluated and AuthenticatedPartialPage should display.
It feels like something is missing, that the dispatch isn't communicating the change back to the parent component and thus nothing happens. Is this correct, and if so, how would I go about wiring up that piece of code?
The HomePage HOC:
import React from 'react';
import { createStore, combineReducers } from 'redux';
import { connect } from 'react-redux';
import AuthenticatedPartialPage from './partials/home-page/authenticated';
import AnonymousPartialPage from './partials/home-page/anonymous';
import { loggedIntoApi, logOutOfApi } from '../actions/authentication';
import authReducer from '../reducers/authentication'
// unconnected stateless react component
const HomePage = (props) => (
<div>
{ !props.auth
? <AnonymousPartialPage />
: <AuthenticatedPartialPage /> }
</div>
);
const mapStateToProps = (state) => {
const store = createStore(
combineReducers({
auth: authReducer
})
);
// When the user logs in, in the Anonymous component, the local storage is set with the response
// of the API when the log in attempt was successful.
const storageAuth = JSON.parse(localStorage.getItem('auth'));
if(storageAuth !== null) {
// Clear auth state in case local storage has been cleaned and thus the user should not be logged in.
store.dispatch(logOutOfApi());
// Make sure the auth info in local storage is contained in the state.auth object.
store.dispatch(loggedIntoApi(...storageAuth))
}
return {
auth: state.auth && state.auth.jwt && storageAuth === null
? state.auth
: storageAuth
};
}
export default connect(mapStateToProps)(HomePage);
with the Anonymous LOC being:
import React from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import { loggedIntoApi } from '../../../actions/authentication';
export class AnonymousPartialPage extends React.Component {
constructor(props) {
super(props);
}
onSubmit = (e) => {
e.preventDefault();
const loginData = { ... };
// This is where I thought the problem initially occurred as I
// would get an error that `this.props` was undefined in the final
// then` of the `fetch`. After doing this, however, the error went
// away and I can see that `props.dispatch is no longer undefined
// when using it. Now though, nothing happens.
const props = this.props;
fetch('https://.../api/auth/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(loginData)
})
.then(function(response) {
return response.json();
})
.then(function(data) {
if(data && data.jwt) {
props.dispatch(loggedIntoApi(data));
localStorage.setItem('auth', JSON.stringify(data));
}
// else show an error on screen
});
};
render() {
return (
<div>
... onSubmit gets called successfully somewhere in here ...
</div>
);
}
}
export default connect()(AnonymousPartialPage);
the action:
// LOGGED_INTO_API
export const loggedIntoApi = (auth_token) => ({
type: 'LOGGED_INTO_API',
auth: auth_token
});
// LOGGED_OUT_OF_API
export const logOutOfApi = (j) => ({
type: 'LOG_OUT_OF_API'
});
and finally the reducer:
const authDefaultState = { };
export default (state = authDefaultState, action) => {
switch (action.type) {
case 'LOGGED_INTO_API':
// SOLUTION : changed this line "return action.auth;" to this:
return { ...action.auth, time_stamp: new Date().getTime() }
case 'LOG_OUT_OF_API':
return { auth: authDefaultState };
default:
return state;
}
};
My suggestion would be to make sure that the state that you are changing inside Redux is changing according to javascript's equality operator!. There is a really good answer to another question posted that captures this idea here. Basically, you can't mutate an old object and send it back to Redux and hope it will re-render because the equality check with old object will return TRUE and thus Redux thinks that nothing changed! I had to solve this issue by creating an entirely new object with the updated values and sending it through dispatch().
Essentially:
x = {
foo:bar
}
x.foo = "baz"
dispatch(thereWasAChange(x)) // doesn't update because the x_old === x returns TRUE!
Instead I created a new object:
x = {
foo:"bar"
}
y = JSON.parse(JSON.stringify(x)) // creates an entirely new object
dispatch(thereWasAChange(y)) // now it should update x correctly and trigger a rerender
// BE CAREFUL OF THE FOLLOWING!
y = x
dispatch(thereWasAChange(y)) // This WON'T work!!, both y and x reference the SAME OBJECT! and therefore will not trigger a rerender
Hope this helps!

Update Redux Store

I am trying to update the redux store but when I try to access both points and sessionId, they come back undefined. I am sure there is a problem with my reducer, but I can't figure it out. Any help would be much appreciated.
Here's my reducer:
import { UPDATE_POINTS, SET_SESSION } from '../path'
const initialState = {
sessionId: null,
points: []
}
export default (state = initialState, action) => {
switch (action.type) {
case UPDATE_POINTS:
return {
points: action.points
}
case SET_SESSION:
return {
sessionId: action.session
}
default:
return state;
}
}
Edit:
Action Creators
export function updatePoints(points){
return {
type: UPDATE_POINTS,
points
}
}
export function setSession(session){
return {
type: SET_SESSION,
session
}
}
Within React Component (for simplicity I took most everything else out of this function)
handleSelect(e) {
this.props.setSession(e);
console.log(this.props.sessionId);
}
This function is used when a menu item is chosen from a drop down menu. On the first selection, the console shows whatever is in the initial state for sessionId. Any further drop down selections result in undefined in the console.
You're super close. A reducer in redux needs to return the a new copy of the entire state. Your reducer is returning only the key it's concerned with, which is going to drop the other key. You need to return a new copy of the state with your key updated. For example:
const initialState = {
sessionId: null,
points: []
}
export default (state = initialState, action = null) => {
// Exit early if you don't have an action (returning old state)
if (!action) return state;
// This function will assign your patch onto the old state, and then
// assign all of that onto a NEW object. For redux to do it's job,
// you can't modulate the old object, you have to return a new one.
const update = patch => Object.assign({}, state, patch);
switch (action.type) {
case UPDATE_POINTS:
return update({
points: action.points
});
case SET_SESSION:
return update({
sessionId: action.session
});
default:
return state;
}
}
And for the record, instead of putting your data payload under a unique key each time in your action creators, if you put the payload under a data key then your action will follow the standard flux action format.
export const updatePoints = (points) => ({
type: UPDATE_POINTS,
data: points
});
export const setSession = (session) => ({
type: SET_SESSION,
data: session
});
There you go. Good luck, and if you get stuck, refer back to the Redux docs (they're really good). Link to Redux Docs

Resources