Strategy for modifying a route parameter dynamically within the app - relayjs

I'm developing a simple app that fetches the weekly lunch menu from a server. The query looks something like:
query {
lunchMenu(date: $date) {
id
name
etc.
}
}
I've defined a Route like so:
class LunchRoute extends Relay.Route {
static queries = {
lunchMenu: () => Relay.QL`
query { lunchMenu(date: $date) }
`,
};
static paramDefinitions = {
date: {required: false},
};
static routeName = 'LunchRoute';
}
I want the user to be able to select the date within the app via a datepicker and I've developed a LunchBrowser component which does that and renders the lunch menu. In index.js I have:
const lunchRoute = new LunchRoute({date: new Date()});
ReactDOM.render(
<Relay.RootContainer
Component={LunchBrowser}
route={lunchRoute}
/>,
document.getElementById('root')
);
The question is: how do I communicate a change in the date selection (from the LunchBrowser component) up one level so that I can modify the 'date' parameter in the Route and re-render?
I suppose I could have the LunchBrowser fire off some window event that is caught in index.js that does this, but this seems very wrong in that communication is flowing "up" from a lower-level component outside of React.
Or should I redefine my schema such that lunchMenu(date: $date) is pushed down one level? As in:
query {
lunch {
lunchMenu(date: $date) {
id
name
etc.
}
}
}
in which case lunch(date: $date) can be pushed down into LunchBrowserContainer's fragment and I no longer have to worry about dynamically modifying the Route's parameter.

Related

How to best access data from QueryRenderer in a parent component in Relay Modern?

As you can see from the picture below, I'm rendering the popover using a react-relay QueryRenderer since the request is slow, and I do not want the rest of the page to wait for events to be fetched.
My problem is that in the navigation I have a button to show/hide the popover. That button should only be rendered when events has loaded, and the button also needs to show a count of how many events there is.
So my question is how to pass events data up from QueryRenderer (popover) to a parent component (toggle button)?
My first idea was to reuse my QueryRenderer for events and pass in dataFrom={'STORE_ONLY'}, to avoid a new HTTP request and use the cache instead, but unfortunately 'STORE_ONLY' is not an option... YET...
From looking at https://github.com/relay-tools/relay-hooks/issues/5 it seems like store-only will be supported by useQuery in the future, so is that the recommended solution to go about it, or how is the recommended way? Surely facebook, and many other applications, must have had this need frequently?
You can achieve redux-like relay store with custom handlers and local schema.
I'll be guessing what your queries, components and fields might be named like so don't forget to change it to correct values
Somewhere in project's src folder create a file ClientState.client.graphql to extend your root query type with new field for client state:
// ClientState.client.graphql
type ClientState {
showToggleButton: Boolean!
eventsCount: Int
}
extend type Query {
clientState: ClientState!
}
this will allow you to wrap Toggle button with fragment like this:
fragment ToggleButton_query on Query {
clientState {
showToggleButton
eventsCount
}
}
and spread this fragment in parent query (probably AppQuery)
Then in your second query, where you'll be fetching events, add #__clientField directive, to define custom handle for that field:
query EventModal {
events #__clientField(handle: "eventsData") {
totalCount
}
}
Create EventsDataHandler for handle eventsData:
// EventsDataHandler.js
// update method will be called every time when field with `#__clientField(handle: "eventsData")` is fetched
const EventsDataHandler = {
update (store, payload) {
const record = store.get(payload.dataID)
if (!record) {
return
}
// get "events" from record
const events = record.getLinkedRecord(payload.fieldKey)
// get events count and set client state values
const eventsCount = events.getValue('totalCount')
const clientState = store.getRoot().getLinkedRecord('clientState')
clientState.setValue(eventsCount, 'eventsCount')
clientState.setValue(true, 'showToggleButton')
// link "events" to record, so the "events" field in EventModal is not undefined
record.setLinkedRecord(events, payload.handleKey)
}
}
export default EventsDataHandler
Last thing to do is to assign custom (and default) handlers to environment and create init store values:
// environment.js
import { commitLocalUpdate, ConnectionHandler, Environment, RecordSource, Store, ViewerHandler } from 'relay-runtime'
import EventsDataHandler from './EventsDataHandler'
// ...
const handlerProvider = handle => {
switch (handle) {
case 'connection':
return ConnectionHandler
case 'viewer':
return ViewerHandler
case 'eventsData':
return EventsDataHandler
default:
throw new Error(`Handler for ${handle} not found.`)
}
}
const environment = new Environment({
network,
store,
handlerProvider
})
// set init client state values
commitLocalUpdate(environment, store => {
const FIELD_KEY = 'clientState'
const TYPENAME = 'ClientState'
const dataID = `client:${FIELD_KEY}`
const record = store.create(dataID, TYPENAME)
record.setValue(false, 'showToggleButton')
// prevent relay from removing client state
environment.retain({
dataID,
variables: {},
node: { selections: [] }
})
store.getRoot().setLinkedRecord(record, FIELD_KEY)
})

