React createRef for a childComponent inside a ViewPager in React Native - javascript

I have created a signup module with three steps in my react native application where I have put all of the fields in the state of the parent component, and that gave me a very overloaded JSX file, So I am trying to move all of the fields validation functions to the related step and catch the returned value 'valid/ not valid' in the parent screen so I can move to the next step by clicking on the next button which will navigate to the next viewpager page.
So I have tried to use the React.createRef to reference the child step in the parent and call the methods within the child in the future:
SignupScreen.jsx (Parent):
constructor(props) {
super(props);
this.state = {
...fields,
}
this.signupStepOneRef = React.createRef();
}
render(){
return (
<ViewPager
initialPage={0}
style={styles.fieldsContainer}
scrollEnabled={false}
ref={(vp) => (this.viewPager = vp)}
>
<SignupStepOne
fieldsContainerStyle={styles.fieldsContainer}
scrollViewStyle={styles.scrollView}
key="1"
onInputChanged={this.handleChange}
stepOneErrors={this.state.stepOneErrors}
ref={this.signupStepOneRef}
/>
...OtherSteps
</ViewPage>
)
}
But this code gives me an Object : {current: null} when I console.log(this.signupStepOneRef).
The official documentation says that this won't work if the child is a function, but changing it to a class component didn't help either.
I have also tried the:
ref={(obj) => this.signupStepOneRef = obj}
syntax but also gives me the same null current object.

I finally found a solution, since viewpager children cannot be referenced directly I have wrapped up the child page in a View component and that solved my issue.
The solution code:
<ViewPager
initialPage={0}
style={styles.fieldsContainer}
scrollEnabled={false}
ref={(vp) => (this.viewPager = vp)}
>
<View key="1">
<SignupStepOne
fieldsContainerStyle={styles.fieldsContainer}
scrollViewStyle={styles.scrollView}
onInputChanged={this.handleChange}
stepOneErrors={this.state.stepOneErrors}
ref={this.signupStepOneRef}
/>
</View>
...OtherSteps
</ViewPage>
The key attribute needs to be moved to the wrapper view.

Related

TypeError: Cannot read properties of undefined (reading 'style')

