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

Major change to fix issues with React Hooks #1268

Merged
merged 11 commits into from
Apr 27, 2020
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,6 @@ npm-debug.*
/gen-examples

.DS_Store

.yalc
yalc.lock
1 change: 0 additions & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
node_modules/
package.json
tmp/
public/webpack/
coverage/
**/app/assets/webpack/
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ install:
- node -v
- travis_retry npm i -g yarn
- travis_retry bundle install
- travis_retry yarn global add yalc
- travis_retry yarn
- travis_retry yarn run build
- bundle exec rake prepare_for_ci
Expand Down
14 changes: 12 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,25 @@ Changes since last non-beta release.

*Please add entries here for your pull requests that are not yet released.*

### [12.0.0]
#### BREAKING CHANGE
In order to solve the issues regarding React Hooks compatability, the number of parameters
for functions is used to determine if you have a generator function that will get invoked to
return a React component, or you are registering a functional React component. Alternately, you can
set JavaScript property `renderFunction` on the function for which you want to return to be
invoked to return the React component. In that case, you won't need to pass any unused params.
[PR 1268](https://github.com/shakacode/react_on_rails/pull/1268) by [justin808](https://github.com/justin808)

See [docs/basics/upgrading-react-on-rails](./docs/basics/upgrading-react-on-rails#upgrading-to-v12)
for details.

* Requires the use of rails/webpacker helpers
* Removed **env_javascript_include_tag** and **env_stylesheet_link_tag** as these are replaced by view helpers
from rails/webpacker
* Removal of support for old Rubies and Rails.
* Removal of config.symlink_non_digested_assets_regex as it's no longer needed with rails/webpacker.
If any business needs this, we can move the code to a separate gem.



### [11.3.0] - 2019-05-24
#### Added
- Added method for retrieving any option from `render_options` [PR 1213](https://github.com/shakacode/react_on_rails/pull/1213)
Expand Down
27 changes: 17 additions & 10 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,16 +97,26 @@ In addition to testing the Ruby parts out, you can also test the node package pa
```sh
cd react_on_rails/
yarn
yarn run build
yarn install-react-on-rails
yarn run build:watch
yalc pubish react-on-rails
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

publish

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @tahsin352 for your review!!!!

@Judahmeek did we get all of these?

```

Install the local package by using yarn link, like this:
Install the local package by using yalc, like this:
```sh
cd spec/dummy
yalc link react-on-rails
yarn
```

Make changes to the node package.

Then run:

```
cd <top dir>
yalc push
```

Note, yarn will run the `postinstall` script of `spec/dummy/client` which runs `yarn link` to set up a sym link to the parent package.

#### Example: Testing NPM changes with the dummy app
Expand All @@ -128,15 +138,12 @@ _Note: running `npm i` automatically builds the npm package before installing. H
After checking out the repo, making sure you have rvm and nvm setup (setup ruby and node), cd to `spec/dummy` and run `bin/setup` to install ruby dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.

### Local Node Package
Because the example and dummy apps rely on the react-on-rails node package, they should link directly to your local version to pick up any changes you may have made to that package. To achieve this, switch to the dummy app's root directory and run this command below which runs something like [this script](spec/dummy/package.json#L14)
The default setup of spec/dummy uses a yarn link to the `/node_package/lib` directory. To test changes
to the node module of react-on-rails, run this from the top directory `yarn run build-watch`.

```sh
cd react_on_rails/spec/dummy
yarn run install-react-on-rails
```
_Note: this runs npm under the hood as explained in **Test NPM for react-on-rails** section above_
Then you just need to run `yarn` in the `spec/dummy` directory.

From now on, the example and dummy apps will use your local node_package folder as the react-on-rails node package. This will also be done automatically for you via the `rake examples:gen_all` rake task.
Note, the example and dummy apps will use your local node_package folder as the react-on-rails node package. This will also be done automatically for you via the `rake examples:gen_all` rake task.

*Side note: It's critical to use the alias section of the webpack config to avoid a double inclusion error. This has already been done for you in the example and dummy apps, but for reference:*

Expand Down
36 changes: 27 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,13 +161,14 @@ Below is the line where you turn server rendering on by setting `prerender` to t
<%= react_component("HelloWorld", props: { name: "Stranger" }) %>
```

- This is what your HelloWorld.js file might contain. The railsContext is always available for any parameters that you _always_ want available for your React components. It has _nothing_ to do with the concept of the [React Context](https://reactjs.org/docs/context.html). See [Generator Functions and the RailsContext](docs/basics/generator-functions-and-railscontext.md) for more details on this topic.
- This is what your HelloWorld.js file might contain. The railsContext is always available for any parameters that you _always_ want available for your React components. It has _nothing_ to do with the concept of the [React Context](https://reactjs.org/docs/context.html). See [render functions and the RailsContext](docs/basics/render-functions-and-railscontext.md) for more details on this topic.

```js
import React from 'react';

export default (props, railsContext) => {
return (
// Note wrap in a function to make this a React function component
return () => (
<div>
Your locale is {railsContext.i18nLocale}.<br/>
Hello, {props.name}!
Expand Down Expand Up @@ -227,21 +228,38 @@ Another way is to use a separate webpack configuration file that can use a diffe

For details on techniques to use different code for client and server rendering, see: [How to use different versions of a file for client and server rendering](https://forum.shakacode.com/t/how-to-use-different-versions-of-a-file-for-client-and-server-rendering/1352). (_Requires creating a free account._)

## Specifying Your React Components: Direct or Generator Functions
## Specifying Your React Components: Direct or render functions

You have two ways to specify your React components. You can either register the React component directly, or you can create a function that returns a React component. Creating a function has the following benefits:
You have two ways to specify your React components. You can either register the React component (either function or class component) directly, or you can create a function that returns a React component, which we using the name of a "render function". Creating a function has the following benefits:

1. You have access to the `railsContext`. See documentation for the railsContext in terms of why you might need it. You **need** a generator function to access the `railsContext`.
1. You have access to the `railsContext`. See documentation for the railsContext in terms of why you might need it. You **need** a render function to access the `railsContext`.
2. You can use the passed-in props to initialize a redux store or set up react-router.
3. You can return different components depending on what's in the props.

ReactOnRails will automatically detect a registered generator function. Thus, there is no difference between registering a React Component versus a "generator function."
Note, the return value of a **render function** should be JSX or an HTML string. Do not return a
function.

## react_component_hash for Generator Functions
ReactOnRails will automatically detect a registered render function by the fact that the function takes
more than 1 parameter. In other words, if you want the ability to provide a function that returns the
React component, then you need to specify at least a second parameter. This is the `railsContext`.
If you're not using this parameter, declare your function with the unused param:

Another reason to use a generator function is that sometimes in server rendering, specifically with React Router, you need to return the result of calling ReactDOMServer.renderToString(element). You can do this by returning an object with the following shape: { renderedHtml, redirectLocation, error }. Make sure you use this function with `react_component_hash`.
```js
const MyComponentGenerator = (props, _railsContext) => {
if (props.print) {
// Wrap in a function so this is a React Component
return () => <H1>{JSON.stringify(props)}</H1>;
}
}
```

Thus, there is no difference between registering a React function or class Component versus a "render function."

## react_component_hash for render functions

Another reason to use a render function is that sometimes in server rendering, specifically with React Router, you need to return the result of calling ReactDOMServer.renderToString(element). You can do this by returning an object with the following shape: { renderedHtml, redirectLocation, error }. Make sure you use this function with `react_component_hash`.

For server rendering, if you wish to return multiple HTML strings from a generator function, you may return an Object from your generator function with a single top-level property of `renderedHtml`. Inside this Object, place a key called `componentHtml`, along with any other needed keys. An example scenario of this is when you are using side effects libraries like [React Helmet](https://github.com/nfl/react-helmet). Your Ruby code will get this Object as a Hash containing keys componentHtml and any other custom keys that you added:
For server rendering, if you wish to return multiple HTML strings from a render function, you may return an Object from your render function with a single top-level property of `renderedHtml`. Inside this Object, place a key called `componentHtml`, along with any other needed keys. An example scenario of this is when you are using side effects libraries like [React Helmet](https://github.com/nfl/react-helmet). Your Ruby code will get this Object as a Hash containing keys componentHtml and any other custom keys that you added:

```js
{ renderedHtml: { componentHtml, customKey1, customKey2} }
Expand Down
2 changes: 1 addition & 1 deletion SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
+ [Client vs. Server Rendering](./docs/basics/client-vs-server-rendering.md)
+ [React Server Rendering](./docs/basics/react-server-rendering.md)
+ [Recommended Project Structure](./docs/basics/recommended-project-structure.md)
+ [Generator Functions and the RailsContext](docs/basics/generator-functions-and-railscontext.md)
+ [Render-Functions and the RailsContext](docs/basics/render-functions-and-railscontext.md)
+ [Caching and Performance: React on Rails Pro](https://github.com/shakacode/react_on_rails/wiki).
+ [Deployment](docs/basics/deployment.md).
+ [React on Rails Internationalization (I18n, localization)](docs/basics/i18n.md)
Expand Down
40 changes: 30 additions & 10 deletions docs/additional-reading/react-helmet.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
# Using React Helmet to build `<head>` content

## Installation and general usage
See https://github.com/nfl/react-helmet for details. Run `yarn add react-helmet` in your `client` directory to add this package to your application.
See [nfl/react-helmet](https://github.com/nfl/react-helmet) for details on how to use this package.
Run `yarn add react-helmet` to add this package to your application.

## Example
Here is what you need to do in order to configure your Rails application to work with **ReactHelmet**.

Create generator function for server rendering like this:
Create a render-function for server rendering like this:

```javascript
export default (props, _railsContext) => {
Expand All @@ -20,17 +21,35 @@ export default (props, _railsContext) => {
return { renderedHtml };
};
```
You can add more **helmet** properties to result, e.g. **meta**, **base** and so on. See https://github.com/nfl/react-helmet#server-usage.
You can add more **helmet** properties to the result, e.g. **meta**, **base** and so on. See https://github.com/nfl/react-helmet#server-usage.

Use regular component or generator function for client-side:
Use a regular React functional or class component or a render-function for your client-side bundle:

```javascript
export default (props, _railsContext) => (
// React functional component
export default (props) => (
<App {...props} />
);
```

Put **ReactHelmet** component somewhere in your `<App>`:
Or a render-function. Note you can't return just the JSX (React element), but you need to return
either a React functional or class component.
```javascript
// React functional component
export default (props, railsContext) => (
() => <App {{railsContext, ...props}} />
);
```

Note, this doesn't work, because this function just returns a React element rather than a React component
```javascript
// React functional component
export default (props, railsContext) => (
<App {{railsContext, ...props}} />
);
```

Put the **ReactHelmet** component somewhere in your `<App>`:
```javascript
import { Helmet } from 'react-helmet';

Expand All @@ -55,15 +74,18 @@ ReactOnRails.register({
});
```
```javascript
// Note the import from the server file.
import ReactHelmetApp from '../ReactHelmetServerApp';

ReactOnRails.register({
ReactHelmetApp
});
```
Now when the `react_component_hash` helper is called with **"ReactHelmetApp"** as a first argument it will return a hash instead of HTML string:
Now when the `react_component_hash` helper is called with **"ReactHelmetApp"** as a first argument it
will return a hash instead of HTML string. Note, there is no need to specify "prerender" as it would not
make sense to use react_component_hash without server rendering:
```ruby
<% react_helmet_app = react_component_hash("ReactHelmetApp", prerender: true, props: { hello: "world" }, trace: true) %>
<% react_helmet_app = react_component_hash("ReactHelmetApp", props: { hello: "world" }, trace: true) %>

<% content_for :title do %>
<%= react_helmet_app['title'] %>
Expand All @@ -76,5 +98,3 @@ So now we're able to insert received title tag to our application layout:
```ruby
<%= yield(:title) if content_for?(:title) %>
```

Note: Use of `react_component` for this functionality is deprecated. Please use `react_component_hash` instead.
5 changes: 4 additions & 1 deletion docs/additional-reading/react-router.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
_This article needs updating for the latest version of React Router_

# Using React Router


React on Rails supports the use of React Router. Client-side code doesn't need any special configuration for the React on Rails gem. Implement React Router how you normally would. Note, you might want to avoid using Turbolinks as both Turbolinks and React-Router will be trying to handle the back and forward buttons. If you get this figured out, please do share with the community! Otherwise, you might have to tweak the basic settings for Turbolinks, and this may or may not be worth the effort.

If you are working with the HelloWorldApp created by the react_on_rails generator, then the code below corresponds to the module in `client/app/bundles/HelloWorld/startup/HelloWorldApp.jsx`.
Expand Down Expand Up @@ -36,7 +39,7 @@ For a fleshed out integration of react_on_rails with react-router, check out [Re

# Server Rendering Using React Router V4

Your generator function may not return an object with the property `renderedHtml`. Thus, you call
Your render function may not return an object with the property `renderedHtml`. Thus, you call
renderToString() and return an object with this property.

This example **only applies to server rendering** and should be only used in the server side bundle.
Expand Down
19 changes: 12 additions & 7 deletions docs/additional-reading/server-rendering-tips.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
# Server Rendering Tips

Be sure to use mini_racer. See [issues/428](https://github.com/shakacode/react_on_rails/issues/428)
For the best performance with Server Rendering, consider using [React on Rails Pro]

Be sure to use mini_racer. See [issues/428](https://github.com/shakacode/react_on_rails/issues/428).



## General Tips
- Your code can't reference `document`. Server side JS execution does not have access to `document`, so jQuery and some
other libs won't work in this environment. You can debug this by putting in `console.log`
statements in your code.
- You can conditionally avoid running code that references document by passing in a boolean prop to your top level react
component. Since the passed in props Hash from the view helper applies to client and server side code, the best way to
do this is to use a generator function.
- Your code can't reference `document`. Server side JS execution does not have access to `document`,
so jQuery and some other libs won't work in this environment. You can debug this by putting in
`console.log` statements in your code.
- You can conditionally avoid running code that references document by either checking if `window`
is defined or using the "railsContext"
your top level react component. Since the passed in props Hash from the view helper applies to
client and server side code, the best way to do this is to use a render function.
- If you're serious about server rendering, it's worth the effort to have different entry points for client and server rendering. It's worth the extra complexity. The point is that you have separate files for top level client or server side, and you pass some extra option indicating that rendering is happening server side.
- You can enable Node.js server rendering via [React on Rails Pro](https://github.com/shakacode/react_on_rails/wiki).

Expand Down
6 changes: 3 additions & 3 deletions docs/api/javascript-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ The best source of docs is the main [ReactOnRails.js](https://github.com/shakaco
/**
* Main entry point to using the react-on-rails npm package. This is how Rails will be able to
* find you components for rendering. Components get called with props, or you may use a
* "generator function" to return a React component or an object with the following shape:
* "render function" to return a React component or an object with the following shape:
* { renderedHtml, redirectLocation, error }.
* For server rendering, if you wish to return multiple HTML strings from a generator function,
* you may return an Object from your generator function with a single top level property of
* For server rendering, if you wish to return multiple HTML strings from a render function,
* you may return an Object from your render function with a single top level property of
* renderedHtml. Inside this Object, place a key called componentHtml, along with any other
* needed keys. This is useful when you using side effects libraries like react helmet.
* Your Ruby code with get this Object as a Hash containing keys componentHtml and any other
Expand Down
4 changes: 2 additions & 2 deletions docs/api/redux-store-api.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# Redux Store

_This redux API is no longer recommended as it prevents dynamic code splitting for performance. Instead, you should use the standard react_component view helper passing in a "generator function."_
_This redux API is no longer recommended as it prevents dynamic code splitting for performance. Instead, you should use the standard react_component view helper passing in a "render function."_

You don't need to use the `redux_store` api to use redux. This api was setup to support multiple calls to `react_component` on one page that all talk to the same redux store.

If you are only rendering one react component on a page, as is typical to do a "Single Page App" in React, then you should _probably_ pass the props to your React component in a "generator function."
If you are only rendering one react component on a page, as is typical to do a "Single Page App" in React, then you should _probably_ pass the props to your React component in a "render function."

Consider using the `redux_store` helper for the two following use cases:

Expand Down
Loading