I'm creating a bank app with react, node js and MongoDB.
The data is posted through 3 different inputs done by the user, but I'm having problems with the delete request. It is in 3 different classes (props passed accordingly). The error I'm getting says that it cannot read the property _id of undefined. The id is received from the DB. How can I make it work? Thanks in advance!
class App extends Component {
constructor() {
super()
this.state = {
transactions: []
}
}
deleteTransaction = async (transaction) => {
await axios.delete('http://localhost:8080/transaction', {data: {id: transaction._id }})
let response = await this.getTransactions()
this.setState({ transactions: response.data })
}
class Transactions extends Component {
deleteTransaction = () => { this.props.deleteTransaction() }
class Transaction extends Component {
<button onClick={this.props.deleteTransaction.bind(this)}><DeleteIcon /></button>
You don't need to use the bind when it is passed as a prop.
First, you have to remove the deleteTransaction function which is declared inside the Transactions component
Second, You have to pass the value the transaction argument in deleteTransaction function which is declared in App component
<button onClick={(event) => this.props.deleteTransaction(transaction)}><DeleteIcon/></button>
You haven't passed the value to the callback function which is why you are getting the cannot read the property _id of undefined. By default transaction argument value in deleteTransaction(App) is undefined. Since you haven't passed any value to the transaction argument, it works as expected.
Note:
Try to use React Context API whenever is possible which will avoid the prop drilling.
Related
I've made simple CRUD app with React and Apollo client on NestJS server with GraphQL API.
I have this simple Mutations:
schema.gql:
type Mutation {
createUser(input: CreateUserInput!): User! // CreateUserInput type you can see in user.input.ts below
updateUser(id: ID!, input: UpdateUserInput!): User!
deleteUser(id: ID!): User!
}
user.input.ts:
import { InputType, Field } from "#nestjs/graphql";
import { EmailScalar } from "../email.scalar-type";
#InputType()
export class CreateUserInput {
// EmailScalar is a custom Scalar GraphQL Type that i took from the internet and it worked well
#Field(() => EmailScalar)
readonly email: string;
#Field()
readonly name: string;
}
"EmailScalar" type checks if "email" input has *#*.* format basically
And when i make createUser Query to GraphQL API like this:
It cannot pass validation
(because Email type works fine)
But when Query sent from client - it passes validation:
NestJS server log (from code below)
users.resolver.ts:
#Mutation(() => User)
async createUser(#Args('input') input: CreateUserInput) { // Type from user.input.ts
Logger.log(input); // log from screenshot, so if it's here it passed validation
return this.usersService.create(input); // usersService makes requests to MongoDB
}
And it gets into MongoDB
Here is client side part:
App.tsx:
...
// CreateUserInput class is not imported to App.tsx (it is at server part) but it seems to be fine with it
const ADD_USER = gql`
mutation AddMutation($input: CreateUserInput!) {
createUser(input: $input) {
id
name
email
}
}
`
function App(props: any) {
const { loading, error, data } = useQuery(GET_USERS);
const [addUser] = useMutation(
ADD_USER,
{
update: (cache: any, { data: { createUser } }: any) => {
const { users } = cache.readQuery({ query: GET_USERS });
cache.writeQuery({
query: GET_USERS,
data: {
users: [createUser, ...users],
},
})
}
}
);
...
if (loading) return <p>Loading...</p>;
if (error) return <p>Error :(</p>;
return <UserTable users={data.users} addUser={addUser} updateUser={updateUser} deleteUser={deleteUser} />;
}
Can someone please explain to me, how does client Query passes validation and what have i done wrong?
Even two empty strings can pass through.
Never worked with NestJS, Apollo, React or GraphQL before, so I'm kinda lost.
For full code:
https://github.com/N238635/nest-react-crud-test
This is how your custom scalar's methods are defined:
parseValue(value: string): string {
return value;
}
serialize(value: string): string {
return value;
}
parseLiteral(ast: ValueNode): string {
if (ast.kind !== Kind.STRING) {
throw new GraphQLError('Query error: Can only parse strings got a: ' + ast.kind, [ast]);
}
// Regex taken from: http://stackoverflow.com/a/46181/761555
var re = /^([\w-]+(?:\.[\w-]+)*)#((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$/i;
if (!re.test(ast.value)) {
throw new GraphQLError('Query error: Not a valid Email', [ast]);
}
return ast.value;
}
parseLiteral is called when parsing literal values inside the query (i.e. literal strings wrapped in double quotes). parseValue is called when parsing variable values. When your client sends the query, it sends the value as a variable, not as a literal value. So parseValue is used instead of parseLiteral. But your parseValue does not do any kind of validation -- you just return the value as-is. You need to implement the validation logic in both methods.
It would also be a good idea to implement the serialize method so that your scalar can be used for both input and response validation.
I was initially just running one query in node.js but I now need two sets of data so I ran two queries and used Promise.all like this:
Promise.all([products, subcats]);
res.status(200).json({
products,
subcats
});
In React I have:
class ProductList extends Component {
state = {
products: []
};
async componentDidMount() {
const catslug = this.props.match.params.catslug;
const { data: products } = await getCatProducts(catslug);
this.setState({ products: products });
}
When I was only running the one query I was running this without any issue:
this.state.products.map(product => (
Now because I have the 2 queries I need to change it to:
this.state.products.products.map(product => (
But as soon as I do that I get the error:
Cannot read property 'map' of undefined
So, I changed it to this and now it works with no errors:
{this.state.products.products &&
this.state.products.products.map(product => (
My question is why did it work before without having to put in the ... && bit but now I have to use it with the Promise.all in node.js?
It's because of the initial shape of your state
state = {
products: []
};
You see this.state.products is already defined as an array, so it can be mapped over (despite being empty). However this.products.products is NOT an array, it is undefined, so trying to map will return the error you are seeing. The line you have entered
{this.state.products.products &&
this.state.products.products.map(product => (
checks that the array exists before attempting to map it (which wont evaluate to true until your async code finishes, and then the array is defined).
An alternative fix would be to set your initial state shape to match your final state shape. i.e
state ={
products:{
products:[]
}
};
Or if you don't want the nested products property you can change
async componentDidMount() {
const catslug = this.props.match.params.catslug;
const { data } = await getCatProducts(catslug);
this.setState({ products: data.products });
}
and return to your old this.state.products.map()
The issue is in the following line:
const { data: products } = await getCatProducts(catslug);
If I understand correctly, when you were sending one value, you were sending it like:
res.status(200).json(products); // an array
But now you are sending an object, which further contains 2 arrays products and subcats.
What you have 2 do is add below changes to make it work:
const obj = await getCatProducts(catslug);
const products = obj.products
const subcats = obj.subcats
I'm having a little trouble with an integration test for my mongoose application. The problem is, that my unique setting gets constantly ignored. The Schema looks more or less like this (so no fancy stuff in there)
const RealmSchema:Schema = new mongoose.Schema({
Title : {
type : String,
required : true,
unique : true
},
SchemaVersion : {
type : String,
default : SchemaVersion,
enum: [ SchemaVersion ]
}
}, {
timestamps : {
createdAt : "Created",
updatedAt : "Updated"
}
});
It looks like basically all the rules set in the schema are beeing ignored. I can pass in a Number/Boolean where string was required. The only thing that is working is fields that have not been declared in the schema won't be saved to the db
First probable cause:
I have the feeling, that it might have to do with the way I test. I have multiple integration tests. After each one my database gets dropped (so I have the same condition for every test and precondition the database in that test).
Is is possible that the reason is my indices beeing droped with the database and not beeing reinitiated when the next text creates database and collection again? And if this is the case, how could I make sure that after every test I get an empty database that still respects all my schema settings?
Second probable cause:
I'm using TypeScript in this project. Maybe there is something wrong in defining the Schema and the Model. This is what i do.
1. Create the Schema (code from above)
2. Create an Interface for the model (where IRealmM extends the Interface for the use in mongoose)
import { SpecificAttributeSelect } from "../classes/class.specificAttribute.Select";
import { SpecificAttributeText } from "../classes/class.specificAttribute.Text";
import { Document } from "mongoose";
interface IRealm{
Title : String;
Attributes : (SpecificAttributeSelect | SpecificAttributeText)[];
}
interface IRealmM extends IRealm, Document {
}
export { IRealm, IRealmM }
3. Create the model
import { RealmSchema } from '../schemas/schema.Realm';
import { Model } from 'mongoose';
import { IRealmM } from '../interfaces/interface.realm';
// Apply Authentication Plugin and create Model
const RealmModel:Model<IRealmM> = mongoose.model('realm', RealmSchema);
// Export the Model
export { RealmModel }
Unique options is not a validator. Check out this link from Mongoose docs.
OK i finally figured it out. The key issue is described here
Mongoose Unique index not working!
Solstice333 states in his answer that ensureIndex is deprecated (a warning I have been getting for some time now, I thought it was still working though)
After adding .createIndexes() to the model leaving me with the following code it works (at least as far as I'm not testing. More on that after the code)
// Apply Authentication Plugin and create Model
const RealmModel:Model<IRealmM> = mongoose.model('realm', RealmSchema);
RealmModel.createIndexes();
Now the problem with this will be that the indexes are beeing set when you're connection is first established, but not if you drop the database in your process (which at least for me occurs after every integration test)
So in my tests the resetDatabase function will look like this to make sure all the indexes are set
const resetDatabase = done => {
if(mongoose.connection.readyState === 1){
mongoose.connection.db.dropDatabase( async () => {
await resetIndexes(mongoose.models);
done();
});
} else {
mongoose.connection.once('open', () => {
mongoose.connection.db.dropDatabase( async () => {
await resetIndexes(mongoose.models);
done();
});
});
}
};
const resetIndexes = async (Models:Object) => {
let indexesReset: any[] = [];
for(let key in Models){
indexesReset.push(Models[key].createIndexes());
}
Promise.all(indexesReset).then( () => {
return true;
});
}
I've got a component that when simplified looks like so:
import Sortable from 'react-sortablejs';
class App extends Component {
constructor(props) {
super(props);
}
updateItemOrder() {
let items = this.props.items;
items.forEach((item, index) => {
console.log(item._id);
console.log(index);
updateItem.call({ _id: item._id, update: { priority: index } });
});
};
render() {
<Sortable
tag='ul'
className='item-list list-group'
onChange={ this.updateItemOrder() }>
{ this.renderItems() }
</Sortable>
}
}
My console lists the item._id and the index on page load, but nothing happens when I drag-and-drop items in my sortable list. Changing to a <ReactSortable> component doesn't help, either (as per the docs).
From your above code specifically this part here,
updateItemOrder() {
let items = this.props.items;
items.forEach((item, index) => {
console.log(item._id);
console.log(index);
updateItem.call({ _id: item._id, update: { priority: index } });
});
};
It would seem that you are trying to mutate props which is a no go in react. To cause a re-render in react, you need to update state and tell react this change is state should cause a re-render. This is done using setState. There 2 options that come to mind right of the bat. First you can capture your props in the local state of this component and update that, or you can call a method back in your parent component to update its state forcing it to re-render.
I cannot find a way to retrieve data from the server and convert them to model instance. I folloed the instructions here but it's still doesn't work.
Here is my setup:
url for the service: services/foo/barOne. The response is : {"calendar":1352793756534,"objectId":"id2"}
The model is defined as follow:
var myModel = can.Model(
model: function(raw) {
newObject = {id:raw.objectId};
return can.Model.model.call(this,newObject);
},
findOne: {
url: 'services/foo/barOne',
datatype: 'json'
}
)
And here is how I use that:
myBar = myModel.findOne()
myBar.then(function() {
console.log("resolved with arguments:",arguments); //[undefined]
})
I put various log and tracked all of function called, the request is correctly done, and the ajax resolved correctly. The model method is also correct and return an object of type Constructor with the right parameters.
But after that, inside the pipe function of canjs, the result is lost (ie, I got the result undefined when d.resolve.apply(d, arguments) is called)
What is wrong with this scenario ?
I am using canJS with jquery version 1.0.7
I don't think you need a custom converter just for changing the id. You can change it by setting the static id property. Try it like this:
var MyModel = can.Model({
id : 'objectId'
}, {
findOne: 'services/foo/barOne'
});
MyModel.findOne({ id : 'test' }).done(function(model) {
console.log(model);
});