Subscription on child component not working in ReactJS - reactjs

I created a publication(on server) and subscription (on client) in MEteor + React application. My problem is, I can't receive the array returned by the publication. I used console.log in publication and it logs the subscription key and option. I check also documents before returning and it is correct. But in client, still cant get array of documents.
Here is my code for componentDidMount,
Meteor.subscribe("messages",{},{},function(err){
console.log("err",err);
console.log(Messages.find().fetch());
});
console.log here are not called.
Here is my code for publication,
if (Meteor.isServer) {
Meteor.publish('messages', function (key,option) {
console.log(key);
return Messages.find(key,option);
});
}
console.log here is working and also i checked the documents before returning and it is correct.
I used also react-router in my application.I add route for my parent component. The problem i have is in child component.
What is wrong with my code? or how to solve this problem?
I used msavin:mongol and still having 0 for the count of documents.

I think Meteor Chef is promoting a nice folder structure. If you happen to use that, just to make sure your server side actions reach the server, meaning they are imported to the server. Just sharing a screen from one of my projects:
register-api.js is further down imported in main.js in my server folder.
Secondly, what if you do your console.log(Messages.find().fetch()) outside the subscription. You put it into the callback function and should not really be there. What you normally do is to pass the result of Messages.find().fetch() to props in the component's container, so the result needs to be captured outside of your subscription.

Related

The role of storybook when testing of React application is conducted

I am new to React and recently I found out there is the tool called storybook which basically helps to test our React components in isolated manner. However, I also found out that storybook is used when we test our React application, that is, there happens the comparison of snapshots. The question is what is the reason for testing the difference of snapshots? I mean, is it like we create a certain component and create a snapshot for it to ensure that other developers do not change that component accidentally, that is, snapshot in storybook helps us ensure there is no accidental change of components that we created. Is it true?
You create snapshot tests to ensure that your Components renders correctly given a certain input.
This is a way to make sure your code still behaves the way it was intended to. It's to ensure you don't accidentally break something in your code base, but also to alert you when anything changes.
An example:
Say I have a component called ProfileLink that generates a link to a user account:
function ProfileLink(props) {
return <a href ={get_url(user.id)}>{props.user.profileName}</a>;
}
and somewhere else I have a function called get_url:
function get_url(id) {
return "www.example.com/user/" + id;
}
Now, to ensure that my ProfileLink is always working, I can create snapshot, and everytime my tests run, the snapshot will be compared to the current rendered component.
Say someone were to change the get_url function, they might not be aware that it's used in ProfileLink, but as soon as the tests are run, you'll know that something has changed in the ProfileLink component as well.

How to update match.params?

The react app has search page. There are input.
The path is 'search/:query', and by default you see zero results.
If you go to 'search/star%20wars' you will see some results. In componentDidMount() I added if statement to load result if match.params.query is not null.
If I type into search input Spider Man and click submit - I trigger a search and show results. But if you reload page - you will see the result about Star Wars. So how update match.params.query? Or may be there other solution of fix this.
You need to update the history object as well.
What you are doing is altering the history object available to you and calculating the results based on that object. But when you will refresh the page it still holds the original history object.
One way of doing it, you need to push or replace a new route in the history.
Because evert search page is a new page, so if you want the previous pages to stay preserved you should use history.push otherwise history.replace
Implement it like this:
var routeObj = {
pathname: samePath,
state: sameState,
query: newQuery
}
//push it in your history using which ever routing library you are using.
//For Example:
router.history.replace(routeObj);
Note: Do not worry about rendering speed on changing the history. React is smart enough to handle that. Basically whenever you will push a route whose component is already mounted it will not unmount and remount the same component again, rather it will just change the props and will re render it.
The callback for this case will be => componentWillReceiveProps
#misha-from-lviv The way I see your problem statement is that you have two source of truth on is the query params, using which you should update your state, and the other is the default state which is populated from the default value of your filters.
As #Akash Bhandwalkar suggested, you do need to update the route in using the History API. But also you also a need a top-level orchestrator for your application state, which will allow you to read and write to the history api ( change your route ) and also do an XHR / fetch for you to get the results.
How I'd approach this is that I'd start with a Parent component, namely FiltersContainer , which actually does this orchestration to read and write to the url. This Container would have all the side-effect knowledge for fetching and updating the routes ( error handling included ). Now the all the child components ( filters and search results maybe ) will just read the state thus orchestrated and re-render.
Hope this guides your thinking. Do revert here if you need further guidance. 😇
Cheers! 🍻

