Meteor: sortablejs method not being called - node.js

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.

Related

Get soft deleted records using Nestjs and TypeOrmQueryService

I am trying to get soft deleted records (deletedAt column) when using query from TypeOrmQueryService but looks like there is no way to do that. I know that I can use withDeleted if I use find or findOne but I cannot switch to these methods or use a query builder since it would require a lot of changed in the front-end.
#QueryService(Patient)
export class PatientQueryService extends TypeOrmQueryService<Patient> {
constructor(#InjectRepository(Patient) repo: Repository<Patient>) {
super(repo);
}
async getOnePatient(currentUser: User, patientId: number) {
const result = await super.query({
paging: { limit: 1 },
filter: { id: { eq: 1 } },
});
}
}

Removing many2many entity in typeorm with cascade

I have an entity called Entry and it relates to another entity called Image as Many2Many.
Here is what the Entry & Image relationships look like:
#Entity('entry')
export class EntryEntity extends BaseEntity implements IDeserializable<EntryEntity> {
#ManyToMany(type => ImageEntity, image => image.entries, { onDelete: 'CASCADE', cascade: true })
#JoinTable()
images: ImageEntity[];
}
and the Image entity class:
#Entity('image')
export class ImageEntity extends BaseEntity implements IDeserializable<ImageEntity> {
#ManyToMany(type => EntryEntity, entry => entry.images)
entries: EntryEntity[];
}
The method I use to delete an entry:
public async delete(entryId: number): Promise<void> {
const queryRunner = this.connection.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
try {
await queryRunner.manager.getRepository(EntryEntity)
.createQueryBuilder('entry')
.delete()
.from(EntryEntity)
.where('entry.id = :entryId', { entryId })
.execute();
await queryRunner.commitTransaction();
} catch (err) {
await queryRunner.rollbackTransaction();
} finally {
await queryRunner.release();
}
}
Expected behaviour:
If I delete some entry then all its images should also be deleted.
Factual behaviour:
Entry gets removed from its table, it also gets removed from the entry_images_image table
but the images associated with this entry stay (they are still present in the image table).
I'm not very familiar with TypeOrm, why does it happen? I would highly appreciate some help.
what the cascade does is to remove the relations in both sides, not the entities themselves. so in you'r case, you will only receive images without relations, as you have mentioned.
Look at it like this: let's say that you have multiple images for multiple entries, all connected to each other. if you delete one entry, would you really like the image entity to be fully deleted? because in that case you will remove alot of data that hasn't been removed.

how to make this axios delete request work?

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.

How to implement a selector in easy: search for Meteor, using React instead of Blaze

