Server Side Rendering with viperHTML - prerender

I'm working on a Symfony application and just got SSR for JS working using https://github.com/spatie/server-side-rendering. So far I only worked with "readymade" SSR solutions for React, but currently I'm trying to use hyperHTML/viperHTML and am facing a few issues that so far I wasn't able to solve by looking at the available docs/examples.
My current test snippet is this:
const viperHTML = require('viperhtml');
class Component extends viperHTML.Component {
constructor(props) {
super();
this.props = props;
}
render() {
return this.html`
<h1>Hello, ${this.props.name}</h1>`;
}
}
console.log(new Component({ name: 'Joe' }).render().toString());
The thing here is that without explicitly calling render() I get no output. Looking at some of the official examples this shouldn't be necessary, at least not with Component. I already tried using setState() in the constructor, for example, but no difference.
Also, without using both, console.log() and toString(), I get no output either. Which is unexpected. I get that toString() might be necessary here (without it a <buffer /> is being rendered), but the console.log() seems odd. This might not be related to viperHTML at all of course. But instantiating the component is the only thing I expected to be necessary.
It's also not clear to me yet how I can write an isomorphic/universal component, i.e. one file which has the markup, event handlers etc., gets rendered on the server and then hydrated on the client. When I add an inline event handler as per the docs (https://viperhtml.js.org/hyperhtml/documentation/#essentials-6) it actually gets inlined into the rendered markup, which is not what I want.
I checked hypermorphic and the viperNews app, but that didn't really help me so far.

In case it helps, you can read viperHTML tests to see how components can be used.
The thing here is that without explicitly calling render() I get no output.
Components are meant to be used to render layout, either on the server or on the client side. This means if you pass a component instance to a hyper/viperHTML view, you don't have to worry about calling anything, it's done for you.
const {bind, Component} = require('viperhtml');
class Hello extends Component {
constructor(props) {
super().props = props;
}
render() {
return this.html`<h1>Hello, ${this.props.name}</h1>`;
}
}
console.log(
// you need a hyper/viperHTML literal to render components
bind({any:'ref'})`${Hello.for({ name: 'Joe' })}`
// by default you have a buffer to stream in NodeJS
// if you want a string you need to use toString()
.toString()
);
Since NodeJS by default streams buffers, any layout produced by viperHTML will be buffers and, as such, can be streamed while it's composed (i.e. with Promises as interpolation values).
It's also not clear to me yet how I can write an isomorphic/universal component, i.e. one file which has the markup, event handlers etc., gets rendered on the server and then hydrated on the client.
The original version of hyperHTML had a method called adopt() which purpose was to hydrate live nodes through same template literals.
While viperHTML has an viperhtml.adoptable = true switch to render adoptable content, hyperHTML adopt feature is still not quite there yet so that, for the time being, you can easily share views between SSR and the FE, but you need to either take over on the client once the SSR page has landed or react, for the very first time, differently and take over on the client at distance.
This is not optimal, but I'm afraid the hydration bit, done right, is time consuming and I haven't found such time to finalize it and ship it.
That might be hyperHTML v3 at this point.
I hope this answer helped understanding how viperHTML works and what's the current status.

Related

Fluxor: An effect which doesn't dispatch an action?

