reactjs - React Redux SSR how to suppress initial actions called from componentDidMount? -
i able preload needed states in server-side , pass these initial states client application's redux store.
i have enabled redux-logger see happens in application , states being refetched store. that's. because components call the necessary action during componentdidmount
. here example reducer, action , component:
// action: import axios 'axios'; export function fetchnewstop() { return { type: 'fetch_news_top', payload: axios.get('/news/top') }; } // reducer: export function newsarchive(state = { pending: false, response: { data: [] }, error: null }, action) { switch (action.type) { case 'fetch_news_top_pending': return { ...state, pending: true, response: { data: [] }, error: null }; case 'fetch_news_top_fulfilled': return { ...state, pending: false, response: action.payload.data, error: null }; case 'fetch_news_top_rejected': return { ...state, pending: false, response: { data: [] }, error: action.payload }; default: return state; } } // component: export class newsarchivefactory extends react.component { componentdidmount() { this.props.fetchnewstop(); } render() { if (this.props.news) { return ( <newsgrid items={this.props.news} /> ); } return null; } }
i using redux-promise-middleware
, creates promise actions (fulfilled, rejected, pending).
the action gets called when component mounted. understanding componentdidmount gets called if component rendered in server-side inform js exists. component doesn't remounted. however, have read better choice run actions in componentdidmount
, think not work if call componentdidupdate
(we enter infinite loop).
i want suppress these initial actions getting called because state coming server-side. how can achieve this?
i'm hoping there better way out there, far have come set ssr
flag in reducer state upon receiving data based on whether data received on server or client.
first, componentdidmount
not fire on server, on client. need mechanism uses react-router
match function along static data fetching functions in components. see universal-react boilerplate has easy-to-understand server-side data fetching mechanism. however, still need fetch data in componentdidmount
methods or things break if component rendered on client or mounted later after initial server render. introduces problem of potential "double fetches" when data pre-loaded on server gets requested again when componentdidmount
fires on client.
below example implementation works around issue (note i'm using redux format actions , reducers in 1 file):
redux/modules/app.js:
import { basepath, fetcher, fetchneeded, checkstatus } '../../lib/api' const isclient = typeof document !== 'undefined' const set_flag_ssr = 'app/set_flag_ssr' const request_data = 'app/request_data' const receive_data = 'app/receive_data' const data_fail = 'app/data_fail' const invalidate_data = 'app/invalidate_data' const initialstate = { ssr: false, initialized: false, isfetching: false, didinvalidate: true, conditionfail: false, data: {} } export default function reducer(state = initialstate, action = {}) { switch (action.type) { case set_flag_ssr: return object.assign({}, state, { ssr: action.flag }) case invalidate_data: return object.assign({}, state, { didinvalidate: true }) case request_data: return object.assign({}, state, { isfetching: true, conditionfail: false, }) case receive_data: return object.assign({}, state, { ssr: !isclient, initialized: true, isfetching: false, conditionfail: false, didinvalidate: false, lastupdated: action.receivedat, data: action.data, }) case data_fail: return object.assign({}, state, { isfetching: false, conditionfail: true, }) default: return state } } function setflagssr(flag) { return { type: set_flag_ssr, flag: flag, } } function requestdata() { return { type: request_data } } function receivedata(json) { return { type: receive_data, data: json, receivedat: date.now() } } function receivefail(err) { return { type: data_fail, err: err, } } function fetchdata(url) { return (dispatch, getstate) => { dispatch(requestdata()) return fetcher(url, { credentials: 'include' }, getstate().cookie) .then(response => response.json()) .then((json) => { return dispatch(receivedata(json)) }) .catch(err => { dispatch(receivefail(err)) }) } } export function getdata() { return (dispatch, getstate) => { const state = getstate() if (fetchneeded(state.app, () => dispatch(setflagssr(false)))) { return dispatch(fetchdata(basepath + '/api/app')) } } }
lib/api.js:
import fetchponyfill 'fetch-ponyfill' import promise 'bluebird' const { fetch, request, response, headers } = fetchponyfill(promise) const isclient = typeof document !== 'undefined' export const basepath = isclient ? '': `${__apiroot__}` // check http response status , throw error if not valid export function checkstatus(response) { if (response.status >= 200 && response.status < 300) { return response } else { let error = new error(response.statustext) error.response = response throw error } } // generic redux check triage fetch requests , ssr flags export function fetchneeded(statebranch, clearflagssr) { if (statebranch.isfetching) return false if (statebranch.ssr && isclient) { clearflagssr() return false } return true } // fetch wrapper set defaults , inject cookie data on server (if present) export function fetcher(url, config={}, cookie={}) { let options = {} let headers = new headers(); // base headers headers.set('accept', 'application/json'); headers.set('content-type', 'application/json'); if (config.headers) { object.getownpropertynames(config.headers).foreach(function(key, idx, array) { headers.append(key, config.headers[key]) }) } if (cookie.value && !isclient) { //inject cookie data options header server-to-server requests headers.set('cookie', cookie.value) } options = { credentials: config.credentials || 'same-origin', method: config.method || 'get', headers: headers, } if (config.body) options.body = config.body return fetch(url, options) }
the basics of how works ssr
flag in redux state set true
if receive_data
action occurs on server. otherwise set false
.
if getdata
function called on client, first calls fetchneeded
function (passing particular slice of state tree along function can dispatch action clear ssr
flag). if ssr
flag true
(and of course on client) fetchneeded
function return false
after calling function clear ssr
flag.
so in end, if server called our getdata
function , data present client won't re-request data server first time (but instead clear ssr
flag). after flag has been cleared (or if never set in first place) client request data server normal. allows avoid initial redundant call data still works if component mounted on client or gets refreshed on client.
hopefully helpful.
Comments
Post a Comment