Non lambda in React-Router V4 render function - reactjs

I have a component that i need to pass props into while being rendered using the Route component. The problem is that typescript disallows lambda functions in JSX and i just can't figure out how to rewrite this into an allowed function:
<Route
key={index}
exact={true}
path={item.urlAddress}
render={() => <DocumentView data={data} />}
/>
I tried this, but it is still being evaluated as a lambda, don't know why:
render={function() { <DocumentView /> }}
Anyone knows how to rewrite it?
Edit: I know this is a linter issure, and i'm looking for the proper way to write this according to ts-lint standards. I know i can add a rule exception, but i want to know how to do this the "proper" way, not adding exceptions every time i encounter a linting problem

You can make a change in the tslint rules to allow for arrow functions in render. However, since you asked an alternative way to write the above. It would be written like
renderDocument = (props) => {
return <DocumentView data={data} {...props}/>
}
render() {
return (
<Route
key={index}
exact={true}
path={item.urlAddress}
render={this.renderDocument}
/>
)
}

Related

React - is there a way to return JSX which contains braces? [duplicate]

I'm new to React and I'm trying to figure out the purpose/use of <MyComponent></MyComponent> vs <MyComponent />. I can't seem to find information on anything except self-closing tags.
I've created a basic tab scroller as a JSFiddle using the self-closing <MyComponent /> and subsequent props, and I'm wondering if there's a better way to write in React than what I've done.
class TabScroller extends React.Component {
render() {
return (
<div className="tabScroller">
<div className="NavList">
<TabNav handleClick={this.handleNavClick} />
<TabList
tabs={this.state.tabs}
activeTab={this.state.activeTab}
scrollPosition={this.state.scrollPosition}
handleClick={this.handleTabClick}
/>
</div>
<TabContent content={this.state.tabs[this.state.activeTab].content} />
</div>
);
}
}
// ========================================
ReactDOM.render(
<TabScroller />,
document.getElementById('root')
);
In React's JSX, you only need to write <MyComponent></MyComponent> when the component has child components, like this:
<MyComponent>
<Child />
<Child />
<Child />
</MyComponent>
If there is nothing between <MyComponent> and </MyComponent>, then you can write it either <MyComponent/> or <MyComponent></MyComponent> (but <MyComponent/> is generally preferred). Details in Introducing JSX.
Just as a side note, you'd access those children in your component via the special props.children property. More in JSX in Depth: Children in JSX.
Note that this is very much not like HTML or XHTML. It's its own (similar) thing with different rules. For instance, in HTML, <div/> is exactly the same thing as <div>: A start tag, for which you must eventually have an end tag. Not so JSX (or XHTML). The rules for HTML are that void elements (elements that never have markup content, such as br or img) can be written with or without / before > and they never get an ending tag, but non-void elements (like div) must always have an ending tag (</div>), they cannot be self-closing. In JSX (and XHTML), they can be.
The purpose of self-closing tags is simply the fact that it is more compact. This is especially useful when said component doesn't have any children that you typically wrap around a parent.
So usually for leaf components (i.e compoents that do not have any children), you use the self-closing syntax. Like: <Component />. And even if it has props, you can do: <Component foo="bar" />.
However, remember that children is a prop, so you could technically do:
<Component children={<span>foo</span>} />
but I find it less readable and advise against it (read disclaimer below).
To summarize, these are equivalent:
<Component /> = <Component></Component>
<Component foo="bar" /> = <Component foo="bar"></Component>
<Component children={<span>foo</span>}></Component> =
<Component><span>foo</span></Component>
You can use whichever approach you prefer. Though praxis is to use the short-hand version when there are no children.
Disclaimer: While defining childen prop by its object key value will technically work, doing so is strongly discouraged as it disrupts the API as it is meant to be used. Use this version only if confident in what you are doing.

useState hook being called inside map function causing infinite loop

