React-Navigation v4 pass state from child to parent on back press - react-hooks

I need to pass state from my child screen to the parent screen . I am having difficulties doing this. I am letting the user press a button to navigate to the child screen. After filling info in the child screen I am trying to pass the value back to the parent screen with props.navigation.goBack() Can someone help me out with this.
I am using react navigation v4
Parent screen:
const Parent = (props) => {
const [day, setDay] = useState("");
return (
<TouchableOpacity
onPress={() =>props.navigation.navigate({ routeName: 'Child' })}>
</TouchableOpacity>
);
};
Child screen (I want to pass the day state back to the parent)
const Child = props => {
const [day, setDay] = useState("");
return (
<View style={styles.container}>
<TextInput onChange={(text)=> setDay(text)}/>
<Button onPress={()=>props.navigation.goBack()}/>
</View>
);
};

If it is not possible to use the normal navigation way maybe try to build your own back function and pass params with it.
Take a look at this maybe:
goBack() {
const { navigation } = this.props;
navigation.goBack();
navigation.setParam({ day: dayData});
}
then the call would be :
<Button onPress={()=>this.goBack()}/>
you can get the param with :
this.props.navigation.getParam("day")
try it somehow like this - if it does not work try the calls with this.props.navigation...
or only with navigation.goBack() and so on because I am not sure wich syntax will work for you.

Related

How to Perform Inplace Animations in React Three Fiber

I imported a 3D model from Mixamo with some animations and was wondering how to perform does animations in place?
The GLTF file I created from npx gltfjsx:
const { nodes, materials, animations } = useGLTF("/Mannequin.glb");
const { actions } = useAnimations(animations, heroRef);
return (
<>
<group ref={heroRef} dispose={null}>
<group rotation={[Math.PI / 2, 0, 0]} scale={0.01}>
<primitive object={nodes.mixamorig1Hips} />
<skinnedMesh
geometry={nodes.Ch36.geometry}
material={materials.Ch36_Body}
skeleton={nodes.Ch36.skeleton}
/>
</group>
{/* <gridHelper args={[25, 25]}/> */}
</group>
</>
);
}
useGLTF.preload("/Mannequin.glb");
For example, I would like the model to perform the run animation without changing locations (running on the spot) so that if I can perform user controls that translate the model and with the animation looping, it would look like they were actually running.
Are there any ways to do this? I have searched and could not find anything.
useEffect(() => {
console.log(actions) // find out the name of your action
actions.someAction.play()
});
This will make the animation play in place, if it was exported like that from mixamo, there is a setting.
Then you can put a around your mesh and make it move based on your control
I couldn't use console.log(actions) to see available actions as they were undefined and I had {} in console. My code was:
const { actions } = useAnimations(animations, group);
useEffect(() => {
console.log(actions);
}, [actions]);
So I digged inside into useAnimations source code and saw two things:
actions object is not being updated, only it's properties, so useEffect doesn't trigger when ref updates.
if ref.current is undefined (as for first render) properties are undefined. that's why I saw {} in console.
So I changed my code to:
const { actions } = useAnimations(animations, group);
useEffect(() => {
console.log(Object.keys(actions));
}, [actions]);
That's how I saw that my animation names were: ['Female_Idle', 'Female_Talk']
And
const { actions } = useAnimations(animations, group);
const action = actions['Female_Idle'];
useEffect(() => {
if (action) action.play();
}, [action]);
In order to play action, when ref updates. That did the trick.

How to get navigation object outside Screen

I have the following code in the App.tsx:
export default function App() {
...
return (
<NavigationContainer>
<Drawer.Navigator
initialRouteName="StackNavigation"
drawerContent={(props) => <MainPageDrawer {...props} />}
screenOptions={{ swipeEnabled: false }}
>
<Drawer.Screen name="StackNavigation" component={StackNavigator} />
</Drawer.Navigator>
</NavigationContainer>
);
...
}
As you see, the MainPageDrawer component isn't inside the Drawer.Screen component. But I still need to get access to navigation object inside MainPageDrawer. When I do something lake this inside MainPageDrawer:
const navigation = useNavigation<MainScreenNavigationProp>();
I get the following error:
Couldn't find a navigation object. Is your component inside a screen in a navigator?
What is the right way to get access to the navigation object outside Screen component?
Solution: I figured out that useNavigation won't work anyway if it is outside Screen, BUT this:
(props) => <MainPageDrawer {...props} />
still pass navigation through props, so you can easily access it without useNavigation. I simply restructured navigation:
function MainPageDrawer({ navigation }) {
...
}
And it works

back button with react-navigation on specific screen dynamically

on react-navigation i am trying to navigate to a specific screen which depends on some condition!
Below it is pointing to 'App' page right now
but sometimes i need it to go to 'Main' page
static navigationOptions = ({ navigation }) => {
return {
title: 'Tools',
headerLeft: (
<Button
onPress={() => navigation.navigate('App')}
title="Back"
/>
),
};
};
when i use state or function
for example
...
...
onPress={() => navigation.navigate(this.someFunction())}
...
...
there is an error undefined is not an object
so how do I point dynamically the desirable page to navigate to?
if came from any screen , want to go back dynamic without defining the screen name in navigation . Use this method :
onPress={()=>navigation.goBack()}
navigation.goBack() method will take you directly on the screen where it came from .

React change navbar colour based on route

