In current setup, I'm building multi-page universal web application (using react-router to do routing on both server and client). This project doesn't use any (redux) store (which I consider unneccessary for now).
One of component responsible for fetch data from remote API, done inside componentWillMount method.
When render on server, the component will fetch data, do rendering and send rendered HTML to client.
When client mount HTML with ReactJS, it fetch data once again from componentWillMount method. That's cause unneccessary double data fetch.
Does it have any solution?
Thanks for answer from Dominic Tobias.
I tried develop a couple solution but I found one that suitable for me.
First, I decided not to fetch data either in componentWillMount or componentDidMount inside component, but create a static method call fetchData which return Promise like this:
class PageContainer extends Component {
static fetchData(params) {
return fetch('...URL...')
}
render() {
const {data} = this.props;
//render logic here
}
}
and on server side, call PageContainer.fetchData and wait until promise is fulfilled, pass data as props to PageContainer and render HTML with hydrated data like this
<html>
<head></head>
<body>
<div id="roor">{..react rendered HTML..}</div>
<script>window.__DATA__ = {..data..}</script>
</body>
</html>
like I said, for this app, I think that redux is unnecessary for now.
Then, there is a problem on client side routing which is react-router cannot load async data. To fix that, I went looking for AsyncProps and write my own tiny version of that, called AsyncComp. You can bootstrap React on client side with react-router like this:
const Root = <Router history={browserHistory} routes={routes} render={(props) => <AsyncComp {...props} data={window.__DATA__}/>}/>
ReactDOM.render(Root, document.getElementById("root"));
That's all.
PS. After I built AsyncComp, it can also be used on server side like this:
AsyncComp.fetchData(routerProps)
.then((data) => {
const html = ReactDOM.renderToString(<AsyncComp {...routerProps} data={data}/>)
res.render('index', { html: html, data: data })
})
Since your data already exists you should have a function which will make a conditional fetch which you can call on both the server and componentDidMount (you're not supposed to make ajax calls in componentWillMount).
For example this is how a component looks for me using redux:
class User extends Component {
static readyOnActions(dispatch, params) {
return Promise.all([
dispatch(UserActions.fetchUserIfNeeded(params.id))
]);
}
componentDidMount() {
User.readyOnActions(this.props.dispatch, this.props.params);
}
If this is the current page then the server will call the readyOnActions, and on the client componentDidMount can call the same thing - the action called fetchUserIfNeeded will only make an AJAX request if the server hasn't already done it by checking if the data already exists (and you can add further checks such as if it's valid, if it's not already fetching etc).
Related
I've been having an issue where the client has provided me a third party Marketo form to include on one of the pages I'm building out which works on a "normal" html page when testing locally, however on Gatsby, it's not working. The script that was provided is as follows:
<script src="//app-xxxx.marketo.com/js/forms2/js/forms2.min.js"></script>
<form id="mktoForm_xxxx"></form>
<script>MktoForms2.loadForm("//app-xxxx.marketo.com", "xxx-xxx-xxx", xxxx);</script>
I've added the top script tag into gatsby-ssr.js like so:
import React from 'react'
export const onRenderBody = ({ setPostBodyComponents }) => {
setPostBodyComponents([
<script
key="mktoForm_test"
src="//app-sjst.marketo.com/js/forms2/js/forms2.js"
/>,
<script
key="/js/site.js"
src="/js/site.js"
/>
])
I've added the following to /static/js/site.js to initialize the form:
MktoForms2.loadForm("//app-xxx.marketo.com", "xxx-xxx-xxx", xxxx);
Then, on the page where I want the form, I've added this:
<form id="mktoForm_xxxx"></form>
The form only displays when I'm on the page and manually refresh the browser. Clicking into the page from any other page does not render the form at all. I'm guess it's how Gatsby/React routes to the page with the form.
Can someone please help??
You probably need to call that .loadForm function when the component that has that form mounts.
React.useEffect(() => {
// load form here...
}, []
If it's just being called in site.js, it's probably being called before the form element is present in the DOM so it has nothing to attach to.
I created this example a while ago, leaving it here in case it's extra help: https://github.com/CharlieDieter/react-marketo-hook
I want to build an Inventory app, everything was smooth until i want to make a page with URL pattern like
http://localhost:8000/product/edit/1
So i have this route in my React Router
<Route path='/product/edit/:product_id' component={MyComponent} />
Im using Redux to handle the state and Axios to get the data.
The problem is when i call the API , it's calling an URL of http://localhost:8000/product/edit/api/get-product-by-id. Of course im not getting any data returned.
The question is how to setup the Laravel Route for this problem?
Here is my web.php file
Route::get('/{path?}', function () {
return view('index');
})->where('path', '^((?!api).)*?');
What's wrong with using this approach?
I hope this solves your problem.
Route::get('/product/edit/{id}', function () {
return view('index');
});
I'm trying to get the recaptcha response so that I can pass along it via redux-form.
Have tried the 2 methods below...
Attempt 1:
Specifying callback in <head>.
index.html:
<script src='https://www.google.com/recaptcha/api.js?onload=callback&render=explicit'></script>
The problem here is that the callback is in global scope but I need to access it in React components.
Attempt 2:
Specifying callback in DOM.
Component: handleRecaptcha(resp)
handleRecaptcha(resp) {
console.log(resp);
}
Component: render():
<div className="g-recaptcha" data-callback={this.handleRecaptcha.bind(this)} data-sitekey="***"></div>
index.html:
<script src='https://www.google.com/recaptcha/api.js'></script>
I get the message ReCAPTCHA couldn't find user-provided function: function () { [native code] } after submitting the recaptcha. Probably a scope problem too.
I'm trying to avoid using another library, since I think this is a rather trivial problem.
Would anyone happen to have any idea how to go about it?
All I need is to get hold of the widget instance, and do grecaptcha.getResponse(widget) to get the response. API ref
Use react-recaptcha
import Recaptcha from 'react-recaptcha'
<Recaptcha
sitekey="xxxxxxxxxxxxxxxxxxxx"
render="explicit"
verifyCallback={verifyCallback}
onloadCallback={callback}
/>
You could also try Reaptcha.
It is a lot cleaner, more modern and has more of a React-way approach in handling the reCAPTCHA widget.
<Reaptcha
sitekey="YOUR_API_KEY"
onVerify={() => {
// Do something
}}
/>
I am using react for creating a sample page which has server side rendering using renderToString() for SEO friendly pages.
this is my server.js code
app.get('*', (req, res) => {
match(
{ routes, location: req.url },
(err, redirectLocation, renderProps) => {
// in case of error display the error message
if (err) {
return res.status(500).send(err.message);
}
// generate the React markup for the current route
let markup;
if (renderProps) {
markup = renderToString(<RouterContext{...renderProps}/>);
} else {
res.status(404);
}
// render the index template with the embedded React markup
return res.render('index', { markup });
}
);
});
My page is search page which is static initially and when the user enters the input it fetches data from backend and renders a list of components in the same page.
When I see view page source in browser, I can see only the initial static content, not the html of list which renders after the backend response.
What is the right method to get the updated HTML when the component state changes.
You still need to include your component, and React itself, on your clientside and call React.render on the same base element in the DOM.
Simply serving the result of React.renderToString as static HTML does will only do exactly that, serve static html.
If you want your component to get mounted properly on the clientside so it can react to clientside events and re-render for state changes, you'll need to mount it on the static html, this is done by calling React.render on page load on the clientside.
There's more about this here: https://strongloop.com/strongblog/node-js-react-isomorphic-javascript-why-it-matters/
Save the fetched data to your state and the component will re-render itself. That way your view will render the new content when it's fetched from the backend.
I am working with this flask react template project here and I am running into an issue
https://github.com/bonniee/react-flask
When rendering index.html, I want to pass back some data from flask
#app.route('/')
def hello_world():
return render_template('index.html', somedata="YOOOO")
And use it in the react components
render() {
this.loadDataFromServer();
return <h1>sup? {{somedata}}</h1>;
}
});
Is there a clean way to do it in this project format?
In your template, you could include a script tag that sets window.somedata to the value you want. You could then access that directly in render, or preferably if you've got flux or redux you could dispatch an action with that data during mounting of the react components.