I'm working on the Meteor using React tutorial and trying to understand createContainer(). From reading here:
http://guide.meteor.com/v1.3/react.html#using-createContainer
I think that its a function defined in meteor/react-meteor-data that is used for data loading. In this specific case, it retrieving data from the Mini Mongo Database (named Task here). My question is, what does the second argument to createContainer do? (named App here). Thank you!
class App extends Component {
//...class definition
}
export default createContainer(() => {
return {
//Tasks is a Mongo.Collection
//returns the matching rows (documents)
//here we define the value for tasks member
tasks: Tasks.find({}, { sort: { createdAt: -1} }).fetch(),
};
}, App);
A component created with createContainer is a simple wrapper around your actual component, but it's powerful in that it handles Meteor's reactivity for you so you don't have to think about how to keep your everything up to date when your data changes (e.g. a subscription loads, ReactiveVar / Session var changes)
A React component is basically just a JavaScript function, it is called with a bunch of arguments (props) and it produces an output. React doesn't know if your data has changed unless you tell it so. The component created with createContainer will re-render when your reactive data changes and send a new set of props to your actual component.
The options for createContainer are a function that returns the reactive data you want, and the component you want to wrap. It's really simple, and the render function for createContainer is literally one line:
return <Component {...this.props} {...this.data} />;
It passes through any props you pass to the wrapped component, plus it adds the reactive data source you set up.
You can see the code for yourself here: https://github.com/meteor/react-packages/blob/devel/packages/react-meteor-data/createContainer.jsx
The <Component {...this.props} syntax is known as a splat and basically turns:
{
prop1: 'some value',
prop2: 'another value'
}
into:
<Component prop1='some value' prop2='another value />
(See: https://facebook.github.io/react/docs/jsx-spread.html)
Asking a coworker, this is the answer that I got:
createContainer's second argument is the class name that you want the data to be encapsulated in. It will then have "reactive data" because every time the data in the DB is changed, the class's props will change to include the new data.
Also, the createContainer() function should be called outside of the class definition.
If anyone has anything to add please feel free to contribute.
createContainer's second argument is the name of the class in which you want to pass on the props to.
Lets say createContainer returns a prop called firstName
Now, whenever there is a new firstName entry or an updated firstName in the db, then createContainer is going to call the second argument which is our class name with the prop that it holds i.e firstName.
I hope that makes sense.
Related
I'm using an external library rendered using Vue3. It has the following component from a third part library [Edit: I realize the GitHub repo for that library is out of date, so updating with the actual code in my node_modules.]
<template>
<div class="socket" :class="className" :title="socket.name"></div>
</template>
<script>
import { defineComponent, computed } from "vue";
import { kebab } from "./utils";
export default defineComponent({
props: ["type", "socket"],
setup(props) {
const className = computed(() => {
return kebab([props.type, props.socket.name]);
});
return {
className
};
}
});
</script>
It renders based on a Socket object passed as a prop. When I updated the name property of the Socket, I see the title updated accordingly. However, the CSS/class does not update. I've tried $forceRefresh() on its parent, but this changes nothing.
Update: I was able to move the rendering code to my own repo, so I can now edit this component if needed.
Based on this updated code, it seems the issue is that the class is computed. Is there any way to force this to refresh?
The only time it does is when I reload the code (without refreshing the page) during vue-cli-service serve.
For reference, the | kebab filter is defined here:
Vue.filter('kebab', (str) => {
const replace = s => s.toLowerCase().replace(/ /g, '-');
return Array.isArray(str) ? str.map(replace) : replace(str);
});
Do filtered attributes update differently? I wouldn't think so.
I was also wondering if it could be a reactivity issue, and whether I needed to set the value using Vue.set, but as I understand it that's not necessary in Vue3, and it's also not consistent with the title properly updating.
Computed properties are reactive, however Vue does not expect you to mutate a prop object.
From the documentation:
Warning
Note that objects and arrays in JavaScript are passed by reference, so
if the prop is an array or object, mutating the object or array itself
inside the child component will affect the parent state and Vue is
unable to warn you against this. As a general rule, you should avoid
mutating any prop, including objects and arrays as doing so ignores
one-way data binding and may cause undesired results.
https://v3.vuejs.org/guide/component-props.html#one-way-data-flow
I know that this says, that you should not mutate it in the child, but the general rule is, that you should not mutate properties at all, but instead create new object with the modified data.
In your case the computed function will look for changes in the properties itself, but not the members of the properties, that is why it is not updating.
i've not really understood how v-model works for components, check my code:
<template>
<a-form-item name="bananas" label="bananas">
<a-input v-model:value="test.bananas" />
</a-form-item>
</template>
<script>
import { computed, defineComponent } from 'vue'
export default defineComponent({
props: {
formData: {
type: Object,
required: true
}
},
setup(props) {
const test = computed(() => props.formData)
return { test }
}
})
</script>
where formData in parent component is a reactive object
<InnerComponent v-model:formData="formData" />
const formData = reactive({
name:'',
.....
bananas: 'bananas',
})
this code "works", or rather it seems that the formData object is updated when the input "bananas" is changed .... but how?
reading the documents using v-model in the components i should also define an update function for it, also this is an object and there are no examples using responsive objects
Can someone explain?
all this because having to create a very large form, I need to divide the various sections into sub-components, passing the reactive object to all the children
from https://stackoverflow.com/a/65604790/197546
v-model is syntactical sugar for :value and #change
Instead of <input v-model="name">, you could use
<input :value="name" #update:model-value="v => name=v">
If you are using v-model, you don't need to use emits and listeners. In vue 3 you can specify which variable you'd like to apply this syntactic sugar to via the colon definition, as you have done with v-model:formData. This allows components to have more than one variables be available through v-model which can be very helpful in more advanced component setups.
if you're passing a reactive variable you don't need to use v-model at all, since reactive exposes Vue's internals so in the case of reactive the reactivity is handled separate of the parent-child interaction. Whether the reactive is passed to the child as a prop or from a global reference, the reactivity will work across all instances.
On a side note, refs will not allow this in the same way. while you can still pass them as global, passing it as a prop will not work. It will behave like a regular prop, in the it will continue to receive updates from the parent, but local changes do not propagate outside of the component scope.
I'm building an app with Meteor using the react-komposer package. It is very simple: There's a top-level component (App) containing a search form and a list of results. The list gets its entries through the props, provided by the komposer container (AppContainer). It works perfectly well, until I try to implement the search, to narrow down the results displayed in the list.
This is the code I've started with (AppContainer.jsx):
import { Meteor } from 'meteor/meteor';
import { composeWithTracker } from 'react-komposer';
import React, { Component } from 'react';
import Entries from '../api/entries.js';
import App from '../ui/App.jsx';
function composer(props, onData) {
if (Meteor.subscribe('entries').ready()) {
const entries = Entries.find({}).fetch();
onData(null, {entries});
};
};
export default composeWithTracker(composer)(App);
App simply renders out the whole list of entries.
What I'd like to achieve, is to pass query parameters to Entries.find({}).fetch(); with data coming from the App component (captured via a text input e.g.).
In other words: How can I feed a parameter into the AppContainer from the App (child) component, in order to search for specific entries and ultimately re-render the corresponding results?
To further clarify, here is the code for App.jsx:
import React, { Component } from 'react';
export default class App extends Component {
render() {
return (
<div>
<form>
<input type="text" placeholder="Search" />
</form>
<ul>
{this.props.entries.map((entry) => (
<li key={entry._id}>{entry.name}</li>
))}
</ul>
</div>
);
}
}
Thanks in advance!
I was going to write a comment for this to clarify on nupac's answer, but the amount of characters was too restrictive.
The sample code you're looking for is in the search tutorial link provided by nupac. Here is the composer function with the corresponding changes:
function composer(props, onData) {
if (Meteor.subscribe('entries', Session.get("searchValues")).ready()) {
const entries = Entries.find({}).fetch();
onData(null, {entries});
};
};
The solution is the session package. You may need to add it to your packages file and it should be available without having to import it. Otherwise try with import { Session } from 'meteor/session';
You just need to set the session when submitting the search form. Like this for instance:
Session.set("searchValues", {
key: value
});
The subscription will fetch the data automatically every time the specific session value changes.
Finally, you'll be able to access the values in the publish method on the server side:
Meteor.publish('entries', (query) => {
if (query) {
return Entries.find(query);
} else {
return Entries.find();
}
});
Hope this helps. If that's not the case, just let me know.
There are 2 approaches that you can take.
The Subscription way,
The Meteor.call way,
The Subscription way
It involves you setting a property that you fetch from the url. So you setup your routes to send a query property to you Component.Your component uses that property as a param to send to your publication and only subscribe to stuff that fits the search criteria. Then you put your query in your fetch statement and render the result.
The Meteor.call way
Forget subscription and do it the old way. Send your query to an endpoint, in this case a Meteor method, and render the results. I prefer this method for one reason, $text. Minimongo does not support $text so you cannot use $text to search for stuff on the client. Instead you can set up your server's mongo with text indexes and meteor method to handle the search and render the results.
See what suits your priorities. The meteor.call way requires you to do a bit more work to make a "Search result" shareable through url but you get richer search results. The subscription way is easier to implement.
Here is a link to a search tutorial for meteor and read about $text if you are interested
I want to implement dropdown with react and redux. Dropdown will be a part of other component, so, it it really "Dumb" component. But dropdown should call to api to fetch items, apply custom filters and etc. Api calls should be authenticated, tokens stored it global state. Should I pass tokens to component props? Or there is a better way to do this?
A dumb component, by definition, should be dumb: it means that it should take everything it needs "from the top", i.e. via the props.
Only a "Smart" ("connected" to Redux) Component, up the hierarchy, would deal with fetching the data using a new (async) Action, which would then modify the state when the API call returns, which would re-render your Dumb Component with its new props.
So in Redux:
Your Dumb component takes two props: one with the values coming from your API (which actually are part of your "state"), the other one a function that is called when your dropdown selected item changes. <MyDumbComponent data={this.props.data} onChange={this.onChange.bind(this)} />
Then a "Smart" component up the hierarchy will listen to that onChange function, and dispatch an Action (FETCH_DATA)
The (async) Action will call the API, and when receiving the data, call another Action (FETCH_DATA_SUCCESS) with the data
Then Redux, with a reducer, would update its state from the Action payload
Which will re-render your Component with its new props (new data), coming from the current state.
You might want to read this: http://redux.js.org/docs/basics/UsageWithReact.html
And regarding async actions: http://redux.js.org/docs/advanced/AsyncActions.html
Dumb component doesn't mean it can do anything like fetch updates, it means it's 'dumb' to the concept of redux, and knows nothing about your store or what library you're using. The benefit is that you can change your flux implementation and you only have the small bit of work to update the smart components.
For the type of scenario you're describing, you would give your <Menu> a function via props that would run when <Menu> wants to update the data. <Menu> knows nothing about Redux - it's just executing a function - but it's still able to fetch new data. Depending on the complexities, you could pass through the raw action creator (bound to dispatchAction) and have it run that.
import * as dataActions from './actions';
// We have dataActions.fetchItems() which is the action creater to get new items.
// Attach items, and the action creator (bound to dispatch) to props
#connect((store) => {
return {items: store.data.items}
}, dataActions)
class ItemsPage extends Component {
static propTypes = {
items: PropTypes.object, // the items from the store, from #connect
fetchItems: PropTypes.func // action dispatcher, from #connect
}
render() {
return (<Menu onChange={this.props.fetchItems} /> )
}
}
// Our 'dumb' component that doesnt know anything about Redux,
// but is still able to update data (via its smart parent)
class Menu extends Component {
static propTypes = {
onChange: PropTypes.func // same as fetchItems
}
render() {
return (<button onClick={this.props.onChange}>Update items</button>);
}
}
I'm getting some pretty undesirable behavior in my app, and I'm having a hard time replicating the issue and/or figuring out what I'm doing wrong or not understanding about React that's causing my components to act this way.
What I want to do is to get some data from Mongo on the App component, then have all of that data readily available for any child that I want.
<App> //get data here, pass to children through props
<ChildElement1 data={this.data.appData}/>
<ChildElement2 data={this.data.appData}/>
<ChildElement3 data={this.data.appData}/>
</App>
Here's how I've attempted to tackle this with React so far:
App = React.createClass({
mixins: [ReactMeteorData],
getMeteorData() {
let _id = 'exampleId';
return {
appData: Collection.findOne({_id})
};
},
render() {
return (<ChildElement1 data={this.data.appData} />);
}
});
ChildElement1 = React.createClass({
getInitialState() {
return {
values: ['val1', 'val2', 'val3', 'val4'] //default values
};
},
componentWillMount() {
if(this.props.data.specificValues) {
this.setState({values: this.props.data.specificValues});
}
},
render() {
let values = this.state.values;
return (<span>{values[0]} {values[1]} {values[2]} {values[3]}</span>);
}
});
So here's where it gets weird. When I call componentWillMount(), sometimes this.props.data is defined and other times it's not, which leads me to believe there's some sort of race conditions going on where sometimes that data gets loaded correctly as a prop and other times it doesn't.
I then figured that, well okay, I can't depend on the data prop being there before the component is initially mounted, so I could instead use componentWillReceiveProps(nextProps) and check the updated props that way (and update the state, if necessary). HOWEVER! After using componentWillReceiveProps, now this.props.data is seemingly ALWAYS correctly attached to the props of ChildElement1 (which means componentWillReceiveProps doesn't run!).
My final solution was to use BOTH componentWillMount and componentWillReceiveProps to account for both situations and to do the exact same check in both locations. This fix works, but boy does it seem messy and probably indicates a lack of understanding of component lifecycles, how the meteor/react should properly interact, both, or something else completely.
I'd sure appreciate a bit of help here.
edit: I've come up with a small improvement - instead of using componentWillMount and componentWillReceiveProps to do the check to see if there are specific values defined in the Mongo Collection, I put that logic in render like so:
render() {
let data = this.props.data,
values = (data) ? data.specificValues : this.state.values;
return (<span>{values[0]} {values[1]} {values[2]} {values[3]}</span>);
}
There's definitely still some sort of underlying issue, however, as I still don't understand why this.props is so inconsistent when given data retrieved from getMeteorData. This version is a bit more succinct, however.
I found a better approach to this rather than passing the data returned from getMeteorData to each of the children as props. Using the methods described here: https://www.tildedave.com/2014/11/15/introduction-to-contexts-in-react-js.html, I explicitly listed the childContextTypes and getChildContext in <App /> and then contextTypes in <ChildElement1 />, which allows this.data.appData to be available by way of this.context in <ChildElement1 /> and presumably within any other children of <App />. Although I gotta admit, declaring every single proptype of the collection is a major PITA, seems like it'd be necessary to write a mixin (or rather, a bunch of mixins) to handle that stuff.