Map over Fragment in React 16 - reactjs

I am trying to map over the children of a fragment that is in turn a child of a component. For example:
const Frag = () => (
<React.Fragment key="test-key">
<div>test1</div>
<div>test2</div>
</React.Fragment>
);
const Outer = ({ children }) => (
<div>
{
React.Children.map(children, (child) => (
<a>
{child}
</a>
))
}
</div>
);
// Usage
<Outer>
<Frag />
</Outer>
This will result in a single a tag even though the fragment has multiple divs inside of it. The docs (https://reactjs.org/docs/react-api.html#reactchildrenmap) seem to indicate that this should work with a keyed fragment and I think I am creating a keyed fragment correctly (https://reactjs.org/docs/fragments.html#keyed-fragments). Any help would be appreciated!

Yes, but as far as I can see - you are iterating over Outer children.
Try to
React.Children.map(children[0].props.children, (child) => (
But, looking closely at your links - I see that documentation says something wrong here
the function will never be passed the container objects
Here is an example, which clearly shows that container is passed to the function:
<script src="https://unpkg.com/#babel/standalone/babel.js"></script>
<script src="https://unpkg.com/react#16.2.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16.2.0/umd/react-dom.development.js"></script>
<div id="root" />
<script type="text/babel">
const Frag = () => (
<>
<div>test1</div>
<div>test2</div>
</>
);
const Outer = ({ children }) => (
<div>
{
React.Children.map(children, (child) => console.log(`Child type is ${child.type.name || child.type.toString()}`) || (
<a>
{child}
</a> ))
}
</div>
);
ReactDOM.render((
<div>
<Outer>
<>
<div>test1</div>
<div>test2</div>
</>
</Outer>
<Outer>
<Frag />
</Outer>
</div>
), document.getElementById('root'));
</script>

Related

How to send multiple elements as React children and position individually where you want

The structure should look like this:
<Modal>
<Header />
<Body />
<Footer />
</Modal>
The output should look like this
<div>
<header>
<Header />
</header>
<main>
<Body />
</main>
<footer>
<Footer />
</footer>
</div>
You can select the passed children by their index and position them.
Here's how to achieve the solution,
const Modal = ({children}) => {
return (
<div>
<header>
{ children[0] }
</header>
<main>
{ children[1] }
</main>
<footer>
{ children[2] }
</footer>
</div>
)
}
We have this option to specify different spaces for multiple components. In React, we don't but this action can be done by passing props. Like this:
<MyComponent
headerComponent={<Header />}
bodyComponent={<Body />}
footerComponent={<Footer />}
>
</div>
And in MyComponent:
const MyComponent = (props) => {
return (
<div>
{props.headerComponent}
{props.bodyComponent}
{props.footerComponent}
</div>
)
}
const Modal = ({ children }) => {
return (
<React.Fragment>
<header>{children[0]}</header>
<main>{children[1]}</main>
<footer>{children[2]}</footer>
</React.Fragment>
)};
or
const Modal = ({ children }) => {
return (
<>
<header>{children[0]}</header>
<main>{children[1]}</main>
<footer>{children[2]}</footer>
</>
)};
which does the same things.

Simple tabs in ReactJS using components

I am new to React.js, I know basic stuff like state, components, but I need to:
Create a MyTabsComponent to be used like this:
<MyTabsComponent>
<div title={"Section title 1"}>Content of section 1</div>
<div title={"Section title 2"}>Content of section 2</div>
<div title={"Section title 3"}>Content of section 2</div>
<div title={"Section title 4"}>Content of section 2</div>
<div title={"Section title 5"}>Content of section 2</div>
.
.
.
.
.and so on..............
.
.
.
.
.
</MyTabsComponent>
<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>
See visual
The above code should render like this:
<div class="tabs">
<button class="btn-active">Section title 1</button>
<button class="btn">Section title 2</button>
<button class="btn">Section title 3</button>
<button class="btn">Section title 4</button>
<!--
.
.
.
.
.
.
and so on..........
-->
<div class="view">
Content of section 1
</div>
</div>
What I've tried:
import React,{useState} from "react";
const MyTabsComponent = () => {
const [title,setTitle] = useState(['Section 1','Section 2','Section 3']);
const [ct,setCt] = useState(null);
return (
<div className="tabs">
<TabButtons tabName={title} tabCt={setCt} />
<div class="view">
Content : {ct}
</div>
</div>
);
};
const TabButtons = (props) => {
return (
<>
{
props.tabName.map((item,i)=>
<button onClick={()=>props.tabCt(i)} className="btn">{item}</button>
)
}
</>
);
};
export default MyTabsComponent;
<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>
I don't know how to do this for any number of tabs. Rest I can handle-- CSS, onClick events I understand well, as I know JS well.
EDIT: I found an article on Compound Components https://blog.logrocket.com/understanding-react-compound-components/
and they say:
import React, { useState, createContext, useContext } from "react";
//the name of this context will be DataContext
const DataContext = createContext({});
function Tab({ id, children }) {
//extract the 'setActiveTabID` method from the DataContext state.
const [, setActiveTabID] = useContext(DataContext);
return (
<div>
<div onClick={() => setActiveTabID(id)}>{children}</div>
</div>
);
}
function TabPanel({ whenActive, children }) {
//get the 'activeTabID' state from DataContext.
const [activeTabID] = useContext(DataContext);
return <div>{activeTabID === whenActive ? children : null}</div>;
}
function TabSwitcher(props) {
const [activeTabID, setActiveTabID] = useState("a");
//since this component will provide data to the child components, we will use DataContext.Provider
return (
<DataContext.Provider value={[activeTabID, setActiveTabID]}>
{props.children}
</DataContext.Provider>
);
}
export default TabSwitcher;
export { Tab, TabPanel };
And to use:
import TabSwitcher, { Tab, TabPanel } from "./TabSwitcher";
function App() {
return (
<div className="App">
<h1>TabSwitcher with Compound Components</h1>
<TabSwitcher>
<Tab id="a">
<button>a</button>
</Tab>
<Tab id="b">
<button>b</button>
</Tab>
<TabPanel whenActive="a">
<div>a panel</div>
</TabPanel>
<TabPanel whenActive="b">
<div>b panel</div>
</TabPanel>
</TabSwitcher>
</div>
);
}
export default App;
The problem is: they are using
TabPanel
as a container whereas I want a <div>
UPDATED
Here is the latest implementation for you
function TabController({ children }) {
if (!children || children.length === 0) {
return null
}
const [currentTab, setCurrentTab] = React.useState(children[0])
return (
<>
{children.map((child) => (
<button key={child.title} onClick={() => setCurrentTab(child)}>
{child.props.title}
</button>
))}
<div className="view">{currentTab.props.children}</div>
</>
)
}
This is how we use it
<TabController>
<div title={'Section title 1'}>Content of section 1</div>
<div title={'Section title 2'}>Content of section 2</div>
<div title={'Section title 3'}>Content of section 3</div>
<div title={'Section title 4'}>Content of section 4</div>
<div title={'Section title 5'}>Content of section 5</div>
</TabController>
OLD ANSWER
I tried to create a simple component for you. You can implement actual data for TAB_DATA and pass it into your MyTabsComponent as props
//I mocked TAB_DATA here for testing purpose
const TAB_DATA = [
{
title: 'Section 1',
content: 'Content 1',
},
{
title: 'Section 2',
content: 'Content 2',
},
{
title: 'Section 3',
content: 'Content 3',
},
]
const MyTabsComponent = () => {
const [ct, setCt] = useState(TAB_DATA[0].content) //set the first tab data as default
return (
<div className="tabs">
{TAB_DATA.map((tab) => (
<button
onClick={() => {
setCt(tab.content)
}}
>
{tab.title}
</button>
))}
<div className="view">{ct}</div>
</div>
)
}
If you want to create a component that has nested elements of an unknown number or type, then you need to make use of the children prop. This is not something you have to define, but represents the child elements of the component when it is called.
Simply nest any number of components or elements between your component tags, and in MyTabsComponent pull out the props and put the props.children inside your return statement where you'd like them to go.
For your example, I have elevated the state ct and title to the parent component of MyTabsComponent, and given that state directly to the rendered buttons. I also pass the ct to the MyTabsComponent so it can display its content.
MyTabsComponent
const MyTabsComponent = (props) => {
return (
<div className="tabs">
{props.children}
<div class="view">
Content : {props.ct}
</div>
</div>
);
};
The parent of MyTabsComponent
import React, { useState } from 'react'
const Main = () => {
const [title, setTitle] = useState(['Section 1','Section 2','Section 3']);
const [ct, setCt] = useState(null);
return (
<MyTabsComponent ct={ct}>
{title.map((item, i) =>
<button onClick={() => setCt(i)} className="btn">{item}</button>
)}
</MyTabsComponent>
)
}

Failing to refactor a React render prop component into 2 components

I'm trying to refactor the following component so I can separate the outer / inner components.
// outer component
<Draggable>
{(provided) => (
<div
className={`${styles.listWrapper} ${scrollbarClassName}`}
ref={provided.innerRef}
{...provided.draggableProps}
>
// This is the inner component I'd like to extract:
<div className={`${styles.column} ${scrollbarClassName}`}>
// However I'm using the provided props here:
<div {...provided.dragHandleProps}>
<ColumnHeader {...columnHeaderProps} />
</div>
</div>
</div>
)}
</Draggable>
I'd like to be able to use it this way or similar, but I am not sure how to pass the props through children so I can use the provided prop in my inner component inside DraggableColumn:
const Draggable = <DraggableContainer>
{(provided) => (
<div
className={`${styles.listWrapper} ${scrollbarClassName}`}
ref={provided.innerRef}
{...provided.draggableProps}
>
// How to pass provided to children and then use it in DraggableColumn below
{props.children}
</div>
)}
</DraggableContainer>
const DraggableColumn = () => <DraggableContainer>
<div className={`${styles.column} ${scrollbarClassName}`}>
// How to get access to provided?
<div {...provided.dragHandleProps}>
<ColumnHeader {...columnHeaderProps} />
</div>
</div>
</DraggableContainer>
I think what you're looking for is something like the following:
const DraggableContainer = props => {
const provided = {
foo: "bar",
baz: () => console.log("hello world")
};
return props.children(provided);
};
const Draggable = props => {
return (
<DraggableContainer>
{provided => props.children(provided)}
</DraggableContainer>
);
};
const DraggableColumn = props => {
console.log('DraggableColumn.props', props)
return (
<div>
<h2>DraggableColumn</h2>
<p>provided: {JSON.stringify(props.provided)}</p>
</div>
);
};
const App = () => {
return (
<Draggable>{provided => <DraggableColumn provided={provided} />}</Draggable>
);
}
ReactDOM.render(
<App />,
document.getElementById("app")
)
<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>
<div id="app"></div>
Basically, what you're doing here is recreating the same functionality that DraggableContainer provides by utilizing the children prop pattern:
reactjs.org/docs/render-props.html#using-props-other-than-render
One caveat is that Draggable will always be expecting props.children to be a function, meaning that the following will work:
<Draggable>
{(provided) => {
return <DraggableColumn provided={provided} />;
}}
</Draggable>
But this will not because props.children is not a function, unlike the example above:
<Draggable>
<p>Hello World</p>
</Draggable>
Hope this helps.
To pass a prop you need to declare it inside of the <>
For example
<Draggable provided={this.state.provided}>
<div>
Some Stuff
</div>
</Draggable>
Then you can do this.props.provided inside the draggable component.

Elegant way to render props regardless of their type

Considering I have a component which accepts one prop, what would be the easiest and most elegant way for both of these to work:
<MessageBlock message="Some message goes here" />
and
<MessageBlock message={() => (
<>
Some message <strong>goes</strong> here
</>
} />
What first comes to my mind is checking the prop type with typeof and rendering the prop according to that like this:
class MessageBlock extends React.Component {
render() {
const { message: Message } = this.props;
return (
<div className="message-block">
{typeof Message === "function" ? (
<Message />
) : (
<>
{message}
</>
)}
</div>
)
}
}
It depends on what your component should be able to do.
If you want your component to be able to display anything inside of it, I would recommend using its children prop as a render function :
const MessageBlock = props => (
<div className={'add the stuff you want to wrap'}>
{props.children()}
</div>
)
const App = props => (
<React.Fragment>
<MessageBlock>
{() =>
<React.Fragment>
Some message <strong>goes</strong> here
</React.Fragment>
}
</MessageBlock>
<MessageBlock>
{() => <p>Another message</p>}
</MessageBlock>
</React.Fragment>
)
ReactDOM.render(<App/>, document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.0/umd/react-dom.production.min.js"></script>
<div id='root'>
But if you want it to display a string and nothing else, use the propTypes with the first solution you proposed :
MessageBlock.propTypes = {
message: PropTypes.string
};
If you want it to do both, you could put a || condition with it, if the message is defined. The message will be shown by default if it exists, and otherwise the children function will be executed :
const MessageBlock = ({ children, message }) => (
<div className={'add the stuff you want to wrap'}>
{message || children()}
</div>
)
/*MessageBlock.propTypes = { //Proptypes are undefined in SO snippets :(
message: PropTypes.string
};*/
const App = props => (
<React.Fragment>
<MessageBlock>
{() =>
<React.Fragment>
Some message <strong>goes</strong> here
</React.Fragment>
}
</MessageBlock>
<MessageBlock message={'A string message'} />
</React.Fragment>
)
ReactDOM.render(<App/>, document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.0/umd/react-dom.production.min.js"></script>
<div id='root'>

Unwrap a div in react, something similar to jQuery unwrap method

I am using React JSX. I have a div with className="shadow" as shown below.
<div className="main">
<div className="shadow" style={{backgroundColor: "#FFFFFF"}}>
<div id="wrapper">
Hello
</div>
</div>
</div>
Based on a certain condition being true or false, I want to remove the div with className="shadow", but want to keep every div including the div with id="wrapper" intact. Something like unwrap() method of jQuery.
Something to the effect of what is written below, but without so many lines of code.
if ( currentPage==="login") {
<div className="main">
<div id="wrapper">
Hello
</div>
</div>
}
else {
<div className="main">
<div className="shadow" style={{backgroundColor: "#FFFFFF"}}>
<div id="wrapper">
Hello
</div>
</div>
</div>
}
I checked React.js: Wrapping one component into another and How to pass in a react component into another react component to transclude the first component's content?, but didn't get what I am looking for.
Maybe what can help you is to change the className based on the page you are on, because what you try to do would be better using react-router to display different components based on the path you are on.
Use something similar to this code, I hope I can help you.
const App = React.createClass({
changePage(nextPage) {
this.setState({ page: nextPage })
},
getInitialState() {
return({
page: 'Login'
})
},
render() {
return(
<div className="main">
<div className={ this.state.page === 'Login' ? 'shadow' : '' }>
<div id="wrapper">
Hello from { this.state.page } page.
</div>
<button onClick={ this.changePage.bind(null, 'Login') }>Go to Login page.</button>
<button onClick={ this.changePage.bind(null, 'Home') }>Go to Home page.</button>
</div>
</div>
)
}
})
ReactDOM.render(<App />, document.getElementById('app'))
div.shadow{ background-color: #000000; color: #ffffff}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id='app'></div>
This is a great use case for Higher Order Components!
const HOC = (currentPage, Shadow, Wrapper) => (
() => <div className="main">
{
currentPage === 'login'
? <Shadow {...this.props} />
: <Shadow {...this.props}><Wrapper {...this.props}>{this.props.children}</Wrapper></Shadow>
}
</div>
)
Usage:
render () {
const Shadow = props => <div className="shadow" style={{backgroundColor: '#FFFFFF'}}>{props.children}</div>
const Wrapper = props => <div id="wrapper">Hello</div>
const Example = HOC(
currentPage,
Shadow,
Wrapper
)
return (
<Example />
)
}
Update:
To render the children of <Wrapper />, use {this.props.children} and use the class syntax in HOC:
const HOC = (currentPage, Shadow, Wrapper) => (
class extends Component {
render () {
return (
<div className="main">
{
currentPage === 'login'
? <Shadow />
: <Shadow><Wrapper>{this.props.children}</Wrapper></Shadow>
}
</div>
)
}
}
)
If you needed to capture props on <Shadow /> and <Wrapper />, then do something like the following. Note: I don't think you can pass props into normal DOM elements like <div> tags. But if they were other components with a Capital starting letter, then I believe passing props through with {...this.props} would work nicely.
const HOC = (currentPage, Shadow, Wrapper) => (
class extends Component {
render () {
return (
<div className="main">
{
currentPage === 'login'
? <Shadow {...this.props} />
: <Shadow {...this.props}><Wrapper {...this.props}>{this.props.children}</Wrapper></Shadow>
}
</div>
)
}
}
)

Resources