How to pass function from statefull to stateless component? - javascript

I want to pass the function from stateful component to stateless component below is my code.
below is stateless code
const ProductsGridItem = props => {
const { result } = props;
const source = result._source;
return (
<ProductCard
ProductName={source.productName}
ProductGuid={source.productGuid}
Key={source.productGuid}
ProductStatus={source.status}
DecimalPrecision={decimalValue}
IsActive={source.isActive}
Image={source.imageName}
ProductCode={source.productCode}
MinPrice={source.minPrice}
Ratings={source.ratings}
CurrencySymbol={source.currencySymbol}
SupplierGuid={source.supplierGuid}
Type="grid"
ListBucketDetails={basketDetails}
WishListDetails={wishListDetails}
CompanyName={source.companyName}
BuyingWindowStatus={source["buyingwindowstatus.raw"]}
NewArrival={source["newarrival_raw.raw"]}
/>
);
};
and below this my class method starts that is stateful code starts.
class ProductListingPage extends Component {
constructor(props) {
super(props);
this.state = {
resources: [],
isFeatureAvailable: false,
loading: false,
decimalPrecesion: "",
filterList: [],
productBucketList: [],
open: false,
rating: 1,
companyGuid: null,
showMobileFilter: false,
dataEmpty: false
};
}
handleDrawerOpen = () => {
this.setState({ open: true });
};
}
I want to pass the handleDrawerOpen in ProductCard component. Could you please help how to do this?
I can fix this issue by moving const ProductsGridItem in class but my seniors not allowing me to do this. I dont know why. Both code are in same file. Please help.
EDITED:
In render the stateless component is using like below
<ViewSwitcherHits
hitsPerPage={16}
sourceFilter={[
"productName",
"productCode",
"imageName",
"manufacturerName",
"productGuid",
"tagAttributes",
"status",
"isActive",
"minPrice",
"ratings",
"currencySymbol",
"supplierGuid",
"companyName",
"buyingwindowstatus.raw",
"listproductsubcategory",
"newarrival_raw.raw"
]}
hitComponents={[
{
key: "grid",
title: getLabelText(
resources.filter(x => {
return x.resourceKey === "grid";
})[0],
"Grid"
),
itemComponent: ProductsGridItem,
InitialLoaderComponent: InitialLoaderComponent,
defaultOption: true
},
{
key: "list",
title: getLabelText(
resources.filter(x => {
return x.resourceKey === "list";
})[0],
"List"
),
itemComponent: ProductsListItem,
InitialLoaderComponent: InitialLoaderComponent
}
]}
scrollTo="body"
/>
In Hitcomponents - itemComponent: ProductsGridItem, I'm using Searchkit ViewSwitcherHits

I assume ProductListingPage's render (which you haven't shown) uses ProductsGridItem. In that location, you'd pass this.handleOpen:
<ProductsGridItem handleOpen={this.handleOpen} YourOtherStuffHere />
Within ProductsGridItem, you'd pass that on to ProductsGrid:
return (
<ProductCard
handleOpen={props.handleOpen}
YourOtherStuffHere
/>
);
Some style rules suggest not using props. within the JSX for child components. If your in-house style rules say not to do that, you can put handleOpen in an initial destructuring of props:
const ProductsGridItem = props => {
const { result: {_source: source}, handleOpen } = props;
return (
<ProductCard
handleOpen={handleOpen}
ProductName={source.productName}
ProductGuid={source.productGuid}
Key={source.productGuid}
ProductStatus={source.status}
DecimalPrecision={decimalValue}
IsActive={source.isActive}
Image={source.imageName}
ProductCode={source.productCode}
MinPrice={source.minPrice}
Ratings={source.ratings}
CurrencySymbol={source.currencySymbol}
SupplierGuid={source.supplierGuid}
Type="grid"
ListBucketDetails={basketDetails}
WishListDetails={wishListDetails}
CompanyName={source.companyName}
BuyingWindowStatus={source["buyingwindowstatus.raw"]}
NewArrival={source["newarrival_raw.raw"]}
/>
);
};

Related

Advanced Vue.js Dynamic Functional Component using `:is` syntax and render function

