Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to Properly Use Telefunc in Vike for Shared Server and Client Logic? #109

Closed
sergeysova opened this issue Jun 10, 2024 · 6 comments
Closed

Comments

@sergeysova
Copy link
Contributor

I am using React, TypeScript, Vike, and Telefunc in my project. I need to use the same code for data fetching on both the server and the client. So using +data, is not acceptable for me. Here's my current setup:

Server-side Data Fetching:

// renderer/+onBeforeRender.ts

import { OnBeforeRenderAsync } from 'vike/types';
import { allSettled, fork, serialize } from 'effector'

// I omitted unnecessary details for clarity
export const onBeforeRender: OnBeforeRenderAsync = async (pageContext) => {
  const { pageStarted } = pageContext.config;
  
  const scope = fork()
  await allSettled(pageStarted, { scope, params: contextWillBeThere });
  return {
    pageContext: { scope, scopeValues: serialize(scope) },
  }
};

Client-side Event Triggering:

// pages/videos/+pageStarted.ts
import { createEvent } from 'effector';

export const pageStarted = createEvent();

Shared Model:

// pages/videos/model.ts
import { createEffect, sample } from 'effector';
import { pageStarted } from "./+pageStarted";
import { onVideosLoad } from './onVideosLoad.telefunc';

const videosLoadFx = createEffect(async () => {
  const videos = await onVideosLoad();
  return videos;
});

// When pageStarted is called via allSettled
// then videosLoadFx is called
// after videosLoadFx.finally resolved — allSettled is resolved too
// so, I can use the data from videosLoadFx internal stores (for example)
sample({
  clock: pageStarted,
  target: videosLoadFx,
});

Client-side Rendering:

// renderer/+onRenderClient.tsx
import { fork } from 'effector';
import { Provider } from 'effector-react'

// I omitted unnecessary details for clarity
export const onRenderClient: OnRenderClient = async (pageContext) => {
  const { Page, scopeValues } = pageContext.config;
	
  const scope = fork({ values: scopeValues })

  const root = ReactDOM.createRoot(document.querySelector("#root"))
  root.render(
    <Provider value={scope}>
      <Page />
    </Provider>
  )
};

Server-side Rendering:

// renderer/+onRenderHtml.tsx
import { fork } from 'effector';
import { Provider } from 'effector-react'

// I omitted unnecessary details and some types for clarity
export const onRenderHtml: OnRenderHtmlAsync = async (pageContext) => {
  const { Page, scope, scopeValues } = pageContext;
  
  const pageHtml = ReactDOMServer.renderToString(
    <Provider value={scope}>
      <Page />
    </Provider>
  )

  return {
    documentHtml: `<html><body><div id="root">${pageHtml}</div></body></html>`,
    pageContext: { scopeValue },
  }
}

Configuration:

// renderer/+config.ts

export default {
  clientRouting: true,
  passToClient: ["scopeValues"],
  
  meta: {
    pageStarted: {
      env: { client: true, server: true },
    },
  },
  hydrationCanBeAborted: true,
}

The Problem

When I attempt to call the Telefunc function from the server (+onBeforeRender.ts), I encounter the error: "Using Telefunc to fetch the initial data of your page is discouraged."

However, I need to perform the same operation both on the server and the client without duplicating the code. Essentially, I want to:

  • Call Telefunc on the client to make a request to /_telefunc.
  • Call the same Telefunc directly with context on the server.

Questions

  1. How can I call Telefunc from Vike's renderer/+onRenderClient.tsx?
  2. What changes or additional information would help better understand and resolve this issue?

Additional Context

  • I am not using streaming.
  • The goal is to maintain DRY principles by using the same code for both server-side rendering and client-side interactions. Just because there is very complex data fetching logic should be used with client routing and server side rendering.
  • If its required, I can make the integration with @effector on my own.
telefunc 0.1.74
vike 0.4.171
vite 5.2.11
react 18.3.1
effector 23.2.2

Any guidance or suggestions to achieve this would be greatly appreciated.

@brillout
Copy link
Owner

I need to use the same code for data fetching on both the server and the client. So using +data, is not acceptable for me.

It isn't clear to me why using Telefunc instead of Vike's data() hook is absolutely necessary for you. Do you mind elaborating why it's a requirement for you?

@brillout
Copy link
Owner

Closing, but let's continue the conversation.

@brillout brillout closed this as not planned Won't fix, can't repro, duplicate, stale Jun 11, 2024
@sergeysova
Copy link
Contributor Author

It isn't clear to me why using Telefunc instead of Vike's data() hook is absolutely necessary for you. Do you mind elaborating why it's a requirement for you?

If I use the data() hook, I would need to separate my shared code into server-side only and client-side only segments. My primary goal is to simplify complex business logic by writing it in one place and sharing it across both environments.

With my current setup, I export pageStarted, and Vike automatically invokes it on both the server and the client. This eliminates the need for the data() hook because I use Effector to fetch data and immediately place it into state manager stores, making the data accessible throughout the app.

Using the data() hook necessitates passing data into component props, which then requires additional handling. This introduces challenges such as prop drilling and complicates the process of integrating the data with the state manager. Moreover, it forces me to write and maintain business logic separately for the server and client, which contradicts my goal of code reusability and simplicity.

Here’s an overview of my architecture:

  1. Server-Side Rendering (SSR):

    • An SSR request is received.
    • Effector runs the logic for the page.
    • Data is fetched within this logic and placed into stores. There’s no need to involve Vike for this task.
    • Components are rendered with data from stores, independent of Vike’s implementation.
  2. Client-Side Workflow:

    • The server-side state is read and placed into Effector stores.
    • React components are hydrated or rendered using this state.
    • During client-side routing, Effector events are triggered to fetch data and update the stores.

This approach decouples my business logic from Vike’s implementation, allowing me to write comprehensive tests for the entire workflow without any specific dependencies on Vike. Vike’s flexibility and customizability make it an excellent choice for my application, while Telefunc provides a seamless way to implement "Server Actions."

However, I believe Telefunc should also be callable from the server to maintain a consistent and unified codebase. This would streamline my development process and ensure that my business logic remains centralized and reusable.

@sergeysova
Copy link
Contributor Author

If I use the data() hook, I would be forced to write permission checks and data loading logic in data() for server-side rendering to obtain the initial data. Simultaneously, I would need to implement permission checks and data loading logic elsewhere for client-side routing.

This duplication of logic increases the complexity of my codebase and the likelihood of inconsistencies between server and client logic. My goal is to maintain a single source of truth for my business logic, ensuring that any changes or updates are automatically reflected across both the server and client. This approach minimizes the risk of errors and simplifies maintenance.

In summary, using Effector with Telefunc allows me to centralize my business logic, streamline data fetching, and maintain consistent behavior across server and client environments. This is why I believe Telefunc should be callable from the server as well, to support a more unified and efficient architecture.

@brillout
Copy link
Owner

It very much is a goal: #102. I've edited the issue description and added information on how you can achieve this as of today.

Let me know if you still have questions.

@sergeysova
Copy link
Contributor Author

Thank you for your responses.

I’ve subscribed to the issue and will keep an eye on the updates.

For now, I’ve implemented the initial data fetching using the data() hook. It’s not the most efficient workaround for me, but it works.

If I can be of any help in writing examples or integrations for Vike + Effector or Telefunc + Effector, please let me know.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants