React issues with createRef for nested navigation + IntersectionObserver - javascript

I have an intersectionObserver that watches some sections and highlights the corresponding navigation item. But I've only managed to get the "main sections Microsoft, Amazon working, but not the subsections Define, Branding, Design, Deduction. As seen in the gif below:
The reason why I want it structured this way is so that I can highlight the "main" sections if the subsections are in view.
Semi working demo: https://codesandbox.io/s/intersection-with-hooks-fri5jun1344-fe03x
It might seems that I might be able to copy and paste the same functionality with the subsections as well. But I'm having a hard time wrapping my head around how to deal with nested data + useRef + reducer. I was wondering if someone could give me a pointer in the right direction.
Here is an gif of the desired effect. Notice the main title (Loupe, Canon) are still highlighted if one of the subsections are in view:
It all starts with an data array
const data = [
{
title: "Microsoft",
id: "microsoft",
color: "#fcf6f5",
year: "2020",
sections: ["define", "branding", "design", "deduction"]
},
{
title: "Amazon",
id: "amazon",
color: "#FFE2DD",
year: "2018",
sections: ["define", "design", "develop", "deduction"]
},
{
title: "Apple",
id: "apple",
color: "#000",
year: "2020",
sections: ["about", "process", "deduction"]
}
];
App.js padding data object into reduce to create Refs
const refs = data.reduce((refsObj, Case) => {
refsObj[Case.id] = React.createRef();
return refsObj;
}, {});
My components passing in the props
<Navigation
data={data}
handleClick={handleClick}
activeCase={activeCase}
/>
{data.map(item => (
<Case
key={item.id}
activeCase={activeCase}
setActiveCase={setActiveCase}
refs={refs}
data={item}
/>
))}
Case.js
export function Case({ data, refs, activeCase, setActiveCase }) {
const components = {
amazon: Amazon,
apple: Apple,
microsoft: Microsoft
};
class DefaultError extends Component {
render() {
return <div>Error, no page found</div>;
}
}
const Tag = components[data.id] || DefaultError;
useEffect(() => {
const observerConfig = {
rootMargin: "-50% 0px -50% 0px",
threshold: 0
};
const observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
if (entry.target.id !== activeCase && entry.isIntersecting) {
setActiveCase(entry.target.id);
}
});
}, observerConfig);
observer.observe(refs[data.id].current);
return () => observer.disconnect(); // Clenaup the observer if unmount
}, [activeCase, setActiveCase, refs, data]);
return (
<React.Fragment>
<section
ref={refs[data.id]}
id={data.id}
className="section"
style={{ marginBottom: 400 }}
>
<Tag data={data} />
</section>
</React.Fragment>
);
}
I've tried mapping the subsections like this but I get stuck at this part:
const subRefs = data.map((refsObj, Case) => {
refsObj[Case] = React.createRef();
return refsObj;
}, {});