I'm currently building a dynamic form engine and I want to display results from the redux store when the Answer Summary component is rendered. The way I figured would be best to do this would be to having a 'complete' status and set it to true once the answerSummary component is loaded, but doing this within the map function does not work and throws the infinite loop react error.
Code is here:
function App() {
let [complete, setComplete] = useState(false);
return (
<div>
<h1>Form App Prototype</h1>
<Router>
<Switch>
{Object.values(Database.steps).map(step => {
step.name === 'answerSummary' ? setComplete(true) : setComplete(false);
return (
<Route exact path={`/${step.name}`} render={() =>
<Step step={step} />
}
/>
)
})}
</Switch>
</Router>
<br></br>
<div style={{display: complete? 'block' : 'none'}}><StoreVisual/></div>
</div>
);
}
export default App;
EDIT: I know you aren't able to setState inside the render - I've written it this way as a way to try and convey what I want to be able to do
My understanding of your problem is that you are trying to display results after the answer summary component is mounted.
You can achieve this by using the useEffect hook which runs when the component mounts. https://reactjs.org/docs/hooks-effect.html
If you only want to render <StoreVisual/> when they are on the last step it might be easier to set up a state hook for the index of the step people are on.
const [index, setIndex] = useState(0);
Every time someone progresses a step increment this value.
You would have to pass setIndex, or whatever you call your setter, into the Step component to do this.
Then you can render <StoreVisual/> with a conditional, like so...
<div>
<h1>Claimer Form App Prototype</h1>
<Router>
<Switch>
{Object.values(Database.steps).map(step =>
<Route exact
path={`/${step.name}`}
render={() => <Step step={step} /> }/> )}
</Switch>
</Router>
<br></br>
{Database.steps[index] === 'answerSummary' && <StoreVisual/>}
</div>
This approach also affords you a simple way to let people start in the middle of the form. Say you want to let people save half-finished forms in the future, you just change/update the default value of index hook.
Instead of running that code inline in your return, build the array in your function logic:
function App() {
let [complete, setComplete] = useState(false);
// build an array of Route components before rendering
// you should also add a unique key prop to each Route element
const routes = Object.values(Database.steps).map(step => {
step.name === 'answerSummary' ? setComplete(true) : setComplete(false);
return <Route exact path={`/${step.name}`} render={() => <Step step={step} />} />
})
return (
<div>
<h1>Claimer Form App Prototype</h1>
<Router>
<Switch>
// render the array
{[routes]}
</Switch>
</Router>
<br></br>
<div style={{display: complete? 'block' : 'none'}}><StoreVisual/></div>
</div>
);
}
export default App;
I don't think you need to call setComplete(false) based on this logic, so you should probably replace your ternary with an if statement:
if (step.name === 'answerSummary') {
setComplete(true)
}
and avoid making unnecessary function calls
You cannot set state inside render which cause infinite loop.[Whenever state is changed, component will re-render(calls render function)]
render()=>setState()=>render()=>setState().......infinite
WorkAround:
<div style={{display: this.props.location.pathname=='answerSummary'? 'block' : 'none'}}><StoreVisual/></div>

Route path="/subject/:selectedSubject" does not change subject when navigated via < Link />

I'm trying to display articles filtered by subject. When you access directly path="/subject/:selectedSubject" (subject/TECH) for example it works perfectly. However if you navigate through <Link to={"/subject/TECH"} /> it will change the URL but will not load new articles.
I've tried: connecting everything with "withRouter".
I know that the <Link/> is changing the redux state, however that is not calling componentWillMount() which is where fetchArticles() is.
So, my question is, where should I put fetchArticles in order for it to be called when is triggered. I tried putting it inside render() but it keeps getting called non-stop. Or maybe I'm approaching this the wrong way.
PS: if another path gets called via <Link/>, like for example path="/", it works as intended (loads up new articles).
at App/
<BrowserRouter>
<div>
<Route path="/" exact component={ArticlesList} />
<Route path="/subject/:selectedSubject" component={ArticlesList} />
</div>
</BrowserRouter>
at ArticleList/
componentDidMount() {
if (this.props.match.params.selectedSubject) {
const selectedSubject = this.props.match.params.selectedSubject.toUpperCase();
this.props.fetchArticles(selectedSubject);
} else {
this.props.fetchArticles();
}
}
at Link/
{this.props.subjects.map(subject => {
return (
<Link to={`/subject/${subject.name}`} key={subject.name}>
<li>
<span>{subject.name}</span>
</li>
</Link>
);
})}
Since you use the same Component for / and /subject/:selectedSubject, he's not calling mounting lifecycle methods, including constructor() and componentDidMount().
You have to use updating lifecycle method, especially componentDidUpdate() to handle a update of your component.

