Although you don t need to look at the code below to understand the question, I added it in case you need to visualize the scenario. Whenever the form submits, addList method is called.
And the component updates itself. But I didn t expect this behaviour, that s why at first I did try to assign my lists to state, so that when the state changed the component would update itself as I wanted.
Anyway it already updates itself, but why ? Which way is more efficient ?
import React,{Component} from 'react';
import TrackerReact from 'meteor/ultimatejs:tracker-react';
import {Lists} from '../lib/collections/lists.js';
export default class App extends TrackerReact(Component) {
constructor(){
super();
// this.state = {lists : this.lists()}
}
lists(){
return Lists.find().fetch();
}
addList(e){
e.preventDefault();
//let text = this.refs.list.value ;
let text = this._inputList.value ;
console.log(this._inputList.value);
Lists.insert({
title : text
});
this._inputList.value = "";
//this.setState({lists : this.lists()});
}
render() {
return (
<div>
<h2>Lists</h2>
<ul>
{this.lists().map((a,b)=>(
<List key={a._id} title={a.title} />
))}
</ul>
<form onSubmit={this.addList.bind(this)}>
<input
type="text"
ref={(input)=>{
this._inputList = input ;
}}
placeholder="add list bro"
/>
</form>
</div>
)
}
}
So if we add a componentWillUpdate react life-cycle method to see if it rerenders ;
componentWillUpdate() {
console.log('will update');
}
When form submitted "will update" is logged on console as expected. However if we update addList as ;
addList(e){
e.preventDefault();
// nothing else.
}
We don t see the output "will update" on console. Which means method being called doesn t require the component to be rerendered. There is no such a rule. In this case , it is probably about TrackerReact. TrackerReact might force the component to rerender.
Related
I'm trying to use Next to power an Electron app. electron-next uses Next's static site mode for its production build, which calls getInitialProps at build-time, rather than launch-time.
start.js (initially rendered page)
import Link from 'next/link'
export default function Start({date}) {
return (
<div>
<div>Date is {date}</div> {/* <- will always be the build time */}
<Link href="/about">
<a>Take me to the About page</a>
</Link>
</div>
)
}
Start.getInitialProps = () => {
return {
date: "" + new Date()
}
}
Interestingly, using Link to navigate elsewhere does, in fact, result in a dynamic getInitialProps call.
about.js
import Link from 'next/link'
export default function About({date}) {
return (
<div>
<div>Date is {date}</div> {/* <- will be the time the link was clicked */}
<div>Important info about this app</div>
</div>
)
}
About.getInitialProps = () => {
return {
date: "" + new Date()
}
}
Is there a non-hacky way to get dynamic behavior for the initial route? I imagine this would have plenty of use cases in static sites, too.
I ended up not using getInitialProps at all. Instead, I'm using a React hook. It works basically like this:
async function useModel() {
const modelRef = useRef(null)
// This hook will render at build-time by Next.js's static site, in which
// case the conditional loading of the model will never happen.
//
// At startup-time, it will be re-renderered on the Electron renderer thread,
// at which time, we'll actually want to load data.
if (process.browser && !modelRef.current) {
const m = new Model()
await m.init() // <- Assumed to have some async fetching logic
modelRef.current = m
}
return modelRef.current
}
Then, the top-level component can easily use the presence of the model to determine what to do next:
function Start() {
const model = useModel()
if (!model) {
return <div>Loading...</div>
} else {
return <MyProperUI model={model} />
}
}
Or, you could easily rig it up to show an unpopulated default UI, or whatever.
So basically, use getInitialProps for code you want to run exactly once, server-side/build-time or client-side. Otherwise, use other means of initialization. As seen here, hooks allow for this with pretty minimal boilerplate.
I'm working on an app that contains send/cancel request functionality.
I have the following code:
import React, { Component, PropTypes } from 'react';
import { Events } from '../../api/collections/events.js';
import { Visitors } from '../../api/collections/visitors.js';
import { createContainer } from 'meteor/react-meteor-data';
class Event extends Component {
handleDelete() {
Event.remove(this.props.event._id);
}
requestInvite() {
let eid = Events.findOne(this.props.event._id).title;
Visitors.insert({
visitor_id: Meteor.userId(),
visitor_email: Meteor.user().emails[0].address,
event_name: eid,
})
// did it to debug function, returns correct value
console.log(Visitors.findOne({id: this._id}) + ', ' + Meteor.userId());
}
cancelInvite() {
Visitors.remove(this.props.visitor._id);
}
render() {
const visitor = this.props.visitor.visitor_id;
const length = Visitors.find({}).fetch().length;
return (
<div>
{this.props.event.owner == Meteor.userId() ?
<div>
<img src={this.props.event.picture} />
<span>{this.props.event.title}</span>
<button onClick={this.handleDelete.bind(this)}>Delete</button>
</div>
</div> :
<div>
<div>
<img src={this.props.event.picture} />
<span>{this.props.event.title}</span>
<div>
{ length > 0 && visitor == Meteor.userId() ?
<button onClick={this.cancelInvite.bind(this)}>Cancel Request</button>
:
<button onClick={this.requestInvite.bind(this)}>Request invite</button>
}
</div>
</div>
</div>
}
</div>
)
}
}
Event.propTypes = {
event: PropTypes.object.isRequired,
};
export default createContainer(() => {
return {
event: Events.findOne({id: this._id}) || {},
visitor: Visitors.findOne({id: this._id}) || {},
};
}, Event)
It works quite simple, this component shows action buttons depend on user's status (if the current user hosts this event, it shows delete related functionality and so one, I just keep is as simple as it can be for this example). If the current user isn't this event's hoster, component lets this user to send (and cancel) a request for invite. Okay, everything works as it should but only for the first user clicked on Send Request button and after that ich changes to Cancel Request (I use different browsers to test cases like this). The rest of users can also click on Send Request but for them it doesn't change to Cancel Request (but it still adds correct document to Visitors collection, also I have a component which displays all the visitors and the data is corret, i.e ids, emails and event titles). By the first time I thought it's an issue with findOne function, but I don't think so because console.log(Visitors.findOne({id: this._id}) + ', ' + Meteor.userId());'s output stays correct giving me current user's id and just created visitor's id which are the same for each case. Also I found a very strange behaviour. When the app rebuilds, send/cancel functionality works as it suppossed to for every single user.
I think I'm kinda close for the solution but need a little gotcha to do it.
Any help would be highly appreciated!
UPD
It's obvious that my question isn't full without describing Visitor document being created in this component. Here it is:
{
"_id": "Qbkhm9dsSeHyge4rT",
"visitor_id": "qunyJ4sXNfz2w8qeR",
"visitor_email": "johndoe#gmail.com",
"event_name": "test",
}
So as you can see I grab visitor_id from Meteor.userId() and that's why I'm using this.props.visitor.visitor_id to check if currently logged in user's id is equal to a particular visitor's id.
Solution
The problem was with my query to fetch visitor's ids in createContainer function. I changed it to visitor = Visitors.findOne({visitor_id: Meteor.userId()}) and it worked the way I described.
Without knowing how your Visitors collection documents are structured, it's difficult to say for sure; however, it seems that your condition for visitor == Meteor.userId() is the issue since you said that the documents are correctly being added to the Visitors collection, which would make length > 0 return true.
The issue could be that you are setting const visitor = this.props.visitor.visitor_id; rather than say const visitor = this.props.visitor._id;.
I'm learning how to use meteor with react so forgive the basic question.
I want to create a form that populates when the page loads if the data has already been submitted. I've been trying to use getInitialState however I'm not getting anywhere. Some help would be really appreciated.
Path: MyResolutions.jsx
export default class MyResolutions extends Component {
getInitialState() {
return {
resolution: Resolutions.find().fetch(),
timeToComplete: Resolutions.find().fetch(),
};
}
render() {
return (
<form onSubmit={this.addResolutions.bind(this)}>
<input
type="text"
ref="resolution"
placeholder="Resolution title"
value={this.state.resolution} />
<input
type="text"
ref="timeToComplete"
placeholder="Time To Complete"
value={this.state.timeToComplete} />
<button type="submit">Submit</button>
</form>
)
}
}
This depends on the shape of your data coming from your initial state:
getInitialState() {
return {
resolution: Resolutions.find().fetch(),
timeToComplete: Resolutions.find().fetch(),
};
}
Assuming that this.state.resolution returns something like:
{
value: 'some string'
}
You would actually have to do something like this.state.resolution.value . So maybe try console.log(this.state.resolution) to get the shape of your data and then use dot notation to display the keys you need.
I'm using Meteor with React. I have a really simple goal, but i have tried a lot and can't solve it for myself. I will show you my attemps below.
I want to create a form for the Ingredients. At the first moment there is only one input (for only one ingredient) and 2 buttons: Add Ingredient and Submit.
class IngredientForm extends Component {
render() {
return(
<form onSubmit={this.handleSubmit.bind(this)}>
<input type="text"/>
{ this.renderOtherInputs() }
<input type="button" value="Add Ingredient" onClick={this.addIngredient.bind(this)}>
<input type="submit" value="Submit Form">
</form>
);
}
}
So when I click Submit then all the data goes to the collection. When I click Add Ingredient then the another text input appears (in the place where renderOtherInputs() ).
I know, that Meteor is reactive - so no need to render something directly. I should underlie on the reactive data storage.
And I know from the tutorials the only one way to render something - I should have an array (that was based on collection, which is always reactive) and then render something for each element of that array.
So I should have an array with number of elements = number of additional inputs. that is local, so I can't use Collection, let's use Reactive Var instead of it.
numOfIngredients = new ReactiveVar([]);
And when I click Add button - the new element should be pushed to this array:
addIngredient(e) {
e.preventDefault();
let newNumOfIngredients = numOfIngredients.get();
newNumOfIngredients.push('lalal');
numOfIngredients.set(newNumOfIngredients);
}
And after all I should render additional inputs (on the assumption of how many elements I have in the array):
renderOtherInputs() {
return numOfIngredients.get().map((elem) => {
return(
<input type="text"/>
);
}
}
The idea is: when I click Add button then new element is pushed to the ReactiveVar (newNumOfIngredients). In the html code I call this.renderOtherInputs(), which return html for the as many inputs as elements I have in my ReactiveVar (newNumOfIngredients). newNumOfIngredients is a reactive storage of data - so when I push element to it, all things that depends on it should re-render. I have no idea why that is not working and how to do this.
Thank you for your help.
Finally I got the solution. But why you guys don't help newbie in web? It is really simple question for experienced developers. I read that meteor and especially react have powerful communities, but...
the answer is: we should use state!
first let's define our state object in the constructor of react component:
constructor(props) {
super(props);
this.state = {
inputs: [],
}
}
then we need a function to render inputs underlying our state.inputs:
renderOtherInputs() {
return this.state.inputs.map( (each, index) => {
return (
<input key={ index } type="text" />
);
});
}
and to add an input:
addInput(e) {
e.preventDefault();
var temp = this.state.inputs;
temp.push('no matter');
this.setState({
inputs: temp,
});
}
p.s. and to delete each input:
deleteIngredient(e) {
e.preventDefault();
let index = e.target.getAttribute('id');
let temp = this.state.inputs;
delete temp[index];
this.setState({
inputs: temp,
});
}
I am using Meteor with React. Consider this simple component below. There is a local mini-Mongo collection CommentsCollection. The component will insert a row in it when componentWillMount will be called. getMeteorData will return the first record in the collection and we'll be able to modify the title. Problem: if I place my cursor at the start of the title and start typing, after the first character update the cursor will jump to the end of the string and the rest of my typing will be placed there. How do I work around this?
CommentsCollection = new Meteor.Collection(null); // Local mini-mongo collection
EventTestComponent = React.createClass({
mixins : [ReactMeteorData],
componentWillMount(){
CommentsCollection.insert({title:"test title", message:"some test message"});
},
getMeteorData(){
return {
comment: CommentsCollection.findOne()
};
},
handleTitleChange(e){
CommentsCollection.update({_id: this.data.comment._id}, {$set:{title: e.target.value}});
},
render(){
if(this.data.comment) {
return (
<div>
<input type="text" value={this.data.comment.title} onChange={this.handleTitleChange}/>
</div>
);
}else{
return <div>Loading...</div>
}
}
});
I came up with this solution right after I posted the question:
<input type="text"
defaultValue={this.data.comment.title}
onKeyUp={this.handleTitleChange}/>
So: change value to defaultValue, and onChange to onKeyUp. Works like a charm!