How to make the UI respond to subscriptions in Apollo

I'm using react-apollo as a client to communicate with a GraphQL server that I created. I managed to successfully get subscriptions working with the data.subscribeToMore() function as detailed in the Apollo documentation and the up-to-date data shows up when I run my web application inside of two windows. What I'm trying to do is make it so that an notification alert gets displayed when another client changes data that I'm currently looking at so that I can tell that something changed in case I wasn't paying attention? What would be the correct way of doing this?
update method?
updateQueries method?
The dataFromObjectId and refetchQueries fields did not seem relevant for what I was trying to do. Since I'm using redux, is there a way I could dispatch actions directly from my subscription? Would notification alerts be something that I have to use client.subscribe() with?
Assuming you're using the latest version of Apollo, you should be handing the component a prop named "updateQuery" that contains logic for handling the data.
http://dev.apollodata.com/react/subscriptions.html#subscribe-to-more
This section goes over what you need to do, but essentially your "updateQuery" function should do the following:
Take in an object of structure argumentName.data which contains the new information.
Adds the new object to the results by creating a new object.
Returns the new results object.
so it might look something like this:
(prev, { subscriptionData }) => {
if (!subscription.data) {
//If no new data, return old results
return prev;
}
var newResults = Object.assign(
{},
prev,
queryName: { [subscriptionData.data, ...prev[queryName]] }
);
return newResults;

Meteor handle.ready() in render() not triggering rerender of component

I have the following code in my render method:
render() {
return (
<div>
{this.props.spatulaReady.ready() ? <p>{this.props.spatula.name}</p> : <p>loading spatula</p>}
</div>
)
}
Which according to my understanding, checks if the subscriptionhandle is ready (data is there) and displays it. If no data is available, it should display a simple loading message. However, when I first load the page this snippet is on, it get's stuck on the loading part. On a page reload the data (usually) displays fine.
If I check the spatulaReady.ready() when the page first loads and while the display is stuck on 'loading spatula', and the data that should be there, the handle reports as ready and the data is there like it is supposed to be. If I refresh the page it all displays fine as well. The problem is, this way of checking for data and rendering if it has arrived has worked fine for me in the past. Is it because the render method is not reactive? Because handle.ready() should be reactive.
What makes it even weirder is that it sometimes DOES correctly display the data on page load, seemingly at random.
CreateContainer code:
export default createContainer(props => {
return {
user: Meteor.user(),
spatulaReady: Meteor.subscribe('spatula.byId', props.deviceId),
spatula: SpatulaCollection.findOne()
}
}, SpatulaConfig)
Publication code:
Meteor.publish('spatula.byId', function(deviceId) {
const ERROR_MESSAGE = 'Error while obtaining spatula by id'
if (!this.userId) //Check for login
throw new Meteor.Error('Subscription denied!')
const spatula = SpatulaCollection.findOne({_id: deviceId})
if(!spatula) //No spatula by this id
throw new Meteor.Error(403, ERROR_MESSAGE)
if(spatula.ownedBy != this.userId) //Spatula does not belong to this user
throw new Meteor.Error(403, ERROR_MESSAGE)
return SpatulaCollection.find({_id: deviceId})
})
I know I'm missing a piece of the puzzle here, but I've been unsuccessful at finding it. If you don't know the solution to my specific problem, pointing me in the right direction with another way of waiting for the data to arrive before displaying it is also greatly appreciated.
EDIT: After doing some trial-and-error and reading various other posts somewhat related to my project, I figured out the solution:
export default createContainer(props => {
const sHandle= Meteor.subscribe('spatula.byId', props.deviceId)
return {
user: Meteor.user(),
spatulaReady: sHandle.ready(),
spatula: SpatulaCollection.findOne()
}
}, SpatulaConfig)
It still makes no sense to me that moving the ready() call to create container fixed all my problems though.
As you figured out, moving the .ready() call to createContainer fixes the problem. This is because Meteor reactivity only works when you call a reactive data source (a reactive function), such as collection.find() or subscriptionHandle.ready() within a reactive context, such as Tracker.autorun or createContainer. Functions within the React component, including render, are not reactive contexts from Meteor's perspective.
Note that React and Meteor reactivity are two different things. React's reactivity works simply so that whenever a component's props or state change, it's render function is re-run. It does not understand anything about Meteor's reactive data sources. Since createContainer (that is re-run by Meteor reactivity when reactive data sources in it change) simply passes props to the underlying component, the component is re-rendered by React when the props given from createContainer change.

How to deal with query params in react + react-router + flux

I'm trying to replace a Backbone.Marionette App to React and am facing difficulty thinking about query params. I think I'm missing a really simple peace in understanding this pattern so I apologize if this question is totally nonsense. I would appreciate any support or just pointing me to some direction that I can google more specifically.
There's a /users page which lists users and you can filter the users via search bar. So if you want to filter the users which contain 'joe' in their username, I would make a request to the server with query params like /users?username=joe. In addition I am able to paginate by adding a page parameter, too (/users?username=joe&page=1).
If I only think about the functionality, the flow would probably be
The Client inserts joe to the input element and clicks Search.
Clicking the Search button fires an Action (like Action.getUser).
The Action makes a request to the server and receives the results
The Dispatcher dispatches a function with the results payload to whomever (usually the Store) is interested in the Action.
The Store's state changes with the new result received by the Action
The View (Component) re-renders by listening to the Store's change.
and it works as expected. However, I would like the Client to be able to bookmark the current filtered result and be able to come back to the same page some time later. This means I will need somewhere to save explicit information about the search term the Client made, which is usually the url (am I right?). So I will need to update the url with query parameters to save the search term (/users?username=joe&page=1).
What I'm confused is where and when to update the url? What I can come up with right now are the 2 options below - and they don't seem to be clean at all.
Option 1
The Client inserts joe to the input element and clicks Search.
Clicking the Search button fires a transition of the ReactRouter with the new query params (/users?username=joe&page=1).
The View (Component) receives the new params via this.props.params and this.props.query.
The View (Component) fires an Action like Action.getUser depending on the query params it receives - in this case username=joe&page=1.
after this, it is the same as above
Option 2 (only 6 is different from what I explained above)
The Client inserts joe to the input element and clicks Search.
Clicking the Search button fires an Action (like Action.getUser).
The Action makes a request to the server and receives the results
The Dispatcher dispatches a function with the results payload to whomever (usually the Store) is interested in the Action.
The Store's state changes with the new result received by the Action
The View (Component) re-renders by listening to the Store's change. And somehow (I don't know how, yet) updates its url depending on its props (like this.props.searchusername, and this.props.searchpage)
What is the best practice on handling query params? (or this may not be specific to query params)
Am I completely misunderstanding the design pattern or architecture? Thanks in advance for any support.
Some articles I've read
Any way to get current params or current query from router (outside of component)?
Async data and Flux stores
Make it easier to add query parameters
React Router and Arbitrary Query Params: Page Refreshes Unintentionally on Load?
Add default params?
I would consider best practice to be the submit button only setting the location query (username). The rest should be taken care by the main react component that is assigned as router component. By this, you can be sure that anytime one revisits or shares the url, they can get the same results. And this is very generic too.
Something like this:
let myQuery = this.props.location.query;
if (myQuery.username) {
let theUser = myQuery.username;
this.setState({
userName = myQuery.username
});
} else {
this.setState({
userName = false //Show All
});
}
And then use this state "userName" to send to the server to search with. By this way, you will not need to iterate the code of the component that takes care of listing users since server already sends the relevant data.
In my experience with using location queries in React, I have been very content with their reactivity cycles and performance. I'd highly recommend keeping every different app state in relevance with the url.
Not entirely sure what you mean by
this means I will need to update the url to save the information (/users?username=joe&page=1).
You will probably have a similar structure to this.
TopContainer.jsx
-- Users.jsx
-- a list of User.jsx
Usually TopContainer will watch all the stores and if anything changed, pass it down to users.jsx. That way in Users.jsx, you can simply render this.props.users without worrying about any reRendering.
The search users actions usually happens in TopContainer's componentWillMount event, and you the page will listen to UserStore. That's a good place to throw in any query params. Something like this would work
componentWillUnmount() {
let searchTerm = router.getCurrentQuery().searchTerm;
UserActions.searchUsers(searchTerm)
},
The page doesn't really care if the url has a query params or not, it just dumbly shows whatever in the user store.
Then when the search finishes, Users.jsx will be reloaded and show the correct results

Resources