I'm having a hard time passing down props to a component. I want to be able to use that object in componentDidMount Method.
My current setup is i have an App component that is passing down an object to a Mainwhich passes down that object as props to a TicketTable Component.
This is my App component
export default class App extends Component {
constructor(props) {
super(props);
this.state= {
keycloak: null,
authenticated: false,
user : {}, role: ''
}
}
componentDidMount() {
const keycloak = Keycloak('/keycloak.json');
keycloak.init({onLoad: 'login-required'}).then(authenticated => {
this.setState({ keycloak: keycloak, authenticated: authenticated});
// this.state.keycloak.loadUserInfo().success(user => this.setState({user}));
}).catch(err=>console.log("ERROR", err));
}
render() {
return (
<div>
<Main keycloak={this.state.keycloak}/>
This is my Main component
export default class Main extends Component {
constructor(props){
super(props);
this.state={keycloak:''}
}
componentDidMount(){
console.log("MAIN PROPS",this.props);
}
}
render() {
return (
<div>
<Switch>
<Route exact path="/" render={(props)=><TicketTable
{...props} keycloak={this.state.keycloak}/>}/>
I want to to able to use the Keycloak object in my TicketTable component.
Thank you.
Ps: I want to able to do that without the need of using a state management framework like redux
The keycloak prop that you are passing to your TicketTable component is actually empty because you are passing the state of the Route component as the prop and not the one which you got from App component.
So make this change and I think it should work:
<Route exact path="/" render={(props)=><TicketTable
{...props} keycloak={this.props.keycloak}/>}/>
Related
I have a React app. I'm using react and react-router. Here's the sandbox link.
I have an App.js file like this:
import React, { Component } from 'react';
import { BrowserRouter, Route } from 'react-router-dom';
import Items from './Items';
class App extends Component {
constructor(props) {
super(props);
this.state = {
items: []
}
}
componentDidMount() {
this.setState({ items: ['a', 'b', 'c'] });
}
render() {
const { items } = this.state;
return (
<BrowserRouter>
<div>
<Route exact path="/" render={(props) => <Items {...props} items={items} />} />
</div>
</BrowserRouter>
)
}
}
export default App;
In this file, in the componentDidMount, I'm getting data from an API, then passing it to the Items component. On the initial page load, of course items will be an empty array, and then it will eventually have content.
In my Items.js file, I have:
import React, { Component } from 'react';
class Items extends Component {
constructor(props) {
super(props);
this.items = this.props.items;
}
render() {
return (
<div>
{this.items.length}
</div>
)
}
}
export default Items;
As you can see, this.items is retrieved from the props. On initial page load, again, this is an empty array. But after the componentDidMount fires in App.js, the constructor in Items.js is not fired, so this.items is never re-populated with the items.
How can I instead fire the constructor in Items.js? I know this is a simple example, and therefore could technically be solved by simply accessing the props in the render method, but I really need the constructor to fire, because in my actual app, I have more complex logic in there.
You can use this.props directly in the render method of Items to extract the data you want.
class Items extends Component {
constructor(props) {
super(props);
}
render() {
const { items } = this.props;
return (
<div>
{items.length}
</div>
)
}
}
Since the constructor of a component is only called once, I will instead move the logic that relies on the props, to the parent component.
I have a problem. The problem is I need somehow to get product id from the ReadProducts component and pass it to ReadOne components, because I can't get an id in function and can't show the product. How can I do this?
The structure is below:
This component is parent for products, to get all and to get one
class Product extends Component {
render() {
return (
<Switch>
<Route path='/products' component={ReadProducts} />
<Route path='/products/:id' component={ReadOne} />
</Switch>
)
}
};
This one is getting all the products from api
class ReadProducts extends Component {
constructor(props) {
super(props);
this.state = {
products: []
}
this.getProduct = this.getProduct.bind(this);
}
render() {
const { products } = this.state;
return(
<ul>
{
products.map(product => (
<li key={product.id}>
<Link to={`/products/${product.id}`}><button onClick={???}>{product.name}</button></Link>
</li>
))
}
</ul>
)
}
}
This is for reading one product
class ReadOne extends Component {
constructor(props) {
super(props);
this.state = {
id: 0,
...
}
}
render() {
return(
<div className='pew'>
{this.state.name}, {this.state.id}...
</div>
)
}
}
The problem is I need somehow to get product id from the ReadProducts component and pass it to ReadOne components, because I can't get an id in function and can't show the product. How can I do this?
Add this in Product.js
We set the initial state of id.
We add setProductId in Product.js because this is where the state of id has to be in order for you to use it in ReadOne.js.
constructor(props) {
super(props);
this.state={
id:' ',
}
}
setProductId(id) => {
this.setState({ id:id })
}
Then pass id down as a prop to ReadOne component.
<Route path='/products/:id' render={()=> <ReadOne id={this.state.id}/>}>
This is the getter for the id, add this inside of ReadProducts.js
showProduct = (id) => {
getProduct(id);
}
<Link to={`/products/${product.id}`}><button onClick={this.showProduct(product.id)}>{product.name}</button></Link>
Now you can use id in any component within Product.js
Quick note from the react-router docs.
When you use component (instead of render or children, below) the
router uses React.createElement to create a new React element from the
given component. That means if you provide an inline function to the
component prop, you would create a new component every render. This
results in the existing component unmounting and the new component
mounting instead of just updating the existing component.
Currently trying to learn React by making a simple application that grabs data from the openFEC API.
I currently have two components defined in my application, a SearchBar and a Candidate component. This is what my App.js currently looks like:
class App extends Component {
constructor(props){
super(props);
this.state = { candidate: [], searchTerm: '' }
}
render() {
return (
<div className="App">
<SearchBar />
<Candidate candidate={this.state.candidate}/>
</div>
);
}
}
export default App;
Problem: I need to update the Candidate component based on the data I receive from the API response. However, I'm making the API call in the SearchBar component and have no way of updating the candidate state defined in the App component.
Should I make the API call in the App component instead? If not, is there a way to send the data I get back from the SearchBar component into my App component?
I think the best way to do this is have the API call in your App Component, and pass that function down as a prop to your SearchBar Component. Your parent component (in this case, App) should be holding on to all of the relevant information and passing down to it's children what they need.
It should look something like this:
class App extends Component {
...
handleSearch(term) {
//handle fetch here
.then(res => this.setState({candidate: res})
}
render() {
<div className="App">
<SearchBar handleSearch={this.handleSearch}/>
<Candidate candidate={this.state.candidate}/>
</div>
}
}
In this way, you can achieve this
class App extends Component {
constructor(props){
super(props);
this.state = { candidate: [], searchTerm: '' }
this.triggerSearch=this.triggerSearch.bind(this);
}
triggerSearch(searchTerm){
this.setState({searchTerm})
}
render() {
return (
<div className="App">
<SearchBar trigerSearch=
{(searchTerm)=>this.triggerSearch(searchTerm)} />
<Candidate candidate={this.state.candidate}/>
</div>
);
}
}
export default App;
You can achieve it this way (without making API call from App).
class App extends Component {
constructor(props){
super(props);
this.state = { candidate: [], searchTerm: '' }
this.onDataReceived = this.onDataReceived.bind(this);
}
onDataReceived(data){
this.setState({ candidate: data });
}
render() {
return (
<div className="App">
<SearchBar onDataReceived={this.onDataReceived}/>
<Candidate candidate={this.state.candidate}/>
</div>
);
}
}
Roughly what happens here is:
You can see how I passed a function as a props to the SearchBar component via onDataReceived props.
You can invoke that function from within SearchBar component (e.g. make API call and call function passed as props with API results).
Invoking onDataReceived function will trigger setState
Calling setState will call render and now the Candidate component will receive more recent data from state.
More.
So here is my problem. I have an root component that contains navigation and Switch with every component in my page. Navigation is sliding in and out from the left and the way I'm doing this, I'm changing the state in root component, passing prop to and deciding whether or not, should I add class to my nav. The problem is that every component in my app is re-rendering on opening/closing nav. Here is my root component:
class App extends Component {
constructor(props) {
super(props);
this.state = {
navOpen: false
}
}
toggleNav = () => {
this.setState({
navOpen: !this.state.navOpen
})
}
closeNav = (e) => {
if (this.state.navOpen) {
this.setState({navOpen: false})
}
}
render() {
return (
<main>
<Header/>
<Hamburger navOpen={this.state.navOpen} toggleNav={this.toggleNav}/>
<Navigation navOpen={this.state.navOpen} toggleNav={this.toggleNav}/>
<section className="container-fluid content" onClick={this.closeNav}>
<Switch>
<Route path="/add-recipe/:groupId?" component={NewRecipe}/>
<Route path="/recipes/:page?/:sortType?/:size?" component={RecipeList}/>
<Route path="/recipe/:id" component={Recipe}/>
<Route path="/sign/" component={SignForm}/>
<Route path="/user/:id" component={User}/>
</Switch>
</section>
</main>
);
}
componentDidMount() {
this.props.userActions.getUser(this.props.url);
}
}
function mapStateToProps(state) {
return {url: state.url.url, user: state.user.loggedUser}
}
function mapDispatchToProps(dispatch) {
return {
userActions: bindActionCreators(userActions, dispatch)
}
}
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(App));
Navigation is the only component ( besides hamburger) that cares about his parents state so I have no idea why everything is re-rendering. Does anyone have some ideas?
EDIT:
I've added sCU to my nested components like that:
shouldComponentUpdate(nextProps, nextState) {
// console.log(this.props)
// console.log("next")
// console.log(nextProps)
if (this.props == nextProps) {
return false
}
return true
}
But it didn't change anything. When I open the navigation props for routes remain the same but they still rerender. I tried to move "navOpen" state to navigation and open it from root component via "ref" but every time I call its method I get "Cannot read property 'toggleNav' of null"
You can prevent re-rendering by implementing shouldComponentUpdate on the affected component. Beware though, that rendering does not mean that the DOM is being changed. Rendering in react means that react creates a new copy of its internal node tree and compares it to the DOM to see if and where it needs to update it. So don't worry about re-renders unless your component is expensive to create (i.e. it performs a request to a backend or is heavily animated)
We're in the process of upgrading our React App, and after many of hours of pain have realised that passing wrapped components into React Router (V4 and maybe others) causes the component to "remount" every time a new prop is passed in.
Here's the wrapped component...
export default function preload(WrappedComponent, props) {
class Preload extends React.Component {
componentWillMount() {
getDataForComponent(props);
}
render() {
return <WrappedComponent {...props} />;
}
}
return Preload;
}
And here's how we're using it...
const FlagsApp = (props) => {
return (
<Route path="/report/:reportId/flag/:id/edit" component{preload(FlagForm, props)} />
);
};
Anytime we're dispatching an action and then receiving a update, the component remounts, causing lots of problems. According to this thread on github, components will remount if:
you call withRouter(..) during rendering which would create a new component class each time
you pass a new function to Route.component each render, e.g. using anonymous function
{...}} />, which would create a new component as well
If I pass the FlagForm component in directly the problem is fixed, but then I can't take advantage of the preload function.
So, how can I achieve the same outcome, but without the component remounting?
Thanks in advance for any help!
The reason Route is mounting a new component on every update is that it's been assigned a new class each time via preload.
Indeed, each call to preload always returns a distinct anonymous class, even
when called with the same arguments:
console.log( preload(FlagForm,props) != preload(FlagForm,props) ) // true
So, since the issue is that preload being called within the FlagsApp component's render method, start by moving it outside of that scope:
const PreloadedFlagForm = preload(FlagForm, props) //moved out
const FlagsApp = (props) => {
return (
<Route path="/report/:reportId/flag/:id/edit"
component={PreloadedFlagForm} /> //assign component directly
);
};
This way the component for Route won't change between updates.
Now about that lingering props argument for preload: this is actually an anti-pattern. The proper way to pass in props just the standard way you would for any component:
const PreloadedFlagForm = preload(FlagForm) //drop the props arg
const FlagsApp = (props) => {
return (
<Route path="/report/:reportId/flag/:id/edit"
component={<PreloadedFlagForm {...props} />} //spread it in here instead
/>
);
};
And so the code for preload becomes:
export default function preload(WrappedComponent) {
class Preload extends React.Component {
componentWillMount() {
getDataForComponent(this.props);
}
render() {
return <WrappedComponent {...this.props} />;
}
}
return Preload;
}
Hope that helps!
If like me you didn't read the instructions, the answer lies in the render prop of the <Route> component
https://reacttraining.com/react-router/web/api/Route/render-func
render: func
This allows for convenient inline rendering and wrapping without the undesired remounting explained above.
So, instead of passing the wrapper function into the component prop, you must use the render prop. However, you can't pass in a wrapped component like I did above. I still don't completely understand what's going on, but to ensure params are passed down correctly, this was my solution.
My Preload wrapper function is now a React component that renders a Route...
export default class PreloadRoute extends React.Component {
static propTypes = {
preload: PropTypes.func.isRequired,
data: PropTypes.shape().isRequired,
location: PropTypes.shape({
pathname: PropTypes.string.isRequired,
}),
}
componentWillMount() {
this.props.preload(this.props.data);
}
componentWillReceiveProps({ location = {}, preload, data }) {
const { location: prevLocation = {} } = this.props;
if (prevLocation.pathname !== location.pathname) {
preload(data);
}
}
render() {
return (
<Route {...this.props} />
);
}
}
And then I use it like so...
const FlagsApp = (props) => {
return (
<Switch>
<PreloadRoute exact path="/report/:reportId/flag/new" preload={showNewFlagForm} data={props} render={() => <FlagForm />} />
<PreloadRoute exact path="/report/:reportId/flag/:id" preload={showFlag} data={props} render={() => <ViewFlag />} />
<PreloadRoute path="/report/:reportId/flag/:id/edit" preload={showEditFlagForm} data={props} render={() => <FlagForm />} />
</Switch>
);
};
The reason I'm calling this.props.preload both in componentWillMount and componentWillReceiveProps is because I then had the opposite issue of the PreloadRoute component not remounting when navigating, so this solves that.
Hopefully this save lots of people lots of time, as I've literally spent days getting this working just right. That's the cost of being bleeding edge I guess!