Как мы возвращаем Promise из store.dispatch в Redux-saga, чтобы мы могли дождаться разрешения и затем выполнить рендеринг в SSR? - PullRequest
2 голосов
/ 03 мая 2020

Я пытаюсь React SSR, используя Redux и Redux-saga.

Я могу заставить клиентский рендеринг работать, но хранилище сервера, похоже, никогда не получает данные и не ждет данных перед рендерингом сервера HTML.

. js

import express from 'express';
import renderer from "./helpers/renderer";
import createStore from "./helpers/createStore";
import {matchRoutes} from 'react-router-config';
import Routes from "./client/Routes";
import rootSaga from "./client/saga";


const app = express();

app.use(express.static('public'));
app.get('*', (req, res) => {
    const store = createStore();
    const branch = matchRoutes(Routes, req.path);
    const promises = branch.map(({ route }) => {
        return route.loadData ? route.loadData(store) : Promise.resolve(null);
    });
    store.runSaga(rootSaga).done.then(() => {
        res.send(renderer(req, store)) // helper method to load static router and provider  
    }) // Not required after Update 2 , Promise.All should work .. 
});

app.listen(3000, () => {
    console.log('Listening on Port 3000');
})

небольшая сага

import { put, takeEvery, all, call } from 'redux-saga/effects'
import {FETCH_USERS, SAVE_USERS} from "../actions";
import axios from 'axios';

export function* fetchUsers() {
    const res = yield call(getUsers);
    yield put({ type: SAVE_USERS, payload: res.data});
}


const getUsers = () => {
    const response = axios.get('http://react-ssr-api.herokuapp.com/users');
    return response;
}

export function* actionWatcher() {
    yield takeEvery(FETCH_USERS, fetchUsers)
}

export default function* rootSaga() {
    yield all([
        actionWatcher()
    ])
}

ошибка

TypeError: Cannot прочитать свойство 'then' из undefined в /Users/rahsinwb/Documents/Mine/code/SSR/build/bundle.js:309:39

Что мне здесь не хватает? Или есть какой-нибудь проверенный способ прослушивания концовки саги? Я думал, что мой помощник loadData в компоненте с store.dispatch для первоначального вызова действия вернет обещание, но это никогда не работает.

LoadData

const loadData = (store) => {
    store.dispatch({type: FETCH_USERS});
}

Обновление

Добавлено это в мой индекс. js Файл

 store.runSaga(rootSaga).toPromise().then(() => {
        branch.map(({ route }) => {
            return route.loadData ? route.loadData(store) : Promise.resolve(null);
        });
        res.send(renderer(req, store))
    })

Теперь код компилируется без ошибок, но проблема в том, что сага отправляет действие из метода loadData, то есть FetchData. До того, как fetchData вызовет действие Save_DATA в саге, упомянутой выше, мой вызов отменяется, как я полагаю, и редуктор остается пустым, из-за чего html не загружается с данными.

Как мы знаете, что загрузка данных для функции генератора завершена, начиная с действия, инициированного до действия по загрузке данных? на редуктор, как только сообщение, что я должен сделать компонент на сервере

Update2

Также проверил lib , но для по какой-то странной причине он вызывает проблемы с регенератором, но я не думаю, что библиотека нужна для такой цели, так как я планирую повторно использовать ту же сагу для клиентских вызовов Redux и хочу сохранить изменения отдельно. index. js

 console.log(promises); // no promise returned  undefined
   //setTimeout((function() {res.send(renderer(req, store))}), 2000);
   Promise.all(promises).then(
      res.send(renderer(req, store))
   )

Добавление Set Timeout помогает, и я могу получить данные на сервере, а затем выполнить рендеринг, так что теперь проблема заключается в том, что нам нужно как-то дождаться Генератор, чтобы решить, затем вызвать render.

Обновление 3

Добавил способ обойти эту проблему в качестве ответа, но я не чувствую, что это лучше решить и можно абстрагировать эту логику c out

Ответы [ 2 ]

1 голос
/ 10 мая 2020

TypeError: Невозможно прочитать свойство 'then' из undefined в

.done метод get устарел с v1, поэтому .toPromise() - способ прослушивания разрешения саги

Что касается ожидания завершения саг, существует специальное действие END, которое вы можете отправить, чтобы прекратить сагу после завершения всех ее дочерних задач, вызывая store.runSaga(rootSaga).toPromise() обещание разрешить.

Когда Обещание выполнено (это означает, что все необходимые данные уже есть), вы должны отправить в ответ результат HTML, полученный от renderToString. Обратите внимание, что компоненты, отображаемые на сервере, на самом деле не смонтированы, поэтому метод componentDidMount() не будет запущен, поэтому ваши данные не будут выбраны дважды.

Таким образом, вы можете изменить свой код следующим образом:

// server.js
...
import { END } from 'redux-saga';

app.get('*', (req, res) => {
    const store = createStore();
    const branch = matchRoutes(Routes, req.path);

    store.runSaga(rootSaga).toPromise().then(() => {
        res.send(renderer(req, store))
    });

    // trigger data fetching
    branch.forEach(({ route }) => {
        route.loadData && route.loadData(store)
    });

    // terminate saga
    store.dispatch(END);
});

Также вы можете следовать этому примеру .

0 голосов
/ 10 мая 2020

Мне удалось найти один способ обойти эту проблему, но это не то, что нужно для решения, и я не хочу делать это для каждого метода loadData + саги. Я смог абстрагироваться, есть ли более чистый способ достижения этого, поскольку я все еще не удовлетворен ответом

loadData в Компоненте

const loadData = (store) => {
    //store.dispatch({type: FETCH_USERS})
    return new Promise((resolve) => {
        store.dispatch({type: FETCH_USERS, callback: resolve })
    });
}

Сага

function asyncCallBackUtil(action){
    if (action.callback) {
        action.callback('Done');
    }
}

export function* fetchUsers(action) {
    const res = yield call(getUsers);
    yield put({ type: SAVE_USERS, payload: res.data});
    asyncCallBackUtil(action);
}

Теперь я могу использовать Promise.All, как упоминалось в сообщении

Promise.all(promises).then(data => {
    console.log('here',data);
    res.send(renderer(req, store))
})

Есть ли более чистый способ решения этой проблемы, как мне нужно сделать это и для других саг.

...