Get data from a reactive container - javascript

From the docs, I get that I need to use a reactive data container around my component to retrieve the currently logged in user (if one exists), but how do I get the data from that container?
import { Meteor } from 'meteor/meteor';
import React, { Component } from 'react';
import { createContainer } from 'meteor/react-meteor-data';
export default FooContainer = createContainer(() => {
return {
user: Meteor.user()
};
}, class FooComponent extends Component {
render() {
return (<div>{ /* this.data.user ??? */ }</div>);
}
});
How do I get the data returned from the container function inside my render method?

I see you returned a user key in your container, therefore your data will be accessible via your this.props.user prop in your component.
Just make sure you add something like this to your container:
export default FooContainer = createContainer(() => {
const subscription = Meteor.subscribe("userData");
subscription.ready() ? Session.set("dataReady", true) : Session.set("dataReady", false);
return {
user: Meteor.user()
};
}
and in your render method:
render() {
if(Session.get("dataReady")){
return (<div>{ /* this.data.user ??? */ }</div>);
}
}
Trust me, it will save you a lot of headache and a lot of errors in the future. This will make sure your data is 100% ready before it's called and rendered in your component.
Oh, also assuming you have autopublish removed, publish the particular user's data in order to subscribe to it like I showed above:
Meteor.publish("userData", function(){
return Meteor.users.find({_id: this.userId});
});
Just a heads up.

Related

Is there a way to use the functions inside a component or shared file in react?

I'm trying to access functions inside a class based component which can be used throughout the project. The reason I'm thinking class based is because these request/ functions require an init() method to be called before accessing such data every time. For example:
SharedSDKFile .js
import Facebook from 'facebook-sdk';
class SharedSDKFile extends Component {
constructor() {
Facebook.init({// init some stuff})
}
async user() {
return Facebook.getUser()
}
render() {
return(// ????????????????)
}
}
// *****************************************************
Dashboard.js
// *****************************************************
import Facebook from '../{path}/SharedSDKFile'
const dashboard = () => {
let [person,setPerson] = useState()
// cool function to get and set Users
let user = getUser();
setPerson(user)
// End of cool function
}
I even tried structuring it with a different approach just exporting functions
SharedSDKFile.js
async init() {
// init stuff
}
export const getUser = async(data) => {
init()
// get user
}
// *****************************************************
Dashboard.js
// *****************************************************
import {getUser}from '../{path}/SharedSDKFile'
const dashboard = () => {
let [person,setPerson] = useState()
// cool function to get and set Users
let user = getUser();
setPerson(user)
// End of cool function
}
While a file that exports your function works, the state disappears on reload/ refresh.
Perhaps there is a better solution to this and I'm overthinking it. I have considered redux or localstate, but I will have several functions inside the sharedSDKFile.js which will require several action and reducers...
I am trying to prevent invoking multiple init() and redundancy if I am to import, for example, the FacebookSDK in every file that needs it.
I like using a context singleton approach to isolate external services that can be used app-wide, which may or may not suit what you want to do. It uses context providers/consumers rather than things like Redux.
This isn't the complete picture but hopefully it provides some idea of how the approach might work for you:
FacebookContext.js (or AWSContext.js, or... any specific service)
import React, { Component, createContext } from "react";
import Facebook from "facebook-sdk";
export const FacebookContext = createContext({});
class FacebookProvider extends Component {
state = {
user: null,
// whatever else you need to expose to calling components, like
// lastLoggedIn: null,
// verified: false,
// ...
};
componentDidMount() {
Facebook.init({
// init some stuff
})
}
setUser = user => {
this.setState({
user
});
}
// whatever other methods/data you want this class to expose
render() {
return (
<FacebookContext.Provider
value={{
user: this.state.user // available with FacebookContext.user
// other state values
//
setUser: this.setUser // available with FacebookContext.setUser
// other class methods
}}
>
{this.props.children}
</FacebookContext.Provider>
);
}
}
export default FacebookProvider;
Add the Provider to your top-line app file, something like this in e.g. App.js:
import FacebookProvider from "/path/to/FacebookContext";
// ...
class App extends React.Component {
render() {
return (
<FacebookProvider>
{yourAppRenderStuff}
</FacebookProvider>
);
}
}
Add the Consumer to any calling components:
import { FacebookContext } from "/path/to/FacebookContext";
class SomethingComponent extends Component {
componentDidMount() {
const { facebookContext } = this.props;
facebookContext.setUser(`some user`);// available in other components
console.log(facebookContext.user);// broadcasted to other components
}
render() {
return (
<></>
);
}
}
const Something = () => (
<FacebookContext.Consumer>
{facebookContext => (
<SomethingComponent facebookContext={facebookContext} />
)}
</FacebookContext.Consumer>
);
export default Something;

reactjs check for data update

I have GUser class, in my project to store data from GitHub-api and process them :
import axios from "axios";
export default class GUser {
constructor(userName) {
this.userName=userName;
this.getUserData(this,userName);
}
getUserData(that, userName) {
if(that.data===undefined)
{
axios.get('https://api.github.com/users/'.concat(userName))
.then(function (response) {
that.data = response.data;
console.log(that);
return that.data;
})
.catch(function (error) {
console.log(userName+ " got error " +error);
});
}
else{return that.data;}
}
}
I'm trying to sync pages, so in my TestRender I wrote :
import React from 'react';
import GUser from "../model/GUser";
class TestRender extends React.Component{
userName='maifeeulasad';
user=new GUser(this.userName);
componentWillMount() {
try {
console.log(this.user.getUserData());
}
catch (e) {console.log(e);}
}
componentDidMount() {
try {
console.log(this.user.getUserData());
}
catch (e) {console.log(e);}
}
render() {
return(
<div>
</div>
);
}
}
export default TestRender;
My target is to console log the data, as soon as it gets received.
How can I do this ?
Previously I used componentWillMount only, when I loaded data from the same script. It's currently giving me TypeError: Cannot read property 'data' of undefined, maybe because of I haven't specified data as state in GUser, but it gets created through time right ?
Looks like you are using object's attribute to store the state's component, that isn't good, because React will not to re-render the component when these values are updated.Every instance of TestRender should have the same value of userName and user as attribute? These values are always the same? If not, use useState for that.
Also, componentDidMount and componentWillMount will be called only when the component is being mounted, and at this moment you didn't finished the fetch, so you don't have yet the value to print. It's an async problem.
To handle with async challenges, I recommend to use useEffect.

Modify nested state with Redux Reducer

I run into a problem that is litterally blowing my mind.
I'm developing my web application using React and Redux, my application use a system of notification implemented with Firebase.
Every notification is structured as below:
var notification = {
from_user:{
name: 'John',
surname: 'Doe'
},
payload:{
message:'Lorem ipsum ...'
}
seen:false,
timestamp:1569883567
}
After fetched, notification is send to notificationReducer with:
dispatch({type:'FETCH_NOTIFICATION_OK',payload:notification})
And so far everything is ok.
My notificationReducer is structured as below:
const INITIAL_STATE = {
loading:false,
notification:{}
}
const notificationReducer = (state=INITIAL_STATE,action)=>{
switch(action.type){
case 'FETCHING_NOTIFICATION':
return {...state,loading:true}
case 'FETCH_NOTIFICATION_OK':
return {...state,loading:false,notification:action.payload} // I THINK PROBLEM IS HERE
default:
return state
}
}
export default notificationReducer;
The problem is that, when I pass state props to my presentational component, notification object is empty
My presentational component is reported below
import React from 'react';
import {getNotification} from '../actions/actioncreators';
import {connect} from 'react-redux';
class NotificationDetail extends React.Component {
componentWillMount(){
this.props.fetch_notification('9028aff78d78x7hfk');
console.log(this.props.notification) // IT PRINTS: {}
}
render(){
return(
<div>
'TODO'
</div>
)
}
}
const mapStateToProps = state =>{
return {
is_loading:state.notificationReducer.loading,
notification:state.notificationReducer.notification
}
}
const mapDispatchToProps = dispatch =>{
return {
fetch_notification: (id_notification)=>dispatch(getNotification(id_notification))
}
}
export default connect(mapStateToProps,mapDispatchToProps)(NotificationDetail)
Any suggestion ?
EDIT: In my reducer I tried to print the new state. I succesfully got this:
But, Anyway In my presentational component I got an empty object
I think the dispatch call hasn't fired yet. Try executing the below
componentDidMount() {
this.props.fetch_notification();
}
render() {
console.log(this.props.notification); // It should print an output here if your xhr/ajax/axios call is correct
}
Also, using componentWillMount is UNSAFE (according to the ReactJS current documentation). Avoid using this lifecycle in the future.

Fetch data only once per React component

I have a simple component that fetches data and only then displays it:
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
loaded: false
stuff: null
};
}
componentDidMount() {
// load stuff
fetch( { path: '/load/stuff' } ).then( stuff => {
this.setState({
loaded: true,
stuff: stuff
});
} );
}
render() {
if ( !this.state.loaded ) {
// not loaded yet
return false;
}
// display component based on loaded stuff
return (
<SomeControl>
{ this.state.stuff.map( ( item, index ) =>
<h1>items with stuff</h1>
) }
</SomeControl>
);
}
}
Each instance of MyComponent loads the same data from the same URL and I need to somehow store it to avoid duplicate requests to the server.
For example, if I have 10 MyComponent on page - there should be just one request (1 fetch).
My question is what's the correct way to store such data? Should I use static variable? Or I need to use two different components?
Thanks for advice!
For people trying to figure it out using functional component.
If you only want to fetch the data on mount then you can add an empty array as attribute to useEffect
So it would be :
useEffect( () => { yourFetch and set }, []) //Empty array for deps.
You should rather consider using state management library like redux, where you can store all the application state and the components who need data can subscribe to. You can call fetch just one time maybe in the root component of the app and all 10 instances of your component can subscribe to state.
If you want to avoid using redux or some kind of state management library, you can import a file which does the fetching for you. Something along these lines. Essentially the cache is stored within the fetcher.js file. When you import the file, it's not actually imported as separate code every time, so the cache variable is consistent between imports. On the first request, the cache is set to the Promise; on followup requests the Promise is just returned.
// fetcher.js
let cache = null;
export default function makeRequest() {
if (!cache) {
cache = fetch({
path: '/load/stuff'
});
}
return cache;
}
// index.js
import fetcher from './fetcher.js';
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
loaded: false
stuff: null
};
}
componentDidMount() {
// load stuff
fetcher().then( stuff => {
this.setState({
loaded: true,
stuff: stuff
});
} );
}
render() {
if ( !this.state.loaded ) {
// not loaded yet
return false;
}
// display component based on loaded stuff
return (
<SomeControl>
{ this.state.stuff.map( ( item, index ) =>
<h1>items with stuff</h1>
) }
</SomeControl>
);
}
}
You can use something like the following code to join active requests into one promise:
const f = (cache) => (o) => {
const cached = cache.get(o.path);
if (cached) {
return cached;
}
const p = fetch(o.path).then((result) => {
cache.delete(o.path);
return result;
});
cache.set(o.path, p);
return p;
};
export default f(new Map());//use Map as caching
If you want to simulate the single fetch call with using react only. Then You can use Provider Consumer API from react context API. There you can make only one api call in provider and can use the data in your components.
const YourContext = React.createContext({});//instead of blacnk object you can have array also depending on your data type of response
const { Provider, Consumer } = YourContext
class ProviderComponent extends React.Component {
componentDidMount() {
//make your api call here and and set the value in state
fetch("your/url").then((res) => {
this.setState({
value: res,
})
})
}
render() {
<Provider value={this.state.value}>
{this.props.children}
</Provider>
}
}
export {
Provider,
Consumer,
}
At some top level you can wrap your Page component inside Provider. Like this
<Provider>
<YourParentComponent />
</Provider>
In your components where you want to use your data. You can something like this kind of setup
import { Consumer } from "path to the file having definition of provider and consumer"
<Consumer>
{stuff => <SomeControl>
{ stuff.map( ( item, index ) =>
<h1>items with stuff</h1>
) }
</SomeControl>
}
</Consumer>
The more convenient way is to use some kind of state manager like redux or mobx. You can explore those options also. You can read about Contexts here
link to context react website
Note: This is psuedo code. for exact implementation , refer the link
mentioned above
If your use case suggests that you may have 10 of these components on the page, then I think your second option is the answer - two components. One component for fetching data and rendering children based on the data, and the second component to receive data and render it.
This is the basis for “smart” and “dumb” components. Smart components know how to fetch data and perform operations with those data, while dumb components simply render data given to them. It seems to me that the component you’ve specified above is too smart for its own good.

