Meteor React Tutorial Expected string, got object - reactjs

Hi guys so I got to step 9 by following this tutorial https://www.meteor.com/tutorials/react/security-with-methods
So i've written all the methods with their checks however I get errors like Exception while invoking method 'tasks.remove' Error: Match error: Expected string, got object
Here are my written codes
This is the tasks.js
import { Meteor } from 'meteor/meteor'
import { Mongo } from 'meteor/mongo'
import { check } from 'meteor/check'
export const Tasks = new Mongo.Collection('tasks')
Meteor.methods({
'tasks.insert' (text) {
check(text, String)
// Make sure the user is logged in before insterting a task
if (!this.userId) {
throw new Meteor.Error('not-authorized')
}
Tasks.insert({
text,
createdAt: new Date(),
owner: this.userId,
username: Meteor.users.findOne(this.userId).username
})
}, // tasks.insert
'tasks.remove' (taskId) {
check(taskId, String)
Tasks.remove(taskId)
},
'tasks.setChecked' (taskId, setChecked) {
check(taskId, String)
check(setChecked, Boolean)
Tasks.update(taskId, { $set: { checked: setChecked } })
}
})
And this is the Task.jsx
import React, { Component, PropTypes } from 'react'
import { Meteor } from 'meteor/meteor'
// import { Tasks } from '../api/tasks.js'
// Task component - represents a single todo item
export default class Task extends Component {
toggleChecked () {
// Set the checked value to the opposite of its current value
Meteor.call('tasks.setChecked',this.props.task._id, !this.props.task.checked)
}
deleteThisTask () {
Meteor.call('tasks.remove', this.props.task._id)
}
render () {
// Give tasks a different className when they are checked off,
// so that we can style them nicely
const taskClassName = this.props.task.checked ? 'checked' : ''
return (
<li className={taskClassName}>
<button className="delete" onClick={this.deleteThisTask.bind(this)}>
×
</button>
<input
type="checkbox"
readOnly
checked={this.props.task.checked}
onClick={this.toggleChecked.bind(this)}
/>
<span className="text">
<strong>{this.props.task.username}</strong>:{this.props.task.text}
</span>
</li>
)
}
}
Task.propTypes = {
// This component gets the task to dipslay through a React prop.
// We can use propTypes to indicate it is required
task: PropTypes.object.isRequired
}
What's the problem my written code seems to be identical with the tutorials code, why then do I get these errors? I got the same error for the update method.
EDIT: After commenting the checks and doing the later steps of the tutorial and then enabling the checks makes them work... but I am not sure which part made them to work

The check function is expecting a String because you passed String as a parameter for checking. But your React Task component is expecting Object.
Task.propTypes = {
task: PropTypes.object.isRequired
}
Just try
'tasks.remove' (taskId) {
check(taskId, Object)
Tasks.remove(taskId)
},
instead of
'tasks.remove' (taskId) {
check(taskId, String)
Tasks.remove(taskId)
},

It still gives error with 'Object'. Try concerting input with javascript String() function -
'tasks.remove'(taskId) {
check(String(taskId), String);
Tasks.remove(taskId);
},
'tasks.setChecked'(taskId, setChecked) {
check(String(taskId), String);
check(setChecked, Boolean);
Tasks.update(taskId, { $set: { checked: setChecked } });
},

Related

State changed in context provider not saved

