backbone.router and React states - javascript

What is the standard way of setting React Component states based on routes? I have the following React/Backbone example:
var myApp = React.createClass({
render: function() {
return (
<div className={this.state}></div>
);
}
})
var App = Backbone.Router.extend({
routes: {
"": "home",
"create": "create"
}
});
var app = new App();
app.on('route', function(page) {
// How do I set state on myApp??
})
React.renderComponent(<myApp />, document.body);
Backbone.history.start();
I believe I need to be able to set the state of myApp from outside, but how? I can’t find any examples on this.
Or maybe I’m thinking in the wrong direction here, is there a better way of organizing routes together with React?

i have no idea what the general solution is, but what I, a bit simplified, did is
var App = Backbone.Router.extend({
routes: {
"": "home",
"create": "create"
}
});
var myComponent = false
function loadOrUpdateComponent(urlState) {
if (!myComponent) {
myComponent = <MyApp urlState={urlState}/>
React.renderComponent(myComponent, document.body);
} else {
myComponent.setProps({urlState:urlState})
}
}
var app = new App();
app.on('route', function(page) {
loadOrUpdateComponent(page)
})
Backbone.history.start();
So the actual answer to your question is to use .setProps(newProps) on a previously rendered component. i load the component in the event handler because else you get a race condition between renderComponent and setProps wich might lead to bad things.
Edit:
Ive since updated my route handling, i now simply do
router.on('list', function() {
React.renderComponent(<Component data={someData} />, mountPoint)
})
router.on("details", function(id) {
React.renderComponent(<Component data={someData} selected={id} />, mountPoint)
})
router.on("extra-details", function(id) {
React.renderComponent(
<Component data={someData} selected={id}>
<SpecificExtraComponent />
</Component>
, mountPoint
)
})

Related

ReactJs: Adding an element to the DOM tree

How can you add an item directly to the main DOM? Using Editor.js, I created a custom block. However, the custom block is created on a seperate DOM tree which makes it inaccessible with Redux.
This is a screenshot of the DOM tree: https://snipboard.io/vrbZLt.jpg
Index.js where the main DOM is rendered:
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>,
document.getElementById('root')
);
This is where the custom block was created:
export default class Checklist {
static get toolbox() {
return {
icon: 'SVG Code...'
title: 'Checklist',
};
}
static get isReadOnlySupported() {
return true;
}
constructor({ data, config, api, readOnly }) {
this.api = api;
this.readOnly = readOnly;
this.data = {
events: data.events || [],
};
this.CSS = {
wrapper: 'walkthrough-timeline',
};
this.nodes = {
holder: null,
};
}
render() {
const rootNode = document.createElement("div");
rootNode.setAttribute("class", this.CSS.wrapper);
this.nodes.holder = rootNode;
const onDataChange = (newData) => {
this.data = {
...newData,
};
};
ReactDOM.render(
<ChecklistTool
onDataChange={onDataChange}
readOnly={this.readOnly}
data={this.data}
isAdmin={true}
/>,
rootNode
);
return this.nodes.holder;
}
save() {
return this.data;
}
}
I realize that document.createElement("div") will create a new DOM. I'm just not sure how to create the ChecklistTool component inside of the main DOM so it gets access to Redux. Any ideas on how to make this happen? Thank you!
Two things might put you in the right direction.
Ref
The way to access the existing DOM tree is through a ref.
const ref = useRef()
return <div ref={ref}>Hello</div>
You can apply all your DOM method via ref.current
Render
Render function is called every time, this is not good, because you don't want to handle the DOM in every render. Instead you have to look for some startup or cleanup location. Use function component as a example.
useEffect(() => {
ref.current && ref.current.textContent = "Hello World"
return () => {
ref.current.textContent = "Notitle"
}
}, [])
The above will set the title once after the component mount and set the title back after unmount.
Based on the above two points, you can give it shot for your own logic.

How can I inject a common code in components in React

