How to use Meteor.settings in a React Component - reactjs

I have a React component which is making an API call on init on the client side. I don't want to hard-code my API key (god forbid in the repo), and it's not much better to put it in Meteor.settings.public since that can just be looked up in the console. I want to keep it in Meteor.settings, but then it's invisible to the client. I've tried using a method, but although it appears to work on the server the method call returns undefined on the client.
On the server:
Meteor.methods({
getFileStackAPIKey: function () {
if (Meteor.settings.fileStackAPIKey) {
console.log(Meteor.settings.fileStackAPIKey) // returns: [fileStackAPIKey] correctly
return Meteor.settings.fileStackAPIKey
}
else {
return {message: "Configure Meteor.settings.fileStackAPIKey to connect to FileStack."}
}
}});
On the client:
console.log(Meteor.call('getFileStackAPIKey')); // returns: undefined
I tried to use ReactiveVar and again it set it on the server but was inaccessible on the client. I have the feeling that I'm missing something obvious. Specifically, what I'm trying to make work is FileStack. Their example code shows the API key hard-coded inline. As does the official FileStack React package. This just doesn't seem like a good idea.

It has to do with callbacks. The method result will be in the callback, so what I needed to do on the client was more like this:
Meteor.call('getFileStackAPIKey', (err, res) => {
console.log("FileStack API Key: " + res);
});
But because what I really wanted to do was pass it into the FileStack init (again, on the client side), so I needed to put the following in the constructor for the FileStack object:
// "this" is the FileStack object we're constructing
const fileStackObj = this;
Meteor.call('getFileStackAPIKey', (err, apiKey) => {
// here we're inside the callback, so we have the resulting API key
const client = filestack.init(apiKey, clientOptions);
// these are synchronous actions dependent on the existence of "client"
// that we could not do outside of the callback
fileStackObj.state = {
client,
picker: action === 'pick' ? client.picker({ ...actionOptions, onUploadDone: fileStackObj.onFinished }) : null,
};
fileStackObj.onFinished = fileStackObj.onFinished.bind(fileStackObj);
fileStackObj.onFail = fileStackObj.onFail.bind(fileStackObj);
});

Related

React Apollo Client - modify query data before it goes to cache

Is there a way to modify query response data before it is saved in the internal cache?
I'm using apollo hooks, but this question is relevant to any of front-end approaches using apollo client (HOC & Components as well).
const { data, updateQuery } = useQuery(QUERY, {
onBeforeDataGoesToCache: originalResponseData => {
// modify data before it is cached? Can I have something like this?
return modifiedData;
}
});
Obviously onBeforeDataGoesToCache does not exist, but that's exactly the behavior I'm looking for. There's an updateQuery function in the result, which basically does what is needed, but in the wrong time. I'm looking for something to work as a hook or a middleware inside the query mutation.
It sounds like you want Afterware which, much like Middleware that allows operations before the request is made, allows you to manipulate data in the response e.g.
const modifyDataLink = new ApolloLink((operation, forward) => {
return forward(operation).map(response => {
// Modify response.data...
return response;
});
});
// use with apollo-client
const link = modifyDataLink.concat(httpLink);

How to call the same api multiple times in Express Route?