Background: I've built a standard single file component that takes a name prop and looks in different places my app's directory structure and provides the first matched component with that name. It was created to allow for "child theming" in my Vue.js CMS, called Resto. It's a similar principle to how WordPress looks for template files, first by checking the Child theme location, then reverting to the parent them if not found, etc.
Usage : The component can be used like this:
<!-- Find the PageHeader component
in the current child theme, parent theme,
or base components folder --->
<theme-component name="PageHeader">
<h1>Maybe I'm a slot for the page title!</h1>
</theme-component>
My goal : I want to convert to a functional component so it doesn't affect my app's render performance or show up in the Vue devtools. It looks like this:
<template>
<component
:is="dynamicComponent"
v-if="dynamicComponent"
v-bind="{ ...$attrs, ...$props }"
v-on="$listeners"
#hook:mounted="$emit('mounted')"
>
<slot />
</component>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
name: 'ThemeComponent',
props: {
name: {
type: String,
required: true,
default: '',
},
},
data() {
return {
dynamicComponent: null,
resolvedPath: '',
}
},
computed: {
...mapGetters('site', ['getThemeName']),
customThemeLoader() {
if (!this.name.length) {
return null
}
// console.log(`Trying custom theme component for ${this.customThemePath}`)
return () => import(`#themes/${this.customThemePath}`)
},
defaultThemeLoader() {
if (!this.name.length) {
return null
}
// console.log(`Trying default component for ${this.name}`)
return () => import(`#restoBaseTheme/${this.componentPath}`)
},
baseComponentLoader() {
if (!this.name.length) {
return null
}
// console.log(`Trying base component for ${this.name}`)
return () => import(`#components/Base/${this.name}`)
},
componentPath() {
return `components/${this.name}`
}, // componentPath
customThemePath() {
return `${this.getThemeName}/${this.componentPath}`
}, // customThemePath()
},
mounted() {
this.customThemeLoader()
.then(() => {
// If found in the current custom Theme dir, load from there
this.dynamicComponent = () => this.customThemeLoader()
this.resolvedPath = `#themes/${this.customThemePath}`
})
.catch(() => {
this.defaultThemeLoader()
.then(() => {
// If found in the default Theme dir, load from there
this.dynamicComponent = () => this.defaultThemeLoader()
this.resolvedPath = `#restoBaseTheme/${this.defaultThemePath}`
})
.catch(() => {
this.baseComponentLoader()
.then(() => {
// Finally, if it can't be found, try the Base folder
this.dynamicComponent = () => this.baseComponentLoader()
this.resolvedPath = `#components/Base/${this.name}`
})
.catch(() => {
// If found in the /components dir, load from there
this.dynamicComponent = () => import(`#components/${this.name}`)
this.resolvedPath = `#components/${this.name}`
})
})
})
},
}
</script>
I've tried SO many different approaches but I'm fairly new to functional components and render functions (never got into React).
The roadblock : I can't seem to figure out how to run the chained functions that I call in my original mounted() function. I've tried running it from inside the render function with no success.
Big Question
How can I find and dynamically import the component I'm targeting before I pass that component to the createElement function (or within my single file <template functional><template/>)?
Thanks all you Vue-heads! ✌️
Update: I stumbled across this solution for using the h() render function and randomly loading a component, but I'm not sure how to make it work to accept the name prop...
Late to the party, but I was in a similar situation, where I had a component in charge of conditionally render one of 11 different child components:
<template>
<v-row>
<v-col>
<custom-title v-if="type === 'title'" :data="data" />
<custom-paragraph v-else-if="type === 'paragraph'" :data="data" />
<custom-text v-else-if="type === 'text'" :data="data" />
... 8 more times
</v-col>
</v-row>
</template>
<script>
export default {
name: 'ProjectDynamicFormFieldDetail',
components: {
CustomTitle: () => import('#/modules/path/to/CustomTitle'),
CustomParagraph: () => import('#/modules/path/to/CustomParagraph'),
CustomText: () => import('#/modules/path/to/CustomText'),
... 8 more times
},
props: {
type: {
type: String,
required: true,
},
data: {
type: Object,
default: null,
}
},
}
</script>
which of course is not ideal and pretty ugly.
The functional equivalent I came up with is the following
import Vue from 'vue'
export default {
functional: true,
props: { type: { type: String, required: true }, data: { type: Object, default: null } },
render(createElement, { props: { type, data } } ) {
// prop 'type' === ['Title', 'Paragraph', 'Text', etc]
const element = `Custom${type}`
// register the custom component globally
Vue.component(element, require(`#/modules/path/to/${element}`).default)
return createElement(element, { props: { data } })
}
}
Couple of things:
lazy imports don't seem to work inside Vue.component, hence require().default is the way to go
in this case the prop 'type' needs to be formatted, either in the parent component or right here

Push object from JSON rest api into empty array not working in React

