Send ref via props in functional component - reactjs

In my parent component I call hook useRef: const flatListRef = useRef(null); and then I want to use this flatListRef in child component. I tried to do like in documentation but without success. When I call my function toTop I get: null is not an object (evaluating 'flatListRef.current.scrollToOffset')
This is my parent component:
const BeautifulPlacesCards = ({navigation}: HomeNavigationProps<"BeautifulPlacesCards">) => {
const flatListRef = useRef(null);
const toTop = () => {
flatListRef.current.scrollToOffset(1)
}
const buttonPressed = () => {
toTop()
}
return(
<Carousel filteredData={filteredData} flatListRef={flatListRef}/>
)
}
This is my child component:
const Carousel = forwardRef((filteredData, flatListRef) => {
return (
<AnimatedFlatList
ref={flatListRef}
/>
)
}

Here is a working example: https://snack.expo.dev/#zvona/forwardref-example
Key takes:
you need to use prop ref when passing it down, not flatListRef
you need to destructure filteredData from props
Here is the relevant code:
const Child = forwardRef(({ filteredData }, ref) => {
return (
<FlatList
ref={ref}
style={styles.flatList}
data={filteredData}
renderItem={({ item }) => (
<Text style={styles.item} key={`foo-${item}`}>
{item}
</Text>
)}
/>
);
});
const App = () => {
const flatListRef = useRef(null);
const toTop = () => {
flatListRef.current.scrollToOffset(1);
};
return (
<View style={styles.container}>
<Button title={'Scroll back'} onPress={toTop} />
<Child filteredData={[1,2,3,4,5,6]} ref={flatListRef} />
</View>
);
};

Related

Children in Parent Functional Component are not Re-rendering on Props Change

I'm dynamically generating children components of HOC parent (see below). I pass the props directly to one of children and set the prop in it. I expect to see child re-rendering on props change but it doesn't.
Is the code incorrect somewhere?
ParentComponent
...
const ParentComponent = ({children}) => {
const [state1, setState1] = useState(true);
...
const changeOpacity = event => setState1(!state1);
const renderChildren = React.useCallback(() => React.Children.toArray(children).map((child, index) => (
<div key={index} style={{opacity: `${state1 ? 0 : 1}`}}>
{child}
</div>
)), [state1]);
return (
<div>
<Button onClick={changeOpacity}>Toggle Opacity</Button>
{renderChildren()}
</div>
);
};
App.js
...
const App = () => {
const [prop1, setProp1] = useState(123);
return (
<ParentComponent>
<Child1 prop1={prop1} setProp1={setProp1} />
<Child2 />
</ParentComponent>
);
};
In your ParentComponent, the children are cloned and then used to render as a part of the return value from the renderChildren function. Since the logic to compute children is not run on change of props to children, your child component is not affected by a change in its prop.
You can add children dependency to useCallback and it will work fine.
const { useState, useCallback } = React;
const ParentComponent = ({children}) => {
const [state1, setState1] = useState(true);
const changeOpacity = event => setState1(!state1);
const renderChildren = useCallback(() => React.Children.map(children, (child, index) => (
<div key={index} style={{opacity: `${state1 ? 0 : 1}`}}>
{child}
</div>
)), [children, state1]);
return (
<div>
<button onClick={changeOpacity}>Toggle Opacity</button>
{renderChildren()}
</div>
);
};
const Child1 = ({prop1, setProp1}) => <div>{prop1} <button onClick={() => setProp1(234)}>Click</button></div>;
const Child2 = () => <div>Hello</div>
const App = () => {
const [prop1, setProp1] = useState(123);
return (
<ParentComponent>
<Child1 prop1={prop1} setProp1={setProp1} />
<Child2 />
</ParentComponent>
);
};
ReactDOM.render(<App />, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="app" />
Is there anything prevent you from the approach below;
const ParentComponent = ({children}) => {
const [state1, setState1] = useState(true);
...
const changeOpacity = event => setState1(!state1);
const renderChildren = useCallback(() => React.Children.toArray(children).map((child, index) => (
<div key={index}>
{child}
</div>
)), [children]);
return (
<div>
<Button onClick={changeOpacity}>Toggle Opacity</Button>
{state1 && renderChildren()}
</div>
);
};

Call function inside component from different component

I'm using react and I have 2 components. Inside Comp1 I have a function that I want to call inside Comp2. How could I do this?
Code:
const Comp1 = () => {
// Call handleSetSearch in Comp2
const handleSetSearch = () => {
const value = document.querySelector('.ais-SearchBox-input').value;
console.log(value)
};
return (
<>
../
</>
);
}
const Comp2 = (handleSetSearch) => {
return (
<div>
<p onClick={ComparisonSearch}></p>
</div>
)
};
In react, using props to communicate with components is very common. For example, if the relation between Comp 1 and Comp 2 is parent to child, you can acquire the function of handleSetSearch by using props.
Const Comp1 = () => {
const handleSetSearch = () => {
const value = document.querySelector('.ais-SearchBox-input').value;
console.log(value)
};
return (
<>
<Comp2 handleSetSearch={handleSetSearch} />
</>
)
}
Const Comp2 = (props) => {
return (
<div>
<p onClick={props.handleSetSearch}></p>
</div>
)
}
If you want to add parameter in the function of handleSetSearch. Here is a example.
const handleSetSearch = (params) => {
console.log(params)
};
<p onClick={() => props.handleSetSearch('123')}></p>
You should use Comp2 as a child in Comp1 and pass Comp1 function into Comp2 as props.
const Comp1 = () => {
const handleSetSearch = () => {};
return (
<>
<Comp2 handleSetSearch={handleSetSearch} />
</>
);
}
const Comp2 = ({handleSetSearch}) => {
return (
<div>
<p onClick={() => handleSetSearch()}></p>
</div>
)
};

Assigning useRef through render props

Using Render props pattern I wanted to see if there was a way to make this work using the current setup. I have a Parent component that uses an Example component as a wrapper to render some children inside it. I wanted to pass off a ref from inside of Example to one of the children in the render prop. Is this possible ?
const Example = ({ children }) => {
const ref = useRef(null);
const [open, SetOpen] = useState(false);
const [controls] = useCustomAnimation(open, ref);
return (
<div>
{children({ ref })}
</div>
);
};
const Parent = () => {
return (
<div>
<Example>
{ref => {
<motion.div
ref={ref}
>
{console.log('ref= ', ref)}
....... more children
</motion.div>;
}}
</Example>
</div>
);
};
Yes, your current file is almost exactly correct. I setup an example, but here is the gist:
const Example = ({ children }) => {
const ref = useRef(null);
return <div>{children({ ref })}</div>;
};
const Parent = () => {
return (
<div>
<Example>
{({ ref }) => {
console.log(ref);
return <input type="text" ref={ref} />;
}}
</Example>
</div>
);
};
Note: You need to destructure the object you are passing into the children function.

Using React Hooks reference with Class and Function

I've been out of the React game for awhile. Come back and I'm trying to implement the Material UI library which has been rewritten with Hooks.
It seems to be extremely confusing + spagetti code in my eyes.
I simply want to reference a function so I can toggle the drawer, how can I do this?
// Old class
export default class DefaultContainer extends Component<ViewProps, any> {
render() {
return (
<View>
<MainAppBar
onPress={() => this.onMenuPressed()}
/>
{this.props.children}
<MainDrawer
ref={'drawer'}
/>
</View>
);
}
onMenuPressed = () => {
// TODO The bit that isn't working
(this.refs['drawer'] as Drawer).handleToggle()
}
}
Now the new material UI drawer
// New Drawer (3x more code now..)
const useStyles = makeStyles({
list: {
width: 280,
},
fullList: {
width: 'auto',
},
})
type Props = {
}
function MainDrawer(props: Props, ref: any) {
const classes = useStyles();
const [state, setState] = React.useState({
left: false,
});
const toggleDrawer = () => (
event: React.KeyboardEvent | React.MouseEvent,
) => {
if (
event.type === 'keydown' &&
((event as React.KeyboardEvent).key === 'Tab' ||
(event as React.KeyboardEvent).key === 'Shift')
) {
return;
}
setState({ ...state, left: true });
};
const inputRef = useRef();
useImperativeHandle(ref, () => {
toggleDrawer()
});
const sideList = () => (
<div
className={classes.list}
role="presentation"
onClick={toggleDrawer()}
onKeyDown={toggleDrawer()}
>
<List>
<ListItem button key={'drawer_item'}>
<ListItemIcon><GroupIcon /></ListItemIcon>
<ListItemText primary={'Test Item'} />
</ListItem>
</List>
</div>
);
return (
<div>
<Button onClick={toggleDrawer()}>Open Left</Button>
<Drawer open={state.left} onClose={toggleDrawer()}>
{sideList()}
</Drawer>
</div>
);
}
export default forwardRef(MainDrawer);
I'm struggling to understand why you need to invoke a function from inside MainDrawer rather than just leveraging the use of props e.g.
Container
export default function DefaultContainer(props: ViewProps) {
const [drawerOpen, setDrawerOpen] = React.useState(false);
// assuming it's a toggle?
const toggleMenu = React.useCallback(() => setDrawerOpen(open => !open));
return (
<View>
<MainAppBar onPress={toggleMenu} />
{this.props.children}
<MainDrawer open={drawerOpen} />
</View>
)
}
MainDrawer
function MainDrawer(props: Props) {
const [open, setOpen] = React.useState(props.open);
...
const toggleDrawer = React.useCallback(() => setOpen(open => !open));
return (
<div>
<Button onClick={toggleDrawer}>Open Left</Button>
// use prop to determine whether drawer is open or closed
<Drawer open={open} onClose={toggleDrawer}>
{sideList()}
</Drawer>
</div>
);
}

React Hook : Send data from child to parent component

I'm looking for the easiest solution to pass data from a child component to his parent.
I've heard about using Context, pass trough properties or update props, but I don't know which one is the best solution.
I'm building an admin interface, with a PageComponent that contains a ChildComponent with a table where I can select multiple line. I want to send to my parent PageComponent the number of line I've selected in my ChildComponent.
Something like that :
PageComponent :
<div className="App">
<EnhancedTable />
<h2>count 0</h2>
(count should be updated from child)
</div>
ChildComponent :
const EnhancedTable = () => {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Click me {count}
</button>
)
};
I'm sure it's a pretty simple thing to do, I don't want to use redux for that.
A common technique for these situations is to lift the state up to the first common ancestor of all the components that needs to use the state (i.e. the PageComponent in this case) and pass down the state and state-altering functions to the child components as props.
Example
const { useState } = React;
function PageComponent() {
const [count, setCount] = useState(0);
const increment = () => {
setCount(count + 1)
}
return (
<div className="App">
<ChildComponent onClick={increment} count={count} />
<h2>count {count}</h2>
(count should be updated from child)
</div>
);
}
const ChildComponent = ({ onClick, count }) => {
return (
<button onClick={onClick}>
Click me {count}
</button>
)
};
ReactDOM.render(<PageComponent />, document.getElementById("root"));
<script src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="root"></div>
You can create a method in your parent component, pass it to child component and call it from props every time child's state changes, keeping the state in child component.
const EnhancedTable = ({ parentCallback }) => {
const [count, setCount] = useState(0);
return (
<button onClick={() => {
const newValue = count + 1;
setCount(newValue);
parentCallback(newValue);
}}>
Click me {count}
</button>
)
};
class PageComponent extends React.Component {
callback = (count) => {
// do something with value in parent component, like save to state
}
render() {
return (
<div className="App">
<EnhancedTable parentCallback={this.callback} />
<h2>count 0</h2>
(count should be updated from child)
</div>
)
}
}
To make things super simple you can actually share state setters to children and now they have the access to set the state of its parent.
example:
Assume there are 4 components as below,
function App() {
return (
<div className="App">
<GrandParent />
</div>
);
}
const GrandParent = () => {
const [name, setName] = useState("i'm Grand Parent");
return (
<>
<div>{name}</div>
<Parent setName={setName} />
</>
);
};
const Parent = params => {
return (
<>
<button onClick={() => params.setName("i'm from Parent")}>
from Parent
</button>
<Child setName={params.setName} />
</>
);
};
const Child = params => {
return (
<>
<button onClick={() => params.setName("i'm from Child")}>
from Child
</button>
</>
);
};
so grandparent component has the actual state and by sharing the setter method (setName) to parent and child, they get the access to change the state of the grandparent.
you can find the working code in below sandbox,
https://codesandbox.io/embed/async-fire-kl197
IF we Have Parent Class Component and Child function component this is how we going to access child component useStates hooks value :--
class parent extends Component() {
constructor(props){
super(props)
this.ChildComponentRef = React.createRef()
}
render(){
console.log(' check child stateValue: ',
this.ChildComponentRef.current.info);
return (<> <ChildComponent ref={this.ChildComponentRef} /> </>)
}
}
Child Component we would create using
React.forwardRef((props, ref) => (<></>))
. and
useImperativeHandle(ref, createHandle, [deps])
to customizes the instance value that is exposed to parent components
const childComponent = React.forwardRef((props, ref) => {
const [info, setInfo] = useState("")
useEffect(() => {
axios.get("someUrl").then((data)=>setInfo(data))
})
useImperativeHandle(ref, () => {
return {
info: info
}
})
return (<> <h2> Child Component <h2> </>)
})
I had to do this in type script. The object-oriented aspect would need the dev to add this callback method as a field in the interface after inheriting from parent and the type of this prop would be Function. I found this cool!
Here's an another example of how we can pass state directly to the parent.
I modified a component example from react-select library which is a CreatableSelect component. The component was originally developed as class based component, I turned it into a functional component and changed state manipulation algorithm.
import React, {KeyboardEventHandler} from 'react';
import CreatableSelect from 'react-select/creatable';
import { ActionMeta, OnChangeValue } from 'react-select';
const MultiSelectTextInput = (props) => {
const components = {
DropdownIndicator: null,
};
interface Option {
readonly label: string;
readonly value: string;
}
const createOption = (label: string) => ({
label,
value: label,
});
const handleChange = (value: OnChangeValue<Option, true>, actionMeta: ActionMeta<Option>) => {
console.group('Value Changed');
console.log(value);
console.log(`action: ${actionMeta.action}`);
console.groupEnd();
props.setValue(value);
};
const handleInputChange = (inputValue: string) => {
props.setInputValue(inputValue);
};
const handleKeyDown: KeyboardEventHandler<HTMLDivElement> = (event) => {
if (!props.inputValue) return;
switch (event.key) {
case 'Enter':
case 'Tab':
console.group('Value Added');
console.log(props.value);
console.groupEnd();
props.setInputValue('');
props.setValue([...props.value, createOption(props.inputValue)])
event.preventDefault();
}
};
return (
<CreatableSelect
id={props.id}
instanceId={props.id}
className="w-100"
components={components}
inputValue={props.inputValue}
isClearable
isMulti
menuIsOpen={false}
onChange={handleChange}
onInputChange={handleInputChange}
onKeyDown={handleKeyDown}
placeholder="Type something and press enter..."
value={props.value}
/>
);
};
export default MultiSelectTextInput;
I call it from the pages of my next js project like this
import MultiSelectTextInput from "../components/Form/MultiSelect/MultiSelectTextInput";
const NcciLite = () => {
const [value, setValue] = useState<any>([]);
const [inputValue, setInputValue] = useState<any>('');
return (
<React.Fragment>
....
<div className="d-inline-flex col-md-9">
<MultiSelectTextInput
id="codes"
value={value}
setValue={setValue}
inputValue={inputValue}
setInputValue={setInputValue}
/>
</div>
...
</React.Fragment>
);
};
As seen, the component modifies the page's (parent page's) state in which it is called.
I've had to deal with a similar issue, and found another approach, using an object to reference the states between different functions, and in the same file.
import React, { useState } from "react";
let myState = {};
const GrandParent = () => {
const [name, setName] = useState("i'm Grand Parent");
myState.name=name;
myState.setName=setName;
return (
<>
<div>{name}</div>
<Parent />
</>
);
};
export default GrandParent;
const Parent = () => {
return (
<>
<button onClick={() => myState.setName("i'm from Parent")}>
from Parent
</button>
<Child />
</>
);
};
const Child = () => {
return (
<>
<button onClick={() => myState.setName("i'm from Child")}>
from Child
</button>
</>
);
};

Resources