How to pass ref to component in React? - javascript

I am using a library called react-swipe (not especially relevant), which exposes next() and prev() methods on the instance, which I am accessing through a ref.
When I have the ReactSwipe component in my main App.js file this works perfectly well, e.g.:
_handlePrev() {
this.reactSwipe.prev()
}
_handleNext() {
this.reactSwipe.next()
}
render() {
let singlePlanets
singlePlanets = this.state.planetData.map(data => {
return (
<div className="single-planet" key={data.id}>
<div className="image">
<img src={emptyPlanet} alt={data.name} />
</div>
<h2>{data.name}</h2>
<div className="extract" dangerouslySetInnerHTML={{ __html: data.extract }} />
</div>
)
})
return (
<div className="app-container">
<TitleBar />
<ReactSwipe ref={reactSwipe => this.reactSwipe = reactSwipe} className="content" key={singlePlanets.length}>
{singlePlanets}
</ReactSwipe>
<MenuBar handleNext={this._handleNext.bind(this)} handlePrev={this._handlePrev.bind(this)} />
</div>
)
}
But what I'm trying to do is separate out the ReactSwipe and planetData mapping logic into its own component (code below), however when I do this (by trying to pass the ref through as a prop) I always get the error this.reactSwipe.prev() (or .next()) is not a function, no matter what I try. I'm wondering - what is the correct way to go about this?
This what I have in my return in App.js:
<PlanetInfo planetData={this.state.planetData} swipeRef={reactSwipe => this.reactSwipe = reactSwipe} />
and in PlanetInfo component:
return (
<ReactSwipe ref={this.swipeRef} className="content" key={singlePlanets.length}>
{singlePlanets}
</ReactSwipe>
)

Replace ref={this.swipeRef} with ref={this.props.swipeRef} in PlanetInfo component.

Related

React: implementing a router

I tried implementing browser router, but to no success. i'm having trouble with useParams hook, and just the router in general. Looked through multiple posts and i just wasn't able to get it working. I'll post the most barebones code below, hoping someone knows the solution. I removed the traces of the router, since it didn't work.
App.js is currently empty:
const App=()=> {
return (
<Main/>
);
}
Main.jsx is my main element, where components change. There isn't a page change per se, everything is in the main element. values get passed through props into main and written into state, so the useEffect can change visibility of components based on what you chose, first category, then recipe.:
const Main =()=> {
const [showElement, setShowElement] = useState("category");
const [selectedCategory, setSelectedCategory] = useState();
const [selectedRecipe, setSelectedRecipe] = useState();
useEffect(()=> {
if (selectedRecipe) {
setShowElement("recipe")
} else if (selectedCategory) {
setShowElement("recipeSelection")
}
window.scrollTo(0, 0)
}, [selectedCategory][selectedRecipe]);
return (
<>
<Header />
<main className="main">
<div>
<div>
{showElement === "category" &&
<CategoryWindow
passSelectedCategory={setSelectedCategory}
/>
}
</div>
<div>
{showElement === "recipeSelection" &&
<RecipeSelection
value={selectedCategory}
passSelectedRecipe={setSelectedRecipe}
/>
}
</div>
<div>
{showElement === "recipe" &&
<RecipeWindow
value={selectedRecipe}
/>
}
</div>
</div>
</main>
</>
)
}
This is the recipe picker component. For example when i click on curry, i'd like the url to show /food/curry. None od the names are hardcoded, everything comes from a javascript object:
const RecipeSelection =(props)=> {
const recipies = Recipies.filter(x=>x.type === props.value);
return (
<div className="selection-div">
<div className="selection-inner">
{recipies.map(selection =>
<>
<img src={require(`../images/${selection.id}.png`)}
className="selection-single"
key={selection.id}
alt={"picture of " + selection.id}
onClick={()=> props.passSelectedRecipe(selection.id)}
>
</img>
<div className="container-h3"
onClick={()=> props.passSelectedRecipe(selection.id)}
>
<h3 className="selection-h3">{selection.name}</h3>
</div>
</>
)}
</div>
</div>
)
}

Passing data from parent class to child function -props undefined