Been stuck on debugging this for quite a while. I'm trying to have a group of items change onClick but with the use of transform but 'style' is undefined. I've also included the Card component functions. Help would be greatly appreciated
import React,{useRef} from 'react';
import { Card } from '../components';
import { CardItemContainer } from './card-item';
export function CardContainer()
{
const listRef=useRef()
const handleClick=(direction)=>
{
if(direction==="left")
{
listRef.current.style.transform=`translate(230)`
}
}
return(
<Card>
<Card.ListTitle> Continue to watch</Card.ListTitle>
<Card.Wrapper >
<Card.ArrowSliderLeft onClick={()=>handleClick('left')}/>
<Card.List ref={listRef}>
<CardItemContainer index={0}/>
<CardItemContainer index={1}/>
<CardItemContainer index={2}/>
<CardItemContainer index={3}/>
<CardItemContainer index={4}/>
<CardItemContainer index={5}/>
<CardItemContainer index={6}/>
</Card.List>
<Card.ArrowSliderRight onClick={() => handleClick("right")}/>
</Card.Wrapper>
</Card>
)
}
Card Components
import {ArrowBackIosOutlined,ArrowForwardIosOutlined} from "#material-ui/icons";
import React, {} from 'react';
import {
Container,
List,
ListTitle,
Wrapper,
ArrowSliderLeft,
ArrowSliderRight
} from './styles/card';
export default function Card({ children, ...restProps }) {
return <Container {...restProps}>{children}</Container>
}
Card.ListTitle=function CardListTitle({children,...restProps})
{
return <ListTitle{...restProps}> {children} </ListTitle>
}
Card.Wrapper=function CardWrapper({children,...restProps})
{
return <Wrapper{...restProps} > {children} </Wrapper>
}
Card.List=function CardList({children,...restProps})
{
return <List{...restProps} >{children}</List>
}
Card.ArrowSliderLeft = function HeaderArrowBackIosOutlinedSymbol({...restProps })
{
return <ArrowSliderLeft {...restProps }>
{/*id allows me to style the icon directly */}
<ArrowBackIosOutlined id="sliderLeft"/>
</ArrowSliderLeft>
}
Card.ArrowSliderRight = function HeaderArrowForwardIosOutlinedSymbol({...restProps}) {
return (
<ArrowSliderRight {...restProps}>
<ArrowForwardIosOutlined id="sliderRight"/>
</ArrowSliderRight>
);
};
Ignore:
Been stuck on debugging this for quite a while. I'm trying to have a group of items change onClick but with the use of transform but 'style' is undefined. I've also included the Card component functions. Help would be greatly appreciated
Function components like CardList don't have a ref property, only class components or DOM elements do.
You haven't posted List component's implementation, but let's assume it has a <ul> tag, and that is what you eventually need to manipulate its .style.transform
CardList >>> List >> ul (this is the element you need to pass the ref)
To pass the listRef all the way to ul from CardList you need to use the forwardRef technique.
Card.List=React.forwardRef(function CardList (props,ref)
{
const {children,...restProps} = props
return <List{...restProps} ref={ref} >{children}</List>
})
the List component itself :
const List = React.forwardRef(function (props,ref) {
return <ul ref={ref}>
... the implementation of your List
Now you can pass listRef in here and it goes down the chain:
<Card.List ref={listRef}>
Side Note: taking from Drew Reese's comment on this answer, since CardList is just transfering the same props from a parent component to List, you can simply assign List to Card.List, then only one step of ref forwarding would be enough:
Card.List = List // CardList component isn't used anymore.
The same thing could work for Card.ListTitle and Card.Wrapper:
Card.ListTitle=ListTitle
Card.Wrapper=Wrapper
I too have just faced this same issue, and have tried to get my code working again. Checking similarity between your given code and my erroneous code snippet helped me fix the error.
Strangely, I have faced this error with a JSX multi-line comment in place after my element (MUI <Snackbar> element, in my case).
Error(s):
My code snippet looked something like:
<Snackbar open={snackbarOpen} autoHideDuration={5000} onClose={()=>setSnackbar(false)} > {/* My Comment Here */}
<>...</>
</Snackbar>
Quite similar place of JSX comment as your Card Component
Card.ArrowSliderLeft = function
...
return <ArrowSliderLeft {...restProps }>
{/*id allows me to style the icon directly */}
<ArrowBackIosOutlined ... />
</ArrowSliderLeft>
Removing just the comment part {/* */} immediately following an opening tag worked for me.
So, try removing your JSX comment or placing it elsewhere,and see if it helps.
Sharing it here just for my and others future reference. :)

How to pass React Component as props to another component

I am trying to call PopupDialog.tsx inside Content.tsx as a sibling of Item.tsx.
Previously PopupDialog.tsx is called inside C.tsx file but due to z index issue i am trying to bring it out and call it in Content.tsx
Is it possible to somehow pass the whole component(popupDialog and its parameters) in Content.tsx so that i could avoid passing back and forth the parameters needed for popupdialog in content.tsx.
Code in C.tsx where PopupDialog component is called.
const C = (props: Props) => (
<>
{props.additionalInfo ? (
<div className="infoButton">
<PopupDialog // need to take this code out and want to add in Content.tsx
icon="info"
callback={props.callback}
position={Position.Right}
>
<div className="popuplist">{props.additionalInfo}</div>
</PopupDialog>
</div>
) : (
<Button className="iconbutton"/>
)}
</>
);
Content.tsx where i would like to call PopupDialog.tsx with its parameters
const Content = (props: Props) => {
const [componentToRender, docomponentToRender] = React.useState(null);
const [isAnimDone, doAnim] = React.useState(false);
return (
<div className="ContentItems">
<PWheel agent={props.agent} />
{isAnimDone && (
<>
<Item {props.agent} />
{componentToRender &&
<PopupDialog/> //want to call here with all its parameters to be passed
}
</>
)}
</div>
);
};
Folder Structure
App.tsx
->ViewPort.tsx
->Content.tsx
->PWheel.tsx
->Item.tsx
->A.tsx
->B.tsx
->C.tsx
{props.additionalinfo &&
->PopupDialog.tsx
->PopupDialog.tsx
So if I understand the question correctly you want to pass one component into another so that you can use the properties or data of the passed componenet in your current component.
So there are three ways to achieve this.
1)Sending the data or entire component as prop.This brings disadvantage that even though components which don't require knowledge
about the passed component will also have to ask as a prop.So this is bascially prop drilling.
2)The other is you can use context api.So context api is a way to maintain global state variale.so if you follow this approach you don't need to pass data or componenet as props.Wherever you need the data you can inport context object and use it in componenet.
3)Using Redux library.This is similar to context api but only disadavantage is that we will have to write lot of code to implement this.Redux is a javascript library.
Let me know if you need more info.
You need to :
<>
<Item {props.agent} />
{componentToRender &&
<PopupDialog abc={componentToRender} /> //you must call in this component, in this case i name it is abc , i pass componentToRender state to it
}
</>
and then PopupDialog will receive componentToRender as abc, in PopupDialog , you just need to call props.abc and done .
If you need to know more about prop and component you can see it here
I think what you want to use is Higher-Order-Components (HOC).
The basic usage is:
const EnhancedComponent = higherOrderComponent(WrappedComponent);
Below is such an implementation that takes a component (with all its props) as a parameter:
import React, { Component } from "react";
const Content = WrappedComponent => {
return class Content extends Component {
render() {
return (
<>
{/* Your Content component comes here */}
<WrappedComponent {...this.props} />
</>
);
}
};
};
export default Content;
Here is the link for higher-order-components on React docs: https://reactjs.org/docs/higher-order-components.html
Make use of
useContext()
Follow this for details:
React Use Context Hook