For multiple React component , I want to inject a common code to the life cycle of React.
Is there something good way?
var ComponentA = React.createClass({
componentWillMount: function () {
},
componentDidUpdate: function(){
//inject common code
},...
var ComponentB = React.createClass({
componentWillMount: function () {
},
componentDidUpdate: function(){
//inject common code
},...
Do you mean just sharing functions across multiple components? If so, you can just keep them in a separate file and import them where ever you need to:
// common js
function hello() {
console.log('hello');
}
module.exports = hello;
// your component
var hello = require('./common');
var ComponentA = React.createClass({
componentDidUpdate: function(){
hello();
},//...
http://www.webpackbin.com/Nk80m1x_W
Another thing you can do is create a wrapper (higher order) component:
var WrapperComponent = React.createClass({
componentDidUpdate: function() {
// code you want to inject.
},
render: function () {
return(<div>{this.props.children}</div>);
}
});
then whenever you need to use a component with that lifecycle, you can do this in jsx:
<WrapperComponent>
<ComponentA />
</WrapperComponent>
Though higher order component approach suggested by #jzm is better, you could also use mixins:
var myMixin = {
componentWillMount: function(){
//common code
},
componentDidUpdate: function(){
//common code
}
};
var ComponentA = React.createClass({
mixin: [myMixin]
});
var ComponentB = React.createClass({
mixin: [myMixin]
})

React component this.props is always empty

I've followed a couple of examples in an attempt to get access to a parameter from a Route in the React component that handles it. However the result of console.log on this.props from inside the render or componentDidMount is always {} when I'd expect it to contain the gameId from the gamesView route.
client.js which starts the Router:
// HTML5 History API fix for local
if (config.environment === 'dev') {
var router = Router.create({ routes: routes });
} else {
var router = Router.create({ routes: routes, location: Router.HistoryLocation });
}
router.run(function(Handler) {
React.render(
<Handler />,
document.getElementById('app')
);
});
routes.js with some routes removed for simplicity:
var routes = (
<Route name='home' path='/' handler={app}>
<DefaultRoute handler={home} location="/" />
<Route name='gamesView' path='/games/:gameId' handler={gamesView} />
</Route>
);
module.exports = routes;
...and app.js which wraps the other routes, I've tried it both with and without {...this.props} in the RouteHandler. If I console.log(this.props) from inside the render function here is also returns {}:
var App = React.createClass({
render: function() {
return (
<div className='container'>
<div className='row'>
<RouteHandler {...this.props} />
</div>
</div>
);
}
});
module.exports = App;
Finally the gamesView React component that I expect to see the props object. Here this.props is also {} and the following results in the error TypeError: $__0 is undefined var $__0= this.props.params,gameId=$__0.gameId;:
var GamesView = React.createClass({
render: function() {
var { gameId } = this.props.params;
return (
<div>
<h1>Game Name</h1>
<p>{gameId}</p>
</div>
);
}
});
module.exports = GamesView;
Anybody have any ideas?
You won't see those params until you are at the component defined in your router. App won't know anything about them. If you put the console.log(this.props.params) in your gamesView component, however, you should see them.
After discussing on the React Router (RR) Github it turned out this was due to me using an older version of RR (v0.11.6).
Looking at the example in the docs for that release it showed that I needed to use a Router.State mixin and then get the expected param via var gameId = this.getParams().gameId;.
Without upgrading RR the working version of my original example for GamesView becomes:
var React = require('react');
var Router = require('react-router');
var { Route, RouteHandler, Link } = Router;
var GamesView = React.createClass({
mixins: [ Router.State ],
render: function() {
var gameId = this.getParams().gameId;
return (
<div>
<h1>Game Name</h1>
<p>{gameId}</p>
</div>
);
}
});
module.exports = GamesView;

react.js: removing a component

I'm fairly new at react.js, so any help is greatly appreciated.
I have this: https://jsfiddle.net/rzjyhf91/
Wherein I have made 2 components: an image and a button.
The goal is to remove the image with a click of the button, I use unmountComponentAtNode for that, but it does not work:
var App = React.createClass({
render: function() {
return (
<div><MyImage /><RemoveImageButton /></div>
);
}
});
var MyImage = React.createClass({
render: function() {
return (
<img id="kitten" src={'http://placekitten.com/g/200/300'} />
);
}
});
var RemoveImageButton = React.createClass ({
render: function() {
return (
<button onClick={this.handleClick}>remove image</button>
)
},
handleClick: function(){
React.unmountComponentAtNode(document.getElementById('kitten'));
}
});
React.render(<App />, document.body);
How can I remove a react component from another component?
Well, it seems you should rethink how the display control is handled. React is all about isolated components, and so, you shouldn't be unmounting a component that is mounted by a parent component. Instead, you should use a callback passed down through props to accomplish something like that.
Your actual implementation will depend on your use case, but an updated version of your example that works is at: https://jsfiddle.net/nt99zzmp/1/
var App = React.createClass({
render: function() {
var img = this.state.showImage ? <MyImage /> : '';
return (
<div>{img}<RemoveImageButton clickHandler={this.removeImage} /></div>
);
},
getInitialState: function() {
return {
showImage: true
};
},
removeImage: function() {
this.setState({ showImage: false });
}
});
var MyImage = React.createClass({
render: function() {
return (
<img id="kitten" src={'http://placekitten.com/g/200/300'} />
);
}
});
var RemoveImageButton = React.createClass ({
render: function() {
return (
<button onClick={this.props.clickHandler}>remove image</button>
)
}
});
React.render(<App />, document.body);
Basically removing a component doesn't make sense in React, you probably still thinking jQuery ways, basically in all modern and new JavaScript libraries including React, you should manage your component using state or a route to handle these things, deleting an element or component is not a good way to do these things in React or Angular for example.
For example you can have a boolean in this case and if it's true, show your image, otherwise hide it, or even return a different element in your component.
So in this case, you have a component which will return differently depends on props or state... something like this:
////
var MyImage = React.createClass({
render: function() {
if(this.state.showImage) {
return (
<img id="kitten" src={'http://placekitten.com/g/200/300'} />
);
} else {
return<p>no image!</p>;
}
}
});
////
In this example, if you set this.state.render = false, the component will be removed from DOM:
render() {
const { render } = this.state;
if (render === false) return null;
return (<p>I am here as long as render isn't false</p>);
}

React Router: cannot pass props to activeRouteHandler

I'm trying to pass some props down to my handled component however react-router fails to do so.
var Objects = React.createClass({
getInitialState: function() {
return {
selected: "All"
}
},
select: function(opt) {
this.setState({
selected: opt
});
},
render: function() {
return (
<div>
<LeftNav select={this.select} />
<this.props.activeRouteHandler selected={this.state.selected} />
</div>
);
}
});
var routes = (
<Routes>
<DefaultRoute name="objects" handler={objecctHandler}/>
</Routes>
);
The router loads fine as I can see '#/' in the url now. The Left nav renders fine and updates the state as well. however there is not props.selected in the handeded component namely objectHandler. Am I missing something here? Thanks.
I'm using react-router 0.7.0
Try making the DefaultRoute for 'objectHandler' a child of another route that defines 'Objects' as the handler. Such as:
var routes = (
<Routes>
<Route handler={Objects}>
<DefaultRoute name="objects" handler={objectHandler}/>
</Route>
</Routes>
);
jsfiddle: http://jsfiddle.net/gq1uym5y/1/
What I'm using right now is something like this.
One top level route that just routes to the App component:
React.renderComponent(
<Routes>
<Route handler={App}>
<Route path="/todos" handler={TodoList} />
</Route>
</Routes>
, mountNode);
The App component looks like this. I pass a Container to all subroutes (ie to the TodoList route). This container contains the list of todos (and anything else I need in the app, including methods for adding/persisting new Todos). This helps with keeping state at the top level and decouple subcomponents. Since the App component is used for every route, it never unmounts, so it doesn't loose its state.
var Container = function(app) {
return {
getTodos: function() {
return app.state.todos;
}
};
};
var App = React.createClass({
getInitialState: function() {
return {
todos: ['Buy milk', 'Call Mike']
};
},
componentWillMount: function() {
this.container = Container(this);
},
render: function() {
return <this.props.activeRouteHandler container={this.container} />;
}
});
Here's what the TodoList looks like. It's actually two components: TodoList and TodoListInner. The TodoListInner remains clean and testable. The TodoList itself is not so easily testable, but since it just wraps around the inner component, this shouldn't be much of a problem.
var TodoListInner = React.createClass({
render: function() {
<ul>
{this.props.todos.map(function(todo) {
return <li>{todo}</li>;
})}
</ul>
}
})
var TodoList = React.createClass({
render: function() {
<TodoListInner todos={this.props.container.getTodos()} />
}
});
It's a little more complex than jsmiff's solution but does the job with some added benefits.

Categories