Working Example
I've found a solution while trying to keep most of your logic intact. Firstly what you need to do is to store the subrefs (the sections ref) in the same object as your Case ref. So you will need an extra reduce function to create those inside the refs object:
App.js
const refs = data.reduce((refsObj, Case) => { // Put this outside the render
const subRefs = Case.sections.reduce((subrefsObj, Section) => {
subrefsObj[Section] = React.createRef();
return subrefsObj;
}, {});
refsObj[Case.id] = {
self: React.createRef(), // self is the Case ref, like Apple, Microsoft...
subRefs // This is going to be the subrefs
};
return refsObj;
}, {});
Then you add an extra state to handle which sub section is active, like const [activeSection, setActiveSection] = React.useState(); And you put it anywhere you also use the activeCase. You need that because you said that the Case and Sections need to work independently. (Both active at the same time).
Case.js
You will need to pass along the subrefs to the child components, so you do:
<Tag data={data} subRefs={refs[data.id].subRefs} />
And you will also need the intersection observer for each of the subrefs. So your useEffect will look like:
useEffect(() => {
const observerConfig = {
rootMargin: "-50% 0px -50% 0px",
threshold: 0
};
const observerCallback = (entries, isCase) => {
const activeEntry = entries.find(entry => entry.isIntersecting);
if (activeEntry) {
if (isCase) setActiveCase(activeEntry.target.id);
else setActiveSection(activeEntry.target.id);
} else if (isCase) {
setActiveCase(null);
setActiveSection(null);
}
};
const caseObserver = new IntersectionObserver(
entries => observerCallback(entries, true),
observerConfig
);
caseObserver.observe(refs[data.id].self.current);
const sectionObserver = new IntersectionObserver(
entries => observerCallback(entries, false),
observerConfig
);
Object.values(refs[data.id].subRefs).forEach(subRef => {
sectionObserver.observe(subRef.current);
});
return () => {
caseObserver.disconnect();
sectionObserver.disconnect();
}; // Clenaup the observer if unmount
}, [refs, data]);
Then in your amazon/index.js ,microsoft/index.js and apple/index.js files. You pass along the ref again:
<Template
data={this.props.data}
caseSections={caseSections}
subRefs={this.props.subRefs}
/>
Finally, in your template.js file you will have the following so you can assign the right ref:
const Template = props => {
return (
<React.Fragment>
<div
sx={{
background: "#eee",
transition: "background ease 0.5s"
}}
>
{props.data.sections &&
props.data.sections.map(subItem => (
<Container
ref={props.subRefs && props.subRefs[subItem]}
id={`${props.data.id}--${subItem}`}
key={subItem}
className="article"
>
<Section sectionId={subItem} caseSections={props.caseSections} />
</Container>
))}
</div>
</React.Fragment>
);
};
I believe most of it is covered in the post. You can check your forked working repo here

You can simplify your code. You don't really need refs or intersectionObservers for your use case. You can simply scrollIntoView using document.getElementById (you already have ids to your navs.
You can do setActiveCase very well in handleClick.
Working demo
Modify handleClick like this
const handleClick = (subTabId, mainTabName) => {
//console.log("subTabName, mainTabName", subTabId, mainTabName);
setActiveCase({ mainTab: mainTabName, subTab: subTabId.split("--")[1] }); //use this for active tab styling etc
document.getElementById(subTabId).scrollIntoView({
behavior: "smooth",
block: "start"
});
};
Navigation.js Call handleClick like this.
{item.sections &&
item.sections.map(subItem => (
<div
className={`${styles.anchor}`}
key={`#${item.title}--${subItem}`}
sx={{ marginRight: 3, fontSize: 0, color: "text" }}
href={`#${item.title}--${subItem}`}
onClick={e => {
handleClick(`${item.id}--${subItem}`, item.id);
e.stopPropagation();
}}
>
{toTitleCase(subItem)}
</div>
))}

Related

React Native/API - onPress returns all links in API instead of the link relevant to the item being pressed