unexpected token, expected "," inside of react render() function in the return statement

The error is unexpected token, expected "," in the render return function. I am using babel and linking this file in an html file. I removed the comment class and component for viewing purposes. Also I removed the comment form component.
Here is main.js:
class App extends React.Component {
constructor(props) {
super(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.state = { comments : [] }
}
handleSubmit(event) {
// ...
}
render() {
const comments = this.state.comments.map((comment) => {
<Comment author={comment.author} message={comment.message} />
});
const formcomment = <CommentForm handleSubmit = {this.handleSubmit} />;
return (
{comments}
{formcomment} // ERROR HERE
)
}
}
ReactDOM.render(<App />, document.getElementById("root"))
The problem occurs, because JSX requires you to only have one root element. In your case you're having two root elements.
If you want to return multiple elements, you need to wrap them into some sort of container, most of the time a simple div will be sufficient:
return (
<div>
{comments}
{formcomment}
</div>
);
If the div is disrupting your styling, you might want to use a Fragment instead.
Read more about JSX in general here and here.
EDIT:
As Emile Bergeron pointed out, you can also return an array as of React 16.2:
render() {
return [comments, formcomment];
}
Reference.
The problem is that you are trying to render multiple elements without a parent container.
You should be able to fix this by adding <Fragment> ... </Fragment> (or, more simply, <> ... </>) around the contents of your return statement. This will give the JSX transpiler a single element to render with.
The usual fix for this is using a "wrapper div", but using a Fragment like this is the new way of wrapping elements without adding nodes to the DOM.
Checkout out this page to learn more about this problem and React fragments.
Have you tried wrapping your return value in a div or fragment(<></>)?

React Native not rendering on prop change

I have created the following component:
type ToggleButtonProps = { title: string, selected: boolean }
export default class ToggleButton extends Component<ToggleButtonProps>{
render(){
return (
<TouchableWithoutFeedback {...this.props}>
<View style={[style.button, this.props.selected ? style.buttonSelected : style.buttonDeselected]}>
<Text style={[style.buttonText, this.props.selected ? style.buttonTextSelected : style.buttonTextDeselected]}>{this.props.title}</Text>
</View>
</TouchableWithoutFeedback>
);
}
}
The styles are simple color definitions that would visually indicate whether a button is selected or not. From the parent component I call (item is my object):
item.props.selected = true;
I've put a breakpoint and I verify that it gets hit, item.props is indeed my item's props with a selected property, and it really changes from false to true.
However, nothing changes visually, neither do I get render() or componentDidUpdate called on the child.
What should I do to make the child render when its props change? (I am on React Native 0.59.3)
You can't update the child component by literally assigning to props like this:
item.props.selected = true;
However, there are many ways to re-render the child components. But I think the solution below would be the easiest one.
You want to have a container or smart component which will keep the states or data of each toggle buttons in one place. Because mostly likely, this component will potentially need to call an api to send or process that data.
If the number of toggle buttons is fixed you can simply have the state like so:
state = {
buttonOne: {
id: `buttonOneId`,
selected: false,
title: 'title1'
},
buttonTwo: {
id: `buttonTwoId`,
selected: false,
title: 'title2'
},
}
Then create a method in the parent which will be called by each child components action onPress:
onButtonPress = (buttonId) => {
this.setState({
[buttonId]: !this.state[buttonId].selected // toggles the value
}); // calls re-render of each child
}
pass the corresponding values to each child as their props in the render method:
render() {
return (
<View>
<ToggleButton onPressFromParent={this.onButtonPress} dataFromParent={this.state.buttonOne} />
<ToggleButton onPressFromParent={this.onButtonPress} dataFromParent={this.state.buttonTwo} />
...
finally each child can use the props:
...
<TouchableWithoutFeedback onPress={() => this.props.onPressFromParent(this.props.dataFromParent.id)}>
<View style={[style.button, this.props.dataFromParent.selected ? style.buttonSelected : style.buttonDeselected]}>
...
I left the title field intentionally for you to try and implement.
P.S: You should be able to follow the code as these are just JS or JSX.
I hope this helps :)
Because children do not rerender if the props of the parent change, but if its STATE changes :)
Update child to have attribute 'key' equal to "selected" (example based on reactjs tho')
Child {
render() {
return <div key={this.props.selected}></div>
}
}

reactjs - Create Toggle For Dynamically Created Buttons

Currently, I am facing a problem with JS/React/React-Native. I am pulling categories from an API, and I am making buttons out of the results (they change often based on different variables in the URL). The code I am using to do this is as follows:
const cats = singles.map((d) => {
return (
<TouchableOpacity key={d} style={styles.Settingcats}><Text style={{color: '#f55f44'}}>{d}</Text></TouchableOpacity>
)}
With the dynamically generated buttons I want them to be able to be toggled in the application. When I tried to utilize the states with the following code:
const cats = singles.map((d) => {
return (
<TouchableOpacity key={d} onPress={ _ => this.changeStyle} style={this.state.style === 0 ? styles.Settingcats : styles.SelSettingcats}><Text style={{color: '#f55f44'}}>{d}</Text></TouchableOpacity>
)}
And realized that, since this was referring to one state for all of the buttons, it would change all of the buttons styles. So I tried to think otuside the box a bit and thought of using the ID as a state name, creating the state, and utilizing the state through an external function to change it's value.
const cats = singles.map((d) => {
return (
<TouchableOpacity key={d} onPress={ _ => this.changeStyle(d)} style={this.state([d]) === 0 ? styles.Settingcats : styles.SelSettingcats}><Text style={{color: '#f55f44'}}>{d}</Text></TouchableOpacity>
)}
changeStyle(d){
this.setState({
[d] : 1
})}
Which throws an error because the state is being utilized as if it is a function.
What practices can I use to make dynamically created buttons have their own separate toggle events?
Other things I have tried: Custom Switches, material-UI but I get stuck at the same problem when it comes to having unique functions for the toggled buttons.
Instead of trying to manage a list of states at the top level, move TouchableOpacity into a component that handles the toggle state internally.
class MyButton extends React.Component {
constructor(props) {
super(props)
this.state = {
toggle: false
}
this.setToggle = this.setToggle.bind(this);
}
setToggle(){
this.setState({
toggle: !this.state.toggle
})
}
render(){
return <TouchableOpacity onClick={this.setToggle} className={this.state.toggle ? 'red' : 'blue'}>{this.props.name}</TouchableOpacity>
}
}
You can render a list of these and each one manages it own toggle state independent of the others.
Here is a fiddle example: https://jsfiddle.net/n5u2wwjg/220149/
The method that I used to achieve this, with many thanks from #simbathesailor
I utilized the variable in the state to dynamically create states that I could utilize in telling whether the buttons were "on" or "off" by using
this.state[d]
Where d was the dynamic variable that I created with my TouchableOpaque component.

Categories

Resources