I have an array of HTML elements that I want to render on a page, but depending on the element I'd like to adjust how they get wrapped.
const sections = [
{
id: 'top',
},
{
id: 'left',
},
{
id: 'right',
},
{
id: 'bottom',
}
]
const Element = (props) => {
return <div id={props.id}>hello</div>
}
const ArticleRightRail = (props) =>
<div>
<header>
</header>
<article>
{sections.map((section, i) => <Element key={i} {...section} >hello!</Element> )}
</article>
</div>
In the example above I want any id which is not top or bottom to be rendered within <article>, and anything that is top or bottom to be rendered within <header> tags. What is the best way of handling a situation like this with React?
Use ES6 array filter method to filter sections array as below:
const listForHeader = sections.filter((section, i) => {
return section === "top" || section === "bottom";
});
const listForArticle = sections.filter((section, i) => {
return section != "top" || section != "bottom";
})
Then use above 2 lists in HEADER and ARTICLE tags respectively using array map method.
check this
const listHeader = sections.filter((section, i) => {
return section === "top" || section === "bottom";
});
const listArticle = sections.filter((section, i) => {
return section != "top" || section != "bottom";
})
#JamesIves, the react code is:
const listForHeader = sections.filter((section, i) => {
return section === "top" || section === "bottom";
});
const listForArticle = sections.filter((section, i) => {
return section != "top" || section != "bottom";
})
const ArticleRightRail = (props) =>
<div>
<header>
{listForHeader.map((section, i) => <Element key={i} {...section} >hello!</Element> )}
</header>
<article>
{listForArticle.map((section, i) => <Element key={i} {...section} >hello!</Element> )}
</article>
</div>
Related
I wanna make follow/unfollow toggle button, and following / follower list(object in array) will be called seperately from server.
Follower list needs to have both unfollow/follow button status.
When I call follower list, how can I check the IDs of the people who follow me matches the IDs of my following list & reflect in on the button?
example following, follower object in array
[{id: 1, profileImg: xxx},{id: 2, profileImg: xxx},{id: 3, profileImg: xxx}... ]
my code in js below
const { select } = props;
const [choice, setChoice] = useState(select);
const [followingList, setFollowingList] = useState([]);
const [followerList, setFollowerList] = useState([]);
const handleChoice = (e) => {
setChoice(e.target.value);
};
useEffect(() => {
getFollowing()
.then((res) => {
setFollowingList(res);
})
.then(
getFollower().then((res) => {
setFollowerList(res);
}),
);
}, []);
my code in html
<Container onClick={(e) => e.stopPropagation()}>
<TogglebtnContainer>
<ToggleBtn onClick={handleChoice} value="following" choice{choice}>Following</ToggleBtn>
<ToggleBtn onClick={handleChoice} value="follower" choice={choice}>Follower</ToggleBtn>
</TogglebtnContainer>
<FollowContainer>
<Follow>
{choice === 'following'? (followingList.map((follow, idx) => {
return (
<div className="follow-item" key={idx}>
<div className="follow-img"><img src={follow.profileImg} alt="UserPic" /> </div>
<div className="follow-name">{follow.nickname}</div>
<FollowBtn key={follow.id}>Unfollow</FollowBtn></div>
);})
: (followerList.map((follow, idx) => {
return (
<div className="follow-item" key={idx}>
<div className="follow-img">
<img src={follow.profileImg} alt="UserPic" />
</div>
<div className="follow-name">{follow.nickname}</div>
<FollowBtn key={follow.id}>follow</FollowBtn>
</div>
})}
</Follow>
</FollowContainer>
</Container>
I thought I could check if this IDs matches IDs of my following list and create a new boolean state.
(ex [isFollowing, setIsFollowing = useState(false)) but couldn't find a way.
getFollower().then((res) => {
setFollowerList(res);
To know which followers the user is already following and follow/unfollow followers
short answer, set a flag when loading the data
useEffect(() => {
let isValidScope = true;
const fetchData = async () => {
const followingList = await getFollowing();
if (!isValidScope) { return; }
setFollowingList(followingList);
let followersList = await getFollower();
if (!isValidScope) { return; }
const followingUserIds = followingList?.map(f => f.id)
followersList = followersList?.map(follower => {
return followingUserIds?.includes(follower.id) ?
{ ...follower, isFollowing: true } : follower
}
setFollowerList(followersList)
}
fetchData()
return () => { isValidScope = false }
}, []);
const onFollowFollower = (followerId) => {
const followersList = followerList?.map(follower => {
return follower.id === followerId ?
{ ...follower, isFollowing: true } : follower
}
setFollowerList(followersList)
}
const onUnfollowFollower = (followerId) => {
const followersList = followerList?.map(follower => {
return follower.id === followerId ?
{ ...follower, isFollowing: false } : follower
}
setFollowerList(followersList)
}
Render code
<Follow>
{choice === 'following'? (followingList.map((follow, idx) => {
return (
<div className="follow-item" key={idx}>
<div className="follow-img"><img src={follow.profileImg} alt="UserPic" /> </div>
<div className="follow-name">{follow.nickname}</div>
<FollowBtn key={follow.id}>Unfollow</FollowBtn>
</div>
);})
: (followerList.map((follow, idx) => {
return (
<div className="follow-item" key={idx}>
<div className="follow-img">
<img src={follow.profileImg} alt="UserPic" />
</div>
<div className="follow-name">{follow.nickname}</div>
{ follow?.isFollowing ? <FollowBtn () => onUnfollowFollower(follow.id)>Unfollow</FollowBtn> : null }
{ !follow?.isFollowing ? <FollowBtn onClick={() => onFollowFollower(follow.id)>Follow</FollowBtn> : null }
</div>
})}
</Follow>
You can read about working with list in the new React docs
if you are refetching the follower and following list on every change it will be better to recalculate the followers list using a useMemo on every change
Hope this helps you in someway
I'm having trouble with creating multiple tabs form in React. Example image:
Every new tab mounts a new component. Example code:
const handleAddTab = tabIndex => {
const exampleTab = {
name: `${Date.now()} / 16.02.2022 г.`,
jsx: <Document />,
deletable: true
}
const updatedTabs = state.tabs.map((t, i) => {
if (tabIndex === i) t.subtabs.push(exampleTab)
return t
})
setState(prev => ({
...prev,
tabs: updatedTabs
}))
}
And I'm rendering those components. Example code:
{state.activeSubtabIndex === 0 ?
<Documents />
:
getScreen().subtabs.map((s, i) =>
i === 0 ?
<>
</>
:
<div
style={state.activeSubtabIndex != i ? { display: 'none' } : {}}
>
{s.jsx}
</div>
)
}
I use getScreen() to fetch the current tab and get the subtabs. Example code:
const getScreen = () => {
const typeId = query.get('type_id')
const screen = state.tabs.find(t => t.typeId == typeId)
return screen
}
The way I remove a tab is like so:
const handleCloseTab = (tabIndex, subtabIndex) => {
const updatedTabs = state.tabs.filter((t, i) => {
if (tabIndex === i) {
t.subtabs = t.subtabs.filter((ts, i) => {
return subtabIndex != i
})
}
return t
})
setState(prev => ({
...prev,
tabs: updatedTabs
}))
}
The problem is that every time I delete (for example) the first tab, the second one gets the state from the first one (based on the index it was mapped).
I solved that problem by adding an extra key-value pair (deleted: false) to the exampleTab object
const handleAddTab = tabIndex => {
const exampleTab = {
name: `${Date.now()} / 16.02.2022 г.`,
jsx: <Document />,
deletable: true,
deleted: false <====
}
const updatedTabs = state.tabs.map((t, i) => {
if (tabIndex === i) t.subtabs.push(exampleTab)
return t
})
setState(prev => ({
...prev,
tabs: updatedTabs
}))
}
Whenever I close a tab I'm not unmounting It and removing its data from the array. I'm simply checking if deleted === true and applying style={{ display: 'none' }} to both the tab and component. Example code:
{state.activeSubtabIndex === 0 ?
<Documents />
:
getScreen().subtabs.map((s, i) =>
i === 0 ?
<>
</>
:
<div
style={state.activeSubtabIndex != i || s.deleted ? { display: 'none' } : {}}
key={s.typeId}
>
{s.jsx}
</div>
)}
I create a sidebar through the object.keys(sidebars), and in each of them there are various menu items that I get from the database, how to change these elements in state.items? To access the item field during generation and display these menu items?
Now I do like this, but nothing comes of it ...
const [sidebarItemsLeagues, setSidebarItemsLeagues] = useState(null); // SIDEBAR 1 ITEMS
const [sidebarItemsCountries, setSidebarItemsCountries] = useState(null); // SIDEBAR 2 ITEMS
const [sidebars] = useState({
leagues: {
title: "SIDEBAR 1",
items: sidebarItemsLeagues // null...
},
countries: {
title: "SIDEBAR 2",
items: sidebarItemsCountries // null...
}
});
useEffect(() => {
api.getTestLeagues() // get sidebar 1 items
.then(data => setSidebarItemsLeagues(data));
}, []);
useEffect(() => {
api.getTestCountries() // get sidebar 2 items
.then(data => setSidebarItemsCountries(data));
}, [])
const onHandleSidebarView = () => { // generate sidebars
return (
Object.keys(sidebars).map((sidebar, idx) => {
const sidebarControl = sidebars[sidebar];
return (
<div className="sidebar" key={idx}>
<div className="sidebar__header">
<p>{sidebarControl.title}</p>
</div>
<div className="sidebar__items">
// SIDEBAR 1 && SIDEBAR 2 ITEMS
</div>
</div>
);
})
);
};
You can use [] notation back onto your object to access its corresponding property dynamically. I changed the map to use key for more clarity
Object.keys(sidebars).map((key, idx) => {
return (
<div className="sidebar" key={idx}>
<div className="sidebar__header">
<p>{sidebars[key].title}</p>
</div>
<div className="sidebar__items">
{
// SIDEBAR ITEMS (assuming it contains an array)
sidebars[key].items.map(itemElement => {
// return some elements
}
}
</div>
</div>
);
})
Can't manage to make useRef/createRef to get any other div's other then what was added last. How can i make it so when the button is clicked the ref to the div changes.
I've tried with both useRef and createRef. Since I want to make a new instance of ref, i've looked more into createRef rather then useRef.
I've also played around useEffect. But my solution didn't help me with my biggest problem
I have made a small project containing 3 components to help you understand what I'm trying to explain.
I also have a database containing mock data -> in my real project this isn't the problem. It's an array containing objects.
[{'id':'1', 'name':'first'},...]
Main:
const MainComponent = () => {
const dataRef = React.createRef(null)
React.useEffect (() => {
if(dataRef && dataRef.current){
dataRef.current.scrollIntoView({ behavior:'smooth', block:'start' })
}
},[dataRef])
const _onClick = (e) => {
dataRef.current.focus();
}
return(
<>
{data && data.map((entry, index) =>{
return <ButtonList
key={index}
entry={entry}
onClick={_onClick}
/>
})}
{data && data.map((entry, index) =>{
return <ListingAllData
key={index}
dataRef={dataRef}
entry={entry}
index={index}/>
})}
</>
)
}
Button Component
const ButtonList = ({ entry, onClick }) => {
return <button onClick={onClick}>{entry.name}</button>
}
Listing data component
const ListingAllData = (props) => {
const {entry, dataRef } = props;
return (
<div ref={dataRef}>
<p>{entry.id}</p>
<p>{entry.name}</p>
</div>
);
}
I've console logged the data.current, it only fetches the last element. I hoped it would fetch the one for the button I clicked on.
I think the main idea here is to create dynamic refs for each element (array of refs), that's why only the last one is selected when app renders out.
const MainComponent = () => {
const dataRefs = [];
data.forEach(_ => {
dataRefs.push(React.createRef(null));
});
const _onClick = (e, index) => {
dataRefs[index].current.focus();
dataRefs[index].current.scrollIntoView({
behavior: "smooth",
block: "start"
});
};
return (
<>
{data &&
data.map((entry, index) => {
return (
<ButtonList
key={index}
entry={entry}
onClick={e => _onClick(e, index)}
/>
);
})}
{data &&
data.map((entry, index) => {
return (
<>
<ListingAllData
key={index}
dataRef={dataRefs[index]}
entry={entry}
index={index}
/>
</>
);
})}
</>
);
};
Created working example in code sandbox.
https://codesandbox.io/s/dynamic-refs-so25v
Thanks to Janiis for the answer, my solution was:
in MainComponent
...
const refs = data.reduce((acc, value) => {
acc[value.id] = React.createRef();
return entry;
}, {});
const _onClick = id => {
refs[id].current.scrollIntoView({
behavior: 'smooth',
block: 'start'
});
}
....
then i passed it through to the child and referred like
<div ref={refs[entry.id]}>
I'm trying to output a list of comma separated links and this is my solution.
var Item = React.createComponent({
render: function() {
var tags = [],
tag;
for (var i = 0, l = item.tags.length; i < l; i++) {
if (i === item.tags.length - 1) {
tag = <span><Tag key={i} tag={item.tags[i]} /></span>;
} else {
tag = <span><Tag key={i} tag={item.tags[i]} /><span>, </span></span>;
}
tags.push(tag);
}
return (
<tr>
<td>
{item.name}
</td>
<td>
{tags}
</td>
</tr>
);
}
});
I was just wondering if there was a better, more clean way to accomplish this?
Thanks
Simply
{tags.map((tag, i) => <span key={i}>
{i > 0 && ", "}
<Tag tag={tag} />
</span>)}
In React 16 it can be done even more simpler:
{tags.map((tag, i) => [
i > 0 && ", ",
<Tag key={i} tag={tag} />
])}
At Khan Academy we use a helper called intersperse for this:
/* intersperse: Return an array with the separator interspersed between
* each element of the input array.
*
* > _([1,2,3]).intersperse(0)
* [1,0,2,0,3]
*/
function intersperse(arr, sep) {
if (arr.length === 0) {
return [];
}
return arr.slice(1).reduce(function(xs, x, i) {
return xs.concat([sep, x]);
}, [arr[0]]);
}
which allows you to write code like:
var tags = item.tags.map(function(tag, i) {
return <Tag key={i} tag={item.tags[i]} />;
};
tags = intersperse(tags, ", ");
Or simply write the list items to an unordered list and use CSS.
var Item = React.createComponent({
render: function() {
var tags = this.props.item.tags.map(function(i, item) {
return <li><Tag key={i} tag={item} /></li>
});
return (
<tr>
<td>
{this.props.item.name}
</td>
<td>
<ul className="list--tags">
{tags}
</ul>
</td>
</tr>
);
}
});
And the CSS:
.list--tags {
padding-left: 0;
text-transform: capitalize;
}
.list--tags > li {
display: inline;
}
.list--tags > li:before {
content:',\0000a0'; /* Non-breaking space */
}
.list--tags > li:first-child:before {
content: normal;
}
import React from 'react';
import { compact } from 'lodash';
// Whatever you want to separate your items with commas, space, border...
const Separator = () => { ... };
// Helpful component to wrap items that should be separated
const WithSeparators = ({ children, ...props }) => {
// _.compact will remove falsey values: useful when doing conditional rendering
const array = compact(React.Children.toArray(children));
return array.map((childrenItem, i) => (
<React.Fragment key={`${i}`}>
{i > 0 && <Separator {...props} />}
{childrenItem}
</React.Fragment>
));
};
const MyPage = () => (
<WithSeparators/>
<div>First</div>
{second && (<div>Maybe second</div>)}
{third && (<div>Maybe third</div>)}
<div>Fourth</div>
</WithSeparators>
);
A function component that does the trick. Inspired by #imos's response. Works for React 16.
const Separate = ({ items, render, separator = ', ' }) =>
items.map((item, index) =>
[index > 0 && separator, render(item)]
)
<Separate
items={['Foo', 'Bar']}
render={item => <Tag tag={item} />}
/>
Here's a solution that allows <span>s and <br>s and junk as the separator:
const createFragment = require('react-addons-create-fragment');
function joinElements(arr,sep=<br/>) {
let frag = {};
for(let i=0,add=false;;++i) {
if(add) {
frag[`sep-${i}`] = sep;
}
if(i >= arr.length) {
break;
}
if(add = !!arr[i]) {
frag[`el-${i}`] = arr[i];
}
}
return createFragment(frag);
}
It filters out falsey array elements too. I used this for formatting addresses, where some address fields are not filled out.
It uses fragments to avoid the warnings about missing keys.
Simple one:
{items.map((item, index) => (
<span key={item.id}>
{item.id}
{index < items.length - 1 && ', '}
</span>
))}
To add to the great answers above Ramda has intersperse.
To comma separate a bunch of items you could do:
const makeLinks = (x: Result[]) =>
intersperse(<>,</>, map(makeLink, x))
Pretty succinct
The solution without extra tags
<p className="conceps inline list">
{lesson.concepts.flatMap((concept, i) =>
[concept, <span key={i} className="separator">•</span>]
, ).slice(-1)}
</p>
generates something like
Function • Function type • Higher-order function • Partial application
The easiest way to do
const elementsArr = ["a", "b", "c"];
let elementsToRender = [] ;
elementsArr.forEach((element, index) => {
let elementComponent = <TAG className="abc" key={element.id}>{element}</TAG>
elementsToRender.push(elementComponent);
if(index !== (elementsArr.length - 1)){
elementsToRender.push(", ");
}
});
render(){
return (
<div>{elementsToRender}</div>
)
}