How to determine active route in react router - redux

I'm have a Redux app that uses react router with nested routes, like this:
<Provider store={store}>
<Router history={browserHistory}>
<Route name="sales" path="/" component={App}>
<IndexRoute name="home" component={Home} />
<Route name="reports" path="/reports" component={Reports}>
<IndexRoute name="reports-home" component={ReportsHome} />
<Route name="report-1" path="/reports/report-1" component={Report1}/>
<Route name="report-2" path="/reports/report-2" component={Report2}/>
</Route>
</Route>
</Router>
</Provider>
I'm trying to write a breadcrumbs component; so would like to be able to deterime the current route.
I've configured the component to receive the router using the withRouter function provided by react router:
Breadcrumbs = withRouter(Breadcrumbs);
This gives me a router object that looks like this:
Object {__v2_compatible__: true}
__v2_compatible__: true
createHref: createHref(location, query)
createKey: createKey()createLocation: createLocation(location)
createPath: createPath(location, query)
go: go(n)
goBack: goBack()
goForward: goForward()
isActive: isActive(location)
listen: listen(listener)
listenBefore: listenBefore(hook)
push: push(location)
pushState: ()
registerTransitionHook: registerTransitionHook(hook)
replace: replace(location)
replaceState: ()
setRouteLeaveHook: listenBeforeLeavingRoute(route, hook)
setState: ()
transitionTo: transitionTo(nextLocation)
unregisterTransitionHook: unregisterTransitionHook(hook)
__proto__: Object
Can I use this to determine the current route? Is there better way?

Getting location etc. via withRouter was added in react-router version 3.0. Dan Abramov recommends upgrading to 3.0 to use withRouter. From 2.7 to 3.0, it only provided the functions you saw.
Source: https://github.com/ReactTraining/react-router/blob/master/CHANGES.md#v300-alpha1

There is already a module that does this for you, I believe it's called react-router-breadcrumbs. I haven't tried it though.
If you want a custom solution, here's what you could do:
Use the this.props.routes and this.props.params objects. You can then map through the routes and for each entry make a lookup for such key in the params object. You can then create a string with said parameters.
Note that I have given each route (except IndexRoutes) a path attribute, because sometimes I want to display a custom name for a given page. For example:
<Route path="/:productId" name="Start" title="Start" component={StartView} />
Here's the solution on my app:
componentDidMount = () => {
this._prepareBreadCrumbs(this.props);
}
componentWillReceiveProps = (newProps) => {
this._prepareBreadCrumbs(newProps);
}
_prepareBreadCrumbs = (props) => {
let {routes, params} = props;
let breadcrumbPath = "";
let temp = routes.map(
(item, i) => {
if(item.path == null) return null; //if we are visiting an item without a path, ignore it.
if(i < routes.length-1 && routes[i+1].path != null) {
let arr = item.path.split(/[:/]|(:\/)/g); //sometimes the path is like "/:product_id/details" so I need to extract the interesting part here.
arr = arr.map(function(obj) {
return (obj in params) ? params[obj] : obj; //We now have ":product_id" and "details" - the first one will exist in the "params" object.
});
breadcrumbPath += arr.filter(Boolean).join("/") + "/"; //clean out some garbage and add the "/" between paths.
if(i == 0) return <li key={i}><Link to={breadcrumbPath}>YourSite.com</Link></li> //If on the root - display the site name
return <li key={i}><Link to={breadcrumbPath}>{item.name}</Link></li>
} else {
document.title = "YourSite.com - " + item.title; //set the document title if you want
if(i == 0) return <li key={i} className="active"><span>YourSite.com</span></li>
return <li key={i} className="active"><span>{item.name}</span></li>
}
}
);
this.setState({breadcrumbPath: temp});
}
render() {
<p>{this.state.breadCrumbPath || ""}</p>
}
You'd want to put this in your top-level React Component.

Related

nextjs localStorage getItem