Apologies, couldn't figure out a straightforward way to title this, so thanks in advance.
I am tasked with creating a page of links supplied by an API I made that connects to a CMS. The screen gathers the below data to be viewed on the screen:
Title,
Content
These items, along with a URL live within a "Resource". The URL is not visible to the user, but it is grouped with its own Title and Content.
Currently things show correctly on the screen, but when trying to connect the URL to the Resource, I'm unable to have the page navigate correctly. When I console.log(thing-I'm-returning), it sends me back all URLs for all Resources, and if the private browser opens to a web page, it might open to any in the list. This happens when I press any of the Resources.
Code below (first time posting, I'm fully desperate. Let me know if this looks like trash and I'll correct however is ideal).
const { resourceData } = useResourceContent(binding);
const resourceList = resourceData?.Resources?.map((r, i) => ({
id: i.toString(),
title: r.Title,
url: r.Url,
content: r.Content,
}));
const resourceDetails = resourceData?.Resources;
const { openUrl } = useWebBrowser();
const { resourceData } = useResourceContent(binding);
const resourceDetails = resourceData?.Resources;
const urlList = [];
const handleOpenSite = () => {
resourceDetails?.map((r, i) =>
{if (resourceDetails !== undefined && resourceDetails) {
urlList.push(r.Url);
}
console.log(urlList[i]); //let's say there are 2 resources, each with their own website. This will return both websites no matter what resource I select
//the below is required, as a private browser is required
return openUrl(urlList[i]);
});
};
API looks something like:
[{"Content": "Test. ", "Url": "https://instagram.com", "Title": "blah blah blah"},
{{"Content": "Test2.", "Url": "https://google.com.com", "Title": "blah blah blah"},]
Here's the View, though I'm unsure if it's necessary here.
<View>
<ResourceNavigationList
onPress={handleOpenSite}
small
listItems={resourceList}
backgroundColor="transparent"
/>
</View>
And here's the ResourceNavigationList component, which is likely the issue since it's a little bit nonsense.:
const ResourceNavigationList = ({
listItems,
backgroundColor,
small,
reverse,
onPress,
}) => {
const { ct } = useCountryTranslation();
const colorScheme = useColorScheme();
const bgColor = backgroundColor || Colors[colorScheme].altBackground;
const { openUrl } = useWebBrowser();
const renderItem = ({ item, rUrl }) => {
const handleOnPress = () => {
if (item) {
openUrl(rUrl).toString();
console.log("WHY AREN'T YOU OPENING?");
}
};
return (
<ResourceNavigationListItem
key={key}
reverse={reverse}
small={small}
item={item}
// url={rUrl}
onPress={onPress}
/>
);
};
return (
<FlatList
renderItem={renderItem}
data={listItems}
keyExtractor={(item) => item.id}
style={{
paddingVertical: 20,
backgroundColor: bgColor,
}}
/>
);
};
ResourceNavigationList.propTypes = {
listItems: PropTypes.array.isRequired,
backgroundColor: PropTypes.string,
small: PropTypes.bool,
reverse: PropTypes.bool,
onPress: PropTypes.func,
};
export default ResourceNavigationList;
Finally, here's the ResourceNavigationListItem
const ResourceNavigationListItem = ({ item, onPress, style, small }) => {
const styles = StyleSheet.create({
//styling is here, but leaving it off because it isn't relevant and took up a lot of space
});
return (
<TouchableOpacity onPress={onPress} style={[styles.item, style]}>
<View style={styles.title}>
<Icon style={styles.linkArrow} size={16} icon={faExternalLink} />
</View>
<View style={styles.title}>
<Text style={styles.titleText}>
{item.title ? decode(item.title) : item.title}
</Text>
</View>
<View style={styles.title}>
<Text style={styles.titleText}>
{item.content ? decode(item.content) : item.content}
</Text>
</View>
</TouchableOpacity>
);
};
ResourceNavigationListItem.propTypes = {
item: PropTypes.shape({
icon: PropTypes.object,
title: PropTypes.string,
content: PropTypes.string,
}).isRequired,
onPress: PropTypes.func,
style: PropTypes.object,
small: PropTypes.bool,
};
export default ResourceNavigationListItem;
Thanks so very much.
I've tried mapping and for-looping. I've tried applying the mapping directly to the component. These have gleaned me the most success. Most everything else I've tried didn't return anything at all, or returned everything many times.
I've been struggling for a few days and have found lots of solutions similar to my problem within stackoverflow, but nothing fully relevant/recent (I'm fairly newb with regard to backend tingz). If y'all happen upon something I missed, please be kind, and if you'd be down to help me, I'd be so very grateful.
I took a look and I think this has to do with confusion regarding the props hierarchy within your example.
Right now you are passing onPress down from the parent of ResourceNavigationList:
Parent > onPress
ResourceNavigationList
ResourceNavigationListItem
While there is nothing inherently incorrect about the parent owning the onPress function, you have everything you need to perform the desired onPress functionality within the ResourceNavigationList component. It looks like you were almost there with your handleOnPress function.
Based on the data contract you provided, it looks like you should be able to do this:
const ResourceNavigationList = ({
listItems,
backgroundColor,
small,
reverse,
onPress,
}) => {
const {
ct
} = useCountryTranslation();
const colorScheme = useColorScheme();
const bgColor = backgroundColor || Colors[colorScheme].altBackground;
const {
openUrl
} = useWebBrowser();
const renderItem = ({
item,
rUrl
}) => {
const handleOnPress = () => {
if (item && item ? .Url) {
openUrl(item ? .Url);
// If you do need to perform additional logic controlled
// by the parent component, you can add a quick line to
// execute the onPress function if it has been provided.
if (onPress) onPress(item);
}
};
return ( <
ResourceNavigationListItem key = {
key
}
reverse = {
reverse
}
small = {
small
}
item = {
item
}
onPress = {
onPress
}
/>
);
};
return ( <
FlatList renderItem = {
renderItem
}
data = {
listItems
}
keyExtractor = {
(item) => item.id
}
style = {
{
paddingVertical: 20,
backgroundColor: bgColor,
}
}
/>
);
};
ResourceNavigationList.propTypes = {
listItems: PropTypes.array.isRequired,
backgroundColor: PropTypes.string,
small: PropTypes.bool,
reverse: PropTypes.bool,
onPress: PropTypes.func,
};
export default ResourceNavigationList;