I'm trying to pass data 'template_titles' to my child component and display them in an option dropdown. Just trying to console.log the data to make sure it's passing. Currently running into an issue where property props are undefined.
Parent Component
render() {
if (!this.props.show) {
return null;
}
const template_titles = this.state.email_template.map((a) => a.title);
console.log(template_titles);
return (
<div className='compose-email'>
<div className='directory'>
<div className='background' />
<div className='box'>
<ion-icon
id='close-email'
name='close'
onClick={this.onClose}
></ion-icon>
<ion-icon
id='square-email'
name='square'
onClick={this.onClose}
></ion-icon>
<h1 className='candidate-name-compose-email'>
Candidate Name
</h1>
<hr></hr>
<h1 className='compose-email-title-compose-email'>
Compose Email
</h1>
<ComposeEmailForm
handleCompose={this.sendNewEmail}
template_titles={this.template_titles}
/>
</div>
<ToastContainer className='toast-container' />
</div>
</div>
);
}
Child Component
export default function ComposeEmailForm({ handleCompose }) {
console.log(this.props.template_titles);
return (
<div className='container'></div>
);
}
Try this instead:
export default function ComposeEmailForm({ handleCompose, template_titles }) {
console.log(template_titles);
return (
<div className='container'></div>
);
You can't use this when your component is a function and not a class.
And when passing it, you need to drop the this too:
<ComposeEmailForm
handleCompose={this.sendNewEmail}
template_titles={template_titles}
/>
since you define it as such in your render function.
Actually, in the function component, you don't too use this, especially when you destruct props in the parentheses of the argument, then you can access to you props like below:
export default function ComposeEmailForm({ handleCompose, template_titles }) {
console.log(template_titles, handleCompose);
return (
<div className='container'></div>
);
};

Conditional rendering on React.js

render() {
const tableStyle = this.getTableStyle();
const tableSettings = this.getTableSettings();
return (
<div style={tables}>
<TablePosition
contextMenuOn={true}
step={this.props.step}
pdfData={this.props.pdfData}
tableSettings={tableSettings}
tableStyle={tableStyle}
fileName={this.state.fileName}
tableSize={this.getTableSize()}
tableOffset={this.state.tableOffset}
desiredWidth={700}
updateXOffset={x => this.updateXOffset(x)}
updateYOffset={y => this.updateYOffset(y)}
markTable={() => this.markTable()}
setOutputLabels={(row, col, val) => this.setOuputLabels(row, col, val)}
/>
</div>
);
if (!this.props.isThirdStep) {
return (
<div>
<div style={sideBySide}>
<PDFViewer
isThirdStep={this.props.isThirdStep}
paginationCallback={this.handlePageChange}
pdfData={this.state.pdfData}
desiredWidth={600}
selectedPage={this.props.savedPageNo}
/>
</div>
</div>
);
} else {
return (
<div>
<ReferenceMenu />
</div>
);
}
}
In my component's render, I try to render several components based on certain conditions.
So, basically, the TablePoisition always stays there, and the PDFViewer and ReferenceMenu renders conditionally.
However, what I see on both conditions is only the TablePosition component.
Is this not supposed to work?
As explained since you want to combine two components you should change your render logic. One component will be sit there always and the other one will be rendered conditionally. So, you need to render that last component with the sticky one in the same return. I would do something like this:
renderPDFViewer = () => (
<div>
<div style={sideBySide}>
<PDFViewer
isThirdStep={this.props.isThirdStep}
paginationCallback={this.handlePageChange}
pdfData={this.state.pdfData}
desiredWidth={600}
selectedPage={this.props.savedPageNo}
/>
</div>
</div>
);
render() {
const tableStyle = this.getTableStyle();
const tableSettings = this.getTableSettings();
return (
<div>
<div style={tables}>
<TablePosition
contextMenuOn={true}
step={this.props.step}
pdfData={this.props.pdfData}
tableSettings={tableSettings}
tableStyle={tableStyle}
fileName={this.state.fileName}
tableSize={this.getTableSize()}
tableOffset={this.state.tableOffset}
desiredWidth={700}
updateXOffset={x => this.updateXOffset(x)}
updateYOffset={y => this.updateYOffset(y)}
markTable={() => this.markTable()}
setOutputLabels={(row, col, val) => this.setOuputLabels(row, col, val)}
/>
</div>
{
!this.props.isThirdStep
? this.renderPDFViewer()
: ( <div><ReferenceMenu /></div> )
}
</div>
);
}
You need to place your conditional renders inside variables or something similar.
var conditionContent1 = null;
var conditionContent2 = null;
if(condition1){
conditionContent1 = <div>conditional content 1</div>;
}
if(condition2){
conditionContent2 = <div>conditional content 2</div>;
}
return (
<div id="wrapper">
<div>
content
</div>
{conditionContent1}
{conditionContent2}
</div>
);
I added a wrapper div; because, I believe render's return doesn't like having multiple root elements.
If the variables are null; then, it won't affect the overall render.

Passing data between components in React

Ultimately I'm trying to pass mapped elements in an array to a child component. I made a WordPress API call to get back posts for a preview page, and now that I'm trying to have that data render in their own pages, I keep getting that the data is undefined. The dynamic links are rendering as expected, but none of the other data is being passed.
Articles.js
// cut for brevity
render() {
let articles = this.state.newsData.map((article, index) => {
if(this.state.requestFailed) return <p>Failed!</p>
if(!this.state.newsData) return <p>Loading...</p>
return(
<div key={index} className="article-container">
<div className="article-preview">
<span className="article-date">{article.date}</span>
<h5>{article.title.rendered}</h5>
<div dangerouslySetInnerHTML={{ __html: article.excerpt.rendered }} />
<Link to={`/news/${article.slug}`}>Read More...</Link>
</div>
<Route path={`/news/:articleSlug`}
render={ props => <Article data={article} {...props} />}
/>
</div>
)
});
return (
<div>
<h3>All Articles from Blog</h3>
{articles}
</div>
)
}
Article.js
import React from 'react';
const Article = ({match, data}) => {
let articleData;
{ console.log(this.data) }
if(data)
articleData = <div>
<h3> {data.title.rendered}</h3>
<div dangerouslySetInnerHTML={{ __html: data.content.rendered }} />
<hr />
</div>
else
articleData = <h2> Sorry. That article doesn't exist. </h2>;
return (
<div>
<div>
{articleData}
</div>
</div>
)
}
export default Article;
How do I get the data from the array into the Article component?
Your problem is with asynchronous requests.
You have a route that will call the render method when the user clicks on a link. At that point in time, javascript has no reference to the article anymore, you need to persist it.
Here's an example of what you are experiencing
for (var i = 0; i < 10; i++) {
setTimeout(function() { console.log(i); }, 1);
}
The code above will always log 10
A solution to this problem is using bind.
for (var i = 0; i < 10; i++) {
setTimeout(function(i) { console.log(i); }.bind(null, i), 1);
}
So, in your code, you need to persist the article variable.
You can do that by calling a method that takes the data.
renderArticle(data) {
return props => <Article data={data} {...props} />
}
render() {
let articles = this.state.newsData.map((article, index) => {
if(this.state.requestFailed) return <p>Failed!</p>
if(!this.state.newsData) return <p>Loading...</p>
return(
<div key={index} className="article-container">
<div className="article-preview">
<span className="article-date">{article.date}</span>
<h5>{article.title.rendered}</h5>
<div dangerouslySetInnerHTML={{ __html: article.excerpt.rendered }} />
<Link to={`/news/${article.slug}`}>Read More...</Link>
</div>
<Route path={`/news/:articleSlug`}
render={this.renderArticle(article)}
/>
</div>
)
});
return (
<div>
<h3>All Articles from Blog</h3>
{articles}
</div>
)
}
Hope this points you in the right direction.

Any use of a keyed object should be wrapped in React.addons.createFragment(object)

let playerInfo = [{
name: 'jose',
country: 'USA',
age: 47
}];
export default class PlayersInfo extends React.Component {
static getProps() {
return {};
}
render() {
let playerInf = playerInfo.map((playerData, index) => {
return <div className="item" key={index}>{playerData}</div>
});
return <div>
<div>
{playerInf}
</div>
<RouteHandler />
</div>;
}
Why am I getting this error in the browser's console?
Warning: Any use of a keyed object should be wrapped in React.addons.createFragment(object) before being passed as a child.
I put together a JSBin for you. Basically, the warning comes from this line:
return <div className="item" key={index}>{playerData}</div>
It's because playerData is an object and ReactJS doesn't know how to render it.
If you change that to the following, it won't give you the warning:
return (
<div className="item" key={index}>
<div>Name: {playerData.name}</div>
<div>Country: {playerData.country}</div>
<div>Age: {playerData.age}</div>
</div>
);
Why am I getting this error in the browser's console?
Because you are passing an object (playerData) as child to a React Component.
I tend to run into this when rendering a Date object by mistake. You need to stringify these manually:
Produces warning:
<div>{ new Date() }</div>
Works ok:
<div>{ String(new Date()) }</div>
Does this work?
return <div>
<div>
{playerInf}
</div>
<div>
<RouteHandler />
</div>;
</div>
}

Categories

Resources