Situation
I'm attempting to render a component based on template jsx pieces. Reason being because I have a few of these situations in my app but with subtle customizations and I'd rather leave the business logic for the customizations in the respective component, not the factory.
Example
Parent Template
<div></div>
Child Template
<p></p>
In the render function I want to add n child components to the parent. So if n=4 then I would expect output like
<div>
<p></p>
<p></p>
<p></p>
<p></p>
</div>
I tried using parentTemplate.children.push with no avail because the group template is still JSX at this point and not yet a rendered component. How can I accomplish this task using jsx + react ?
Extra
Here is what my actual code looks like so far
render() {
let groupTemplate = <ListGroup></ListGroup>
let itemTemplate = (item) => {
return <ListGroup.Item>{item.name}</ListGroup.Item>;
}
if (this.props.itemTemplate) itemTemplate = this.props.itemTemplate;
let itemsJsx;
this.props.navArray.forEach((item) => {
groupTemplate.children.push(itemTemplate(item))
});
return (
[groupTemplate]
);
}
From the parent:
const groupTemplate = <ListGroup className='custom-container-classes'></ListGroup>
const itemTemplate = <ListGroup.Item className='customized-classes'></ListGroup.Item>
<NavigationFactory groupTemplate={groupTemplate} itemTemplate={itemTemplate}>
</NavigationFactory>
You could use render props, which is the best way to achieve something like that:
The parent:
<NavigationFactory
itemTemplate={itemTemplate}
render={children => <ListGroup className='custom-container-classes'>
{children}
</ListGroup>}
/>
The Child:
render() {
this.props.render(this.props.navArray.map(item => <ListGroup.Item key={item.id}>
{item.name}
</ListGroup.Item>))
}
This will let you define the container on your calling function and enables you to create the chidlren as you want.
Hope this helps.
Unclear on your question but I have attempted to figure out based on your code on what you are trying to do, see if it helps, else I'll delete my answer.
The mechanism of below code working is that it takes in groupTemplate which in your case you'll be passing in as a prop which then takes children within it and returns them composed all together. There's default children group template which you can use if no related prop is passed and you have a mapper function within the render which determines which template to use and renders n number of times based on navArray length.
componentDidMount() {
// setup default templates
if (!this.props.navButtonContainerTemplate) {
this.prop.navButtonContainerTemplate = (items) => {
return <ListGroup>{items}</ListGroup>
}
}
if (!this.props.navButtonTemplate) {
this.props.navButtonTemplate = (item) => {
return (
<ListGroup.Item>
{item.name}
</ListGroup.Item>
)
}
}
}
render() {
const itemsJsx = this.props.navArray.map(obj => {
const itemJsx = this.props.navButtonTemplate(obj)
return itemJsx;
});
return this.props.navButtonContainerTemplate(itemsJsx);
}
Pass the contents as regular children to the ListGroup via standard JSX nesting:
render() {
let itemTemplate = (item) => {
return <ListGroup.Item>{item.name}</ListGroup.Item>;
}
if (this.props.itemTemplate) itemTemplate = this.props.itemTemplate;
return (
<ListGroup>
{this.props.navArray.map(item => (
itemTemplate(item)
))}
</ListGroup>
);
}
Related
In my application I have a list of "chips" (per material-ui), and on clicking the delete button a delete action should be taken. The action needs to be given a reference to the chip not the button.
A naive (and wrong) implementation would look like:
function MemberList(props) {
const {userList} = this.props;
refs = {}
for (const usr.id of userList) {
refs[usr.id] = React.useRef();
}
return <>
<div >
{
userList.map(usr => {
return <UserThumbView
ref={refs[usr.id]}
key={usr.id}
user={usr}
handleDelete={(e) => {
onRemove(usr, refs[usr.id])
}}
/>
}) :
}
</div>
</>
}
However as said this is wrong, since react expects all hooks to always in the same order, and (hence) always be of the same amount. (above would actually work, until we add a state/any other hook below the for loop).
How would this be solved? Or is this the limit of functional components?
Refs are just a way to save a reference between renders. Just remember to check if it is defined before you use it. See the example code below.
function MemberList(props) {
const refs = React.useRef({});
return (
<div>
{props.userList.map(user => (
<UserThumbView
handleDelete={(e) => onRemove(user, refs[user.id])}
ref={el => refs.current[user.id] = el}
key={user.id}
user={user}
/>
})}
</div>
)
}
I'm trying to create a component that tests english vocabulary.
Basically, there are 4 options with 1 correct.
When user chooses option, the right option is highlited in green, and the wrong one in red.
Then user can push the "next" button to go to the next batch of words.
I store refs in object (domRefs, line 68).
Populate it at line 80.
And remove all refs at line 115.
But it doesnt get removed, and leads to error (line 109)
https://stackblitz.com/edit/react-yocysc
So the question is - How to store these refs and what would be the better way to write this component?
Please help, Thanks.
You shouldn't keep refs for component in global variable, since it's making your component singleton. To apply some styles just use conditional rendering instead. Also, it's better to split your test app into several separate components with smaller responsibilities:
const getClassName(index, selected, rightAnswer) {
if (selected === null) {
return;
}
if (index === rightAnswer) {
return classes.rightAnswer;
}
if (index === selected) {
return classes.wrongAnswer;
}
}
const Step = ({ question, answers, rightAnswer, selected, onSelect, onNext }) => (
<div ...>
<div>{ question }</div>
{ answers.map(
(answer, index) => (
<Paper
key={ index }
onClick={ () => onSelect(index) }
className={ getClassName(index, selected, rightAnswer) }
) }
{ selected && <button onClick={ onNext() }>Next</button> }
</div>
);
const Test = () => {
const [ index, setIndex ] = useState();
const word = ..., answers = ..., onSelect = ..., onNext = ...,
return (
<Question
question={ word }
answers={ answers }
... />
);
}
I have a list of "selections" that are displayed using a component. I need to find the rendered width of all these selections. My template looks like this:
{props.selections.map((chip: SelectOptionType) => {
return (
<Chip text={chip.label} />
)
}
Typically, in a non-React application, I'd probably put a class on the <Chip /> and use jquery to select elements of that class name, then loop over them and just sum the widths together:
let sum: number = 0;
$(".someClassName").forEach(($el) => sum += $el.offsetWidth);
I know the suggested way of doing something similar to this is using refs, but it seems you cant create an array of refs. I tried doing something like this:
{props.selections.map((chip: SelectOptionType, index: number) => {
chipsRefs[index] = React.createRef<HTMLDivElement>();
return (
<div ref={chipsRefs[index]}>
<Chip text={chip.label} />
</div>
)
}
But as I quickly learned each Ref inside chipsRefs ended up with a null current.
Now I'm a bit at a loss for this and have tried finding examples of this use case but have come up empty.
Can you try this ?
ref={ref => {
chipsRefs[index] = ref
}}
Try doing something like this: https://codesandbox.io/s/awesome-haibt-zeb8m
import React from "react";
class Selections extends React.Component {
constructor(props) {
super(props);
this._nodes = new Map();
}
componentDidMount() {
this.checkNodes();
}
checkNodes = () => {
let totalWidth = 0;
Array.from(this._nodes.values())
.filter(node => node != null)
.forEach(node => {
totalWidth = totalWidth + node.offsetWidth;
});
console.log(totalWidth);
};
render() {
const { selections } = this.props;
return (
<div>
{selections.map((value, i) => (
<div key={i} ref={c => this._nodes.set(i, c)}>
{value}
</div>
))}
</div>
);
}
}
export default Selections;
The function we defined in the ref prop is executed at time of
render.
In the ref call-back function, ref={c => this._nodes.set(i, c)}
we pass in the index (i) provided by .map() and the html element
(c) that is provided by the ref prop, in this case the div itself.
this._nodes.set(i, c) will create a new key-value pair in our
this._nodes iterable, one pair for each div we created. Now we have recorded HTML elements (nodes) to work with that contain all the methods we need to calculate the totalWidth of your rendered list.
Lastly in checkNodes() we get the .offsetWidth of each node to get our totalWidth.
I currently have a react project I'm working on. My render method looks like this going into my return method:
render() {
let elements = [];
this.dropdownCounter().forEach(item => {
if(item != "attributeProduct") {
console.log('setting');
elements.push(
<Dropdown
title={this.state[item][0]['title']}
arrayId={item}
list={this.state[item]}
resetThenSet={this.resetThenSet}
/>
);
}
});
this.state.attributeProduct.map(attributeItem => {
elements.push(
<Dropdown
title={attributeItem.name}
arrayId='attributeMetaProduct'
list={
this.state.attributeMetaProduct.filter(metaItem => metaItem.attribute_id == attributeItem.ID)
}
resetThenSet={this.resetThenSet}
/>
);
});
return (
I have a lot of code going on in the render area due to different drop downs dependent on other methods. Is there a way that I can do something like this instead?
render() {
allMyPrereturnStuff()
return()
}
Then just place all this code in allMyPrereturnStuff()? I've tried creating this function and passing everything there but it doesn't work due to all the "this". Any ideas?
Yes, you can easily drop in normal javascript expressions into JSX:
return (
<div>
{this.renderStuff()}
{this.renderOtherStuff()}
{this.renderMoreStuff()}
</div>
);
You can even base it on flags:
const shouldRenderMoreStuff = this.shouldRenderMoreStuff();
return (
<div>
{this.renderStuff()}
{this.renderOtherStuff()}
{shouldRenderMoreStuff ? this.renderMoreStuff() : null}
</div>
);
Do note that it is often an anti-pattern to have render* methods in your components other than the normal render method. Instead, each render* method should probably be its own component.
Don't forget to bind your allMyPrereturnStuff() method in the constructor so "this" will work inside it.
constructor(props) {
super(props);
// ... your existing code
this.allMyPrereturnStuff = this.allMyPrereturnStuff.bind(this);
}
allMyPrereturnStuff = (params) => {
// ... all the code
}
However, you might want to consider breaking out the code to components, which is more Reacty way to do things.
For example, you could refactor this
this.state.attributeProduct.map(attributeItem => {
elements.push(<Dropdown
title={attributeItem.name}
arrayId='attributeMetaProduct'
list={
this.state.attributeMetaProduct.filter(metaItem => metaItem.attribute_id == attributeItem.ID)
}
resetThenSet={this.resetThenSet}
/>);
});
To something like (somewhat pseudocody):
const DropdownList = (props) => {
return (<Dropdown
title={props.attributeItem.name}
arrayId='attributeMetaProduct'
list={props.list}
resetThenSet={props.resetThenSet}
/>);
}
And in the original component's render function, have something like
render() {
return (this.state.attributeProduct.map(attributeItem => {
<DropdownList attributeItem={attributeItem}
list={ this.state.attributeMetaProduct.filter(metaItem => metaItem.attribute_id == attributeItem.ID) }
resetThenSet={this.resetThenSet}
/>);
}
Say I have this List stateless component:
const List: React.StatelessComponent<ListProps> = props => {
const listClasses =
'atom-list uk-list ' +
props.className +
(props.striped ? ' uk-list-striped' : '')
return (
<ul className={listClasses}>
{props.items.map((item, index) => {
const itemClasses = [item.classes, props.childClasses]
.filter(prop => prop)
.join(' ')
return (
<li key={index} className={itemClasses}>
{item.content}
</li>
)
})}
</ul>
)
}
And I use the List component in another stateless component, SortableList:
const SortableList: React.StatelessComponent<SortableListProps> = props => {
const classes = 'molecule-sortableList ' + props.className
const items = props.items.map((item, index) => {
return { content: <div><div className="uk-sortable-handle" />{item.content}</div> }
})
return <List className={classes} items={items} data-uk-sortable="handle: .uk-sortable-handle" />
}
Because I'm using UIKit to make the list sortable, I need to apply a data attribute to the List element when I use it in SortableList, and have that data attribute output in the resulting ul.
Applying the data attribute directly to the ul in List works (the attribute is output in the resulting HTML), but that's not ideal, since it requires that List knows something irrelevant about how it's being used.
I really want to apply the data attribute directly to List's usage and have that output in the resulting ul. Unfortunately, doing something like this in SortableList doesn't seem to work:
return <List className={classes} items={items} data-uk-sortable="handle: .uk-sortable-handle" />
Why doesn't this work, and what do I need to do to pass the data attribute into List and have it output in the resulting ul?
You can do this by using approach similar to the following:
const List: React.StatelessComponent<ListProps> = (props) => {
return (
<div {...props}>List</div>
);
};
The example is overly simplified and you should not destruct props directly as they contain some extra properties you do not want to be rendered as attributes. Instead you can copy all of the props properties that start for example with "data-" into separate object and destruct it instead.