Relay query that that depends on data from a Relay query

I'm trying to perform a Relay query that that depends on data from another relay query
Assuming this runs under a url like /job/{jobID}
React
render() {
const job = this.props.job
return(
<h1>{job.name}</h1>
<TasksOutstanding
project={job.project}
from={job.startDate}
to={job.finishDate} />
)
}
Relay
fragments: {
job: () => Relay.QL`
fragment on Job {
name
startDate
finishDate
project {
${TasksOutstanding.getFragment('project')}
}
}
`,
So I need to get startDate and finishDate into the fragment, something like ${TasksOutstanding.getFragment('project',{from, to})}
But these values (from to) are unknown on the initial fetch ( all I have then is the jobID)
How are people dealing with this? Should I just execute a second request on component did mount once I have the startDate and finishDate values ?
You need to create field with arguments, this is GraqhQL feature and you should be able to do it with your tool for modeling the schema.
Relay's variables also will be useful. They will solve the problem that you don't know them on initial fetch.
So declare project field with from and to arguments, query the field using arguments are return appropriate data for project field. Your container should look like this:
initialVariables: {
from: null,
to: null
},
fragments: {
job: () => Relay.QL`
fragment on Job {
name
startDate
finishDate
project(from: $from, to: $to) {
${TasksOutstanding.getFragment('project')}
}
}
`,
}
Then during the application life, you can set your variables using setVariables to job.startDate and job.finishDate and have proper project fetched.

ajax request php class function

i have content listed in a div and i have a dropdown with various options to order and filter that content.
I'm using ajax to filter/order that content and is working but i use other php page with the content i want on the div that has the content, like this
function order(str){
$.post("order_products.php",
{
q: str,
},
function(data, status){
document.getElementById("txtHint").innerHTML = data;
});
}
What i wanted was to instead of putting the code (data) to change in another page just for that, i could put that code inside a class php function that i have.
<?php
class products{
function list(){
blablabla
}
That way i would "save space" and organize everything, considering that i have many other things to order/filter but i don't know to to make the ajax request to that function, or if it's possible without having a page in between and then get the response from the function and put it on the div.
You can do this using Laravel by setting up a route to a function that will do the ordering. Please note I've made a lot of assumptions in the following answer as I can't see all your code and have made it quite general, please adjust the code to your project or provide more details of your code if you don't understand the answer fully.
routes.php
Route::post('products/order', [
'as' => 'products.order',
'uses' => 'ProductsController#orderProducts'
]);
Your view (assuming you're using blade)
$txtHint = $('#txtHint'); // grab the reference to txtHint dropdown
$.post( '{{ route("products.order") }}', // hit our route
{
q: str,
},
function(data, status){
$txtHint.empty(); // clear the dropdown
// loop through the data and assign each element to the dropdown
$.each(data, function(value, key) {
$txtHint.append($("<option></option>")
.attr("value", value)
.text(key));
});
});
ProductsController.php
public function orderProducts()
{
$orderBy = \Input::get('q');
return \Products::lists('name', 'id')->orderBy($orderBy);
}
For outside of a framework just change the url to your php file and add in a data attribute for the method you require to be fired from the file.
$.post( 'products.php', // hit our route
{
action: 'order',
q: str,
},
...
Then in products.php you'd do something like this
if(isset($_POST['action']) && !empty($_POST['action'])) {
$action = $_POST['action'];
switch($action) {
case 'order' : order();break;
case 'otherFunction' : otherFunction();break;
}
}
function order()
{
// order logic here
// get $_POST['q']
// do your ordering
// return data as json
}
See here for similar question: using jquery $.ajax to call a PHP function

Refreshing lookups in SPA with Breeze.js when value has changed

I have an application that records a transaction and the user can pick the category from a drop down.
Categories are loaded up at application startup as they are "mostly" static / rarely going to change.
So, in my datacontext.js I do the usual and prime my data;
var primeData = function () {
var promise = Q.all([
getLookups(),
getBankAccountPartials(null, true)])
.then(applyValidators);
return promise.then(success);
function success() {
datacontext.lookups = {
categories: getLocal('Categories', 'name', true),
transactiontypes: getLocal('TransactionTypes', 'name', true),
payees: getLocal('Payees', 'name', true)
};
log('Primed data', datacontext.lookups);
}
function applyValidators() {
model.applyBankAccountValidators(manager.metadataStore);
}
};
function getLookups() {
return EntityQuery.from('Lookups')
.using(manager).execute()
.then(processLookups)
.fail(queryFailed);
}
Now, occasionally in an Admin screen the user can edit and add a category.
In the categoryadd.js viewmodel my save code looks something like this (extract shown);
save = function () {
isSaving(true);
datacontext.saveChanges()
.then(goToEditView).fin(complete);
function goToEditView(result) {
router.replaceLocation('#/categorydetail/' + category().id());
}
function complete() {
isSaving(false);
}
},
How do I refresh just the Categories lookup data? Or, am I just doing this wrong and should perhaps NOT have categories as a lookup?
Thanks.
Breeze.js synchronises automatically and knows to search out the Category and update it in its lookup list.
I checked this by calling datacontext.lookups from the browser console after the save had been performed and inspecting the objects it showed me the category name had been refreshed.

Pass a URL into a Dart app

I have a page with links. These links all end in the same way. For example www.site.com/fruit/apples, www.site.com/fruit/bananas, www.site.com/fruit/oranges, etc. I want all these links to call the same Dart app and have the app do some processing and then redirect you wherever you need to go (the bananas page vs. the oranges page). This way, I avoid having an actual HTML file for every single fruit. I can instead have a single landing template that gets populated with variable fruit data.
The part I'm hung up on is passing the url into the Dart app so it can do the handling. I understand main() cannot receive arguments, so what's another way?
You can use the route package to handle the URL's for you.
For example:
import 'package:route/client.dart';
final fruitUrl = new UrlPattern(r'/fruit/(\w+)');
main() {
var router = new Router()
..addHandler(fruitUrl, showFruit)
..listen();
}
void showFruit(String path) {
var fruit = fruitUrl.parse(req.path)[0];
// Display the page according to the fruit type
}
If you don't need to handle actual routes, and you just want to handle any query parameters passed of the form ?fruit=apple you don't have to use the routes package and can instead manually parse the URL:
Map params = {};
// If arguments were provided, decode them into params map
if(window.location.search.length > 1) {
// Break all arguments into form: fruit=apple
List<String> queryPairs = window.location.search.substring(1).split('&');
for(String queryPair in queryPairs) {
// Add arguments into params map: key=fruit, value=apple
List<String> queryPairList = queryPair.split('=');
params[queryPairList[0]] = queryPairList[1];
}
}
// Handle the proper action based on the fruit
switch(params['fruit']) {
case 'apple':
// ...
break;
// ...
case 'orange':
// ...
break;
}

Resources