React Meteor Data - Component Not Re-Rendering With New Data While Using withTracker

I'm using Meteor, React, react-meteor-data, and React Router. I am linking to a page and adding an /:id, which I am then trying to use to query the database and build out the component with the returned data.
The problem is the initial query returns an empty array. I can then see in my render method and componentWillReceiveProps that it returns the data I expect a moment later. The problem is that my component does not re-render. I am using withTracker because I want the component to update and re-render every time the database changes in the targeted Collection.
Here is the React code on the client-side:
export default withTracker((props) => {
const handle = Meteor.subscribe('game', props.match.params.id);
return {
listLoading: !handle.ready(),
game: ActiveGames.find({ _id: props.match.params.id}).fetch(),
};
})(Game);
And here is the publication in 'imports/api/activeGames.js':
import { Meteor } from 'meteor/meteor';
import { Mongo } from 'meteor/mongo';
import { check } from 'meteor/check';
export const ActiveGames = new Mongo.Collection('activeGames');
if (Meteor.isServer) {
Meteor.publish('game', function (id) {
check(id, String);
return ActiveGames.find({ _id: id });
});
Meteor.publish('activeGames', function activeGamesPublication() {
return ActiveGames.find();
});
}
Here is a screenshot of the output I'm getting, with console logs to track the pertinent life cycle methods.
I believe it's a simple matter of doing something with the listLoading prop.
For instance, your Game component could be:
class Game extends Component {
constructor(props){
super(props);
}
renderGames = () => {
return this.props.games.map(g => {
return (
<li>{g.title}</li>
)
})
}
render() {
return (
<ul>
{this.props.listLoading ? <span>Loading...</span> : this.renderGames()}
</ul>
);
}
}
export default withTracker((props) => {
const handle = Meteor.subscribe('game', props.match.params.id);
return {
listLoading: !handle.ready(),
game: ActiveGames.find({ _id: props.match.params.id}).fetch(),
};
})(Game);
This is how I handle the time between subscribing and receiving data.
Hope it helps, though I'm sure you found a solution by now ;)

Categories