after searching half a day I still not able to getItem from local storage.
the idea is to save some data to local storage and based on that I want to route a user in the Layout component. I am able to save to local storage and delete but not able to get data from it. I get error 'local storage not defined' or 'destroy is not a function'
I have 3 components save, delete and get. save and delete I execute after a client side api call, the get function I need to be working in the Layout as it is the top level for all routes.
I Need a bit help to the right direction please.
---Upadte
I found something that works
export const IsAuth = ()=>{
const [auth, setAuth] = useState();
useEffect(()=>{
if(typeof windows === undefined) return;
const item = localStorage.getItem('ltu');
setAuth(!!item);
},[]);
return auth;
}
now my problem is I have not much understanding of nextjs. I used the Layout to create a theme template, I basically have only 3 pages that can be visited if not logged in and the rest one needs to be logged in. I get so many examples but it seems like I need to verify auth on every single page instead of being able to do this on root/layout level.
all examples I get are without the use of Layout and I am totally stuck.
I want a simple login system just with jwt and check if thats there to show pages.
I could not get the localStorage.getItem() to work in the layout template.
My solution while maybe not perfect is.
in the _app.js I create useState() and pass those along to the menu trough the Layout, in in the menu useEffect() with 'use client' in the useEffect I set the state I need global.
_app.js
export default function App({ Component, pageProps }){
const [isAuth, setAuth] = useState()
const [user, setUser] = useState()
return (
<Layout setAuth={setAuth} isAuth={isAuth} user={user} setUser={setUser}>
<Component user={user} setUser={setUser} isAuth={isAuth} {...pageProps} />
</Layout>
)
}
Layout.js
export default function Layout({ children, setAuth, isAuth, user, setUser }) {
return (
<>
<Headd />
<SideMenu setAuth={setAuth} isAuth={isAuth} user={user} setUser={setUser}/>
<main>
<div className="menu-spacer"></div>
<content>
{children}
</content>
</main>
</>
)
}
menu.js
'use client';
const SideMenu = ({setAuth, isAuth, user, setUser}) => {
useEffect(()=>{
if(typeof windows === undefined) return;
const item = localStorage.getItem('ltu');
setAuth(!!item);
if(item) setUser(JSON.parse(localStorage.getItem('Ud')))
}, [router, router.isReady])
}
Now I can use the {isAuth, user,} on any page and component.
I am pretty sure this is not the right solution, but I could not find any other working solution and no one here yet posted a answer.

Launch-time initialization in Next.js static/exported site

I'm trying to use Next to power an Electron app. electron-next uses Next's static site mode for its production build, which calls getInitialProps at build-time, rather than launch-time.
start.js (initially rendered page)
import Link from 'next/link'
export default function Start({date}) {
return (
<div>
<div>Date is {date}</div> {/* <- will always be the build time */}
<Link href="/about">
<a>Take me to the About page</a>
</Link>
</div>
)
}
Start.getInitialProps = () => {
return {
date: "" + new Date()
}
}
Interestingly, using Link to navigate elsewhere does, in fact, result in a dynamic getInitialProps call.
about.js
import Link from 'next/link'
export default function About({date}) {
return (
<div>
<div>Date is {date}</div> {/* <- will be the time the link was clicked */}
<div>Important info about this app</div>
</div>
)
}
About.getInitialProps = () => {
return {
date: "" + new Date()
}
}
Is there a non-hacky way to get dynamic behavior for the initial route? I imagine this would have plenty of use cases in static sites, too.
I ended up not using getInitialProps at all. Instead, I'm using a React hook. It works basically like this:
async function useModel() {
const modelRef = useRef(null)
// This hook will render at build-time by Next.js's static site, in which
// case the conditional loading of the model will never happen.
//
// At startup-time, it will be re-renderered on the Electron renderer thread,
// at which time, we'll actually want to load data.
if (process.browser && !modelRef.current) {
const m = new Model()
await m.init() // <- Assumed to have some async fetching logic
modelRef.current = m
}
return modelRef.current
}
Then, the top-level component can easily use the presence of the model to determine what to do next:
function Start() {
const model = useModel()
if (!model) {
return <div>Loading...</div>
} else {
return <MyProperUI model={model} />
}
}
Or, you could easily rig it up to show an unpopulated default UI, or whatever.
So basically, use getInitialProps for code you want to run exactly once, server-side/build-time or client-side. Otherwise, use other means of initialization. As seen here, hooks allow for this with pretty minimal boilerplate.

Meteor - Trying to return a built URL in a helper, coming back blank?

