React native: passing in multiple parent props to child component - javascript

I have a react native application and I am trying to pass in props to my AppwithNavigationComponent. I want to do this so that the props created in Authenticator component are passed into the AppwithNavigationState component. However I am stuck on passing in multiple variables to a component:
---- index.js-----
render() {
return (
<ApolloProvider store={store} client={client}>
<Authenticator hideDefault={true} onStateChange={this.handleAuthStateChange}
theme={Object.assign(AmplifyTheme, styles)}>
<AppWithNavigationState { ...this.props }/> // passing in props
</Authenticator>
</ApolloProvider>
)
}
}
export default App = codePush(App);
--- AppNavigator.js ------
const AppWithNavigationState = (props,{ dispatch, nav }) => { // trying to pass in props from parent component, get following error when I do so
console.log(props);
return (
<AppNavigator navigation={addNavigationHelpers({ dispatch, state: nav })} screenProps={{ ...props }} />
)};
AppWithNavigationState.propTypes = {
dispatch: PropTypes.func.isRequired,
nav: PropTypes.object.isRequired,
};
const mapStateToProps = state => ({
nav: state.nav,
});
export default connect(mapStateToProps)(AppWithNavigationState);
^^ When I try to pass in parent props into this component, its as if the dispatch and nav components are ignored. Am I not passing in props the way I am supposed to? is there a way to pass in multiple props? if I pass in the parent props I then get the following error message:
Unhandled JS Exception: TypeError: Cannot read property 'index' of undefined
This error is located at:
in Transitioner (at CardStackTransitioner.js:60)
in CardStackTransitioner (at StackNavigator.js:48)
in Unknown (at createNavigator.js:36)
in Navigator (at createNavigationContainer.js:198)
However if I don't try to add in the parent props then everything works fine.

I think you just need to change:
const AppWithNavigationState = (props,{ dispatch, nav }) =>
to:
const AppWithNavigationState = ({ dispatch, nav }) =>
Functional components by default have just a single parameter, the props object. If the passed in props have the dispatch and nav on the props object, the above destructuring syntax just gives you the dispatch and nav variables straight away.
See docs for examples of functional components.

Related

React - Passing callbacks from React Context consumers to providers

I have the following context
import React, { createContext, useRef } from "react";
const ExampleContext = createContext(null);
export default ExampleContext;
export function ExampleProvider({ children }) {
const myMethod = () => {
};
return (
<ExampleContext.Provider
value={{
myMethod,
}}
>
{children}
<SomeCustomComponent
/* callback={callbackPassedFromConsumer} */
/>
</ExampleContext.Provider>
);
}
As you can see, it renders a custom component which receive a method as prop. This method is defined in a specific screen, which consumes this context.
How can I pass it from the screen to the provider?
This is how I consume the context (with a HOC):
import React from "react";
import ExampleContext from "../../../contexts/ExampleContext";
const withExample = (Component) => (props) =>
(
<ExampleContext.Consumer>
{(example) => (
<Component {...props} example={example} />
)}
</ExampleContext.Consumer>
);
export default withExample;
And this is the screen where I have the method which I need to pass to the context provider
function MyScreen({example}) {
const [data, setData] = useState([]);
const myMethodThatINeedToPass = () => {
...
setData([]);
...
}
return (<View>
...
</View>);
}
export default withExample(MyScreen);
Update:
I am trying to do this because in my real provider I have a BottomSheet component which renders two buttons "Delete" and "Report". This component is reusable, so, in order to avoid repeating myself, I am using a context provider.
See: https://github.com/gorhom/react-native-bottom-sheet/issues/259
Then, as the bottom sheet component which is rendered in the provider can receive optional props "onReportButtonPress" or "onDeleteButtonPress", I need a way to pass the method which manipulates my stateful data inside the screen (the consumer) to the provider.
You can't, in React the data only flows down.
This is commonly called a “top-down” or “unidirectional” data flow. Any state is always owned by some specific component, and any data or UI derived from that state can only affect components “below” them in the tree.
Your callbacks ("onReportButtonPress", "onDeleteButtonPress") must be available at provider's scope.
<ExampleContext.Provider
value={{
onReportButtonPress,
onDeleteButtonPress,
}}
>
{children}
</ExampleContext.Provider>;
Render SomeCustomComponent in Consumer component. This is the React way of doing things :)

mapStateToProps and mapDispatchToProps is not sending props to component