So I'm trying to centralize some alert-related logic in my app in a single .tsx file, that needs to be available in many components (specfically, an "add alert" fuction that will be called from many components). To this end I am trying to use react context to make the alert logic available, with the state (an array of active alerts) stored in App.tsx.
Alerts.tsx
export interface AlertContext {
alerts: Array<AppAlert>,
addAlert: (msg: React.ReactNode, style: string, callback?: (id: string) => {}) => void,
clearAlert: (id: string) => void
}
[...]
export function AlertsProvider(props: AlertsProps) {
function clearAlert(id: string){
let timeout = props.currentAlerts.find(t => t.id === id)?.timeout;
if(timeout){
clearTimeout(timeout);
}
let newCurrent = props.currentAlerts.filter(t => t.id != id);
props.setCurrentAlerts(newCurrent);
}
function addAlert(msg: JSX.Element, style: string, callback: (id: string) => {}) {
console.log("add alert triggered");
let id = uuidv4();
let newTimeout = setTimeout(clearAlert, timeoutMilliseconds, id);
let newAlert = {
id: id,
msg: msg,
style: style,
callback: callback,
timeout: newTimeout
} as AppAlert;
let test = [...props.currentAlerts, newAlert];
console.log(test);
props.setCurrentAlerts(test);
console.log("current alerts", props.currentAlerts);
}
let test = {
alerts: props.currentAlerts,
addAlert: addAlert,
clearAlert: clearAlert
} as AlertContext;
return (<AlertsContext.Provider value={test}>
{ props.children }
</AlertsContext.Provider>);
}
App.tsx
function App(props: AppProps){
[...]
const [currentAlerts, setCurrentAlerts] = useState<Array<AppAlert>>([]);
[...]
const alertsContext = useContext(AlertsContext);
console.log("render app", alertsContext.alerts);
return (
<AlertsProvider currentAlerts={currentAlerts} setCurrentAlerts={setCurrentAlerts}>
<div className={ "app-container " + (error !== undefined ? "err" : "") } >
{ selectedMode === "Current" &&
<CurrentItems {...currentItemsProps} />
}
{ selectedMode === "History" &&
<History {...historyProps } />
}
{ selectedMode === "Configure" &&
<Configure {...globalProps} />
}
</div>
<div className="footer-container">
{
alertsContext.alerts.map(a => (
<Alert variant={a.style} dismissible transition={false} onClose={a.callback}>
{a.msg}
</Alert>
))
}
{/*<Alert variant="danger" dismissible transition={false}
show={ error !== undefined }
onClose={ dismissErrorAlert }>
<span>{ error?.msg }</span>
</Alert>*/}
</div>
</AlertsProvider>
);
}
export default App;
I'm calling alertsContext.addAlert in only one place in CurrentItems.tsx so far. I've also added in some console statements for easier debugging. The output in the console is as follows:
render app Array [] App.tsx:116
XHRGEThttp://localhost:49153/currentitems?view=Error [HTTP/1.1 500 Internal Server Error 1ms]
Error 500 fetching current items for view Error: Internal Server Error CurrentItems.tsx:94
add alert triggered Alerts.tsx:42
Array [ {…}, {…} ] Alerts.tsx:53
current alerts Array [ {…} ] Alerts.tsx:55
render app Array []
So I can see that by the end of the addAlert function the currentAlerts property appears to have been updated, but then subsequent console statement in the App.tsx shows it as empty. I'm relatively new to React, so I'm probably having some misunderstanding of how state is meant to be used / function, but I've been poking at this on and off for most of a day with no success, so I'm hoping someone can set me straight.
const alertsContext = useContext(AlertsContext);
This line in App is going to look for a provider higher up the component tree. There's a provider inside of App, but that doesn't matter. Since there's no provider higher in the component tree, App is getting the default value, which never changes.
You will either need to invert the order of your components, so the provider is higher than the component that's trying to map over the value, or since the state variable is already in App you could just use that directly and delete the call to useContext:
function App(props: AppProps){
[...]
const [currentAlerts, setCurrentAlerts] = useState<Array<AppAlert>>([]);
[...]
// Delete this line
// const alertsContext = useContext(AlertsContext);
console.log("render app", currentAlerts);
[...]
{
currentAlerts.map(a => (
<Alert variant={a.style} dismissible transition={false} onClose={a.callback}>
{a.msg}
</Alert>
))
}
}

Gutenberg React - Struggling to use withSelect

