How can I pass a ref to a react-native-reanimated component? - javascript

I'm trying to pass a ref to a component with a similar approach to the following code block, but the current value always returns undefined. This approach works fine with a plain FlatList from react-native, however it doesn't work once I'm using either an Animated.FlatList or an Animated.createAnimatedComponent(FlatList) :
const Parent = () => {
const flatListRef = useRef();
useEffect(() => {
console.log(flatListRef.current) // undefined
})
return (
<View>
<Child ref={flatListRef} />
</View>
)
}
const Child = React.forwardRef((props, ref) => {
return (
<Animated.FlatList
ref={ref}
/>
)
})

The library react-native-reanimated works a little bit different in comparison to react-native-animated.
If we create the animated component via Animated.createAnimatedComponent(FlatList), then everything works as expected.
Here is a working version of your code. I have logged the function scrollToIndex of the FlatList ref for testing purposes.
import Animated from "react-native-reanimated"
const ReanimatedFlatList = Animated.createAnimatedComponent(FlatList);
const Parent = (props) => {
const flatListRef = useRef(null);
useEffect(() => {
console.log(flatListRef.current.scrollToIndex)
}, [])
return (
<View>
<Child ref={flatListRef} />
</View>
)
}
const Child = React.forwardRef((props, ref) => {
return (
<ReanimatedFlatList
ref={ref}
/>
)
})

Related

How can i access my WebView from another component?

I have a basic WebView:
const WebViewComponent = () => {
function sendDataToWebView() {
webviewRef.current.postMessage('Hello world');
}
const webviewRef = useRef();
return (
<SafeAreaView>
<WebView
ref={webviewRef}
source={{
uri: 'https://www.google.com/',
}}
/>
</SafeAreaView>
);
}
Calling sendDataToWebView() works in this component since i have the reference. (useRef()).
But in another component:
const anotherComponent = () => {
const webviewRef = useRef();
webviewRef.current.postMessage('Hello world');
}
webviewRef is undefined.
How can i access my WebView from another component?
There are 2 ways to do this, with and without using name ref for the reference property.
Without using ref prop name, i.e. use any other name.
const WebViewComponent = ({ webviewRef }) => {
return (
<WebView
ref={webviewRef}
source={{ uri: 'https://www.google.com/' }}
/>
);
};
const AnotherComponent = () => {
const webviewRef = useRef();
useEffect(() => {
const test = () => {
const run = "window.alert('haha');";
webviewRef.current?.injectJavaScript(run);
};
setTimeout(test, 3000);
}, []);
return (
<SafeAreaView>
<WebViewComponent
webviewRef={webviewRef}
// ....
/>
</SafeAreaView>
);
}
Using ref. This is possible using forwardRef feature of React. You can read more about it here.
const WebViewComponent = React.forwardRef((props, ref) => {
return (
<WebView
ref={ref}
source={{ uri: 'https://www.google.com/' }}
/>
);
});
const AnotherComponent = () => {
const webviewRef = useRef();
// .....
return (
<SafeAreaView>
<WebViewComponent
ref={webviewRef}
// ....
/>
</SafeAreaView>
);
}
you can't just create a new ref and use that in your anotherComponent. If you want to display the webview in your anotherComponent, you need to import it and return is as part of the anotherComponent.

Change Textinput value when rendering Using .map() React native

i am using map function to render my component which have textInput . I want to change value of textInput using onchangeText function.
//main component
const [Value0, setValue0] = useState('');
const [Value1, setValue1] = useState('');
const [Value2, setValue2] = useState('');
..
const handleOnSubmit = () => { //fired when click from this
compnent button
console.log(Value0,"Value0");
console.log(Value1,"Value1");
}
//in my return i use :
{
data.map((item, id) => {
return (
<ViewDeatilCard1 key={id} /> //data having length 4
)
})
}
<ViewDeatilCard1 key={id} setChangeText={(value)=>{`'setValue${id}${(value)}'`}} /> //this
<ViewDeatilCard1 key={id} setChangeText={()=>{`'setValue${id}'`}} /> //this
<ViewDeatilCard1 key={id} setChangeText={`'setValue${id}'`} /> //this
// none of this work
// in my component i use
export const ViewDeatilCard1 = ({
setChangeText
}) => {
console.log(setChangeText,"setChangeText");
return (
<View style={styles.container}>
<View style={styles.body}>
<FormInput
style={styles.bodyText}
labelText="Enter pick up loaction"
iconName="null"
onChangeText={setChangeText}
/>
</View>
</View>
)}
how can i change my value using this approach

How to abstract React state logic to custom hook?

