Redirection with React Router Component - reactjs

I'm having a problem with react-router-component. I'm trying to make a redirect library that does a "soft" redirect (equivalent to clicking a link, as opposed to window.location = ...) that I can require from a .jsx component.
Per the docs I should just be able to call router.navigate(path) to redirect.
When, for example, I call this on page A, the url in the address bar changes to page B as desired. However, page A simply reloads, leaving the address bar as page B and the display as page A. Suggestions?

You should be able to solve this using the Navigation mixin.
Then you can use this.transitionTo with the name of your page.
var PageAdd = React.createClass({
mixins : [Router.Navigation],
handleSubmit : function(e) {
e.preventDefault();
this.transitionTo('pages');
},
render: function () {
return (
<form onSubmit={this.handleSubmit}>
<button type="submit" className="btn btn-default">Submit</button>
</form>
);
}
});
Read more: https://github.com/rackt/react-router/blob/master/docs/api/mixins/Navigation.md

We ended up doing a hacky solution: calling the following when a redirect should happen, where path is something like /questions/23.
// call an event to get the router from somewhere else that's listening
// to this event specifically to provide the router
document.dispatchEvent(new CustomEvent('router.get', {
detail: {
callback: function(router) {
router.navigate(path);
}
}
}));
Then, in the router, the event listener:
var App = React.createClass({
listenForRouter: function listenForRouter () {
var self = this;
document.addEventListener('router.get', function(e) {
setTimeout(function() {
e.detail.callback(self
.refs.router
.refs.locations);
}, 1);
});
},
componentDidMount: function componentDidMount () {
this.listenForRouter();
},
// additional lifecycle methods
});