I'm using Fluxor to manage state in a Blazor wasm app.
I have the following effect which is triggered after getting a result from deleting an item:
[EffectMethod]
public Task HandleDeleteBudgetResultAction(DeleteBudgetResultAction action, IDispatcher dispatcher)
{
if (action.Success)
{
NavigationManager.NavigateTo("/budgets", false);
}
return Task.CompletedTask;
}
Essentially, if the delete was successful, navigate back to the list page. If it wasn't, do nothing as we need to remain on the detail page.
In this scenario I do not need to dispatch an action, but I have to include the dispatcher parameter, as demanded by the EffectMethod attribute. And since I have no async processes in this method, I am returning Task.CompletedTask.
This obviously feels wrong, so my question is: is this a limitation of Fluxor, or have I architected the flow incorrectly? As far as I'm aware, an effect doesn't have to dispatch an action.
I was thinking I might need to move my navigation state into the store, but I think I'll just come across the same problem again because I'll still need to call NavigationManager from somewhere.
Any help or better solutions appreciated :)
It is a limitation of Fluxor. You can dispatch a GoAction instead of injecting the NavigationManager, as long as you have called UseRouting on the Fluxor options.
builder.Services.AddFluxor(options => options
.UseRouting()
.ScanAssemblies(typeof(Program).Assembly)

redux-injectors: Using yield select in saga before reducer and saga are injected

Hello this is my first question. I am trying to set up a project where modules along with the redux and sagas will be injected into the main app, using redux-injectors. In my sagas I want to use yield select, to check if an action has updated the state and then carry on. For example, when I post an image, I want to make sure there were no errors in posting the file and then move on. I use the following function:
export const imageErrors = (state: RootState): IImagesErrorState => state.image.errors
and then in the saga.ts file I use it as such:
if (imagesErrors?.postImageError !== null) {
throw imagesErrors.postImageError
}
this works fine as long as the state.image exists in the root state from the beginning. However, how do I do that when I want to inject this state later on using useInjectReducer and useInjectSaga? I obviously get an error
Property 'image' does not exist on type 'Reducer<CombinedState<{ user: CombinedState<{ auth: IAuthState; errors: IErrorState; }>; }>, AnyAction>'.ts(2339)
So how do we handle selectors of specific pieces of state, since state does not yet include them?
Thank you so much.
Can't talk about the Typescript part of things, but in terms of architecture you've got two options.
One is the obvious - that is to add conditions or ? everywhere to avoid errors from accessing missing properties, but that can get tedious quickly.
The other probably better option is to rethink your state & application chunks. What is this saga that is accessing state that isn't existing yet? Does it need to run before you have such state? If not, let's move the saga to the same chunk as the reducer. In the opposite case, where you need the saga to be running e.g. as part of the runtime chunk, then perhaps the image state should be in the runtime chunk as well.

Why is my Observable emitting more values than expected, and why is auditTime a fix?

I asked a question here recently about observables and you guys were of really great help (as always). Now I'm having a similar situation, and me and my team-mate are bending our brains over it.
The bug to fix was: user sees a collection of assets, and on browser refresh the wrong set of assets was being loaded. It turns out the key to the problem was one particular pipe observing the currently selected collection. Here's the relevant code:
this.selectedCollection.pipe(
filter((v) => !!v)).subscribe((v) => {
console.log('PIPE: selected collection', v.collectionId);
this.store.dispatch(
// action jackson on redux
)
);
});
The action to be dispatched here is for loading the assets of the collection. One collection was always loaded first as default and it was conflicting with further selections made by the user.
I've also added console.logs on the relevant reducers and effect to visualize behavior.
What happens on browser refresh is this:
Collection 9-em... is the default collection we don't want to see, and collection 9uem... is the user's choice whose asset's we want to see.
The first five lines show the expected output of the observable:
default collection set as selected collection
reducer 'is loading' assets
the user triggers a change selected collection action
the selected collection value is being updated and emitted accordingly
Now we would have expected the effect to load the assets and that's it. But what happens is that the pipe keeps emitting the same values once again, which is weird, because I'm 100% sure no further value is being set from anywhere. But it would also be fine, since we end up with the desired value. Yet strangely, the reducer is handling the load actions in reverse order, which led to the wrong assets being loaded (this could be a whole different issue on top).
Adding auditTime(200) as first operator to the pipe above fixed the issue. No further values were emitted.
Now, my questions are:
Why are the values emitted twice? Could it be an inappropriate operator/subscription some place else (didn't see anything suspicious)?
And why is auditTime(200) magically fixing this?
The effect also works as a pipe of actions being filtered, and it contains an auditTime(200) operator before executing, so that it executes only on the last action. While I do understand on principle what it does, I'm not quite sure if using auditTime like that just because it works is such a good idea.
I assume this is an issue out of noob confusion resulting in using rxjs not the right way. Unfortunately, I couldn't find anything useful on google. I really don't like 'fixing' a bug by adding a line of code that I just don't understand.
Thank you so much in advance!
As requested by fridoo, here's the code for this.selectedCollection:
get selectedCollection(): Observable<collectionState.CollectionsData> {
return this.store
.select(collectionState.getSelectedCollection)
.pipe(distinctUntilChanged());
}
And for getSelectedCollection:
export const getSelectedCollection: (state: any) => CollectionsData = (state: any) =>
getCollectionsState(state)
? getCollectionsState(state).selectedCollection
: undefined;
The rest is pretty forward just objects of state, the observable created via the select method. We're not using any library for redux (not my decision), so select is implemented like this:
select<T>(fn: (state: any) => T): Observable<T> {
return this.state$.pipe(map(fn), distinctUntilChanged());
}
Does this help any further?

Could not invoke RNFirebaseFirestore.documentSet

Hi guys I add this error and I don't understand why... I'm using the starter project of react-native-firebase, and I'm trying to use firestore
Although there might be an issue with the library which needs investigating, your code is generally wrong. State in a React component should be data which when changed, causes a re-render. You do not need to assign your collection to state, this can be done as a class property. Also, using a constructor & componentWillMount is wrong as they're essentially the same thing - I don't know enough how it'd handle both cases internally though.
Your code would better work like so:
constructor() {
super();
this.ref = firebase.firestore().collection('users');
}
componentDidMount() {
this.ref.add({ name: 'moo' });
}
I have got same error. Please create the collection with the name 'users' and add a dummy document. Then try.

Component is not unmount after its delete in store

Project (Todolist) was created with immutable library, source here
Store structure: project have many tasks, In redux store: State - map, projects, tasks - Records
When I asyncly remove project ...
export const removeProject = project => (dispatch) => {
if (!isProjectExist(project)) return Promise.resolve()
return projectService
.delete(project)
.then(
() => {
dispatch(remove(project))
console.log("post removeProject resolved")
},
handleError,
)
}
.... that was created after initialization - it will be deleted and properly unmounted, but when project was passed as initialState - ProjectList will not be rerendered, and ProjectItem try to render itself with stale data, and fail, as in picture
It have tests
It looks like reducer returs changed data, but I use immutablejs, and previously i use normalizr-immutable, but I thought that source of issue in this library and write my own normalizeInitialState (source), it did not help, now I think that maybe source of problem in redux-immutable
I struggled entire day on solving of this problem
creator of redux says
I don't think this is something we can fix. React state changes are
asynchronous and React may (or may not) batch them. Therefore, the
moment you press “Remove”, the Redux store updates, and both Item and
App receive the new state. Even if the App state change results in
unmounting of Items, that will happen later than mapStateToProps is
called for Item.
Unless I'm mistaken, there is nothing we can do. You have two options:
Request all required state at App (or a lower, e.g. ItemList) level
and pass it down to “dumb” Items. Add safeguards to mapStateToProps
for “currently unmounting” state. For example, you may return null
from render in this case. Potentially we could have the component
generated by connect() return null from its render if mapStateToProps
returned null. Does this make any sense? Is this too surprising?
Hm, I never saw stubs like return (<div></div>) or safeguards in mapStateToProps in others code
markerikson
I'm not entirely sure I follow what exactly your problem is, but as a
guess: it sounds like the child component is re-rendering before the
parent is. This is a known issue with React-Redux v4 and earlier. The
v5 beta fixes that issue. Try installing react-redux#next and see if
that takes care of your problem.

Resources