Redirect to third party url using react-router-dom

I'm very much aware of the react-router-dom I want to do a conditional rendering of the component. If use is not logged in redirect him to some third party URL
for example, the below code looks neat and works fine
<Route exact path="/home" render={() => (
isLoggedIn() ? (
<Redirect to="/front"/>
) : (
<Home />
)
)}/>
Let's say in the above example if I want to redirect to https://www.google.com how can I do it?
if I write
<Redirect to="https://www.google.com"> it gives me error.
How can I redirect to a third party website?
You can use a tag for external urls,
<a href='https://domain.extension/external-without-params'>external</a>
but also you can provide component like this:
<Route path='/external' component={() => { window.location = 'https://domain.extension/external-without-params'; return null;} }/>
You can use window.open() to redirect to any website.
For example:
window.open('https://www.google.com');
implement redirection in your code:
render () {
if(this.isLogin) {
return(/* your components*/);
} else {
window.open('https://www.google.com');
return (<div></div>); //render function should return something
}
}
You can also specifies the target attribute or the name of the window, for more details, see w3school tutorial about this function.
In case you're using Typescript you probably will get the following error with the Accepted Answer:
Type 'string' is not assignable to type 'Location'
To fix that you just need to use window.location.href
<Route path="/external" component={() => {window.location.href = config.UPGRADE_URL return null }} />
Have on mind that you cannot perform that with <Link /> or <NavLink /> since they're mainly for routing throughout Single Page Application.
You should use anchor tag instead.
Example: Google.
Can easily use a button just do this:
<button onClick={(e) => (window.location = 'https://www.google.com')}>Click me</button>

React transition group - Don't animate children

I'm using react-transition-group to animate a router switch in React:
<CSSTransitionGroup transitionName="zoom" transitionEnterTimeout={200} transitionLeaveTimeout={200}>
<Switch key={key} location={this.props.location}>
<Route path={this.props.match.url+"/tasks/:task_id"} component={SingleTask} key={'none'} />
<Route slug={this.props.match.params.project_slug} path={this.props.match.url+"/"} render={(params) => (
<ProjectIndex {...params} slug={this.props.match.params.project_slug} />
)} key={'none'} />
</Switch>
</CSSTransitionGroup>
It was also triggering the animation whenever any sub-routes change. So, to get around that I'm getting the pathname using this.props.location.pathname, then using some really gross code to get the last segment:
pathname = pathname.split('?')[0].split('/').filter(function (i) { return i !== ""}).slice(-1)[0];
...and if it's 'tasks', 'activity' or 'notes, I'm just setting the key to 'noanimate' (i.e some generic string so the switch doesn't notice):
switch(pathname){
case 'tasks':
case 'activity':
case 'notes':
key = 'noanimate';
break;
default:
key = pathname;
break;
}
Now, the redirect from /project to /project/tasks does a double transition as it goes from 'project' to 'noanimate', and I'm not sure whether I'm meant to write some even worse string manipulation just to get either the last or second-last term, depending on whether it's 'tasks'/'activity'/'notes' or any other string.
Is there a better solution, or is that... just how we're meant to do things?
I had the same problem so I wrote my own solution: switch-css-transition-group
after install you can rewrite your code to:
import SwitchCSSTransitionGroup from 'switch-css-transition-group'
<SwitchCSSTransitionGroup location={this.props.location} transitionName="zoom" transitionEnterTimeout={200} transitionLeaveTimeout={200}>
<Route path={this.props.match.url+"/tasks/:task_id"} component={SingleTask} key={'none'} />
<Route slug={this.props.match.params.project_slug} path={this.props.match.url+"/"} render={(params) => (
<ProjectIndex {...params} slug={this.props.match.params.project_slug} />
)} key={'none'} />
</SwitchCSSTransitionGroup>
It basically go throw all routes inside this "Switch" and using matchPath function recognize if some route is properly matching.

Resources