I am trying to push a object from a JSON response from a rest api into an empty array in React, so far my code is not working, I am fairly new to React so can't see where I am going wrong? Maybe something to do with the function in the state? I am getting error:
Cannot read property 'saved' of undefined
code so far:
import React, { Component } from 'react';
import './news-hero.css';
import Carousel from "react-multi-carousel";
import "react-multi-carousel/lib/styles.css";
const responsive = {
superLargeDesktop: {
breakpoint: { max: 4000, min: 3000 },
items: 1,
},
desktop: {
breakpoint: { max: 3000, min: 1024 },
items: 1,
},
tablet: {
breakpoint: { max: 1024, min: 464 },
items: 1,
},
mobile: {
breakpoint: { max: 464, min: 0 },
items: 1,
},
};
class NewsHero extends Component {
state = {
loading: false,
data: [],
headline: [],
saved: []
}
saved() {
this.saved.push(this.headline);
//alert('this is saved kind of');
}
onError() {
this.setState({
imageUrl: "../assets/img-error.jpg"
})
}
componentDidMount() {
this.setState({ loading: true })
fetch('https://newsapi.org/v2/everything?q=timbaland&domains=rollingstone.com,billboard.com&excludeDomains=townsquare.media&apiKey=8')
.then(headline => headline.json())
.then(headline => this.setState({ headline: headline.articles, loading: false }, () => console.log(headline.articles)))
}
render() {
return (
<div className="hero">
<h2 className="text-left">News</h2>
{this.state.loading
? "loading..."
: <div>
<Carousel
additionalTransfrom={0}
showDots={true}
arrows={true}
autoPlaySpeed={3000}
autoPlay={false}
centerMode={false}
className="carousel-hero"
containerClass="container-with-dots"
dotListClass="dots"
draggable
focusOnSelect={false}
infinite
itemClass="carousel-top"
keyBoardControl
minimumTouchDrag={80}
renderButtonGroupOutside={false}
renderDotsOutside
responsive={responsive}>
{this.state.headline.map((post, indx) => {
return (
<div className="text-left mt-5" key={indx}>
<img className="media-img card-img-top card-img-hero" src={post.urlToImage} alt="Alt text"></img>
<div className="card-body container hero-text-body">
<h1 className="card-title hero-title text-truncate">{post.title}</h1>
<button className="btn-primary btn mt-2 mb-4" onClick={this.saved}>Add this article</button>
<p className="card-text">{post.description}</p>
Read More
</div>
</div>
)
})}
</Carousel>
</div>
}
</div>
)
}
}
export default NewsHero;
Any idea's and insight?
Cannot read property 'saved' of undefined
You are not correctly referencing this.state for saved or headline.
Cannot read property 'state' of undefined
Either add a constructor and bind this to your saved function.
constructor(props) {
super(props);
this.saved = this.saved.bind(this);
}
saved() {
this.saved.push(this.headline);
//alert('this is saved kind of');
}
Or define saved as an arrow function to bind this
saved = () => {
this.saved.push(this.headline);
//alert('this is saved kind of');
}
Should be
saved() {
const { headline, saved } = this.state;
this.setState({ saved: [...saved, headline] });
}
or
saved = () => {
const { headline, saved } = this.state;
this.setState({ saved: [...saved, headline] });
}
UPDATE: Save a specific post/headline
I see now, if you wish to save a specific headline then you need to update the signature of your saved function and how you call it.
saved = headline => {
this.setState(
prevState => ({ saved: [...prevState.saved, headline] })
);
}
and when you invoke it as a callback
<button
className="btn-primary btn mt-2 mb-4"
onClick={() => this.saved(post)} // pass the current post defined in the map callback
>
Add this article
</button>
One minor comment about naming, consistently referencing the same "object" the same way throughout code goes a long way in helping readability. I.E. how you reference headlines, posts, and articles. Pick one and be consistent.
this.state.headlines => this.state.headlines.map(headline => ...
this.state.posts => this.state.posts.map(post => ...
this.state.articles => this.state.articles.map(article => ...
saved() {
this.saved.push(this.headline);
//alert('this is saved kind of');
}
You cannot defined class methods like this without writing a constructor like this:
class NewsHero extends Component {
constructor(props){
super(props)
this.saved = this.saved.bind(this)
}
...the rest of everything you wrote
}
Without doing this, your saved function is not bound to the instance of the class. Its silly, yes, but that's how class methods work in javasript. The other option is to use an arrow function, which preserves the context if this:
saved = () => {
this.saved.push(this.headline);
//alert('this is saved kind of');
}
I think this is faster and cleaner, and its pretty commonly used.
That is problem 1. Problem 2 is that it appears you are trying to modify the state object directly when writing this.saved.push(this.headline). That's not how to do it. To modify your state, you need to use the this.setState function:
saved = () => {
this.setState({
saved: whatever you want this to be
})
}
But it looks like you are already setting your state correctly in your fetch call, so I'm not sure what you're trying to accomplish with your saved function...?

Material-table with component as data not passing the component context in onChange event

I tried to add component as data in material table but i'm not able to access 'this' context of the component to update state in the onChange function. The editable feature provided by the material-table does not fit my requirement.
class ClubList extends Component {
state = { clubs: '', tableClubs: [] };
changeDate=(change)=>{
console.log(this) //returns undefined. I need to update state here but not able to access 'this' context of component
}
addClub = (event, clubs) => {
let enteredClubs = this.state.clubs;
let allClubs = this.props.clubs;
let tableClubs = [];
let enteredClubsArray = enteredClubs.split(/[ ,]+/);
for (let clubs in enteredClubsArray) {
tableClubs.push(allClubs.find(({ number }) => number == enteredClubsArray[clubs]));
}
tableClubs.map(
(clubs) => (
(clubs.scheduledDate = (
<DateComponent name="eventDate"
value={this.props.createData.eventDate}
handleChange={this.changeDate} /> //Im calling the function here
)),
(clubs.city = clubs.city)
)
);
this.setState({ clubs: '', tableClubs });
this.clubsForCreate(tableClubs);
};
render() {
return (
<div className="clubSection">
<TextComponent
label="Enter Clubs"
name="clubs"
variant="outlined"
handleChange={this.changeState}
value={this.state.clubs}
/>
<Fab color="primary" aria-label="add" onClick={this.addClub} className="clubAddButton">
<MaterialTable
title="Clubs"
columns={[
{ title: 'Date', field: 'scheduledDate' },
{ title: 'Status', field: 'statusDesc' },
]}
data={this.state.tableClubs}
/>
</div>
);
}
}
Changing the function "changeDate" from arrow function to a normal function solved this issue. I guess the arrow function was removing the context of the callback function

Put image as an object into state variable and pass it as a props

I was trying to put an image as an object into state and pass the state as a props in react but it's not passing anything.
import xaprikaScreen from "./images/xaprikaScreenShot.jpg";
import linguadenScreen from "./images/linguadenScreenShot.jpg";
export class Portfolio extends Component {
constructor() {
super();
this.state = {
currentProject: 0,
projects: [
{
id: 1,
projectName: "Linguaden.com",
projectLink: "www.linguaden.com",
githubLink: "",
displayPicture: { linguadenScreen },
projectDescription: ""
},
{
id: 2,
projectName: "Xaprika.com",
projectLink: "www.Xaprika.com",
githubLink: "",
displayPicture: { xaprikaScreen },
projectDescription: ""
}
]
};
}
render() {
return (
<Projects imageSource={this.state.projects[this.state.currentProject["displayPicture"]}/>
);
}
}
}
Here i have imported 2 images as an object, then i have assigned them into state and then i am trying to pass those variables as props.
The problem here is that you're doing this.state.currentProject["displayPicture"]. The value of currentProject is 0, and 0.displayPicture is undefined. You're then doing this.state.projects[this.state.currentProject["displayPicture"], which not only doesn't have a closing bracket, even if it did that would be equivalent to this.state.projects[undefined] which is also undefined.
Replace your render function with the below code and it should work.
render() {
return (
<Projects imageSource={this.state.projects[this.state.currentProject].displayPicture} />
);
}

How to design a generic filter like ecommerce website have using ReactJs?

i am planning to build a generic filter like Gbif Have.
My question is how to approach this problem.
I like to use ReactJs for this project.
What other technology i need to look into along with React and redux in order to design such a generic filter.
I try to design this filter using React and redux only.
In my approach, i try to maintain the query parameter inside the state variable of the get_data method, in which i am fetching the data from the server. As somebody click on any filter button, then i pass custom event from that filter component along with query parameter and handle this event in get_data method. In get_data method again i am saving this value in get_data state parameter and again getting the new filtered data.
Now the Problem with above approach is that as the number of parameter increases it become very difficult to maintain.
my get_data constructor look like this.
constructor(props){
super(props);
this.state={
params:{
max:10,
offset:0,
taxon:[],
sGroup:[],
classification:undefined,
userGroupList:[],
isFlagged:undefined,
speciesName:undefined,
isMediaFilter:undefined,
sort:"lastRevised",
webaddress:""
},
title:[],
groupName:[],
userGroupName:[],
view:1
}
this.props.fetchObservations(this.state.params)
this.loadMore=this.loadMore.bind(this);
};
The way i am getting data from filter component is something like this.
this is my handleInput method which fire onSelect method from one of the filter.
handleInput(value,groupName){
this.setState({
active:true
})
this.props.ClearObservationPage();
var event = new CustomEvent("sGroup-filter",{ "detail":{
sGroup:value,
groupName:groupName
}
});
document.dispatchEvent(event);
};
the way i am handling this event in my get_data component is look something like this.
sGroupFilterEventListner(e){
const params=this.state.params;
if(!params.sGroup){
params.sGroup=[];
}
console.log("params.sGroup",params.taxon)
params.sGroup.push(e.detail.sGroup)
params.sGroup=_.uniqBy(params.sGroup)
const groupName=this.state.groupName;
var titleobject={};
titleobject.sGroup=e.detail.sGroup;
titleobject.groupName=e.detail.groupName;
groupName.push(titleobject);
let newgroupname=_.uniqBy(groupName,"sGroup")
params.classification=params.classification;
let isFlagged=params.isFlagged;
let speciesName=params.speciesName;
let MediaFilter=params.isMediaFilter;
let taxonparams=params.taxon;
taxonparams= taxonparams.join(",");
let sGroupParams=params.sGroup;
sGroupParams=sGroupParams.join(",");
let userGroupParams=params.userGroupList;
userGroupParams=userGroupParams.join(",");
let newparams={
max:10,
sGroup:sGroupParams,
classification:params.classification,
offset:0,
taxon:taxonparams,
userGroupList:userGroupParams,
isFlagged:isFlagged,
speciesName:speciesName,
isMediaFilter:MediaFilter,
sort:params.sort
}
this.props.fetchObservations(newparams);
this.setState({
params:{
max:10,
sGroup:params.sGroup,
classification:params.classification,
offset:0,
taxon:params.taxon,
userGroupList:params.userGroupList,
isFlagged:isFlagged,
speciesName:speciesName,
isMediaFilter:MediaFilter,
sort:params.sort
},
groupName:newgroupname
})
}
I registered and unRegistered the sGroupFilterEventListner in my componentDidMount and componentunmount method.
Presently i am also not considering the case where if somebody type in url bar, the filter panel change automatically.
Please consider all the above scenario and suggest me a generic way to do the same. thanks.
My Current Filter Panle look like this
Here's a quick example (React only, no Redux) I whipped up with a dynamic number of filters (defined in the filters array, but naturally you can acquire that from wherever).
const filters = [
{ id: "name", title: "Name", type: "string" },
{
id: "color",
title: "Color",
type: "choice",
choices: ["blue", "orange"],
},
{
id: "height",
title: "Height",
type: "choice",
choices: ["tiny", "small", "big", "huge"],
},
{
id: "width",
title: "Width",
type: "choice",
choices: ["tiny", "small", "big", "huge"],
},
];
const filterComponents = {
string: ({ filter, onChange, value }) => (
<input
value={value || ""}
onInput={e => onChange(filter.id, e.target.value)}
/>
),
choice: ({ filter, onChange, value }) => (
<select
value={value || ""}
onInput={e => onChange(filter.id, e.target.value)}
size={1 + filter.choices.length}
>
<option value="">(none)</option>
{filter.choices.map(c => (
<option value={c} key={c}>
{c}
</option>
))}
</select>
),
};
class App extends React.Component {
constructor(props) {
super(props);
this.state = { filters: {} };
this.onChangeFilter = this.onChangeFilter.bind(this);
}
onChangeFilter(filterId, value) {
const newFilterState = Object.assign({}, this.state.filters, {
[filterId]: value || undefined,
});
this.setState({ filters: newFilterState });
}
renderFilter(f) {
const Component = filterComponents[f.type];
return (
<div key={f.id}>
<b>{f.title}</b>
<Component
filter={f}
value={this.state.filters[f.id]}
onChange={this.onChangeFilter}
/>
</div>
);
}
render() {
return (
<table>
<tbody>
<tr>
<td>{filters.map(f => this.renderFilter(f))}</td>
<td>Filters: {JSON.stringify(this.state.filters)}</td>
</tr>
</tbody>
</table>
);
}
}
ReactDOM.render(<App />, document.querySelector("main"));
body {
font: 12pt sans-serif;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<main/>
(originally on https://codepen.io/akx/pen/JyemQQ?editors=0010)
Hope this helps you along.

Categories

Resources