I am new to react. I am using react-route-dom and . I would like to change the colour of my NavBar functional component when the route changes.
In my NavBar component I am using the HOC (connect) from react-redux and withRouter from react-router to get route props and information from my redux store.
const mapStateToProps = (state) => {
return {
moduleCards: state.moduleCards
}
}
export default connect(mapStateToProps)(withRouter(NavBar));
Here is a snippet of how I am getting route information to then find the moduleCard entry and get it's colour.
const NavBar = (props) => {
console.log("Hello world?");
const modules = props.moduleCards.find(c => `/${c.name.replace(/ /g,'')}` === props.location.pathname);
const color = typeof modules !== 'undefined' ? modules.color : 'blue';
return (
<nav>
<div className={`nav-wrapper ${color} darken-2`}>
The problem is that the colour does not change when I change route. Can you please help with how to trigger a render on my NavBar component? Or if there is a better way to handle this?
Here is my root App component.
class App extends Component {
render() {
return (
<BrowserRouter>
<div className="App">
<NavBar/>
<Switch>
<Route exact path='/' component={Home}/>
<ModuleRoutes/>
</Switch>
</div>
</BrowserRouter>
);
}
}
Here is a snap shot of my store, this is where I am grabbing the colour from.
const initialState = {
moduleCards: [
{ id: 0, name: 'Client Register', icon: 'person', color: 'red' },
{ id: 1, name: 'Property Register', icon: 'domain', color: 'blue' },
The colour is blue on the home page and unless I refresh it remains blue. When I refresh it rerenders and the navbar gets its correct colour.
In documentation they wraps component first with redux and then with router
I propose you change your:
export default connect(mapStateToProps)(withRouter(NavBar));
to this:
export default withRouter(connect(mapStateToProps)(NavBar));

Re-rendering a single row of a list without re-rendering the entire list

we're trying to implement a contact list that works just like the new Material Design Google Contacts (you must enable the material design skin to see it) using material-ui.
Specifically we're trying to show a checkbox instead of the avatar on row hover.
We'd like to catch and re-render only the interested row (when hovered) and show the avatar/checkbox accordingly... this seems an easy task but we're not able to isolate the render to the hovered row (instead of re-rendering the entire list)
Do you have any suggestion on how to do something like this?
Our temporary solution uses a container component that handles the table:
When a row is hovered we capture it from onRowHover of the Table component and save it in the container state. This triggers a re-render of the entire list with really poor perfomance.
You can see a video of the issue here.
Here is a code sample:
import React from 'react'
import Avatar from 'material-ui/lib/avatar'
import Checkbox from 'material-ui/lib/checkbox'
import Table from 'material-ui/lib/table/table'
import TableHeaderColumn from 'material-ui/lib/table/table-header-column'
import TableRow from 'material-ui/lib/table/table-row'
import TableHeader from 'material-ui/lib/table/table-header'
import TableRowColumn from 'material-ui/lib/table/table-row-column'
import TableBody from 'material-ui/lib/table/table-body'
import R from 'ramda'
export default class ContactsList extends React.Component {
constructor (props) {
super(props)
this.state = { hoveredRow: 0 }
this.contacts = require('json!../../public/contacts.json').map((e) => e.user) // Our contact list array
}
_handleRowHover = (hoveredRow) => this.setState({ hoveredRow })
_renderTableRow = ({ hovered, username, email, picture }) => {
const checkBox = <Checkbox style={{ marginLeft: 8 }} />
const avatar = <Avatar src={picture} />
return (
<TableRow key={username}>
<TableRowColumn style={{ width: 24 }}>
{hovered ? checkBox : avatar}
</TableRowColumn>
<TableRowColumn>{username}</TableRowColumn>
<TableRowColumn>{email}</TableRowColumn>
</TableRow>
)
}
render = () =>
<Table
height='800px'
fixedHeader
multiSelectable
onRowHover={this._handleRowHover}
>
<TableHeader displaySelectAll enableSelectAll>
<TableRow>
<TableHeaderColumn>Nome</TableHeaderColumn>
<TableHeaderColumn>Email</TableHeaderColumn>
<TableHeaderColumn>Telefono</TableHeaderColumn>
</TableRow>
</TableHeader>
<TableBody displayRowCheckbox={false} showRowHover>
{this.contacts.map((contact, index) => this._renderTableRow({
hovered: index === this.state.hoveredRow,
...contact }))
}
</TableBody>
</Table>
}
Thank you in advance.
You could wrap your rows into a new component implementing shouldComponentUpdate like so :
class ContactRow extends Component {
shouldComponentUpdate(nextProps) {
return this.props.hovered !== nextProps.hovered || ...; // check all props here
}
render() {
const { username, email, ...otherProps } = this.props;
return (
<TableRow { ...otherProps } >
<TableRowColumn style={{ width: 24 }}>
{this.props.hovered ? checkBox : avatar}
</TableRowColumn>
<TableRowColumn>{this.props.username}</TableRowColumn>
<TableRowColumn>{this.props.email}</TableRowColumn>
</TableRow>
);
}
}
Then you can use it in your ContactList component like so :
this.contacts.map((contact, index) => <ContactRow key={contact.username} {...contact} hovered={index === this.state.hoveredRow} />)
If you don't want to manually implement shouldComponentUpdate, you can use React's PureRenderMixin or check a lib like recompose which provides useful helpers like pure to do so.
EDIT
As pointed out by the OP and #Denis, the approach above doesn't play well with some features of the Table component. Specifically, TableBody does some manipulation on its children's children. A better approach would be to define your ContactRow component like so:
class ContactRow extends Component {
shouldComponentUpdate(nextProps) {
// do your custom checks here
return true;
}
render() {
const { username, email, ...otherProps } = this.props;
return <TableRow { ...otherProps } />;
}
}
and then to use it like this
<ContactRow { ...myProps }>
<TableRowColumn>...</TableRowColumn>
</ContactRow>
But I guess having TableRow re-render only when necessary is a feature everyone would benefit from, so maybe a PR would be in order :)

Resources