I have started learning Relay by writing a new Next.js application. I have so far been following the with-relay-modern example in the Next.js repo, and it has been working just fine for fetching data from the server. However, I have now moved beyond that example by adding a mutation, and things immediately stopped working.
The mutation updater looks like this:
function updateStore(
store: RecordSourceSelectorProxy,
formInstanceUuid: string,
) {
const mutation = store.getRootField("updateFormValue");
const newFormValue = mutation?.getLinkedRecord("formValue");
if (!newFormValue) {
throw "Expected new form value from server";
}
const localFormInstance = store.get(formInstanceUuid)
const localValueRecords = localFormInstance?.getLinkedRecords("values") || [];
for (const record of localValueRecords) {
if (record.getValue("partUuid") == newFormValue.getValue("partUuid")) {
console.log("Copying fields from server provided value to local value");
record.copyFieldsFrom(newFormValue);
}
}
console.debug("New state of store:", initEnvironment().getStore().getSource())
}
All it does is inject a "value" object returned from the server into a list of value objects in a local "form" object. As you can see I am dumping the state of the store on the last line, so I can confirm that the mutation worked as expected, and the local value was modified as expected.
However, the UI doesn't refresh. I have to reload the window to see the new state.
I can't for the life of me figure out what I've done wrong, so I'm starting to wonder if the example I was following only works for fetching data. I assume it's the QueryRenderer object that is responsible for refreshing the UI when the underlying store changes, and the example doesn't use one. I also can't imagine how a QueryRenderer could be added to the example without ruining SSR.
TL;DR
Does the "with-relay-modern" example work when adding mutations, or is my issue somewhere else?
I also started a Discussion on the Next.js GitHub page about this
Related
I am creating an easy chat app, with different text channels. I am facing an infinite loop issue when using the useEffect hook to update the messagesList on real time. You can see the code below but this is what I am trying to achieve:
First useEffect is for the chat window to scroll to the last message every time there is a change in the messagesList array. This means: I am in the middle of the messages window, I write a new message and it takes me to the bottom. This is working fine.
Second useEffect is for the messagesList to be rendered whenever the channel is changed or there is any change in the messagesList. Adding the messagesList as a dependency is causing the infinite loop... but I think I need it cause otherwise the following happens: user1 is inside the chat channel and user2 writes a new message. User1 wont see the new message displayed as his chat is not being re-rendered. How would you make it for the new message to be displayed for user1?
Sorry for the confusing question and thanks a lot in advance!
useEffect(() => {
anchor.current.scrollIntoView(false);
}, [messagesList]);
useEffect(() => {
const unsubscribe = onSnapshot(
collection(firestore, `channels/${activChannel}/messages`),
(snapshot) => {
console.log(snapshot.docs);
getMessagesList();
}
);
return () => unsubscribe();
}, [activChannel, messagesList]);
I am not familiar with firestore, but perhaps you could tie the updating of the messages to the event that an user submits his message or use useSyncExternalStore. This piece of documentation on useEffect use cases might help you.
an excerpt from the docs:
Here, the component subscribes to an external data store (in this
case, the browser navigator.onLine API). Since this API does not exist
on the server (so it can’t be used to generate the initial HTML),
initially the state is set to true. Whenever the value of that data
store changes in the browser, the component updates its state.
Although it’s common to use Effects for this, React has a
purpose-built Hook for subscribing to an external store that is
preferred instead. Delete the Effect and replace it with a call to
useSyncExternalStore:
Im pretty new to Vue and JS, so Im having a hard time understanding how it all works. Im coming from Swift and maybe that is the problem.
Im trying to get a Primevue datable to update its values when changing out the values in a ref connected to it. But nothing happens on screen, however the console.log file shows the new data.
//Vue composition api setup()
const { flights } = getCollection("Jobs", shift.value.email, fullDay + platform.value.name)// Firebase Listener
//This function is triggered by some select buttons which changes the shift.value and platform.value so to listen for data in a different collection
const loadFlights = () => {
let {flights: fl} = getCollection("Jobs", shift.value.email, fullDay + platform.value.name)
flights.value = fl.value
console.log('flights ref:', fl, ) // shows new the data, but the datable does not.
}
If Im not clicking to change to a different collection the datable updates the current collection fine. Is there some sort of "tableview.reload()" fuctionality here Im missing?
Solved it. I used watch from vue instead which triggers everytime the select value changes.
I then stored the response in a reactive parameter instead of a ref. And now it works like it should. Cant tell you why.
https://youtu.be/Qypxpm-llnQ
Program with Erik most likely can.
In a Sapper app, I want to be able to persist the state of some UI components so I can navigate the app without losing state when the user returns to the pages using those components.
In a Svelte-only app, this is usually done with a custom store that uses the sessionStorage or localStorage API. A good example of that can be found in R. Mark Volkmann's book Svelte and Sapper in Action, §6.24:
store-util.js
import {writable} from 'svelte/store';
function persist(key, value) {
sessionStorage.setItem(key, JSON.stringify(value));
}
export function writableSession(key, initialValue) {
const sessionValue = JSON.parse(sessionStorage.getItem(key));
if (!sessionValue) persist(key, initialValue);
const store = writable(sessionValue || initialValue);
store.subscribe(value => persist(key, value));
return store;
}
Unfortunately, using stores that way breaks immediately in Sapper because the scripts run on the server first, where sessionStorage is not defined. There are ways to prevent some parts of code from running on the server (using the onMount lifecycle function in a component, or checking process.browser === true), but that doesn't seem possible here.
Persisting some state locally looks like a very common use case so I'm wondering what's the right way to do it in a Sapper app (considering that I haven't even found the wrong way).
Provide a dummy store for SSR.
It is always possible to do feature detection with something like typeof localStorage !== 'undefined'.
Your component code will re-run in the browser, even if the page was SSR'd. This means that if it is fed a different store, the browser-only values will take over and update existing state (inherited from the server).
See this answer for an example.
I'm currently building a Vue app that consumes data from the Contentful API. For each entry, I have a thumbnail (image) field from which I'd like to extract the prominent colours as hex values and store them in the state to be used elsewhere in the app.
Using a Vuex action (getAllProjects) to query the API, run Vibrant (node-vibrant) and commit the response to the state.
async getAllProjects({ commit }) {
let {
fields: { order: order }
} = await api.getEntry("entry");
let projects = order;
projects.forEach(p =>
Vibrant.from(`https:${p.fields.thumbnail.fields.file.url}`)
.getPalette()
.then(palette => (p.fields.accent = palette.Vibrant.hex))
);
console.log(projects);
// Commit to state
commit("setAllProjects", projects);
}
When I log the contents of projects right before I call commmit, I can see the hex values I'm after are added under the accent key. However, when I inspect the mutation payload in devtools, the accent key is missing, and so doesn't end up in the state.
How do I structure these tasks so that commit only fires after the API call and Vibrant have run in sequence?
You cannot add a property to an object in Vue and have it be reactive; you must use the Vue.set method.
Please try replacing that forEach block with the following, which adds the new property using Vue.set:
for (i=0; i<projects.length; i++)
Vibrant.from(`https:${projects[i].fields.thumbnail.fields.file.url}`)
.getPalette()
.then(palette => (Vue.set(projects[i].fields, accent, palette.Vibrant.hex)))
);
UPDATE: changing the format from forEach to a conventional for loop may be gratuitous in this case, since the assignment being made is to an object property of projects and not to a primitive.
I'm not spending a lot of time on StackOverflow, and if the above answer works, I am happy for you indeed.
But I expect from that answer you will get console warnings telling you not to mutate state directly.
Now when this happens, it's because while Vue.set(), does in fact help Vue understand reactively a change has been made, potentially deeply nested in an object.
The problem here is that since you are looping the object, changing it all the time, the commit (Mutator call) is not the one changing state - Vue.set() is actually changing it for every iteration.
I started using ParseReact (https://github.com/ParsePlatform/ParseReact), but i want to know if there are any way of realtime data ? Like in MeteorJS or Firebase.
To add Parse data to a component, it simply needs to subscribe to a standard Parse Query. This is done through an implementation of the newly-proposed observe() API for React. The ParseReact Mixin allows a version of this new lifecycle method to be used today with Parse Queries.
If you're using React with ES6 classes, we also provide a subclass of React.Component that allows you to use the observe() and Query-specific APIs.
var CommentBlock = React.createClass({
mixins: [ParseReact.Mixin], // Enable query subscriptions
observe: function() {
// Subscribe to all Comment objects, ordered by creation date
// The results will be available at this.data.comments
return {
comments: (new Parse.Query('Comment')).ascending('createdAt')
};
},
render: function() {
// Render the text of each comment as a list item
return (
<ul>
{this.data.comments.map(function(c) {
return <li>{c.text}</li>;
})}
</ul>
);
}
});
Whenever this component mounts, it will issue the query and the results will be attached to this.data.comments. Each time the query is re-issued, or objects are modified locally that match the query, it will update itself to reflect these changes.
Mutations are dispatched in the manner of Flux Actions, allowing updates to be synchronized between many different components without requiring views to talk to each other. All of the standard Parse data mutations are supported, and you can read more about them in the Data Mutation guide.
// Create a new Comment object with some initial data
ParseReact.Mutation.Create('Comment', {
text: 'Parse <3 React'
}).dispatch();
I tried the example, but always have to reload view. It`s not the same as Firebase and MeteorJS
I would also like to hear more about this...Not sure if this is actually a supported feature or not. As the documentation states, Queries you are subscribed to in the observe function will be updated with new props/state, as well as any time a Mutation occurs. In this sense it is very much like Meteor in that changes changes to state (much like changes to Session variables) can reload queries to the backend.
Where it differs is that, unlike Meteor, changes in Parse (say, directly in the db or from another front-end instance) are not communicated to all subscribed React front-ends. At least as far as I can tell. Which is kinda disappointing. Would love to hear from someone more experienced, who hasn't just been messing with ParseReact for the past 24 hours.