I have a component like this:
export const MyComp = () => {
const [showRules, setShowRules] = useState(false)
return (
<TouchableOpacity onPress={() => setShowRules(!showRules)} activeOpacity={1}>
<View>
<Inner expand={showRules}>
<Text>Content here </Text>
</Inner>
</View>
</TouchableOpacity>
)
}
I have another component where I expand/collapse stuff just like this. the logic is duplicated, just the state has different names. thought it could be a good use case for a custom hook
so I wrote this
export const useExpander = (setExpand) => {
const [show, setShow] = useState(false)
useEffect(() => {
if (setExpand) {
setShow(true)
} else {
setShow(false)
}
}, [])
return show
}
but I can't figure out how to use it?
I cant change this line: <TouchableOpacity onPress={() => useExpander(expand)} activeOpacity={1}>
as I think this violates hook rules
any ideas?

Trying to update child component 1 with data from child component 2 without rerender child 2

I'm generating a heavy JSX array from a loop.
It creates a lot of table.
I would like to update a Badge with the data on a row selected. But it rerender all my tables. It's pretty long for updating a single badge.
I tried to use useMemo() to prevent the creation of the table if my data doesn't change, but the callback fonction from the parent does not update state.
A code example what i'm trying to do =>
function App() {
const [tableData, setTableData] = useState(null)
const [badgeData, setBadgeData] = useState(null)
const jsx = useMemo(() => createTable(tableData), [tableData])
function updateBadge(selectedRows) {
setBadgeData(addNewRow(selectedRows))
}
function createTable(data) {
let jsx = []
data.forEach((item) => {
jsx.push(<TableComponent data={data.var} onRowSelect={updateBadge}/>)
})
return jsx;
}
return (
<div>
<HandleDataGeneration setData={setTableData}/>
<MyBadgeComponent data={badgeData}/>
{jsx}
</div>
);
}
In this case, only the first call to updateBadge function rerender the parent, but not the nexts calls (i guess it's because i don't send the new props and the function is copied and not linked)
Maybe my architecture is bad, or maybe there is some solution for update this badgeComponent without rerender all my Tables. Thanks you for your help
EDIT:
TableComponent
const TableCompoennt = React.memo((props) => { // after edit but it was a classic fn
const classes = useStyles();
const [expanded, setExpanded] = useState(props.data ? `panel${props.i}` : false);
let disabled = false;
const handleChange = (panel) => (event, isExpanded) => {
setExpanded(isExpanded ? panel : false);
};
if (isNaN(props.data.var)) {
props.data.var = x
}
if (!props.data)
disabled = true;
return (
<ExpansionPanel TransitionProps={{ unmountOnExit: false }} expanded={expanded === `panel${props.i}`} onChange={handleChange(`panel${props.i}`)} disabled={disabled}>
<ExpansionPanelSummary
expandIcon={<ExpandMoreIcon/>}
aria-controls="panel1bh-content"
id="panel1bh-header"
>
<Tooltip title={props.data.var}>
<Typography className={classes.heading}>{props.data.var}-{props.data.var}</Typography>
</Tooltip>
{!disabled ?
<Typography
className={classes.secondaryHeading}>{expanded ? "click to hide data" : "click to display data"}</Typography> :
<Typography
className={classes.secondaryHeading}>no data</Typography>
}
</ExpansionPanelSummary>
<ExpansionPanelDetails>
<MyTable data={props.data} var={props.var}
key={props.i} id={props.i} style={{width: "100%"}} updateBadge={props.updateBadge}/>
</ExpansionPanelDetails>
</ExpansionPanel>
)
})
MyTable
export default React.memo((props) => { // same here
const [open, setOpen] = useState(false);
const [rowData, setRowData] = useState(null);
const [rows, setRows] = useState(props.myRates);
calcTotal(rows);
useEffect(() => {
setRows(props.myRates)
}, [props]);
return (
<div style={{width: "100%"}}>
{(rows && rows.length) &&
<div style={{width: "100%"}}>
<Modal open={open} rowData={rowData} setRowData={setRowData}
setOpen={(value) => setOpen(value)}/>
<Paper style={{height: 400, width: '100%'}}>
<SimpleTable
columns={columns}
rows={rows}
handleRowClick={(row) =>{
setOpen(true);
setRowData(row);
}}
handleSelect={(row) => {
if (!row.selected)
row.selected = false;
row.selected = !row.selected;
props.updateBadge(row)
}}
/>
</Paper>
</div>}
</div>
);
})
SimpleTable
const SimpleTable = React.memo((props) => {
const classes = useStyles();
let dataLabel = generateLabel(props.columns);
function getRowData(row) {
props.handleRowClick(row);
}
return (
<TableContainer component={Paper}>
<Table className={classes.table} aria-label="simple table">
<TableHead>
<TableRow>
{dataLabel}
</TableRow>
</TableHead>
<TableBody>
{props.rows.map((row) => (
<TableRow key={row.unique_code} selected={row.selected} hover onClick={() => {getRowData(row)}}>
{generateRow(props.columns, row)}
<TableCell onClick={(event) => {
event.stopPropagation();
}
}>
<Checkbox onClick={(event) => {
event.stopPropagation();
props.handleSelect(row);
}}
/>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
);
})
Instead of using useMemo inside the App Component, you must make use of React.memo for the TableComponent assuming its a functional component or extend it with React.PureComponent if its a class component
However in such a case, you must make sure that you are not recreating the updateBadge Function on each re-render. To make sure of that, use useCallback hook
Also don't forget to add a unique key to TableComponent's instances which are rendered from the loop
function App() {
const [tableData, setTableData] = useState(null)
const [badgeData, setBadgeData] = useState(null)
const updateBadge = useCallback((selectedRows) {
setBadgeData(addNewRow(selectedRows))
}, []);
return (
<div>
<HandleDataGeneration setData={setTableData}/>
<MyBadgeComponent data={badgeData}/>
{data.map((item) => {
return <TableComponent key={item.id} props={item} onRowSelect={updateBadge}/>)
})}
</div>
);
}
and in TableComponent
const TableComponent = React.memo((props) => {
// Rest of the TableComponent code
})
EDIT:
Adding a working demo based on your comment codesandbox: https://codesandbox.io/s/clever-fast-0cq32
Few changes
made of use of useCallback method and also converted state updater to use callback approach
Removed the logic for JSON.parse and JSON.stringify and instead stored the entire data in array. The reason for this is that everytime you use JSON.parse it returns you a new object reference and hence memo functionality fails in child since it just does a reference check. This will happen each time your component re-renders i.e on update of badge state.
if you still need to use JSON.parse and JSON.stringify, add a custom comparator that compares the data values deeply

slightly adapted HOC in react DRY

I have a simple HOC which injects a react context as a prop in the wrappedcomponent.
function withTranslate(WrappedComponent) {
//we forward the ref so it can be used by other
return React.forwardRef((props, ref) => (
<TranslatorContext.Consumer>
{context => (<WrappedComponent {...props} translate={context} ref={ref} />)}
</TranslatorContext.Consumer>)
)
}
Now I want a secondary HOC which uses the same context, but changes some predefined props using this context. I succeed with following code:
export function withTranslatedProps(WrappedComponent,propsToBeTransLated) {
//propsToBetranslated is array with all props which will be given via keys
const translateProps=(translate,props)=>{
const ownProps=Object.assign({},props)
propsToBeTransLated.forEach(p=>{
if(ownProps.hasOwnProperty(p)){
ownProps[p]=translate(ownProps[p])
}
})
return ownProps
}
return React.forwardRef((props, ref) => {
console.log("render contextconsumer")
return (
<TranslatorContext.Consumer>
{context => (
<WrappedComponent {...translateProps(context,props)} ref={ref} />
)}
</TranslatorContext.Consumer>)
})
}
But I almost exactly use the same HOC as withTranslate. Is there a better option (without repeating myself) to do this?
edit
I think i solved it:
const _translateProps=(propsToBeTransLated,translate,props)=>{
const ownProps=Object.assign({},props)
propsToBeTransLated.forEach(p=>{
if(ownProps.hasOwnProperty(p)){
ownProps[p]=translate(ownProps[p])
}
})
return ownProps
}
export function withTranslatedProps(WrappedComponent,propsToBeTransLated) {
//propsToBetranslated is array with all props which will be given via keys
let retrieveProps=propsToBeTransLated?_translateProps.bind(null,propsToBeTransLated):(context,props)=>({translate:context,...props})
return React.forwardRef((props, ref) => {
console.log("render contextconsumer")
return (
<TranslatorContext.Consumer>
{context => (
<WrappedComponent {...retrieveProps(context,props)} ref={ref} />
)}
</TranslatorContext.Consumer>)
})
}
Anyone with other possibly better solutions?
You can reuse withTranslate HOC or use the same HOC adding options.
Reusing withTranslate HOC:
/* function that translate the propsToBeTransLated */
const translateProps = (propsToBeTransLated, translate, props) =>
propsToBeTransLated.reduce((translatedProps, prop) => {
if(props.hasOwnProperty(prop))
translatedProps[prop] = translate(props[prop]);
return translatedProps;
}, {});
export function withTranslatedProps(WrappedComponent, propsToBeTransLated = []) {
// HOC inside HOC
const addTranslationsToProps = WrappedComponentWithContext =>
React.forwardRef((props, ref) => (
<WrappedComponentWithContext
{...props}
{...translateProps(propsToBeTransLated, props.translate, props)}
ref={ref}
/>
)
);
// first call withTranslate to add the context
return addTranslationsToProps(withTranslate(WrappedComponent));
}
Adding options to withTranslate HOC
const translateProps = (propsToBeTransLated, translate, props) =>
propsToBeTransLated.reduce((translatedProps, prop) => {
if(props.hasOwnProperty(prop))
translatedProps[prop] = translate(props[prop]);
return translatedProps;
}, {});
export function withTranslate(WrappedComponent, options) {
const { propsToBeTransLated = [] } = options;
return React.forwardRef((props, ref) => (
<TranslatorContext.Consumer>
{context => (
<WrappedComponent
{...props}
{...translateProps(propsToBeTransLated, context, props)}
translate={context}
ref={ref}
/>
)}
</TranslatorContext.Consumer>
));
}

Categories

Resources