How to render AdMob banner in React Flatlist between items?

I have a React Native Flatlist that only re-renders when its data has changed.
I give it the following data (as prop):
const posts = [
{
...post1Data
},
{
...post2Data
},
{
...post3Data
},
{
...post4Data
},
{
...post5Data
},
]
And here is my FlatList renderItem:
const renderItem = useCallback(({ item, index }) => {
const { id, userData, images, dimensions, text } = item;
return (
<View
onLayout={(event) => {
itemHeights.current[index] = event.nativeEvent.layout.height;
}}
>
<Card
id={id}
cached={false}
userData={userData}
images={images}
dimensions={dimensions}
text={text}
/>
</View>
);
}, []);
How can I add an AdMob ad between the FlatList data with a probability of 5% without skiping any data in the posts array?
I have tried this:
const renderItem = useCallback(({ item, index }) => {
const { id, userData, images, dimensions, text } = item;
if (Math.random() < 0.05) return <Ad ... />
return (
<View
onLayout={(event) => {
itemHeights.current[index] = event.nativeEvent.layout.height;
}}
>
<Card
id={id}
cached={false}
userData={userData}
images={images}
dimensions={dimensions}
text={text}
/>
</View>
);
}, []);
But this causes 2 problems:
Some items from data are skipped (not returned)
When the flatlist re-renders (because of some of its props changes) the ads might disappear (there is a chance of 95%).
Any ideas? Should I render the ads randomly in the footer of my Card component like this?
const Card = memo ((props) => {
...
return (
<AuthorRow ... />
<Content ... />
<SocialRow ... /> {/* Interaction buttons */}
<AdRow />
)
}, (prevProps, nextProps) => { ... });
const AdRow = memo(() => {
return <Ad ... />
}, () => true);
I am not really sure about this option, it works but it could violate the admob regulations (?) (because I am adapting the ad to the layout of my card component)
I would appreciate any kind of guidance/help. Thank you.
I'm not sure if you ever found a solution to this problem, but I accomplished this by injecting "dummy" items into the data set, then wrapping the renderItem component with a component that switches based on the type of each item.
Assuming your flatlist is declared like this:
<FlatList data={getData()} renderItem={renderItem}/>
And your data set is loaded into a variable called sourceData that is tied to state. Let's assume one entry in your sourceData array looks like this. Note the 'type' field to act as a type discriminator:
{
"id": "d96dce3a-6034-47b8-aa45-52b8d2fdc32f",
"name": "Joe Smith",
"type": "person"
}
Then you could declare a function like this:
const getData = React.useCallback(() => {
let outData = [];
outData.push(...sourceData);
// Inject ads into array
for (let i = 4; i < outData.length; i += 5)
{
outData.splice(i, 0, {type:"ad"});
}
return outData;
}, [sourceData]);
... which will inject ads into the data array between every 4th item, beginning at the 5th item. (Since we're pushing new data into the array, i += 5 means an ad will be placed between every 4th item. And let i = 4 means our first ad will show after the 5th item in our list)
Finally, switch between item types when you render:
const renderItem = ({ item }) => (
item.type === 'ad'
?
<AdComponent ...props/>
:
<DataComponent ...props/>
);

Deleting list Item in react keep older items instead

I would like to delete selected item from list.
When I click on delete the right item get deleted from the list content but on UI I get always the list item fired.
I seems to keep track of JSX keys and show last values.
Here's a demo
const Holidays = (props) => {
console.log(props);
const [state, setState] = useState({ ...props });
useEffect(() => {
setState(props);
console.log(state);
}, []);
const addNewHoliday = () => {
const obj = { start: "12/12", end: "12/13" };
setState(update(state, { daysOffList: { $push: [obj] } }));
};
const deleteHoliday = (i) => {
const objects = state.daysOffList.filter((elm, index) => index != i);
console.log({ objects });
setState(update(state, { daysOffList: { $set: objects } }));
console.log(state.daysOffList);
};
return (
<>
<Header as="h1" content="Select Holidays" />
<Button
primary
icon={<AddIcon />}
text
content="Add new holidays"
onClick={() => addNewHoliday(state)}
/>
{state?.daysOffList?.map((elm, i) => {
console.log(elm.end);
return (
<Flex key={i.toString()} gap="gap.small">
<>
<Header as="h5" content="Start Date" />
<Datepicker
defaultSelectedDate={
new Date(`${elm.start}/${new Date().getFullYear()}`)
}
/>
</>
<>
<Header as="h5" content="End Date" />
<Datepicker
defaultSelectedDate={
new Date(`${elm.end}/${new Date().getFullYear()}`)
}
/>
</>
<Button
key={i.toString()}
primary
icon={<TrashCanIcon />}
text
onClick={() => deleteHoliday(i)}
/>
<span>{JSON.stringify(state.daysOffList)}</span>
</Flex>
);
})}
</>
);
};
export default Holidays;
Update
I'm trying to make a uniq id by adding timeStamp.
return (
<Flex key={`${JSON.stringify(elm)} ${Date.now()}`} gap="gap.small">
<>
<Header as="h5" content="Start Date" />
<Datepicker
defaultSelectedDate={
new Date(`${elm.start}/${new Date().getFullYear()}`)
}
/>
</>
<>
<Header as="h5" content="End Date" />
<Datepicker
defaultSelectedDate={
new Date(`${elm.end}/${new Date().getFullYear()}`)
}
/>
</>
<Button
primary
key={`${JSON.stringify(elm)} ${Date.now()}`}
icon={<TrashCanIcon />}
text
onClick={() => deleteHoliday(i)}
/>{" "}
</Flex>
);
I was hoping that the error disappear but still getting same behaviour
Issue
You are using the array index as the React key and you are mutating the underlying data array. When you click the second entry to delete it, the third element shifts forward to fill the gap and is now assigned the React key for the element just removed. React uses the key to help in reconciliation, if the key remains stable React bails on rerendering the UI.
You also can't console log state immediately after an enqueued state update and expect to see the updated state.
setState(update(state, { daysOffList: { $set: objects } }));
console.log(state.daysOffList);
React state updates are asynchronous and processed between render cycles. The above can, and will, only ever log the state value from the current render cycle, not the update enqueued for the next render cycle.
Solution
Use a GUID for each start/end data object. uuid is a fantastic package for this and has really good uniqueness guarantees and is incredibly simple to use.
import { v4 as uuidV4 } from 'uuid';
// generate unique id
uuidV4();
To specifically address the issues in your code:
Add id properties to your data
const daysOffList = [
{ id: uuidV4(), start: "12/12", end: "12/15" },
{ id: uuidV4(), start: "12/12", end: "12/17" },
{ id: uuidV4(), start: "12/12", end: "12/19" }
];
...
const addNewHoliday = () => {
const obj = {
id: uuidV4(),
start: "12/12",
end: "12/13",
};
setState(update(state, { daysOffList: { $push: [obj] } }));
};
Update handler to consume id to delete
const deleteHoliday = (id) => {
const objects = state.daysOffList.filter((elm) => elm.id !== id);
setState(update(state, { daysOffList: { $set: objects } }));
};
Use the element id property as the React key
{state.daysOffList?.map((elm, i) => {
return (
<Flex key={elm.id} gap="gap.small">
...
</Flex>
);
})}
Pass the element id to the delete handler
<Button
primary
icon={<TrashCanIcon />}
text
onClick={() => deleteHoliday(elm.id)}
/>
Use an useEffect React hook to log any state update
useEffect(() => {
console.log(state.daysOffList);
}, [state.daysOffList]);
Demo
Note: If you don't want (or can't) install additional 3rd-party dependencies then you can roll your own id generator. This will work in a pinch but you should really go for a real proven solution.
const genId = ((seed = 0) => () => seed++)();
genId(); // 0
genId(); // 1

