passing data between children in react-router - javascript

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.

Related

React - change props at child1 with callback and pass new value to child2

In my app I have a child component, 'Menu', where a 'select' state is updated by a click event, like so:
Menus.jsx (child):
import React, { Component } from 'react';
import { Redirect } from 'react-router-dom';
import Brewing from './Brewing.jsx';
import { withRouter } from 'react-router-dom';
class Menus extends Component{
constructor (props) {
super(props);
this.state = {
select: '',
isLoading: false,
redirect: false
};
};
(...)
gotoCoffee = (index) => {
this.setState({isLoading:true, select:this.state.coffees[index]})
setTimeout(()=>{
this.setState({isLoading:false,redirect:true})
},5000)
console.log(this.state.coffees[index])
}
renderCoffee = () => {
if (this.state.redirect) {
return (<Redirect to={'/coffee/'+this.state.select} />)
}
}
render(){
const coffees = this.state.coffees;
return (
<div>
<h1 className="title is-1"><font color="#C86428">Menu</font></h1>
<hr/><br/>
{coffees.map((coffee, index) =>
<span key={coffee}>
<div>
{this.state.isLoading && <Brewing/>}
{this.renderCoffee()}
<div onClick={() => this.gotoCoffee(index)}
style={{textDecoration:'underline',cursor:'pointer'}}>
<strong><font color="#C86428">{coffee}</font></strong></div>
<div>
</div>
</div>
</span>)
}
</div>
);
}
}
export default withRouter(Menus);
the above works.
However, let's say I have another child component, 'Coffee', which should inherit this changed state.
I have learned that passing this event change, and state, from child to another child component, is an anti-pattern. Considering the ways of React, data can only flow from top-to-bottom i.e., from parent-to-child.
So have I tried to manage 'select' state from top to bottom, like so:
App.jsx (parent)
class App extends Component {
constructor() {
super();
this.state = {
select: '',
};
this.onSelectChange = this.onSelectChange.bind(this);
};
then I would use a callback here at 'App.jsx', like so:
onSelectChange(newSelect){
this.setState({ select: newSelect });
}
and pass it to 'Menus' component, like so:
<Route exact path='/menus' render={() => (
<Menus
onSelectChange={this.onSelectChange}
/>
)} />
finally, at child 'Menus', I would user event change to change props, which could be passed to other childs etc:
gotoCoffee = (index) => {
this.setState({isLoading:true})
this.props.onSelectChange(index)
setTimeout(()=>{
this.setState({isLoading:false,redirect:true})
},5000)
console.log(this.props.select)
}
but I'm getting console.log(this.props.select) 'undefined'.
what am I missing?
You are only passing onSelectChange method as a prop to Menu component right now, to access this.props.select, you need to pass select as prop to Menu.
<Route exact path='/menus' render={() => (
<Menus
onSelectChange={this.onSelectChange}
select={this.state.select}
/>
)} />
Whenever this.onSelectChange method gets called and state changes in your App.jsx, your Menu component will be rendered. You can use the updated this.props.select in your render method or in any non static method of your Menu component.
class Menu extends Component {
render() {
console.log(this.props.select);
return (
...
);
}
}

Facing problems with passing down props in ReactJs

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}/>}/>

react-router Route component constructor is not called when props are updated in BrowserRouter component

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.

Mutating child components based on parent component's state, without re-rendering

