I have 3 different react native components and I am using the Navigator to navigate between them. In my first view I define the navigator:
View 1
<Navigator
ref="nav"
renderScene={#renderScene}
initialRoute={#renderContent(I18n.t("Incidents"))}
configureScene={ ->
transition = Navigator.SceneConfigs.HorizontalSwipeJump
transition.gestures = null
transition
}
/>
As you can see the transition is HorizontalSwipeJump.
View 2
#props.navigator.push
component: IncidentScreen
incidentId: incident.id
sceneConfig: -> Navigator.SceneConfigs.FloatFromBottomAndroid
As you can see, I am trying move into view #3 using FloatFromBottomAndroid, however, it's not working.
By looking at the source code for RN I can see that the navigator.push method get's the animation from the props:
var nextAnimationConfigStack = activeAnimationConfigStack.concat([
this.props.configureScene(route),
]);
So what can I do?
Thanks a lot.
You have to go digging into the react-native source here for the list of SceneConfigs, but here's the current list as of writing:
PushFromRight
FloatFromRight
FloatFromLeft
FloatFromBottom
FloatFromBottomAndroid
FadeAndroid
HorizontalSwipeJump
HorizontalSwipeJumpFromRight
VerticalUpSwipeJump
VerticalDownSwipeJump
Example usage:
<Navigator
configureScene={(route) => {
if (someCondition) {
return Navigator.SceneConfigs.HorizontalSwipeJump;
} else {
return Navigator.SceneConfigs.PushFromRight;
}
}}
/>
Ok, I figure it out. I was missing this part in View 1:
configureScene={ (route) ->
if route.sceneConfig
route.sceneConfig
else
transition = Navigator.SceneConfigs.HorizontalSwipeJump
transition.gestures = null
transition
}
If anyone is still looking at this, you can push without animation by just reseting the routes to what you want them to end up being. This is assuming that you don't do anything special with your routes like save the forward routes or anything.
if( !shouldAnimate )
{
var routeStack = this.refs.mainNav.state.routeStack;
routeStack.push(newRoute);
this.refs.mainNav.immediatelyResetRouteStack(routeStack);
}
else
{
this.refs.mainNav.push(feature);
}
Where mainNav is the ref of my Navigator. Hope this helps.
Related
I am trying to call an api base on scroll View current position but not sure how to I achieve that.
This is my code
<ScrollView
ref={scrollViewRef}
scrollEventThrottle={0}
onScroll={({nativeEvent}) => {
console.log(
nativeEvent.contentSize.height -
nativeEvent.layoutMeasurement.height,
);
console.log(nativeEvent.contentOffset);
}}>
I tried to call the api inside onScroll but that didnt work well.
Try adding an event listener at the particular scroll location you want the function to execute.
useEffect(() => {
Window.addEventListener(‘scroll’_.debounce(setScroll,1000));},[]);
I have solved the issue by using adding an if check. If the api data exist then the function wont execute anymore.
here's the code
const [apiData, setApiData] = useState();
onScroll={({nativeEvent}) => {
if (!apiData) {
if (
nativeEvent.contentSize.height -
nativeEvent.layoutMeasurement.height -
nativeEvent.contentOffset.y <
250
) {
getApiDataHandler();
}
}
}}
Here's a weekly mini calendar, that turns into a monthly mini calendar component.
When it turns from weekly to monthly we have some entering/exiting animations
So far so good.
Problem:
The problem is, that those animations (being entering/exiting animations) also take place while the user is scrolling.
As you can see in the gif, animations play when I scroll horizontally, which isn't what I want, I only want animations when the component changes from weekly to monthly (expands/collapses)
Code:
import Animated, {
FadeInDown,
FadeInUp,
SlideOutUp,
SlideOutDown,
} from 'react-native-reanimated';
const MiniCalendarItem = () => {
let animationEnter;
let animationExit;
if (this.props.itemRepresents === ITEM_REPRESENTS.MONTH) {
if (this.dayIsPartOfCurrentWeek(day)) {
animationEnter = FadeInUp;
} else {
animationEnter = FadeInUp.delay((weekIndex * 150)).duration(350)
}
animationExit = SlideOutDown.duration(400);
} else {
animationEnter = FadeInDown.duration(500);
animationExit = SlideOutUp.duration(400);
}
return (
<Animated.View
entering={animationEnter}
exiting={animationExit}
key={`dayData_${dayProps.id}`}
>
{...}
</Animated.View>
);
};
and here's the parent:
renderItem = () => {
return (
<MiniCalendarItem
animationsEnabled
key={itemKey}
mode={mode}
itemRepresents={visible ? ITEM_REPRESENTS.MONTH : ITEM_REPRESENTS.WEEK}
/>
)
}
}
Essentially the parent is a ScrollView (not a FlatList)
Question:
How can I stop react-native-reanimated#2 from playing any animations and when is it a good time to do that.
I added a animationsEnabled prop, but ideally I'd love to feed it with an Animated.Value(true) object. I'm just not sure how to conditionally disable animations based on that prop, from within the MiniCalendarItem.
I am creating a block with InnerBlocks component.
If no content added to the InnerBlocks (and even with content in fact) it is very difficult to popup the block toolbar
I would like to add an iconbutton on top corner that will show the block floating toolbar
How can I tell the .block-editor-block-contextual-toolbar to show?
I don't see any method of the .wp-block in the inspector that would do that and the documentation of Block Controls: Block Toolbar and Settings Sidebar https://developer.wordpress.org/block-editor/how-to-guides/block-tutorial/block-controls-toolbar-and-sidebar/ is quite basic
Many thanks
You can use useSelect() to determine if there are any blocks present in the InnerBlocks component:
import { useSelect } from '#wordpress/data';
const hasInnerBlocks = useSelect((select) => (
select('core/block-editor').getBlock(clientId).innerBlocks.length > 0
), [clientId]);
Then you can use hasInnerBlocks to conditionally render whatever you'd like within the edit function:
{ !!hasInnerBlocks && (
<BlockControls group="block">
<ToolbarGroup
// Toolbar group settings here
/>
</BlockControls>
) }
Try to use same code structure among the edit and save methods. The RIchText need to be waraped inside div.
<div>
<RichText.Content
className={ `sticky-note-${ props.attributes.alignment }` }
style={ {
fontSize: props.attributes.fontSize,backgroundColor: props.attributes.color,
} }
tagName="p"
value={ props.attributes.content }/>
</div>
Example
I created this example to illustrate your situation.
import { InnerBlocks, BlockControls } from '#wordpress/block-editor';
// ...
edit: () => {
const blockProps = {
// your own props
};
return (
<div { ...blockProps }>
<BlockControls>
// your controls
</BlockControls>
<InnerBlocks />
</div>
);
}
Problem
For the BlockControls to decide whether or not it should appear, it needs to get some context from its parent which your own props don't have.
Solution:
Use the block props instead for the parent of BlockControls.
Steps:
Import useBlockProps from #wordpress/block-editor:
import { InnerBlocks, BlockControls, useBlockProps } from '#wordpress/block-editor';
Pass your own props as an argument to useBlockProps():
const blockProps = useBlockProps({
// your own props
});
Result
import { InnerBlocks, BlockControls, useBlockProps } from '#wordpress/block-editor';
// ...
edit: () => {
const blockProps = useBlockProps({
// your own props
});
return (
<div { ...blockProps }>
<BlockControls>
// your controls
</BlockControls>
<InnerBlocks />
</div>
);
}
Links
I hope that helped.
My answer is based on Wordpress's official Block Editor Handbook:
https://developer.wordpress.org/block-editor/how-to-guides/block-tutorial/block-controls-toolbar-and-sidebar/#block-toolbar
https://developer.wordpress.org/block-editor/how-to-guides/block-tutorial/nested-blocks-inner-blocks/
https://developer.wordpress.org/block-editor/reference-guides/block-api/block-edit-save/#block-wrapper-props
UPDATE: Now with a Snack Demo
I've created a demo on snack so you can see the issue first hand and help me demonstrate a solution in actual code.
Steps to duplicate
launch app
Tap "GO TO EVENTTABS" button
Tap each tab, noticing that the eventId is in scope for the first three tabs
Tap "More" tab
Tap "TEAM MEMBERS", noticing that eventId is no longer in scope. This is where the problem lies. How do I pass along eventId?
_____________________________
My App has the following navigation hierarchy, where every instance of <> is just a regular component
App <StackNavigator> {
EventList <>
EventTabs <BottomTabNavigator> {
Quests <>
Leaderboard <>
Gallery <>
More <StackNavigator> {
MoreList <>
TeamMembers <>
}
}
}
Upon entering the app, the user's first screen is EventList. They click a button to navigate into EventTabs, so I'm able to use the navigation.navigate() to transition while passing state like so...
EventList.navigation.navigate(EventTabs, passedParams);
To this point, everything makes sense. But TeamMembers also needs access to the passedParams. I'm confused how to pass those along. Hence my question...how do access passedParams from the TeamMembers component? They seem to be scoped just to the EventTabs.
If the answer is to use navigate.setParams(), then I'm not sure where I'd do that.
If the answer is to use NavigationActions.setParams(), then I'm also not sure where I'd do that.
Unfortunately we don't have good support for this, but you could use a function like this to recursively walk your navigation parents in search of the correct param.
function getParam(navigation, paramName) {
const { getParam, dangerouslyGetParent } = navigation;
let parent = dangerouslyGetParent();
let val = getParam(paramName);
while (val === undefined && parent && parent.getParam) {
val = parent.getParam(paramName);
parent = parent.dangerouslyGetParent();
}
return val;
}
The problem seems to continue for version 5.x of react navigation.
For me this works.
function getParentParam(navigation, paramName) {
const { dangerouslyGetParent } = navigation;
let paramValue = null;
const parent = dangerouslyGetParent();
const routes = parent.dangerouslyGetState().routes;
routes.some((r) => {
paramValue = r.params ? r.params[paramName] : null;
return paramValue !== null;
});
return(paramValue);
}
You can iterate over parent.dangerouslyGetParent() according to the depth level you have
I make automatization react native test with detox, It has the next screen sequence A -> B -> C and i wish to go back to the screen B <- C.
Is there a solution for this?
There's a testId on the back button, so you can do this:
await element(by.id("header-back")).tap();
sometimes
await element(by.id("header-back")).tap();
does not work and
await element(by.traits(['button']))
.atIndex(0)
.tap();
selects another button. In that case, you can try to use swipe right on ios assuming that it is a stack navigator. Use the outer container view
await element(by.id('containerView')).swipe('right', 'fast', 0.1);
the solution was to use traits button as follows:
await element(by.traits(['button'])).atIndex(0).tap();
You could go ahead and create a utility
export const pressBack = async () => {
if (device.getPlatform() === 'android') {
await device.pressBack(); // Android only
} else {
await element(by.traits(['button']))
.atIndex(0)
.tap();
}
};
Android: device.pressBack()
iOS: go back last screen #828
If you are using react-native-navigation you can use:
const backButton = () => {
if (device.getPlatform() === 'ios') {
return element(by.type('_UIBackButtonContainerView'));
} else {
return element(by.label('Navigate Up'));
}
};
...
await backButton().tap();
For iOS in detox#17.3.6 & react-native-navigation#6.10.1 you can use:
return element(by.id('pop'));
Another way that works is
await element(by.id('header-back')).atIndex(0).tap()
This uses the built in testID that the default back button that comes with react-navigation v5. You may need to mess with the atIndex() number since for me it seems to match 2 back buttons but the first one was the one I was looking for.