I'm trying to follow the documentation and examples to add a server-side selector to a search function in my Meteor app, implemented using the Easy Search plugin. The end goal is to ensure that only documents the user has permission to see are returned by searching.
I can see a selector working in the Leaderboard example, but I can't get it to work in my code.
Versions:
Meteor 1.7.0.1
easy:search#2.2.1
easysearch:components#2.2.2
easysearch:core#2.2.2
I modified the Meteor 'todos' example app to demonstrate the problem, and my demo code is in a repo.
NOTE! to demonstrate the problem, you need to create an account in the demo app, then create a list and make it private. This add the 'userId' field to the list.
Then you can search for the name of the list, by typing in the search box near the top of the main section; search results are written to the browser console.
The first problem is that if I copy the code from the example in the documentation, I see a server error 'searchObject is not defined:
copied from docs, causes an error: imports/api/lists/lists.js
export const MyIndex = new Index({
'collection': Lists,
'fields': ['name'],
engine: new MongoDBEngine({
selector(searchDefinition, options, aggregation) {
// retrieve the default selector
const selector = this.defaultConfiguration()
.selector(searchObject, options, aggregation)
// options.search.userId contains the userId of the logged in user
selector.userId = options.search.userId
return selector
},
}),
});
It seems there is an error in the docs.
Working instead from the leaderboard example, the code below runs but intermittently returns no results. For example if I have a list called "My list", and I type the search term 's', sometimes the list is returned from the search and sometimes it is not. If I use the MiniMongo engine it all works perfectly.
index selector {"$or":[{"name":{"$regex":".*my.*","$options":"i"}}],"userId":"Wtrr5FRHhkKuAcrLZ"}
client and server: imports/api/lists/lists.js
export const MyIndex = new Index({
'collection': Lists,
'fields': ['name'],
'engine': new MongoDBEngine({
selector: function (searchObject, options, aggregation) {
let selector = this.defaultConfiguration().selector(searchObject, options, aggregation);
selector.userId = options.search.userId;
console.log('index selector', JSON.stringify(selector));
return selector;
}
}),
permission: () => {
return true;
},
});
client: imports/ui/components/lists-show.js
Template.Lists_show.events({
'keyup #search'(event) {
console.log('search for ', event.target.value);
const cursor = MyIndex.search(event.target.value);
console.log('count',cursor.count());
console.log('results', cursor.fetch());
},
});
client: imports/ui/components/lists-show.html
<input id="search" type="text" placeholder="search..." />
Edit: I think the problem is that while the Minimongo engine runs on the client, the MongoDBEngine runs on the server and there are timing issues with the results. The docs show using Tracker.autorun, but that's not a natural fit with my React / Redux app. I'll post an answer if I manage to figure something out - I can't be the only person trying to do something like this.
I got it working in my React / Redux / Meteor app. Things to note:
The cursor MyIndex.search(searchTerm) is a reactive data source - you can't just use it as a return value. When searching on the client with MiniMongo this isn't an issue, but it's important when you use MongoDBEngine to search on the server, because it's asynchronous. In React you can wrap the cursor in withTracker to pass data to the component reactively. In Blaze you would use autorun.tracker. This is shown in the docs but not explained, and it took me a while to understand what was happening.
The docs have an error in the selector example, easily corrected but it's confusing if you have other problems in your code.
With MongoDBEngine, 'permission' must be specified - it does not default to 'true'. Without it, you will see no results.
Writing out the default selector object to the console let me see how it's constructed, and then create a new selector that returns MyDocs that are either public or created by the user.
My code is below. In case it helps anybody else, I've shown how to search on tags also, which are objects with a name property stored in a collection Tags. Each MyDoc has a 'tags' property which is an array of tag ids. The selector first searches the Tags collection to find tags whose name matches the search term, then selects docs in MyDocs with the ids of those tags in their doc.tags array.
There may be a better way to find the search term, or to structure the Tags search, but this is what I could get working.
On server and client:
import { Index, MongoDBEngine } from 'meteor/easy:search';
export const MyDocs = new Mongo.Collection('mydocs');
export const Tags = new Mongo.Collection('tags');
export const MyIndex = new Index({
'collection': MyDocs,
'fields': ['name'],
'engine': new MongoDBEngine({
'selector': function (searchObject, options, aggregation) {
const selector = this.defaultConfiguration().selector(searchObject, options, aggregation);
console.log('default selector', selector); // this searches on name only
// find docs by tag as well as by name
const searchTerm = searchObject.name;
const matchingTags = Tags.find({ 'name': { '$regex': searchTerm } }).fetch();
const matchingTagIds = matchingTags.map((tag) => tag._id);
selector.$or.push({ 'tags': { '$in': matchingTagIds } });
const newSelector = {
'$and': [
{
'$or': [
{ 'isPublic': { '$eq': true } },
{ 'createdBy': options.search.userId },
],
},
{
'$or': selector.$or,
},
],
};
return newSelector;
},
'fields': (searchObject, options) => ({
'_id': 1,
'createdBy': 1,
'name': 1,
}),
'sort': () => ({ 'name': 1 }),
}),
'permission': () => true,
});
React component in client only code:
import React from 'react';
import { connect } from 'react-redux';
import { withTracker } from 'meteor/react-meteor-data';
import PropTypes from 'prop-types';
import store from '../modules/store';
import {
getSearchTerm,
searchStart,
} from '../modules/search'; // contains Redux actions and partial store for search
import { MyIndex } from '../../modules/collection';
function Search(props) {
// functional React component that contains the search box
...
const onChange = (value) => {
clearTimeout(global.searchTimeout);
if (value.length >= 2) {
// user has entered a search term
// of at least 2 characters
// wait until they stop typing
global.searchTimeout = setTimeout(() => {
dispatch(searchStart(value)); // Redux action which sets the searchTerm in Redux state
}, 500);
}
};
...
// the component returns html which calls onChange when the user types in the search input
// and a list which displays the search results, accessed in props.searchResults
}
const Tracker = withTracker(({ dispatch }) => {
// searchTerm is saved in Redux state.
const state = store.getState();
const searchTerm = getSearchTerm(state); // Redux function to get searchTerm out of Redux state
let results = [];
if (searchTerm) {
const cursor = MyIndex.search(searchTerm); // search is a reactive data source
results = cursor.fetch();
console.log('*** cursor count', cursor.count());
return {
'searchResults': results,
};
})(Search);
export default connect()(Tracker);

How to populate custom React component in Keystone Admin

We're using Keystone (version 4.0.0-beta.8) as our Node.js CMS for the first time for a project and we're really liking it. However we're running into a vital missing feature of the CMS and that is adding custom components to the admin views.
What we would like to achieve is to add a custom React component to one of the Keystone admin screens and populate it with data from a field watch function
This component should only be visible for one of the list models. Right now, the only way to do this, is to edit one of the core files:
/node_modules/keystone/admin/client/App/screens/Item/index.js
and add some conditional rendering around it:
{(this.props.params.listId === 'my-list-model') ? (
<MyComponent>{this.props.params.listId}</MyComponent>
) : null }
This works, but is not ideal off-course since you're overwriting core files. But I could overcome this short-come if I knew how I can feed this custom component with data from the Keystone list model declaration:
In: /server/models/MyListModel.js
import keystone from 'keystone';
import { someFunction } from './myFunctions';
var MyListModel = new keystone.List('MyListModel', {
map: { name: 'title' },
});
MyListModel.add({
title: { type: String, required: true },
data: { type: Types.Code, language: 'json', watch: 'title', value: watchTitle, noedit: true },
});
MyListModel.register();
function watchTitle(callback) {
if (this.title) {
function cb(error, result) {
if (result) {
// Send result to React Component in Admin screen
}
callback(error, result);
}
someFunction(this.title, cb);
} else {
callback(null, '');
}
}
Does anyone bump into the same issue or have any clue how to send data to a custom component in the react admin view of Keystone?
Thanks a lot!

Resources