Calling a props provided method that uses react-router properties - javascript

I'm having a hard time wrapping my head around this issue. I was wondering if someone here would be able to take a look? I have a component that I'm passing a method called this.fetchContent to as props called Filter. this.fetchContent fires an action creator that uses axios via Redux to get some data from an API, I pass it the current page location using react-router viathis.props.params.
The issue occurs with calling the method from the child component. What I've done is bound this.props.fetchContent to an onClick handler inside the Filter component. Whenever I click the <Link /> tag the function fires, and the page route gets updated. However the value of the parent components props doesn't update until after the function has fired, causing only every other click to produce the correct API call.
App:
class App extends Component {
constructor(props) {
super(props)
this.fetchContent = this.fetchContent.bind(this);
}
static contextTypes = {
router: PropTypes.object
};
componentDidMount() {
this.fetchContent();
}
fetchContent() {
let query = `${this.props.params.sub}/${this.props.params.filter}`;
this.props.fetchList(query, this.props.location.search);
}
render() {
return (
<div>
<Filter
filter={this.props.params.filter}
sub={this.props.params.sub}
search={this.props.location.search}
fetchContent={this.fetchContent} />
{React.cloneElement(this.props.children, this.props)}
</div>
);
}
}
Filter
class Filter extends Component {
render() {
return (
<div className="mdl-tabs mdl-js-tabs mdl-js-ripple-effect">
<div className="mdl-tabs__tab-bar">
<Link to={`/r/${this.props.sub}/new/${this.props.search}`} onClick={this.props.fetchContent}>
<span className="mdl-tabs__tab is-active">Hot</span>
</Link>
</div>
</div>
)
}
}
I understand what's happening here but I'm not sure what the React-friendly way of solving this issue is. How can I re-factor my code to produce the results I need and what are the best practices for solving this sort of issue?
Edit: Updated the syntax but still seeing the same issue.

You have some syntax mistake I believe. You are executing the function rather than returning it.
This
<Link to={`/r/${this.props.sub}/new/${this.props.search}`} onClick={this.props.fetchContent()}>
<span className="mdl-tabs__tab is-active">Hot</span>
</Link>
Should be like this;
<Link to={`/r/${this.props.sub}/new/${this.props.search}`} onClick={this.props.fetchContent}>
<span className="mdl-tabs__tab is-active">Hot</span>
</Link>
OR like this
<Link to={`/r/${this.props.sub}/new/${this.props.search}`} onClick={() => { this.props.fetchContent() }}>
<span className="mdl-tabs__tab is-active">Hot</span>
</Link>
PS: I believe you have a typo on App component since you don have a closing on Filter component at render.

Related

Get `children` prop from component

UPDATE:
For anyone learning and confused on this as I was, this page describes what's going on -- my confusion was between a React component vs React elements
https://reactjs.org/docs/rendering-elements.html
ORIGINAL QUESTION:
The page linked below suggests the children prop is passed by default when using FunctionComponent, however it doesn't seem to work for me. Given the following case, props.children is undefined. I'm still picking up React -- what am I doing wrong?
import React, { FunctionComponent } from "react"
type Props = {
myProp: string
}
const Nav: FunctionComponent<Props> = (props) => {
console.log(props.myProp, props.children)
return (
<main>
<nav>
FOO
BAR
</nav>
</main>
)
}
// This component is rendered from within another component, e.g. `return (<div><Nav /></div>)
My end goal is to access a child attribute somehow. The active class, for example.
source: https://fettblog.eu/typescript-react/children/
props.children === undefined because you're rendering
<div>
<Nav />
</div>
and <Nav /> has no child element in it.
If you change it to something like
<Nav myProp="Hello">
World!
</Nav>
then console.log(props.myProp, props.children) will print "Hello World!" in the console.

How to create a conditional rendering of Gatsby's Link component and <a> tag for links?