Not sure if this really answers the question, and an answer has already been accepted but I would use link from react-router:
in the routes you define names (this from a music database page:
var routes = (
<Route name="app" path="/" handler={require('./components/app')}>
<DefaultRoute handler={require('./components/homePage')}/>
<Route name="artist" handler={require('./components/artist/artistsPage')}/>
<Route name="artistSearch" path="artist/:artistSearchName" handler={require('./components/artist/artistsPage')}/>
<Route name="artistSearchPage" handler={require('./components/artist/manageArtistSearchPage')}/>
<Route name="album" handler={require('./components/album/albumsPage')}/>
<Route name="searchAlbums" path="album/:artistId" handler={require('./components/album/albumsPage')}/>
<Route name="song" handler={require('./components/song/songsPage')}/>
<NotFoundRoute handler={require('./components/pageNotFound')}/>
</Route>
);
and then in your component import Link from React-router:
var Link = require('react-router').Link; //(ES5)
import {link as Link} from 'react-router'; //(ES6)
and then you can use as the tag and it will work like an a tag with href = "something"
<div>
<h1>Artist Search Page</h1>
<ArtistSearchForm
artistName={this.state.artistName}
onChange={this.onArtistChangeHandler}
/>
<br/>
<Link to="artistSearch" params={{artistSearchName: this.state.artistName}} className="btn btn-default">Search</Link>
</div>
I also used it in a list to make the artist name a link to there albums page.

This is not well-documented, but it is possible to perform a soft redirect using the navigate(path) method of the environment.defaultEnvironment object.
import {environment} from 'react-router-component'
...
environment.defaultEnvironment.navigate("/");
This doesn't take into account the current routing context though, so it won't allow you to redirect to paths relative to inner routers. For an approach that respects the routing context, see NavigatableMixin. Mixins are not an option for my solution, since I'm using ES6 classes, but the implementation could be copied from there.

Related

ReactJS: Create components, not using render for non-react libraries

Can you, using React, create router Links (or other components) from data returned from ajax-calls? NOT using the render function.
Background: We have a large 3rd party non-react javascript library that dynamically renders HTML from an AJAX call. We control the input (i.e. the ajax-response), but not the output. I want to input links (a href) and get them rendered as React Router Links. To wrap the non-react component I have created a component which basically have two parts: componentDidMount where I initiate the components and render where I output a div (for the 3rd party javascript library). Ideally we would like to inject reactJS component directly from the ajax-response:
[{'textToRender': '<Link to="/home" title="home">home</Link>'}]
Unfortunately this would only render the text:
<Link to="/home" title="home">home</Link>
Any idea if this is possible?
UPDATE: The non-react component is somewhat complex but for the sake of this question let us say it takes one ajax-url parameter and renders output to that div. The nonreact component expects HTML input from the AJAX, but I have the ability to execute javascript before injecting the HTML into that component.
I.e. the non-react component fetches data from the AJAX call
and update its div.
So the init looks like this:
componentDidMount() {
$('#nonreact').NonReact({ 'ajax': 'http://someurl..', 'transform' : function(data) { //here I can run some JS-code prior to nonrect render; return data; } });
}
And my component render looks like this:
render() {
return (
<div id="nonreact"></div>
)
}
You could do something like this:
componentDidMount() {
$('#nonreact').NonReact({
ajax: 'http://someurl..',
transform: (data) => {
// 1. transform data to some intermediate format
// {to: '/home', title: 'home', text: 'home'}
const {text, ...props} = transform(data);
// 2. create React element and set it to `link` state
this.setState({
link: React.createElement(Link, props, text);
});
}
});
}
render() {
// 3. Use {this.state.link} here
...
}
In React, <Link to="/home" title="home">home</Link> is just the sugar for
React.createElement(
Link,
{ to: "/home", title: "home" },
"home"
);
It does is transformed into that before your code is executed. Simply placing the text <Link to="/home" title="home">home</Link> won't do anything special.
You will probably make your life easier if the response is more like
[{'textToRender': {to: "/home" title: "home" text: "home }}]
Then, in a component that actually renders these you would iterate over these and create Links for that, in render
render: function() {
var linksResponse = ... // how ever you get them
var elements = linkResponses.map(function(entry) {
return Object.keys(entry).map(function(text) {
var linkData = entry[text];
// real action happens here
var link = <Link to={linkData.to} title={linkData.title}>{linkData.text}</Link>;
return link;
});
});
return elements;
}
It's absolutely possible. At my current project I fetch a large chunk of markup from an API. This is normal HTML with a few custom tags in it. These custom tags have corresponding React components in my app. What you want to to is make us of a library such as html-to-react (https://github.com/mikenikles/html-to-react). The way this works is that it parses the markup string and creates an object that represents the DOM for that markup. This object is in turn rendered to a React element. You can have custom tag handlers, telling html-to-react what to do when it finds a non-standard tag in the markup.
It works something like this:
let markupstring = '<div>lots of markup, 100kb</div>' // String
let parsedMarkup = htmlparser.parseWithInstructions(markupstring, function(){return true;}, processingInstructions);
How to configure prosessingInstructions is documented in html-to-react documentation. parsedMarkup is a valid React element.

React-router: how to get previous path using Link

sorry for my poor English..my question is,when i use <Link/> to change current route into next route,how i can get previous path in next route's handler? here is some code
// route config
<Route handler={Route2} name="route2" path="route2" />
// eg: current location is #/route1?key=value
<Link to="route2">click to jump to route2</Link>
// route2
var Route2 = React.createClass({
componentDidMount: function() {
// how i can get previous path '#/route1?key=value'
},
render: function() {
return (
<div />
)
}
})
thx ~ :-)
Apparently there's no sanctioned way to do that.
As suggested in this issue, you can save the previous path in router.run.
var prevPath = null;
router.run(function(Handler, state) {
React.render(<Handler prevPath={prevPath} />, document.body);
prevPath = state.path;
});
Beware, however, that this will point "forward" after using the browser back button.

React Router with RefluxJS - Changing route Programmatically from a Store

In my project I deiced to include React Router. One of my Reflux Stores need to figure out the path based on some BL and than change it. First I've tried including the Navigation mixin inside the Store and running this.transitionTo("Foo"); which threw an error : "Uncaught TypeError: Cannot read property 'router' of undefined".
Someone suggested that : "this.transitionTo is probably accessing the router property through contexts (this.context.router) which do not exist in RefluxJS stores" ... Which I understand.
However there must be a way to change the router path from a Store Programmatically, or any given external JS Module.
My Code goes something like this:
// routes.js
//////////////////////////////////////////////////////////
var Router = require('react-router');
var Route = Router.Route;
var App = require('./app');
var Comp = require('./components/comp');
var routes = (
<Route path='/' handler={App}>
<Route name='Foo' path='/foo' handler={Comp}/>
</Route>
);
module.exports = routes;
// main.js
//////////////////////////////////////////////////////////
var React = require('react');
var Router = require('react-router');
var routes = require('./Routes');
var appElement = document.getElementsByTagName('body');
Router.run(routes, Router.HistoryLocation, function(Root, state) {
React.render(<Root params={state.params} query={state.query} />, appElement);
});
// comp.js
//////////////////////////////////////////////////////////
var React = require("react");
var Reflux = require("reflux");
var Actions = require("../actions/actions.js");
var SomeStore = require("../stores/some-store.js");
var Comp = React.createClass({
render: function() {
return (
<h1>Hello World</h1>
);
}
});
module.exports = Comp;
// store.js
//////////////////////////////////////////////////////////
var SomeStore = Reflux.createStore({
onSomeAction: function() {
// CHANGE ROUTER PATH HERE TO /foo
}
});
module.exports = SomeStore;
Any help will be appreciated!
The router is only known by the components (React views). You need to pass the router from the context as a parameter in your action. That way, you can use this parameter to make a transition to a different route. I've been using it this way.
I have something like below in one of my action listeners in a store.
_onMyAction: function (router) {
MyApi.doSomething()
.then(function (id) {
// do something ...
router.transitionTo('myNewRoute', { ref: id });
})
.catch(function(message) {
// handle message
});
}
Reflux actions return a promise, so rather than doing this in the store (which IMO is wrong) you can do it in your component:
Actions.someAction().then(function() {
// route transition
});
Whether this is completely right... well, I'm not sure the community has really settled on an opinion.

Pass updated data to root component

here is my code :
var data = "hello";
var RootComponent = React.createClass({
render: function() {
return (
<p className="root">
{this.props.data}
</p>
);
}
});
React.renderComponent(
<RootComponent data="data" />,
document.getElementById('content')
);
I want to change the value of data(initially hello), so the page is refreshed with the new data value. But the function that change data is not in React, and I can't find a way to "plug" them. Any idea how to do so?
Thanks.
Listen on the change and call React.renderComponent() as mentioned by Felix. Per React's documentation:
Render a React component into the DOM in the supplied container and return a reference to the component.
If the React component was previously rendered into container, this
will perform an update on it and only mutate the DOM as necessary to
reflect the latest React component.
If the optional callback is provided, it will be executed after the
component is rendered or updated.
If you have the ability to look into using Flux Stores for your model layer I would strongly encourage you to do so.

Ractive ,Components and routing

Let's say I have one root Ractive on the page,and various widgest to show when an hypothetic backbone router navigate to a route :
var widget1=Ractive.extend({template:"<div>{{foo}}</div>"});
var widget2=Ractive.extend({template:"<div>{{bar}}</div>"});
var view=new Ractive({
template:"<nav></nav><widget />",
components:{widget:widget1}
});
var Router=Backbone.Router.extend({/* the code ... */})
so widget1 would be shown when I navigate to /widget1 and widget2 when the route is /widget2,
What would be the best way to swap widgets depending on the current route without creating seperate root Ractives or hiding/showing widgets? thanks.
An alternative solution to my previous suggestion, which allows routes to be set in a more dynamic fashion (i.e. without having to declare them in a template up-front):
<nav>...</nav>
<!-- we have a single <route> component representing all possible routes -->
<route current='{{currentRoute}}'/>
This could be implemented like so:
Ractive.components.route = Ractive.extend({
template: '<div class="container"></div>',
init: function () {
this.container = this.find( '.container' );
this.observe( 'current', function ( currentRoute ) {
var View = routes[ currentRoute ];
if ( this.view ) {
this.view.teardown();
}
this.view = new View({
el: this.container
});
});
}
});
Then, to switch routes:
router.on( 'route', function ( route ) {
ractive.set( 'currentRoute', route );
});
With this approach all you'd need to do is register all the possible routes at some point in your app:
routes.widget1 = Ractive.extend({template:"<div>{{foo}}</div>"});
...and so on. If necessary you could interact with each view object by retrieving a reference:
route = ractive.findComponent( 'route' );
route.view.on( 'someEvent', someEventHandler );
One way would be to explicitly include the components representing each route in the top-level template:
<nav>...</nav>
{{# route === 'widget1' }}
<widget1/>
{{/ route === 'widget1' }}
{{# route === 'widget2' }}
<widget2/>
{{/ route === 'widget2' }}
Then, you could do something like:
router.on( 'route', function ( route ) {
ractive.set( 'route', route );
});
This would tear down the existing route component and create the new one.
If your route components had asynchronous transitions, you could ensure that the new route didn't replace the old route until any transitions had completed by doing this:
router.on( 'route', function ( route ) {
ractive.set( 'route', null, function () {
ractive.set( 'route', route );
});
});
(Note that as of version 0.4.0, ractive.set() will return a promise that fulfils when any transitions are complete - though callbacks will still be supported.)
Having said all that I'd be interested to hear about any other patterns that people have had success with.

Resources