I am currently building a custom block using Gutenberg, I usually use the save like this....
edit({attributes, setAttributes}) {
/* Set Constants */
const {
mytitle,
mycontent
} = attributes;
function ontitleChange(newTitle) {
setAttributes({ title: newTitle});
}
return ([
<TextControl
value={title}
label="Title"
onChange={(value) => {ontitleChange}
/>
])
},
This is working great, but now I am trying to add in a media upload that uses withSelect, all of the examples I have seen use it in this format....
edit: withSelect((select, props) => {
return { media: props.attributes.mediaId ? select('core').getMedia(props.attributes.mediaId) : undefined };
})(BlockEdit),
How can I modify my version to suit this new code? Does anybody have an example of one written another way that is compatible?
To extend on your existing function, you can utilise compose to wrap your existing edit function and create an enhanced component. So that your code remains mostly the same and easier to manage, take what you have as your edit() function and create a new file:
edit.js
import { withSelect } from '#wordpress/data';
import { compose } from '#wordpress/compose';
import { TextControl } from '#wordpress/components';
export function Edit({ attributes, setAttributes, media }) {
console.log(media); // media contains the returned value of applyWithSelect
/* Set Constants */
const {
title,
content
} = attributes;
// nb: removed function ontitleChange() in favour of directly calling setAttributes()
return (
<TextControl
value={title}
label="Title"
onChange={(value) => setAttributes({ title: value })}
/>
)
}
const applyWithSelect = withSelect((select, props) => {
// media is the name of the returned value
return { media: props.attributes.mediaId ? select('core').getMedia(props.attributes.mediaId) : undefined };
});
/**
* Use compose to return the result of withSelect to Edit({...})
* #see https://developer.wordpress.org/block-editor/packages/packages-compose/
*/
export default compose(
applyWithSelect,
)(Edit);
index.js
/**
* Internal dependencies
*/
import Edit from './edit';
registerBlockType('myproject/blockname', {
...
attributes: {
mediaId: {
type: 'number',
... // other props as needed
},
title: {
type: 'string',
... // other props as needed
},
content: {
type: 'string',
... // other props as needed
}
edit: Edit, // this is now the name of exported component from edit.js
...
});

data is not properly set in react and redux

I am getting details from java API .I want to set some of the details to commonvo at UI side and send the
commonvo object to server side. I am using below code:
React component
import { getDetails, populateobjDetails } from './CommonAction'
function mapDispatchToProps(dispatch) {
return {
getDetails: (formType, commonVO) => dispatch(getDetails(formType, commonVO)),
populateobjDetails: (obj, commonVO) => dispatch(populateobjDetails(obj, commonVO)) //edit 1
}
}
Redux Action:
export function getDetails(requestFormType, commonVO) {
return (dispatch) => {
axios.get(`${ROOT_URL}/byName/${commonVO.name}`, { headers: { 'Cache-Control': 'no-cache,no-store,must-revalidate,max-age=-1,private', 'Pragma':'no-cache' } }).then(
(response) => {
let obj= dispatch(setDetails(response.data[0]));
dispatch(populateobjDetails(obj, commonVO))
dispatch(setobjSearchLoadingFlag(false))
}
)
}
}
export function populateobjDetails(obj, commonVO) {
const objDetails = {
projectAlias: obj.projectAlias,
dateExpenseClose: obj.dateExpenseClosed,
projectID: obj.projectId,
id: obj.id,
idClass: obj.idClass
}
return {
type: POPULATE_OBJ_DETAILS,
payload: { ...commonVO, ...objDetails }
}
}
I am adding objDetails in commonVO.But during "Submitting the form" for which these details have been set, i am not able to see new properties being set into the commonVO.
I am new to React-Redux and not much familiar with it.Please let me know if i need to do something more on Redux-react side?
Thank you
Edit: I added method 'populateobjDetails' in 'mapDispatchToProps' function (which was missing in code) and imported the method in the component
Inside your redux action getDetails you're calling:
dispatch(populateobjDetails(obj, commonVO))
However, obj is not really defined inside that action anywhere, so an undefined object is being passed to populateobjDetails, that's why you're not seeing the new properties being set.

Cypress_Test_Automation: how to trigger events for components created during runtime

I'm using Cypress.io to automate one file upload test case based on a react page. The input component(type=file) for file upload is created during runtime when the page is rendered.
Seems the button (by clicking the 'Choose file') opens a native file picker, which cypress Webdriver doesn't seem to support interacting with, so probably trigger an event to simulate file selection can be an option in this case. But the input(type=file) can't be located by Cypress because it is not a part of DOM, which means cy.get('input[type=file]') returns null.
Could you please give me some thoughts how to do it?
this button opens a native file picker
I've tried with this -
const testfile = new File(['test data to upload'], 'upload.csv')
cy.get('input[type=file]').trigger('change', {
force: true,
data: testfile,
});
this brings no luck,because of
CypressError: Timed out retrying: Expected to find element: 'input[type=file]', but never found it.
The source code of the page:
import React, { Component } from 'react'
interface Props {
text?: string
type?: string | undefined
fileID?: string
onFileSelected: (file: any) => void
}
interface State {
name: string
}
export default class FileUpload extends Component<Props, State> {
fileSelector = document.createElement('input')
state: State = {
name: '',
}
componentDidMount() {
this.fileSelector = this.buildFileSelector()
}
buildFileSelector = () => {
const { fileID, type } = this.props
this.fileSelector.setAttribute('type', 'file')
this.fileSelector.setAttribute('id', fileID || 'file')
this.fileSelector.setAttribute('multiple', 'multiple')
this.setAcceptType(type)
this.fileSelector.onchange = this.handleFileChange
return this.fileSelector
}
setAcceptType = (type: string | undefined) => {
if (type) {
type = type[0] === '.' ? type : type.replace(/^/, '.')
this.fileSelector.setAttribute('accept', type)
}
}
handleFileChange = (event: any) => {
const file = event.target.files[0]
if (file) {
this.setState({ name: file.name })
this.props.onFileSelected(file)
}
}
render() {
const { name } = this.state
return (
<div>
<button
onClick={(event: React.ChangeEvent<any>) => {
event.preventDefault()
this.fileSelector.click()
}}
style={{ marginRight: 10 }}
>
{this.props.text || 'Choose file'}
</button>
<label>{name || 'No file chosen'}</label>
</div>
)
}
}
I look forward to receiving suggestions how to automate this 'choose file' action in Cypress. Thanks in advance.
I sorted out this issue by putting an input(type=file) element into the DOM, so that cypress can locate the element and manipulate it.
But regarding the issue I had before I still would like to hear some insights from you if this is still possible to be handled in cypress.

Displaying image stored in ostrio:files

In ostrio:files docs used findOne function, that is not convinient in my case, I need find() with parameters to filter by user id and so on.
I use common Meteor React approach from official tutorial as follows:
renderImages(){
return this.props.images.map((image) => (
<Pic src={image.link()} />
));
}
render() {
...
return (
<form>
<FormGroup controlId="formControlsFile">
<Media>
{this.renderImages()}
</Media>
...
</FormGroup>
</form>);
}
}
export default createContainer(props => {
Meteor.subscribe("files.images.all");
return {
images: Images.find({name: "1.jpeg"}).fetch(),
}
}, App);
Here is collection:
import { Meteor } from 'meteor/meteor';
import { FilesCollection } from 'meteor/ostrio:files';
const Images = new FilesCollection({
collectionName: 'Images',
allowClientCode: false, // Disallow remove files from Client
onBeforeUpload(file) {
...
});
if (Meteor.isServer) {
Meteor.publish('files.images.all', function () {
return Images.find().fetch();
});
}
export default Images;
and Pic element:
import React from 'react';
import {createContainer} from 'meteor/react-meteor-data'
class Pic extends React.Component {
render() {
return (
<img width={64} height={64} src={this.props.src} alt="Image"/>
);
}
}
export default createContainer(props => {
console.log("Image src: " + props.src);
return {
src: (props.src ? props.src : "no-image-64x64.jpg"),
}
}, Pic);
This code causes the following exception:
I20170727-13:46:02.470(3)? Exception from sub files.images.all id uXo24fBLqH8rsMiDy Error: Publish function returned an array of non-Cursors
I20170727-13:46:02.472(3)? at [object Object]._.extend._publishHandlerResult (packages/ddp-server/livedata_server.js:1098:20)
I20170727-13:46:02.472(3)? at [object Object]._.extend._runHandler (packages/ddp-server/livedata_server.js:1060:10)
I20170727-13:46:02.473(3)? at [object Object]._.extend._startSubscription (packages/ddp-server/livedata_server.js:859:9)
I20170727-13:46:02.473(3)? at [object Object]._.extend.protocol_handlers.sub (packages/ddp-server/livedata_server.js:625:12)
I20170727-13:46:02.474(3)? at packages/ddp-server/livedata_server.js:559:43
Here are two factors important two know:
1. Return a cursor instead of array
Your publish function needs to return a cursor, currently it returns an array (by calling fetch), which is no cursor.
You don't need fetch in the publication but on the client side. The publication is solely there to sync the client collection with the server collection by a given filter/transform/sort etc.
2. You need to get the data from the Mongo.Collection and not the FilesCollection
The FilesCollection itself is no collection but makes use of a Mongo.Collection. If you want to use the data from the collection, you need to reference the collection like so: Images.collection.find
Example that returns a cursor:
Meteor.publish('files.images.all', function () {
// find data and return cursor if
// data exists
const data = Images.collection.find();
if (data && data.count() >0)
return data;
this.ready(); // set ready if no data exists
});
The same applies for your client:
export default createContainer(props => {
Meteor.subscribe("files.images.all");
return {
images: Images.collection.find({name: "1.jpeg"}).fetch(),
}
}, App);
There is also more to read on the ostrio:files documentation

Resources