From Gatsby's official docs regarding Gatsby's Link component, it states that the Link component is used only for internal links, whereas for external links, one has to use the tag.
I'm building a Button component that has inbuilt props for links. The problem is right now I have to create 2 separate Button components for internal and external links due to the limitation.
My goal is to use one freeLink component that can be used as both internal and external links
I've tried creating a subcomponent (Button) for the button, but I'm unsure of the parent component (freeLink) which requires conditional rendering. The subcomponent is as of follows:
const Button = props => (
<button className={props.btnType}>
<span>{props.text}</span>
</button>
)
This is the visual logic to what I want to achieve:
For Internal links
<freeLink intLink="/about" btnType="btn-cta" text="Read about us">
</freeLink>
...which will render...
<Link to="/about">
<button className="btn-cta">
<span>Read about us</span>
</button>
</Link>
It is relatively similar for external links
<freeLink extLink="https://google.com" btnType="btn-cta" text="Visit Our Partner">
</freeLink>
...which will render...
<a href="https://google.com">
<button className="btn-cta">
<span>Visit Our Partner</span>
</button>
</a>
I'm quite new to Javascript, Gatsby and React so I'm unsure to how to apply a conditional rendering based on props applied.
Any advice, suggestion, or direction to how to code up the freeLink component is greatly appreciated.
P.S: I've seen Conditionally Use Gatsby Link in React Compoment but the chosen answer is too complicated for me to understand, and I don't have enough points to comment to ask for further elaboration.
You could try something simple like this:
const MyLink = (href, text, ...props) => {
if (href.startsWith("http") {
return <a href={href} {...props}>{text}</a>
} else {
return <Link href={href} {...props}>{text}</Link>
}
}
Your component could return different stuff based on weather you pass it a to or a href prop:
import { Link } from "gatsby"
const freeLink = props => {
if (props.to) return <Link {...props} />
return <a {...props} />
}`

Simple button component not accepting variables

In reviewing and making my code more modular and robust, as any programmer should, I noticed I was using a similar button component multiple times. As such I decided to create a button component and just render it with the new route link and text as in the page rendered.
I'm completely new to react (~ 5 days in learning) with a fairly well versed programming background.
Simple component button, I use react-route-dom : Link prop to route to new pages.
function ActionButton () {
return (
<div className="Action">
<button className="ActionButton">
<Link to={this.props.navLink}>
{this.props.text}
</Link>
</button>
</div>
);
}
using/constructing of the button component
function ActionPage () {
return (
<div className="ActionPage">
<ActionButton
navLink="/urlLink1"
text="btn1"
/>
<ActionButton
navLink="/urlLink2"
text="btn2"
/>
</div>
);
}
this doesn't work, I get the following:
TypeError: Cannot read property 'props' of undefined
When using a stateless functional component as you are (as opposed to a class based one) the component is called with ComponentName(props) - you can access props by updating the signature of the component to:
ActionButton (props) {
Which will allow you to access props.navLink etc inside the function.
Your ActionButton component is a dump component, so you have to pass the props as a argument to the function. Update your ActionButton component as shown below.
function ActionButton (props) {
return (
<div className="Action">
<button className="ActionButton">
<Link to={props.navLink}>
{props.text}
</Link>
</button>
</div>
);}
As you are new to React, read more about dump vs smart components here: https://medium.com/#thejasonfile/dumb-components-and-smart-components-e7b33a698d43

react-router v4: triggering a redirect programmatically (without having to render a <Redirect / >)

I'm currently switching my web app to react. The old one is located here.
What I'm trying to do is: when an user enter a player's username into the text field and submit, the app would redirect to the corresponding route (/:username), and the text field is cleared.
In the react version, this is what I'm doing currently:
https://github.com/AVAVT/g0tstats-react/blob/master/src/components/SideBar/SearchBox.js
submit(event){
...
this.setState({
redirect : true
});
...
}
And
render(){
...
{
this.state.redirect && (
<Redirect to={`/${this.state.username}`} push />
)
}
}
Which kinda work. But there are 2 things I don't like about it:
I'm rendering an element in order to redirect. It feels stupid and roundabout. It stinks of potential bug in the future.
I'm stuck with the text field not cleared. Because I if I set state.username to null the <Redirect /> component will not redirect correctly. In fact I don't have any precise control over when the redirection occur (unless I do it in another roundabout way).
I have searched for alternative, but couldn't find one. withRouter doesn't work because <SearchBox /> is not a <Route /> and doesn't receive the history props.
So how can I say "redirect me to that place NOW" in react-router v4?
Here is an example that shows when using the withRouter HOC, the routing props get injected to components even if they are not routed to.
Here is my App.js
class App extends Component {
render() {
return (
<div className="App">
<BrowserRouter>
<div>
<Route path='/test' component={Sample} />
<Sibling />
</div>
</BrowserRouter >
</div>
);
}
}
export default App;
Here is my Sample.js. This is like an example container that is rendering a child.
export default class Sample extends React.Component {
render() {
return (
<div>
<span>{this.props.location.pathname}</span>
<br />
<Nested />
</div>
)
}
}
This component can display information about the current route even without the withRouter HOC since it is being routed to.
Here is my Nested.js.
class Nested extends React.Component {
render() {
return (
<div>
<span>I am nested {this.props.location.pathname}</span>
</div>
)
}
}
export default withRouter(Nested);
My nested component needs the withRouter HOC in order to display the current route.
Finally here is my Sibling.js. (This is like your example where <SearchBox /> is a sibling.)
class Sibling extends React.Component {
render() {
return (
<span>{this.props.location.pathname}</span>
)
}
}
export default withRouter(Sibling);
Here all that is needed is to make sure that the sibling is nested within the router as you can see in my App.js, and then using the withRouter HOC it can display the current pathname.
To clarify: If a component can access the current pathname then it can also change the routes programmatically by doing this. this.props.history.push(some path).
I hope this helps.

Adding tab dynamically upgradeElement

When I add a tab with react, its parent element is already upgraded. So calling upgradElement has no effect and the added tab doesn't work.
What solution, recreate all tabs with the container and upgrade it ? React just update DOM component in this case I need to dismount component ?
How about calling componentHandler.upgradeElement() or componentHandler.upgradeDom() in the componentDidUpdate phase of the specific component
componentDidUpdate() {
componentHandler.upgradeElement(this.refs.myElement);
//or componentHandler.upgradeDom('MaterialTabs');
}
EDIT 1 Tab Component
componentDidUpdate() {
componentHandler.upgradeDom();
}
newTab() {
this.setState({
newtab: 1
});
}
render() {
return (<div className="mdl-layout mdl-js-layout mdl-layout--fixed-header" key={this.state.newtab}>
<button onClick={this.newTab.bind(this)}>Add Tab</button>
<header className="mdl-layout__header">
<div className="mdl-layout__tab-bar mdl-js-ripple-effect">
<Link to="/tabtest" hash="#scroll-tab-1" className="mdl-layout__tab is-active">Tab 1</Link>
<Link to="/tabtest" hash="#scroll-tab-2" className="mdl-layout__tab">Tab 2</Link>
{ this.state.newtab ?
<Link to="/tabtest" hash="#scroll-tab-3" className="mdl-layout__tab">Tab 3</Link> : null}
</div>
</header>
<div className="mdl-layout__content">
<section className="mdl-layout__tab-panel is-active" id="scroll-tab-1">
<div className="page-content">Tab 1</div>
</section>
<section className="mdl-layout__tab-panel" id="scroll-tab-2">
<div className="page-content">Tab 2</div>
</section>
{ this.state.newtab ? <section className="mdl-layout__tab-panel" id="scroll-tab-3">
<div className="page-content">Tab 2</div>
</section> : null}
</div>
</div>);
}
I did some tests and could reproduce the problem. What helped was to put the key attribute on the root element of the tab component. When adding a new tab this key must change and react will throw away the component and rerender completly. This way all material-design-lite properties get lost and after calling upgradeDom or upgradeElement it works.
React and Material-Design-Lite
Material-Design-Lite source
From https://facebook.github.io/react/docs/component-api.html.
forceUpdate
void forceUpdate(
[function callback]
)
By default, when your component's state or props change, your component will re-render. However, if these change implicitly (eg: data deep within an object changes without changing the object itself) or if your render() method depends on some other data, you can tell React that it needs to re-run render() by calling forceUpdate().
Calling forceUpdate() will cause render() to be called on the component, skipping shouldComponentUpdate(). This will trigger the normal lifecycle methods for child components, including the shouldComponentUpdate() method of each child. React will still only update the DOM if the markup changes.
Normally you should try to avoid all uses of forceUpdate() and only read from this.props and this.state in render(). This makes your component "pure" and your application much simpler and more efficient.
In order to highlight the most important part of Christian Steinmann's answer:
Give the surrounding .mdl-js-tabs div element the tabs count as the key attribute.
Whenever a tab is added, the key will change and React will rerender the whole tabs component. This will then the MDL upgrade.

Categories

Resources