I built tabbing functionality in my web app using Links and an Outlet with subroutes
const { currentTab } = useLoaderData();
function tabClassName(tab: string) {
if (currentTab == tab) { return "is-active"; }
return "";
}
return ([
<div className="tabs is-centered m-5 pr-5 pl-5">
<ul>
<li className={tabClassName("tab1")}><Link to="tab1">one</Link></li>
<li className={tabClassName("tab2")}><Link to="tab2">two</Link></li>
<li className={tabClassName("tab3")}><Link to="tab3">three</Link></li>
</ul>
</div>,
<Outlet></Outlet>
]);
The loader function supplies the component with the current subroute like this:
const parts = new URL(request.url).pathname.split("/");
const tab = parts.pop() || parts.pop() || ""; // get final part of path /settings/tab1 -> tab1
if (tab === "settings") {
return redirect("/settings/tab1");
}
return json<LoaderData>({ currentTab: tab });
When I click the links the correct route is loaded instantly. However, the css class is only applied after clicking the same link a second time. I found out that the loader function is only executed after the second click. How can i force remix to make a server request again? or should i use client side js for this? I cant use the NavLink component because of the way my css framework is structured.
Thanks!
Related
I need to wrap a navigational component in React in a div that acts as a link/a (I have an <a></a> nested in the nav item already and can nest another <a></a> within. I need re-route the user to a new route on click of this outer most div that is acting as a wrapper. I have multiple navigational sections and want to use something like an onClick={this.handleNavClick(newRoute)} but am not having any success. I am console logging the correct linkPath but nothing happens on click.
Here is my function:
handleNavClick(linkPath) {
console.log(linkPath)
let currentPath = window.location.pathname;
currentPath = window.location.linkPath;
},
Here is an example of me trying to re-route from a nav section:
getNavItem() {
const currentPath = window.location.pathname;
const reportPath = "/reporting";
return (
<div onClick={this.handleNavClick(dashboardsPath)}>
<li id="nav-item-view" className={ classNames({"active": currentPath === reportPath}, "sidebar-item")}>
<div className="active-carat"></div>
</li>
</div>
);
},
You should try something like this:
onClick={() => {this.handleNavClick(dashboardsPath)}}
This is because onClick accepts a function, but here you're passing the result of a function.
You can do something like this
getNavItem() {
const currentPath = window.location.pathname;
const reportPath = "/reporting";
const handleNavClick = () => {
let currentPath = window.location.pathname;
currentPath = dashboardPath;
};
return (
<div onClick={handleNavClick}>
<li id="nav-item-view" className={ classNames({"active": currentPath === reportPath}, "sidebar-item")}>
<div className="active-carat"></div>
</li>
</div>
);
},
However I'd avoid creating a handler on every render for performance reasons. You should instead create a NavItem component with it's own handleNavClick method and dashboardPath prop.
I have a sub component that does not need to be loaded immediately that I want to split out. I am trying to conditionally load in a react component via require.ensure. I am not getting any console errors but I am also not seeing anything being loaded. Here is the code I am calling :
renderContentzones() {
if (this.props.display ) {
return require.ensure([], () => {
const Component = require('./content-zones/component.jsx').default;
return (
<Component
content={this.props.display}
/>
);
});
}
return null;
}
It is just rendering a blank screen currently (no errors). This previously worked when I used import 'displayComponent' from './content-zones/component.jsx' and just returned it like you normally would in react, instead of this require.ensure but. Not sure what I am doing wrong here, any idea how to make something like this work? Thanks!
This is one way to do it, using the state to show the dynamic loaded component:
constructor(){
this.state = {cmp:null};
}
addComponent() {
const ctx = this;
require.ensure(['../ZonesComponent'], function (require) {
const ZonesComponent = require('../ZonesComponent').default;
ctx.setState({cmp:<ZonesComponent />});
});
}
render(){
return (
<div>
<div>Some info</div>
<div><button onClick={this.addComponent.bind(this)}>Add</button></div>
<div>
{this.state.cmp}
</div>
</div>
);
}
When you press the button add the component will be shown.
Hope this help.
I am trying to copy this fiddle:
http://jsfiddle.net/jhudson8/135oo6f8/
(I also tried this example
http://codepen.io/adamaoc/pen/wBGGQv
and the same onClick handler problem exists)
and make the fiddle work for server side rendering, using ReactDOMServer.renderToString
I have this call:
res.send(ReactDOMServer.renderToString((
<html>
<head>
<link href={'/styles/style-accordion.css'} rel={'stylesheet'} type={'text/css'}></link>
</head>
<body>
<Accordion selected='2'>
<AccordionSection title='Section 1' id='1'>
Section 1 content
</AccordionSection>
<AccordionSection title='Section 2' id='2'>
Section 2 content
</AccordionSection>
<AccordionSection title='Section 3' id='3'>
Section 3 content
</AccordionSection>
</Accordion>
</body>
</html>
)));
the Accordion element looks like so:
const React = require('react');
const fs = require('fs');
const path = require('path');
const Accordion = React.createClass({
getInitialState: function () {
// we should also listen for property changes and reset the state
// but we aren't for this demo
return {
// initialize state with the selected section if provided
selected: this.props.selected
};
},
render: function () {
// enhance the section contents so we can track clicks and show sections
const children = React.Children.map(this.props.children, this.enhanceSection);
return (
<div className='accordion'>
{children}
</div>
);
},
// return a cloned Section object with click tracking and 'active' awareness
enhanceSection: function (child) {
const selectedId = this.state.selected;
const id = child.props.id;
return React.cloneElement(child, {
key: id,
// private attributes/methods that the Section component works with
_selected: id === selectedId,
_onSelect: this.onSelect
});
},
// when this section is selected, inform the parent Accordion component
onSelect: function (id) {
this.setState({selected: id});
}
});
module.exports = Accordion;
and the AccordionSection component looks like so:
const React = require('react');
const AccordionSection = React.createClass({
render: function () {
const className = 'accordion-section' + (this.props._selected ? ' selected' : '');
return (
<div className={className}>
<h3 onClick={this.onSelect}>
{this.props.title}
</h3>
<div className='body'>
{this.props.children}
</div>
</div>
);
},
onSelect: function (e) {
console.log('event:',e);
// tell the parent Accordion component that this section was selected
this.props._onSelect(this.props.id);
}
});
module.exports = AccordionSection;
everything works, and the CSS is working, but the problem is that the onClick doesn't get registered. So clicking on the accordion elements does nothing. Does anyone know why the onClick handler might not get registered in this situation?
React DOM render to string only sends the initial HTML as a string without any JS.
You need a client side react router as well which will attach the required JS handlers to the HTML based on their react-id's. The JS needs to run on both sides.
Universal rendering boilerplate for quick start. https://github.com/erikras/react-redux-universal-hot-example
Another question which is similar to yours. React.js Serverside rendering and Event Handlers
None of the hooks will register with ReactDOMServer.RenderToString. If you want to accomplish server side rendering + hooks on your react component, you could bundle it on the client (webpack, gulp, whatever), and then also use ReactDOMServer.RenderToString on the server.
Here's a blog post that helped me accomplish this:
https://www.terlici.com/2015/03/18/fast-react-loading-server-rendering.html
I have a Bootstrap implementation where the navigation bar is an unordered list with each items set to highlight when active. The highlight itself is styled using CSS but the check for active status is made using AngularJS:
<ul class="nav navbar-nav navbar-right">
<li ng-class="{ active: isActive('/blog')}">
<a onClick="pagetop()" href="#blog">BLOG</a>
</li>
</ul>
The isActive() method is defined in an AngularJS Controller as:
function HeaderController($scope, $location){
$scope.isActive = function (viewLocation) {
return viewLocation === $location.path();
};
}
As you can see, AngularJS simply adds an .active class to the <li> item if the linked URL is active. This is the class that is styled using CSS. This works fine when the currently open page is http://localhost:8888/a-s/#/blog/ but not when it's http://localhost:8888/a-s/#/blog/sub-page/ or http://localhost:8888/a-s/#/blog/sub-page/sub-sub-page/. How can I modify the code to ensure all paths under /blog trigger the .active class logic? Is there any way one could use wild-cards in this syntax?
Now you checking whether path and the passed value are equal, instead you can check whether the passed value exists in the path
function HeaderController($scope, $location) {
$scope.isActive = function(viewLocation) {
return $location.path().indexOf(viewLocation) > -1;
};
}
This is not clean solution. Because it work on http://localhost:8888/a-s/#/blog-another/ b or http://localhost:8888/a-s/#/blog_edit/ b and so on. It is need to update like this:
function HeaderController($scope, $location) {
$scope.isActive = function(viewLocation) {
var path = $location.path();
var addViewLocation = viewLocation+"/";
var inPath = false;
if(path.indexOf(addViewLocation)==-1)
{
inPath = path.indexOf(viewLocation)+viewLocation.length==path.length;
}
else
inPath = true;
return inPath;
};
}
This is test locations:
isActive("www.location.ru/path","/path");
true
isActive("www.location.ru/path/tr","/path");
true
isActive("www.location.ru/path-r/tr","/path");
false
isActive("www.location.ru/path/","/path");
true
isActive("www.location.ru/path/we/ew","/path");
true
isActive("www.location.ru/path-another/we/ew","/path");
false
I don't know if it's more a React or Meteor concern, maybe both, but I am currently building a web app with these two frameworks and I am facing a programmer issue. I am not a Javascript developer but a Java developer (I use daily GWT), so maybe I made some rookie mistakes.
My app keeps growing and I have more and more React components, about twenty or so. Now that I have a view that is fine, I have added some functionalities to my components but it turns up I add more and more logic in the react components which is, I believe, against MVC principles.
However, I don't know how to move the logic in "Meteor controller components". Right now, I use Meteor for its model and that's just about all. I saw many times this Pete Hunt's talk and how he built his application but it has only one 'simple' component.
In fact, without React, the view would be in html files, defined with templates. The controller would be in js files and the logic will appear to be there. I can clearly see the split between the view and the controller.
Html file (from leaderboard example):
<template name="leaderboard">
...
</template>
<template name="player">
<div class="player {{selected}}">
...
</div>
</template>
Javascript file (from leaderboard example):
...
Template.leaderboard.players = function () {
return Players.find({}, {sort: {score: -1, name: 1}});
};
Template.leaderboard.selected_name = function () {
var player = Players.findOne(Session.get("selected_player"));
return player && player.name;
};
...
Since React is javascript, it's really easy and tempting to put all we want in React components.
I am aware of these frameworks are relatively new for everybody but I wonder wheter some conventions exist about how to design such an MVC application in order to have a flexible and maintainable web application, any guidelines to follow? I am not looking for the 'best' way to do it but for some opinions.
Note: I deliberately didn't put a lot of code here to not be focus on it but feel free to illustrate your answer with whatever you want (code, schema, links...).
Here is an example of what I am doing. In this example, everything is done in react classes, maybe it's a the best way to do it, maybe not, I need your thoughts.
To sum up, it creates a list of elements (Boostrap list group) from an array given as input (something like [{name: itemName1, type:itemType1}, {name: itemName2, type:itemType2} ...] which generates a view like:
itemName1
itemName2
...
Each item as its own style according to its type. Then through the input text box, user can make a search trough this list, it filters the list and generates a new one that composed with the matching elements (the search algorithm is not right and will be changed). Plus, there are additional commands with certain keyboard key. Everything works fine but as you can notice, all is in react classes, I don't figure out how to fit Meteor with React.
Meteor file:
if (Meteor.isClient) {
Meteor.startup(function() {
//Build the view
React.renderComponent(
<Search items={initialItems}/>,
document.getElementById('main')
);
});
}
React file:
Search = React.createClass({
getInitialState : function() {
return (
{
items : flat(this.props.items),
active : 0
}
);
},
setListVisibility: function(visibility) {
this.refs.list.getDOMNode().style.visibility = visibility;
},
onchangeHandler: function() {
var re = new RegExp(this.refs.search.getDOMNode().value, "i");
var res = [];
//filter on props.items and not state.items
flat(this.props.items).map(function(item){
if(item.name.search(re) >= 0)
res.push(item);
});
this.setState({ items : res, active : 0});
},
onkeydownHandler: function(event){
if(event.key == "ArrowDown" || event.key == "ArrowUp"){
var shift = event.key == "ArrowDown" ? 1 : -1;
var newActive = this.state.active + shift;
if(newActive >= 0 && newActive < this.state.items.length)
this.setState({ active : this.state.active + shift });
} else if(event.key == "ArrowRight"){
if(this.state.items.length > 0){
var item = this.state.items[this.state.active];
var newItems = retrieveItem(this.props.items, item.name, typeToSubType[item.type]);
newItems = flat(newItems);
if(newItems.length > 0)
this.setState({ items : newItems, active : 0 });
}
} else if(event.key == "ArrowLeft"){
this.setState({ items : flat(this.props.items), active : 0});
} else if(event.key == "Enter"){
if(this.state.items.length > 0){
var item = this.state.items[this.state.active];
console.log("Add "+item.name+" "+item.type+" to the view");
}
}
},
render: function () {
return (
<div>
<nav className="navbar navbar-default" role="navigation">
<div className="container-fluid">
<div className="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<form className="navbar-form navbar-left" role="search">
<div className="form-group">
<input ref="search" type="text" className="form-control" placeholder="Search" size="100"
onChange={this.onchangeHandler}
onKeyDown={this.onkeydownHandler}
onFocus={this.setListVisibility.bind(this, "visible")}
onBlur={this.setListVisibility.bind(this, "hidden")}/>
</div>
</form>
</div>
</div>
</nav>
<List ref="list" items={this.state.items} active={this.state.active}/>
</div>
);
}
});
List = React.createClass({
render: function () {
var createItem = function(item, index) {
var cl = "list-group-item";
if(index == this.props.active)
cl += " active";
var gly = "glyphicon ";
switch(item.type){
case "dimension":
gly += "glyphicon-certificate";
break;
case "hierarchy":
gly += "glyphicon-magnet";
break;
case "level":
gly += "glyphicon-leaf";
break;
case "measure":
gly += "glyphicon-screenshot";
break;
}
return (<a href="#" className={cl} key={item.type+"/"+item.name}>
<span className={gly}></span>{" "}{item.name}
</a>);
};
return (
<div className="list-group search-list">
{this.props.items.map(createItem, this)}
</div>
);
}
});
Your approach is sound: Meteor for the model and React for the View and ViewController.
Anything functionality that has nothing to do with presentation should be in the model (business logic, validation rules, etc).
Anything to do with presentation and responding to user input should be in React (input, validation output, etc).
Today you could consider this nice peace of code:
https://github.com/reactjs/react-meteor
This repository defines a Meteor package that automatically integrates the React rendering framework on both the client and the server, to complement or replace the default Handlebars templating system.
As of Meteor 1.3 there is an officially supported way to integrate React with Meteor. So for anyone stumbling across this question today, here's the Meteor 1.3+ answer:
To use React as Meteor's view layer, first add the React packages from npm:
meteor npm install --save react react-dom
Now you can simply import React and use it in your project. To render a simple React component, create a simple HTML container:
client/main.html
<body>
<div id="app"></div>
</body>
And render your React component in it:
client/main.jsx
import React from 'react';
import { Meteor } from 'meteor/meteor';
import { render } from 'react-dom';
class HelloWorld extends React.Component {
render() {
return <p>Hello World!</p>;
}
}
Meteor.startup(() => {
render(<HelloWorld />, document.getElementById('app'));
});
To use reactive Meteor data sources such as Minimongo collections within React components, you should use the react-meteor-data package.
Read more in the official Meteor guide