Return multiple Components depending on array prop - reactjs

Trying to return multiple action buttons from a ActionButtons component:
export default class ActionButtons extends Component {
render() {
console.log(this.props.actions)
return(
this.props.actions.map((field, i) => {
<div key={field.href}>
<DefaultButton
key={field.href}
text={field.label}
href={field.href}
/>
</div>
})
)
}
}
Calling it from another component with the following code:
const actions = [
{"label": "Go Back", "href":"www.google.com"}
];
<ActionButtons actions={actions} />
On the ActionButtons component if i return just one single button without the map then it works. What am i missing ?

You need to explicitly return your jsx from inside map
//inside render
return this.props.actions.map((field, i) => {
return (
<div key={field.href}>
<DefaultButton
key={field.href}
text={field.label}
href={field.href}
/>
</div>
)
}
When using a jsx block () (the example above returns an array which is also valid) you also need to declare your operations inside curly brackets.
export default class ActionButtons extends Component {
render() {
console.log(this.props.actions)
return (
<>
{
this.props.actions.map((field, i) => {
return (
<div key={field.href}>
<DefaultButton
key={field.href}
text={field.label}
href={field.href}
/>
</div>
)
})
}
</>
)
}
}

Related

Modify class name of react element programmatically

Is there a native way to add a style class name to a react element passed as a property WITHOUT using jQuery or any 3rd-party libraries.
The following example should demonstrate what I'm trying to do. Note, react class names are made up.
Edit: The point is to modify the class name of a react element that is passes as a property to the Books class! The Books class needs to modify the class name. Apparently, it does not have access to Authors class's state to use within Authors class.
File authors.js
class Authors {
render() {
return (
<ul>
<li>John Doe</li>
<li>Jane Doe</li>
</ul>
);
}
}
File shelf.js
class Shelf {
render() {
return (
<div>
<Books authors={<Authors/>}/>
</div>
);
}
}
File books.js
class Books {
this.props.authors.addClass('style-class-name'); <- HERE
render() {
return (
<div>
{this.props.authors}
</div>
);
}
}
Potentially need more context, but in this kind of scenario, I would use state to dynamically add/remove a class. A basic example would be:
const App = () => {
const [dynamicClass, setDynamicClass] = useState("");
return (
<div className={`normalClass ${dynamicClass}`}>
<button onClick={() => setDynamicClass("red")}>Red</button>
<button onClick={() => setDynamicClass("green")}>Green</button>
<button onClick={() => setDynamicClass("")}>Reset</button>
</div>
);
};
The state changes schedule a re-render, hence you end up with dynamic classes depending on the state. Could also pass the class in as a property, or however you want to inject it into the component.
React elements do have an attribute called className. You can use that to set CSS classes to your component. You can pass static data (strings) or dynamic ones (basically calculated ones):
<div className="fixedValue" />
<div className={fromThisVariable} />
Keep in mind, that you have to pass down your className, if you wrap native HTML elements in a component:
class Books {
render() {
const {
authors,
// other represents all attributes, that are not 'authors'
...other
}
return (
<div {...other}>
{this.props.authors}
</div>
);
}
}
If you want to add data to your authors attribute (which I assume is an array), you could implement a thing like the following:
let configuredAuthors = this.props.authors.map((author) => ({
return {
...author,
className: `${author.firstName}-${author.lastName}`
}
}))
Keep in mind, that either way, you have to manually assign this className property in your child components (I guess an Author component)
To handle updates from a child component, use functions: https://reactjs.org/docs/faq-functions.html
Full example:
import React from "react";
class Shelf extends React.Component {
render() {
const authors = [
{
firstName: "John",
lastName: "Tolkien"
},
{
firstName: "Stephen",
lastName: "King"
}
];
return (
<div>
<Books authors={authors} />
</div>
);
}
}
const Books = ({authors, ...other}) => {
const [configuredAuthors, setAuthors] = React.useState(authors)
const updateClassName = (authorIndex, newClassName) => {
const newAuthors = [...configuredAuthors]
newAuthors[authorIndex] = {
...configuredAuthors[authorIndex],
className: newClassName
}
setAuthors(newAuthors)
}
return (
<ul {...other}>
{configuredAuthors.map((author, index) => {
return <Author key={index} author={author}
index={index}
updateClassName={updateClassName}
/>;
})}
</ul>
);
}
const Author = ({ author, index, updateClassName, ...other }) => {
const [count, setCount] = React.useState(0);
return (
<li className={author.className} {...other}>
<span>{`${author.firstName} ${author.lastName}`}</span>
<button
onClick={() => {
updateClassName(index, `author-${count}`);
setCount(count + 1);
}}
>
update Class ()
{`current: ${author.className || '<none>'}`}
</button>
</li>
);
};
export default function App() {
return <Shelf />;
}

React compound component without looping Children.map

Is there a way to use compound component but without using Children.map?
Here my example.
export class Dashboard extends Component {
static Header = ({ children }) => {
return <header className="header">{children}</header>;
};
static Sidebar = ({ children }) => {
return <aside className="aside">{children}</aside>;
};
static Main = ({ children }) => {
return <main className="main">{children}</main>;
};
static Footer = ({ children }) => {
return <footer className="footer">{children}</footer>;
};
render() {
const { children } = this.props;
const elements = Children.toArray(children);
return (
<div>
{cloneElement(elements[0])}
<div>
{cloneElement(elements[1])}
{cloneElement(elements[2])}
<div>
{cloneElement(elements[3])}
</div>
</div>
</div>
);
}
}
Usage example
<Dashboard>
<Dashboard.Sidebar>THIS IS Sidebar</Dashboard.Sidebar>
<Dashboard.Header>THIS IS HEADER</Dashboard.Header>
<Dashboard.Main>THIS IS MAIN</Dashboard.Main>
<Dashboard.Footer>THIS IS FOOTER</Dashboard.Footer>
</Dashboard>
But as you can see, it is based on array index which is not a good solution.
Is there something like
return (
<div>
{ cloneElement(Children.byName('Sidebar'))}
<div>
{cloneElement(Children.byName('Header'))}
{cloneElement(Children.byName('Main'))}
<div>
{cloneElement(Children.byName('Footer'))}
</div>
</div>
</div>
);
No, but you can just use javascript for it:
export class Dashboard extends Component {
render() {
const { children } = this.props;
const [sidebar, header, main, footer] = Children.toArray(children);
return (
<div>
{sidebar}
<div>
{header}
{main}
<div>{footer}</div>
</div>
</div>
);
}
}
Either way, it's an error-prone API where the user must know the children order, for example, changing the order breaks the component:
<Dashboard>
<Dashboard.Footer>THIS IS FOOTER</Dashboard.Footer>
<Dashboard.Main>THIS IS MAIN</Dashboard.Main>
<Dashboard.Sidebar>THIS IS Sidebar</Dashboard.Sidebar>
<Dashboard.Header>THIS IS HEADER</Dashboard.Header>
</Dashboard>
I would recommend passing the components via props:
<Dashboard footer={footerComponent}/>
// Usage inside Dashboard
<Dashboard.Footer>
{footerComponet}
</Dashboard.Footer>
// Or lazy render
<Dashboard footer={() => footerComponent}/>
// Usage inside Dashboard
const FooterComponent = footerComponent
<Dashboard.Footer>
<FooterComponent/>
</Dashboard.Footer>

Functional Component unable to render return value based on props values

Goal: To implement a Toast Message modal (using Functional Component) which will show or hide based on the props value (props.showToastModal) within the return of ToastModal component
Expected: Using props.showToastModal directly would determine if Toast appears
Actual: Modal does not appear based on props.showToastModal
Here's the code:
Parent Component
class Datasets extends Component {
constructor(props) {
super(props)
this.state = {
showToastModal: false,
toastModalText: ''
}
}
toggleOff = () => {
this.setState({ showToastModal: false, toastModalText: '' })
}
render() {
{this.state.showToastModal && (
<ToastModal
showToastModal={this.state.showToastModal}
toastModalText={this.state.toastModalText}
toggleOff={this.toggleOff}
/>
)}
}
}
Child Component
This works:
const ToastModal = (props) => {
const isOpen = props.showToastModal
return (
<div className={`${css.feedbackModal} ${isOpen ? css.show : css.hide}`}>
{props.toastModalText}
<i
className={`bx bx-x`}
onClick={() => props.toggleOff()}
/>
</div>
)
}
export default ToastModal
But this doesn't (using the props value directly):
const ToastModal = (props) => {
return (
<div className={`${css.feedbackModal} ${props.showToastModal ? css.show : css.hide}`}>
{props.toastModalText}
<i
className={`bx bx-x`}
onClick={() => props.toggleOff()}
/>
</div>
)
}
export default ToastModal
Using a const isOpen = props.showToastModal works as expected instead. I am confused why this happens. Is this is a React Lifecycle issue, or a case where it is bad practice to use props values which may be updated during the render?
Please try destructuring objects
const ToastModal = ({ showToastModal, toggleOff }) => {
return (
<div className={`${css.feedbackModal} ${showToastModal ? css.show : css.hide}`}>
{props.toastModalText}
<i
className={`bx bx-x`}
onClick={toggleOff}
/>
</div>
)
}
export default ToastModal

How to pass Grandchild data into GrandParent Component without passing data to intermediate components in Reactjs?

i have created three components Aboutus,AboutusChild & GrandChild and now i want to pass grandchild state value in my grandparent component that is "Aboutus" component but without using intermediate component(AboutusChild), is it possible in react js without using redux state management library.
i dont' want to use redux right now until some data-communication concept are not clear.
class AboutUs extends Component {
constructor(props) {
super(props)`enter code here`
this.state = {
childState: false
}
}
myMehtod(value) {
this.setState({
childState: value
})
}
render() {
console.log('AboutUs', this.state.childState)
return (
<div>
<h1 className={`title ${this.state.childState ? 'on' : ''}`}>
About us {this.props.aboutusProp}
</h1>
<AboutUsChild />
</div>
)
}
};
class AboutUsChild extends Component {
myMehtod(value) {
this.setState({
childState: value
},
this.props.myMehtodProp(!this.state.childState)
)
}
render() {
console.log('AboutUsChild', this.state.childState)
return (
<div>
<h1 className={`title ${this.state.childState ? 'on' : ''}`}>About Us Child</h1>
<GrandChild>
without function
</GrandChild>
<h1>About Us Child</h1>
<GrandChild Method={true} myMehtodProp={this.myMehtod.bind(this)} />
</div>
)
}
};
class GrandChild extends Component {
constructor() {
super()
this.state = {
TitleClick: false
}
}
TitleClick() {
this.setState(
{
TitleClick: !this.state.TitleClick
},
this.props.myMehtodProp(!this.state.TitleClick)
)
}
render() {
console.log('GrandChild', this.state.TitleClick)
return (
<div>
{this.props.Method ?
<h1 onClick={this.TitleClick.bind(this)} className={`title ${this.state.TitleClick ? 'on' : ''}`}>Grand Child</h1>
: null}
<h1>{this.props.children}</h1>
</div>
)
}
};
There's really no way to pass child state up without passing it to some callback. But frankly speaking this does not seem like a good design choice to me. You should always have common state on top of all components consuming that state. What you can do, is to pass stuff as children:
class SomeComponent extends React.Component {
...
render() {
const { value } = this.state;
return (
<Child value={value}>
<GrandChild value={value} onChange={this.handleValueChange} />
</Child>
);
}
}
const Child = ({ value, children }) => (
<div>
<h1 className={`title ${value ? 'on' : ''}`}>About Us Child</h1>
{children}
</div>
)
const GrandChild = ({ value, onChange }) => (
<div>
<h1 onClick={onChange} className={`title ${value ? 'on' : ''}`}>Grand Child</h1>
</div>
);
This way you got control from parent component of everything. If this is not the way, because you are already passing children, and for some reason you want to keep it this way, you can pass "render" prop:
// JSX in <SomeComponent /> render function:
<Child
value={value}
grandChild=(<GrandChild value={value} onChange={this.handleValueChange} />)
>
Some other children
</Child>
...
const Child = ({ value, grandChild, children }) => (
<div>
<h1 className={`title ${value ? 'on' : ''}`}>About Us Child</h1>
{grandChild}
{children}
</div>
)
If you want to be more fancy and there will more than few levels of nesting, you can always use context (highly recommend reading docs before using):
const someContext = React.createContext({ value: true, onChange: () => {} });
class SomeComponent extends React.Component {
...
render() {
const { value } = this.state;
return (
<someContext.Provider value={{ value: value, onChange: this.handleValueChange }}>
<Children>
</someContext.Provider>
);
}
}
...
const SomeDeeplyNestedChildren = () => (
<someContext.Consumer>
{({ value, onChange }) => (
<h1 onClick={onChange}>{value}</h1>
)}
</someContext.Consumer>
)
I would pick first two, if your structure is not that complex, but if you are passing props deeply, use context.
The only way to do something of this sort without external library would be leveraging React's Context API, although bare in mind that it is not nearly as robust as redux or mobX.

how to pass argument in props using functional component

how to pass an argument in props using functional component, here I had given my worked example code,
Let me explain, My click event will trigger from PopUpHandle when I click on the PopUpHandle I need to get the value from ContentSection component. ContentSection will be the listing, when clicking on each listing want to get the value of the current clicked list. I tried with this code my console printed undefined but I don't know how to handle with functional component.
class mainComponent extends Component {
constructor(props) {
super(props);
this.popTrigger = this.popTrigger.bind(this);
}
popTrigger(data){
console.log(data);
}
render(){
return(
<Popup popTrigger={this.popTrigger} />
)
}
}
export default mainComponent;
Popup component
const PopUpHandle = ({ popTrigger, value}) => <li onClick={popTrigger.bind(this, value)} />;
const ContentSection =({popTrigger, value}) =>(
<div>
{value === 'TEST1' && (
<div>
<PopUpHandle popTrigger={popTrigger} value={value} />
</div>
</div>
)}
{value === 'TEST2' && (
<div>
<PopUpHandle popTrigger={popTrigger} value={value} />
</div>
</div>
)}
</div>
)
const ContentList = (({ items, popTrigger}) => (
<div>
{items.map((value, index) => (
<ContentSection
key={`item-${index}`}
popTrigger={popTrigger}
index={index}
value={value}
/>
))}
</div>
)
);
class example extends Component {
constructor(props) {
super(props);
this.state = {
items: ['TEST1', 'TEST2', 'TEST3', 'TEST4'],
};
this.popTrigger = this.popTrigger.bind(this);
}
popTrigger(){
this.props.popTrigger()
}
render(){
return(
<ContentList popTrigger={this.popTrigger} items={this.state.items} />
)
}
}
export default example;
popTrigger(data){
console.log(data);
}
You didn't pass the data while calling this.props.popTrigger(). In javascript if you didn't pass the arguments, it will consider it as undefined.
The ContentSection component is not passed a value prop and hence its not passed on to the PopUpHandle component. Pass it like
render(){
return(
<ContentSection popTrigger={this.popTrigger} value={"test1"} />
)
}

Resources