I'm trying to return a URL that I build up in a helper and add it to my href, but its coming back blank. I'm console.logging my final URL and its correct. I"ve tried single quotes, double and tripple {{{myhelper}}}. But it returns blank and removes the HREF attribute altogether???
my .html within a loop, passes the row.
<td class="table__cell">
<a href={{ buildUCDLink row }} >go</a>
</td>
my .js helper
buildUCDLink(process){
const thisTemplate = Template.instance();
const integrations = thisTemplate.integrations.get();
integrations.forEach((integration) => {
if (integration._id._str === process.integration_id._str ) {
const finalUcdUrl = integration.ucd_url + '/#applicationProcessRequest/' + process.id;
console.log('finalUcdUrl: ', finalUcdUrl);
return finalUcdUrl;
}
});
},
That is not so easy to follow but if you are trying to trigger it from an anchor. I think you would want to run it like this:
<a href="javascript:void(0);" onclick="buildUCDLink(row);">
Which I think is long hand for:
<a href="javascript:buildUCDLink(row);">
If I am wrong, please let me know a bit more about what you are trying to do here.
for some reason I needed to reassign the var outside the loop. This worked:
buildUCDLink(process){
const thisTemplate = Template.instance();
const integrations = thisTemplate.integrations.get();
let returnURL = '';
integrations.forEach((integration) => {
if (integration._id._str === process.integration_id._str ) {
const finalUcdUrl = integration.ucd_url + '/#applicationProcessRequest/' + process.id;
returnURL = finalUcdUrl;
}
});
return returnURL;
},

AsyncStorage to save state with React Native

I want to save the switch button state so users can't vote twice.
I have a message error : Exception '-[NSDictionaryM length...was thrown while invoking multiSet on target AsyncLocalStorage Any Idea ?
this.state= {
trueSwitchIsOn: false,
};
onSwitchChange(_key){
const{trueSwitchIsOn}=this.state;
switch (this.state.trueSwitchIsOn){
case false:
return(
<TouchableHighlight onClick={this.onPressIcon(_key)}>
{this.setState({trueSwitchIsOn: true})}
</TouchableHighlight>
);
case true:
return(
<TouchableHighlight onClick={this.onUnPressIcon(_key)}>
{this.setState({trueSwitchIsOn: false})}
</TouchableHighlight>
);
}
}
onPressIcon(word){
AsyncStorage.setItem('AlreadyLiked', {trueSwitchIsOn});
const{trueSwitchIsOn}=this.state;
this.setState({trueSwitchIsOn : true});
}
onUnPressIcon(word){
AsyncStorage.setItem('NotAlreadyLiked', {trueSwitchIsOn: false});
const{trueSwitchIsOn}=this.state;
this.setState({trueSwitchIsOn : false});
<Switch>
onValueChange={(value)=>this.onSwitchChange(_key)}
</Switch>
The value passed to AsyncStorage.setItem() needs to be a string. You need to either pass your object to JSON.stringify() or just use a pure string value instead of an object with a "trueSwitchIsOn" boolean property.

React Router - one of two routes

I know the title may be vague, so I'll try to describe my problem as good as I can.
I am doing a WordPress theme and I need to link to both categories and tags pages. Their structure is /[categories|tags]/item. Now I need to make a route for those two items only. I tried doing it like this:
<Route path="/category/:slug" component={Archives} />
<Route path="/tag/:slug" component={Archives} />
But with this I have no distinction of wether this is a category or a tag. I dont wan't to go with <Route path="/:type/:slug" component={Archives} /> because this may get confusing when I'll need to use nested pages. I also don't want to change the url scheme.
You can use queries to determine what the type is:
<Link to={{ pathname: '/category/[slug]', query: { type: 'category' } }} activeClassName="active">Some Link</Link>
Then access the queries in your Archives view via:
this.props.location.query // you could set up some conditional logic based on type
UPDATE: Without changing the url scheme
...
<Route path="/category/:slug" component={Archives} />
<Route path="/tag/:slug" component={Archives} />
// Then in Archives component you could do
class Archive extends React.Component {
...
render() {
const path = this.props.location.pathname;
const category = path.indexOf('/category/') !== -1;
const tag = path.indexOf('/tag/') !== -1;
... // some logic based on tag and category
return {
...
}
}
}

Resources