I'm working on a Node app with Express. I'm chaining several http calls to data api's, each dependent on the previous req's responses.
It's all working except the last call. The last call needs to happen multiple times before the page should render.
Searching has turned up excellent examples of how to chain, but not make a call to the same API (or HTTP GET, data endpoint, etc.) with different params each time.
I'm trying to do something like this: Using a generator to call an API multiple times and only resolve when all requests are finished?
var getJSON = (options, fn) => {
.....
}
router.route("/")
.get((req, res) => {
var idArray = [];
var results = [];
getJSON({
.... send params here, (result) => {
//add response to results array
results.push(result);
//create var for data nodes containing needed id params for next call
let group = result.groupsList;
//get id key from each group, save to idArray
for(i=0;i<groups.length;i++){
idArray.push(groups[I].groupId);
}
//use id keys for params of next api call
dataCallback(idArray);
});
function dataCallback(myArray){
// number of ID's in myArray determine how many times this API call must be made
myArray.forEach(element => {
getJSON({
.... send params here, (result) => {
results.push(result);
});
// put render in callback so it will render when resolved
}, myRender());
};
function myRender() {
res.render("index", { data: results, section: 'home'});
}
})
I learned the problem with the above code.
You can call functions that are outside of the express route, but you can't have them inside the route.
You can't chain multiple data-dependent calls, not in the route.
Anything inside route.get or route.post should be about the data, paths, renders, etc.
This means either using an async library (which I found useless when trying to build a page from multiple data sources, with data dependent on the previous response), or having an additional js file that you call (from your web page) to get, handle and model your data like here: Using a generator to call an API multiple times and only resolve when all requests are finished You could also potentially put it in your app or index file, before the routes.
(It wasn't obvious to me where that code would go, at first. I tried putting it inside my router.post. Even though the documentation says "Methods", it didn't click for me that routes were methods. I hadn't really done more than very basic routes before, and never looked under the hood.)
I ended up going with a third option. I broke up the various API calls in my screen so that they are only called when the user clicks on something that will need more data, like an accordion or tab switch.
I used an XMLHttpRequest() from my web page to call my own front-end Node server, which then calls the third party API, then the front-end Node server responds with a render of my pug file using the data the API provided. I get html back for my screen to append.
In page:
callFEroutetoapi(_postdata, _route, function (_newdata){
putData(_newdata);
});
function putData(tData){
var _html = tData;
var _target = document.getElementById('c-playersTab');
applyHTML(_target, _html);
}
function callFEroutetoapi(data, path, fn){
//url is express route
var url = path;
var xhr = new XMLHttpRequest();
console.log('data coming into xhr request: ', data);
//xhr methods must be in this strange order or they don't run
xhr.onload = function(oEvent) {
if(xhr.readyState === xhr.DONE) {
//if success then send to callback function
if(xhr.status === 200) {
fn(xhr.response);
// ]console.log('server responded: ', xhr.response);
}
else {
console.log("Something Died");
console.log('xhr status: ', xhr.status);
}
}
}
xhr.onerror = function (){console.log('There was an error.', xhr.status);}
xhr.open("POST", url, true);
xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
xhr.send(JSON.stringify(data));
}
It adds an extra layer, but was necessary to show the latest, frequently changing data. It's also reusable which is better for a multiscreen web app. If there were fewer views (completely different screens and co-dependent datasets), a more centralized model.js file mentioned above would work better.

ReactJS where do I store the API URI?

Where would I store the API URI centrally in a ReactJS Application? The URI only changes between environments and should be easily configurable (i.e. through environment variables).
I have looked into this package and into the new Context API, but am unsure it's the best way to achieve this. I have also looked into dotenv, but I don't like that I would have to use process.env.REACT_APP_SERVICE_URI in every component that wants to access the API. What is the usual approach?
I am not using Redux.
I don't think you need an external dependency to do that.
I usually create simple module called api-client.js, which is responsible for calls to external API and defining endpoints.
In your case you might have:
import axios from 'axios' // some http client lib
const endpoint = process.env.REACT_APP_SERVICE_URI? process.env.REACT_APP_SERVICE_URI : 'https://foo.api.net/'
export default {
getAllProducts () {
return axios.get(endpoint + 'products').then(response => {
log.debug(`api client fetched ${response.data.length} items`)
return response.data
}).catch(err => {
log.error(err.message)
throw err
})
}
},
getProductById (id) {
...
},
}
You read process.env.REACT_APP_SERVICE_URI only once.
I like to put this module inside api directory (and any other API related stuff).

Can't use "this" in stomp client subscribe - React

I have my Spring-Boot service setup so I can send messages through websocket to my browser and it works.
//#MessageMapping
#RequestMapping(value = "/notify")
#SubscribeMapping("/notification")
#SendTo("/topic/notification")
public String sendNotification() throws Exception {
sendMessage();
return "Request to update Tanks has been sent!";
}
public void sendMessage() {
this.messagingTemplate.convertAndSend("/topic/notification", "IT WORKS");
}
Here's the console log from chrome:
<<< MESSAGE
destination:/topic/notification
content-type:text/plain;charset=UTF-8
subscription:sub-1519225601109-13
message-id:f2qodiqn-8
content-length:8
IT WORKS
I want to be able to receive a message from the service and update the state in react, so, that it refetches from the backend. This is what my client looks like:
var socket = new SockJS("http://localhost:6667/refresh");
var stompClient = Stomp.over(socket);
stompClient.connect({}, function(frame) {
console.log('connected: ' + frame);
stompClient.subscribe('/topic/notification', function(notification){
console.log(notification.body);
//this.showNotification(JSON.parse(notification.body).content);
//this.showNotification(notification.body);
})
}, function(err) {
console.log('err', err);
});
And the fetch in componentDidMount()
fetch(`http://localhost:6666/front/objects`)
.then(result=>result.json())
.then(fuelTanks=>this.setState({fuelTanks}))
.catch(function(err) {
console.log('Could not fetch: ' + err.message);
}
)
I can't use this.showNotification(notification.body), hence I can't set the state to be able to refetch my objects. I tried making methods outside the class but then I can't use anything from the main class.
Is there a way to make react run componentDidMount again, or better, just access the fetch method in my class when I get a message from spring through the websocket?
Like this:
componentDidMount(){
var socket = new SockJS("http://192.168.1.139:8610/refresh");
var stompClient = Stomp.over(socket);
stompClient.connect({}, function(frame) {
console.log('connected: ' + frame);
stompClient.subscribe('/topic/notification', function(notification){
refetchTanks(); // call fetch tanks -> can't use "this"
})
}, function(err) {
console.log('err', err);
});
Thanks!
I know, it is a bit old question, but since it pops every time when you search for stomp issue, i thought of answering it. The way to access this in callbacks is to bind callbacks with this first, then the whole of object can be accessed in the callback.
Example:
connectCallBack(){
this.setState({loading:false})
}
errorCallback=()=>{
}
componentDidMount() {
axios.post('http://localhost:8080/subscribe', null, { params: {
deviceId
}})
.then(response => response.status)
.catch(err => console.warn(err));
const socket = new SockJS('http://localhost:8080/test');
const stompClient = Stomp.over(socket);
//stompClient.connect();
stompClient.connect( {}, this.connectCallBack, this.errorCallback);
If see above code both callbacks can access this.
I tried everything to be able to use my class methods and the state in stompClient's .subscribe method. I was able to connect and reconnect if the service died, nevertheless it wasn't working.
I decided to use react-stomp, which worked. I could use a class method in onMessage=.... This is what my code looks like:
<SockJsClient
url = 'http://localhost:8610/refresh/'
topics={['/topic/notification']}
onConnect={console.log("Connection established!")}
onDisconnect={console.log("Disconnected!")}
onMessage={() => this.update()} <------ this method performs a new GET
request
debug= {true}
/>
I also had to send the message in a specific way on the server side, since I was getting a JSON error when sending a string.
this.messagingTemplate.send("/topic/notification", "{"text":"text"}");
<<< MESSAGE
destination:/topic/notification
content-type:text/plain;charset=UTF-8
subscription:sub-0
message-id:aaylfxl4-1
content-length:49
{
"text": "text"
}
It currently works, but I am curious if there are other, better solutions to this issue.
EDIT: a much better solution here! Use the code from the first post and create a variable before connect to be able to access this like this var self = this;, then just access is as self.update() after subscribe!

react js / superagent / file dropzone

I have a comments widget, in React js (jsx), and I'm using React Dropzone and uploading the dropped files to the server, using superagent.
I need to get the file object (containing my app's file id,etc) returned from my app, and associate them with the comment that the user will submit. I am attempting to assign the file objects to a state variable, 'attachments'. Because of the async nature of superagent, I think, I am actually populating my state variable with an empty array.
I have tried to use a callback, but got an 'undefined' error.
Here is the code:
onDrop: function (newFiles) {
newFiles.forEach((file)=>
{
this.setState({files: this.state.files.concat(file)});
})
var attachments = [];
var req = request.post('/attachments/create');
req.set('Accept', 'application/json');
newFiles.forEach((file)=> {
req.attach('img_attach', file);
req.field('filename', file.name);
req.field('itemType', 'comment');
req.field('itemId', false);
req.end(function(err,res){
var json = $.parseJSON(res.text);
attachments.push(json);
attIds.push(json.id);
});
});
attachments.forEach((file)=>
{
this.setState({
attachments:this.state.attachments.concat([file])});
});
},
Here is the callback attempt which returns "Cannot read property 'setState' of undefined":
function fileAttach(err,res)
{
var json = $.parseJSON(res.text);
this.setState({attachments:this.state.attachments.concat([json])});
}
For the callback, instead of this
req.end(function(err,res){
var json = $.parseJSON(res.text);
attachments.push(json);
attIds.push(json.id);
});
I use this
req.end(fileAttach);
So, one possibility is that I'm looking for a 'context' option, similar to jquery, that allows me to use 'this' in the callback.
So, you were on the right track for the first issue I see. You need to bind the context to that function. It's already been answered by LodeRunner28 in the comments, but you'd do:
req.end(fileAttach.bind(this))
If you're not familiar, Function.prototype.bind allows you to manually force a context variable for any function. It's incredibly handy, and it means you never have to rely on the library (eg. jQuery) to provide a context arg, you can just specify it yourself :)
The bigger issue I see is the way you're using SuperAgent. I believe you're actually sending a whole bunch of requests; calling .end triggers SuperAgent to make the request, and you're doing it inside the forEach loop.
I'm not super familiar with SuperAgent, but I believe you can just do:
newFiles.forEach( file => {
req.attach('img_attach', file);
req.field('filename', file.name);
req.field('itemType', 'comment');
req.field('itemId', false);
});
req.end(fileAttach.bind(this));

Resources