Conditionally render part of object onClick inside a map (REACT.js)

I am trying to conditionally render part of an object (user comment) onClick of button.
The objects are being pulled from a Firebase Database.
I have multiple objects and want to only render comments for the Result component I click on.
The user comment is stored in the same object as all the other information such as name, date and ratings.
My original approach was to set a boolean value of false to each Result component and try to change this value to false but cannot seem to get it working.
Code and images attached below, any help would be greatly appreciated.
{
accumRating: 3.7
adheranceRating: 4
cleanRating: 2
date: "2020-10-10"
place: "PYGMALIAN"
staffRating: 5
timestamp: t {seconds: 1603315308, nanoseconds: 772000000}
userComment: "Bad"
viewComment: false
}
const results = props.data.map((item, index) => {
return (
<div className='Results' key={index}>
<span>{item.place}</span>
<span>{item.date}</span>
<Rating
name={'read-only'}
value={item.accumRating}
style={{
width: 'auto',
alignItems: 'center',
}}
/>
<button>i</button>
{/* <span>{item.userComment}</span> */}
</div >
)
})
You have to track individual state of each button toggle in that case.
The solution I think of is not the best but you could create a click handler for the button and adding a classname for the span then check if that class exists. If it exists then, just hide the comment.
Just make sure that the next sibling of the button is the target you want to hide/show
const toggleComment = (e) => {
const sibling = e.target.nextElementSibling;
sibling.classList.toggle('is-visible');
if (sibling.classList.contains('is-visible')) {
sibling.style.display = 'none'; // or set visibility to hidden
} else {
sibling.style.display = 'inline-block'; // or set visibility to visible
}
}
<button onClick={toggleComment}>i</button>
<span>{item.userComment}</span>
You can try like this:
const [backendData, setBackendData] = useState([]);
...
const showCommentsHandler = (viewComment, index) => {
let clonedBackendData = [...this.state.backendData];
clonedBackendData[index].viewComment = !viewComment;
setBackendData(clonedBackendData);
}
....
return(
<div>
....
<button onClick={() => showCommentsHandler(item.viewComment, index)}>i</button>
{item.viewComment && item.userComment}
<div>
You can store an array with that places which are clicked, for example:
const [ selectedItems, setSelectedItems] = React.useState([]);
const onClick = (el) => {
if (selectedItems.includes(el.place)) {
setSelectedItems(selectedItems.filter(e => e.place !== el.place));
} else {
setSelectedItems(selectedItems.concat(el));
}
}
and in your render function
const results = props.data.map((item, index) => {
return (
<div className='Results' key={index}>
<span>{item.place}</span>
<span>{item.date}</span>
<Rating
name={'read-only'}
value={item.accumRating}
style={{
width: 'auto',
alignItems: 'center',
}}
/>
<button onClick={() => onClick(item)}>i</button>
{ /* HERE */ }
{ selectedItems.includes(item.place) && <span>{item.userComment}</span> }
</div >
)
})
You need to use useState or your component won't update even if you change the property from false to true.
In order to do so you need an id since you might have more than one post.
(Actually you have a timestamp already, you can use that instead of an id.)
const [posts, setPosts] = useState([
{
id: 1,
accumRating: 3.7,
adheranceRating: 4,
cleanRating: 2,
date: "2020-10-10",
place: "PYGMALIAN",
staffRating: 5,
timestamp: { seconds: 1603315308, nanoseconds: 772000000 },
userComment: "Bad",
viewComment: false
}
]);
Create a function that updates the single property and then updates the state.
const handleClick = (id) => {
const singlePost = posts.findIndex((post) => post.id === id);
const newPosts = [...posts];
newPosts[singlePost] = {
...newPosts[singlePost],
viewComment: !newPosts[singlePost].viewComment
};
setPosts(newPosts);
};
Then you can conditionally render the comment.
return (
<div className="Results" key={index}>
<span>{item.place}</span>
<span>{item.date}</span>
<Rating
name={"read-only"}
value={item.accumRating}
style={{
width: "auto",
alignItems: "center"
}}
/>
<button onClick={() => handleClick(item.id)}>i</button>
{item.viewComment && <span>{item.userComment}</span>}
</div>
);
Check this codesandbox to see how it works.

How to set refs in React useRef

I'm trying to play a gsap animation on component did mount in a Gatsby site but my refs aren't being applied.
const PricingList = ({ classes }) => {
let pricingCard = useRef([]);
useEffect(() => {
console.log('start Animation', pricingCard.current);
TweenMax.staggerFrom(pricingCard.current, 0.4, { opacity: 0, y: 100 }, 0.5);
}, []);
return (
<StaticQuery
query={graphql`
{
Prices: contentfulConfig {
pricing {
priceBand {
title
price
}
priceBand2 {
price
title
}
priceBand3 {
price
title
}
}
}
}
`}
render={(data) => (
<Fragment>
<div className={classes.Container}>
<PricingItem
ref={(el) => {
pricingCard.current[0] = el;
}}
/>
<PricingItem
ref={(el) => {
pricingCard.current[1] = el;
}}
/>
<PricingItem
ref={(el) => {
pricingCard.current[2] = el;
}}
/>
</div>
</Fragment>
)}
/>
);
};
I have tried -
pricingCard.current.push(el);
without any luck, I just get an empty array in console.
I have also tried -
useEffect(() => {
console.log('start Animation', pricingCard.current);
TweenMax.staggerFrom(pricingCard.current, 0.4, { opacity: 0, y: 100 }, 0.5);
}, [pricingCard]);
Thinking it might need to wait to be updated after the component mounted, but no luck.
Any help would be appreciated.
Thanks in advance.
For animation you might want to use useLayoutEffect instead of useEffect.
Is it going to be always 3 PricingItems? In this case you can create 3 separate refs for each (const pricingItem1 = useRef(null)) and in render <PricingItem ref={pricingItem1} />.
Also can you confirm that the function from the render prop has been called?

Categories