Goal
I'm trying to manage mouseenter and mouseleave events from a parent component, for a collection of child components that keep getting re-rendered.
I'm building a reusable component for the collection of listings, that does several things like pagination, and a few other things when a listing is hovered.
So to make this reusable, I have to maintain the state of the hovered listing from the parent CollectionComponent, and mutate each individual listing component based on the state of the parent.
Code
Here are the components I'm using (I stripped them all down to their most basic forms):
Listings Component:
import React from 'react'
import $ from 'jquery'
import CollectionComponent from './CollectionComponent'
import Listing from './Listing'
export default class Listings extends React.Component {
constructor(props) {
super(props)
this.state = {
listings: this.props.listings,
}
}
render() {
return (<section className="listing-results">
{this.state.listings.map( listing =>
<CollectionComponent results={this.state.listings} IndividualResult={Listing} perPage={this.props.perPage} options={options}/>
)}
</section>)
}
}
Collection Component:
import React from 'react'
export default class CollectionComponent extends React.Component {
constructor(props) {
super(props)
this.state = {
results: this.props.results,
hoveredId: null
}
}
componentDidMount() {
this.$listings = $('.result-card')
$(this.$listings).mouseenter(this.toggleInfoIn).mouseleave(this.toggleInfoOut)
}
toggleInfoIn = e => {
var { target } = e
var infoId = $(target).data('id')
this.setState({hoveredId: infoId})
}
toggleInfoOut = e => {
this.setState({hoveredId: null})
}
render() {
const {results, IndividualResult, perPage, options} = this.props
return (
<div className="paginated-results">
{this.state.results.map( result =>
<IndividualResult key={result.id} result={result} options={options}/>
)}
</div>
)
}
}
Individual Listing Component:
import React from 'react'
export default class Listing extends React.Component {
constructor(props) {
super(props)
}
render() {
const { listing, hoveredId } = this.props
return (
<div className="result-card" data-id={listing.id}>
<div className={hoveredId === listing.id ? 'hovered' : ''}>
Listing Content
</div>
</div>
)
}
}
I know I can probably structure the CollectionComponent a little cleaner with a higher order component, but I'll leave that for refactoring later once I get it working properly with this basic setup.
Problem
My problem is that every time I hover and change the state of the parent component, it re-renders the child components, because their props are dependent on the parent's state. Once this happens, the reference to my jQuery collection of listings is no longer valid. So the mouse events are attached to old DOM elements that no longer exist.
How can I structure this differently, so that either:
the child elements' props update without re-rendering, or
the jQuery collection reference doesn't change
I'd really like to avoid getting a new the jQuery collection every time the component updates.
The behavior of hover should be confined to the individual listing component and not the Collections component.
As the Collections component maintains the state of currently hovered item, it is good idea to pass an handler as part of props and then render the list again based on the change in state set by the Collections component.
Use react based event handlers where ever necessary which makes it for a controlled component. It is not a good idea to put state in the DOM where react can take care of it for you.
Listings
import React from 'react'
export default class Listing extends React.Component {
constructor(props) {
super(props);
this.onMouseEnter = this.onMouseEnter.bind(this);
this.onMouseLeave = this.onMouseLeave.bind(this);
}
onMouseEnter() {
this.props.onMouseEnter({ listingId: this.props.listing.id });
}
onMouseLeave() {
this.props.onMouseLeave();
}
render() {
const { listing, hoveredId } = this.props
const listingId = listing.id;
const isHovered = this.props.hoveredId === listing.id;
return (
<div className="result-card" onMouseEnter={this.onMouseEnter} onMouseLeave={onMouseLeave}>
<div className={isHovered ? 'hovered' : ''}>
Listing Content
</div>
</div>
)
}
}
Collections
import React from 'react'
export default class CollectionComponent extends React.Component {
constructor(props) {
super(props)
this.state = {
results: this.props.results,
hoveredId: null
}
}
onMouseEnter({ listingId }) {
this.setState({ listingId });
}
onMouseLeave() {
this.setState({ listingId: null });
}
render() {
const {results, IndividualResult, perPage, options} = this.props
return (
<div className="paginated-results">
{this.state.results.map( result =>
<IndividualResult key={result.id} hoveredId={this.state.hoveredId} result={result} options={options} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}/>
)}
</div>
)
}
}

Saving incoming multiple data in list in React.js

I would really appreciate if you could help me with that one. Namely, I have a parent component and it gets new data from a child component. In child component array-data is mapped so incoming data into parent component is not one but multiple. I would like to save it inside the list tags to get as many list items as incoming values from child component.
Here's the code:
import React, { Component } from 'react';
import TextEnter from './TextEnter.jsx';
class Terminal extends Component {
constructor(props) {
super(props);
this.state = {
incomingData: '',
};
}
updateParent(val) {
this.setState({
incomingData: <li> {val} </li> // here I would like to save every incoming mapped data
});
}
render() {
return (
<div className="terminal">
<TextEnter
afterCommand={this.props.afterCommand}
triggerParent={(val) => this.updateParent(val)}
/>
<ul className="listContainer">
{this.state.incomingData}
</ul>
</div>
);
}
}
export default Terminal;
I don't really like to store React Elements (created with <li> in a jsx file) as state. I'd rather store the list, and leave how it is rendered to the render() function. I would change it to something like:
import React, { Component } from 'react';
import TextEnter from './TextEnter.jsx';
class Terminal extends Component {
constructor(props) {
super(props);
this.state = {
incomingData: [],
};
}
updateParent(val) {
this.setState({
incomingData: this.state.incomingData.concat(val)
});
}
render() {
const listItems = this.state.incomingData.map(item => {
return <li>{ item }</li>
});
return (
<div className="terminal">
<TextEnter
afterCommand={this.props.afterCommand}
triggerParent={this.updateParent}
/>
<ul className="listContainer">
{listItems}
</ul>
</div>
);
}
}
export default Terminal;
Notice that I also pass this.updateParent directly instead of wrapping it in an anonymous function which should give the same result. You'll get a key warning from react, but I don't know what unique identifier you have access to.

Categories

Resources