Please I am working on a redux project but the data that was returned to mapStateToProps was not send to the component. Console.log props are undefined
LiveEvent.protoTypes = {
event: PropTypes.object.isRequired,
error: PropTypes.string,
};
const mapDispatchToProps = (dispatch) => {
return {
getSingle: (values) => {
console.log(Creators, "creators")
dispatch(Creators.getOneEvent(values));
},
};
};
const mapStateToProps = (state) => (
console.log(state.event.event, "state from liveEvent"),
{
event: state.event.event,
error: state.event.error_message,
}
);
export default connect(mapStateToProps, mapDispatchToProps)(LiveEvent);
function LiveEvent({match}, props) {
console.log(props, "props")
Is there anything am not doing right
function LiveEvent({match}, props) {
The props object comes in the first argument to the function. Nothing is passed as the second argument. So you're destructuring the props to get match, and then creating a useless variable which is named props, but is unrelated to the actual props.
If you'd like to get the entire props object without destructuring, then change your code to:
function LiveEvent(props) {
// Do whatever with props, including things like
// const { match } = props
}
Alternatively, use destructuring to assign the props you care about to local variables:
function LiveEvent({ match, event, error, getSingle }) {
}
Ah, you have comma, I read it as semi-colon after console. It works fine.
There are miscellaneous bugs in your code.
Console doesn't work here:
Implicit return
const mapStateToProps = (state) => ( // <= returns implicitly
console.log(state.event.event, "state from liveEvent"),
{
event: state.event.event,
error: state.event.error_message,
}
);
To debug with the console, you have to use curly brace use return statement:
const mapStateToProps = (state) => {
console.log(state.event.event, "state from liveEvent"),
return {
event: state.event.event,
error: state.event.error_message,
}
};
2. Defining proptypes in your component indicates that the component should pass the props.
Component props
LiveEvent.protoTypes = {
event: PropTypes.object.isRequired,
error: PropTypes.string,
};
This code expects to have props in component itself:
<LiveEvent event={...} error={...} />
The connected component with redux won't get props from it as it's first priority is to check and get props from the component itself.
So, remove proptypes from your component so that the component can receive redux props.

Use ref in Higher Order Components

I have a Table component that I want ref to be attached to.
Use: Table.js
class Table extends React.Component {
constructor(props) {
super(props);
this.state = {
rows: 1,
dataLength: props.dataLength,
}
this.tableRef = React.createRef();
}
componentDidUpdate() {
//using ref
this.tableRef.current ..... //logic using ref
this.state.rows ..... //some logic
}
render() {
<TableContainer ref={this.tableRef} />
<CustomPagination />
}
}
This works fine, but now my requirement has changed, and I want to reuse the Table component with pagination applied to all the Tables in my App. I have decided to make a HOC withCustomPagination.
Use: withCustomPagination.js HOC
import CustomPagination from 'path/to/file';
const withCustomPagination = tableRef => Component => {
return class WithCustomPagination extends React.Component {
constructor(props) {
super(props);
this.state = {
rows: 1,
dataLength: props.dataLength,
}
}
componentDidUpdate() {
tableRef.current.state ..... //logic using ref, Error for this line
this.state.rows ..... //some logic
}
render() {
return (
<Component {...state} />
<CustomPagination />
)
}
}
}
export default withCustomPagination;
New Table.js:
import withCustomPagination from '/path/to/file';
const ref = React.createRef();
const Table = props => (
<TableContainer ref={ref} />
);
const WrappedTable = withCustomPagination(ref)(Table);
HOC withCustomPagination returns a class WithCustomPagination that has a componentDidUpdate lifecycle method that uses Table ref in the logic. So I try to pass ref created in Table.js as argument to withCustomPagination, i.e curried with ref and Table stateless component.
This use of ref is wrong and I get error: TypeError: Cannot read property 'state' of null.
I tried using Forwarding Refs, but was unable to implement it.
How do I pass the Table ref to withCustomPagination and be able to use it in HOC?
In this case you can use useImperativeHandle
It means you have to forward ref and specify which function or object or,...
you want to share with ref inside your functional component.
Here is my Hoc example :
import React from 'react';
import { View } from 'react-native';
export function CommonHoc(WrappedComponent) {
const component = class extends React.Component {
componentDidMount() {
this.refs.myComponent.showAlert();
}
render() {
return (
<>
<WrappedComponent
ref='myComponent'
{...this.state}
{...this.props}
/>
</>
);
}
};
return component;
}
and it's my stateless component
const HomeController=(props,ref)=> {
useImperativeHandle(ref, () => ({
showAlert() {
alert("called");
},
}));
return (
<Text>home</Text>
);
};
export default CommonHoc(forwardRef(HomeController));
Either restructure your code to not use a HOC for this or try using React.forwardRef:
Refs Aren’t Passed Through
While the convention for higher-order components is to pass through
all props to the wrapped component, this does not work for refs.
That’s because ref is not really a prop — like key, it’s handled
specially by React. If you add a ref to an element whose component is
the result of a HOC, the ref refers to an instance of the outermost
container component, not the wrapped component.
The solution for this problem is to use the React.forwardRef API
(introduced with React 16.3). Learn more about it in the forwarding
refs section.
via Higher-Order Components: Refs Aren’t Passed Through
In the forwarding refs section there are code examples you could use to pass refs down, but trying to yank them up will fail in your case with:
Warning: Stateless function components cannot be given refs. Attempts to access this ref will fail.
In a project we took a different approach. There's an EnhancedTable component that handles all of the pagination logic and in itself has the dumb table component and the pagination component. It works pretty well but this means you would have to drill props (or use a store lib like Redux or Mobx) and add new ones that will handle pagination options. This will result in some refactoring of Table uses and you'll have to be more explicit but I would take it as a boon rather than a hindrance.
I was able to solve a simmilar issue that brought me to this thread without using forwardRef or useImperativeHandle.
By creating the ref at a higher level, and passign it down into the component and sub components that I needed to act on with the ref.
/** Parent Component has access to ref and functions that act on ref **/
import { useRef } from 'react';
const formRef = useRef(); // ref will have dom elements need accessing
const onClickFunction=()=>{ //sample function acts on ref
var inputs = formRef.current.querySelectorAll('input')
/* Act on ref here via onClick function, etc has access to dom elements
in child component and childs child components */
};
return(
<ComponentGetsAttachedRef formRef={formRef} />
//^ref sent down to component and its children
<ComponentNeedingRef onClickFunction={onClickFunction}/>
//^function with access to ref sent down to component
)
/** Child component needs to act on ref**/
export const ComponentNeedingRef = ({ onClickFunction}) =>{
return(
<button onClick={onClickFunction}>
)
}
/* Child component recieves ref and passes it down */
export const ComponentGetsAttachedRef = ({ formRef}) =>{
//ref comes in as prop gets attached to props or utilized internally
return (
<ChildsChildComponent formRef={formRef}/> //sub component passed ref down
)
}

Sync local react state with data from redux

I have an App component which contains all the routes
App.jsx
import Editor from './Editor';
function App(props) {
return (
<BrowserRouter>
<Switch>
<Route path="/cms/post/edit/:postId" component={Editor} />
</Switch>
</BrowserRouter>
);
}
export default App;
I have an Editor component where user can edit a post. This component maps the data from redux store to local state as data needs to be manipulated locally.
Editor.jsx
// Usual Imports
class Editor extends Component {
constructor(props) {
super(props);
this.state = {
title: props.post ? props.post.title : '',
content: props.post ? props.post.content : ''
};
this.handleChange = this.handleChange.bind(this);
}
componentDidMount() {
this.props.post ? null : this.fetchPosts(this.props.match.params.postId);
}
handleChange(e) {
this.setState({
[e.target.name]: e.target.value
});
}
updatePost(data) {
// function to updatePost
}
fetchPosts(id) {
// function to fetch Posts
}
render() {
return (
<React.Fragment>
<input type="text" name="title" value={this.state.title} onChange={this.handleChange} />
<input type="text" name="content" value={this.state.content} onChange={this.handleChange} />
</React.Fragment>
);
}
}
const mapStateToProps = (state, ownProps) => ({
post: state.posts[ownProps.match.params.postId] || false,
...ownProps
}),
mapDispatchToProps = dispatch => ({
updatePost: data => dispatch(updatePost(data)),
fetchPosts: params => dispatch(fetchPosts(params))
});
export default connect(mapStateToProps, mapDispatchToProps)(Editor);
Now my questions are
Initially, post data is available and fetchPosts is not called. However, if user refreshes the page then post becomes false and fetchPosts is called and redux store is updated.
In which lifecycle method should I update the local react state with data from props?
Possible solutions which I think could be
A. Updating state in componentWillReceiveProps
componentWillReceiveProps(nextProps) {
this.setState({
title: nextProps.post.title,
content: nextProps.post.content
});
}
However, React docs discourages using componentWillReceiveProps as it might be invoked many times in React 16 and so on.
B. Update state in componentDidUpdate
componentDidUpdate(prevProps, prevState) {
if (this.props.post != prevProps.post) {
this.setState({
title: this.props.title,
content: this.props.content
});
}
}
I am not sure about this as I think there might be hidden side effects of this approach.
C. Not setting initial state and providing value to input tags via props i.e. value={this.props.post.title} and updating state via onChange handlers. However, when value is undefined then React will throw an error.
D. Newer lifecycle methods like getDerivedStateFromProps. Not too sure as React docs says it should be used in rare cases when state changes based on props over time.
I need to maintain the state as current component is also used for creating a fresh post also.
Which approach would be the best? And if I am missing out on something then let me know! Thanks!

How to pass a wrapped component into React Router without it constantly remounting

We're in the process of upgrading our React App, and after many of hours of pain have realised that passing wrapped components into React Router (V4 and maybe others) causes the component to "remount" every time a new prop is passed in.
Here's the wrapped component...
export default function preload(WrappedComponent, props) {
class Preload extends React.Component {
componentWillMount() {
getDataForComponent(props);
}
render() {
return <WrappedComponent {...props} />;
}
}
return Preload;
}
And here's how we're using it...
const FlagsApp = (props) => {
return (
<Route path="/report/:reportId/flag/:id/edit" component{preload(FlagForm, props)} />
);
};
Anytime we're dispatching an action and then receiving a update, the component remounts, causing lots of problems. According to this thread on github, components will remount if:
you call withRouter(..) during rendering which would create a new component class each time
you pass a new function to Route.component each render, e.g. using anonymous function
{...}} />, which would create a new component as well
If I pass the FlagForm component in directly the problem is fixed, but then I can't take advantage of the preload function.
So, how can I achieve the same outcome, but without the component remounting?
Thanks in advance for any help!
The reason Route is mounting a new component on every update is that it's been assigned a new class each time via preload.
Indeed, each call to preload always returns a distinct anonymous class, even
when called with the same arguments:
console.log( preload(FlagForm,props) != preload(FlagForm,props) ) // true
So, since the issue is that preload being called within the FlagsApp component's render method, start by moving it outside of that scope:
const PreloadedFlagForm = preload(FlagForm, props) //moved out
const FlagsApp = (props) => {
return (
<Route path="/report/:reportId/flag/:id/edit"
component={PreloadedFlagForm} /> //assign component directly
);
};
This way the component for Route won't change between updates.
Now about that lingering props argument for preload: this is actually an anti-pattern. The proper way to pass in props just the standard way you would for any component:
const PreloadedFlagForm = preload(FlagForm) //drop the props arg
const FlagsApp = (props) => {
return (
<Route path="/report/:reportId/flag/:id/edit"
component={<PreloadedFlagForm {...props} />} //spread it in here instead
/>
);
};
And so the code for preload becomes:
export default function preload(WrappedComponent) {
class Preload extends React.Component {
componentWillMount() {
getDataForComponent(this.props);
}
render() {
return <WrappedComponent {...this.props} />;
}
}
return Preload;
}
Hope that helps!
If like me you didn't read the instructions, the answer lies in the render prop of the <Route> component
https://reacttraining.com/react-router/web/api/Route/render-func
render: func
This allows for convenient inline rendering and wrapping without the undesired remounting explained above.
So, instead of passing the wrapper function into the component prop, you must use the render prop. However, you can't pass in a wrapped component like I did above. I still don't completely understand what's going on, but to ensure params are passed down correctly, this was my solution.
My Preload wrapper function is now a React component that renders a Route...
export default class PreloadRoute extends React.Component {
static propTypes = {
preload: PropTypes.func.isRequired,
data: PropTypes.shape().isRequired,
location: PropTypes.shape({
pathname: PropTypes.string.isRequired,
}),
}
componentWillMount() {
this.props.preload(this.props.data);
}
componentWillReceiveProps({ location = {}, preload, data }) {
const { location: prevLocation = {} } = this.props;
if (prevLocation.pathname !== location.pathname) {
preload(data);
}
}
render() {
return (
<Route {...this.props} />
);
}
}
And then I use it like so...
const FlagsApp = (props) => {
return (
<Switch>
<PreloadRoute exact path="/report/:reportId/flag/new" preload={showNewFlagForm} data={props} render={() => <FlagForm />} />
<PreloadRoute exact path="/report/:reportId/flag/:id" preload={showFlag} data={props} render={() => <ViewFlag />} />
<PreloadRoute path="/report/:reportId/flag/:id/edit" preload={showEditFlagForm} data={props} render={() => <FlagForm />} />
</Switch>
);
};
The reason I'm calling this.props.preload both in componentWillMount and componentWillReceiveProps is because I then had the opposite issue of the PreloadRoute component not remounting when navigating, so this solves that.
Hopefully this save lots of people lots of time, as I've literally spent days getting this working just right. That's the cost of being bleeding edge I guess!

Categories

Resources