react navigation - prop value being used as navigation setOption: paremeter key - javascript

Is it possible to the something as follows:
const MyComponent = (position) => {
useLayoutEffect(() => {
navigation.setOptions({
position: () => <CustomComponent />
});
}, [navigation, position]);
...
};
So the position value being passed to MyComponent will either be: 'headerLeft' or 'headerRight'

Assuming that position is a string containing 'headerLeft' or 'headerRight', you could just create the object and pass it in:
useLayoutEffect(() => {
let opts = { };
opts[position] = () => <CustomComponent />;
navigation.setOptions(opts);
}, [ navigation, position ]);

Related

Passing a function from a react component to its parent then from the parent to another child

I have a component named Products, and it has this function declared in it:
const filterBySearch = (value: string) => {
setAllProducts((prevProducts) => {
const filtered = sourceProducts.filter((product) =>
product.name.toLowerCase().includes(value.toLowerCase())
);
return filtered;
});
};
I also have 2 other components, Navbar, and App, the Navbar contains a search input field, I want things to work in a way that whenever the value of that input inside the Navbar changes, the FilterBySearch function is called with the input value as its argument.
The problem is that Navbar is neither a child of Products nor a parent, but they're both children of the App component.
How do I pass the FilterBySearch from Products to App then from App to Navbar ?
Rather than passing the function you can set up the filterBySearch function inside the App component with the products state and call the function inside the Navbar component inside the change event listener of the input element.
Next, pass the allProducts state to the Products components
const App = () => {
const [allProducts, setAllProducts] = useState([]);
const filterBySearch = (value: string) => {
setAllProducts((prevProducts) => {
const filtered = sourceProducts.filter((product) =>
product.name.toLowerCase().includes(value.toLowerCase())
);
return filtered;
});
};
return (
<Navbar filterBySearch={filterBySearch} />
<Products allProducts={products} />
)
}
const Navbar = ({filterBySearch}) => {
return <input onChange={(e) => filterBySearch(e.target.value)} />
}
const Products = ({allProducts}) => {
//...
}
Or
You can define the function inside the Navbar component and pass the setAllProducts function as a prop to the Navbar component
const App = () => {
const [allProducts, setAllProducts] = useState([]);
return (
<Navbar setAllProducts={setAllProducts} />
<Products allProducts={products} />
)
}
const Navbar = ({setAllProducts}) => {
const filterBySearch = (value: string) => {
setAllProducts((prevProducts) => {
const filtered = sourceProducts.filter((product) =>
product.name.toLowerCase().includes(value.toLowerCase())
);
return filtered;
});
};
return <input onChange={(e) => filterBySearch(e.target.value)} />
}
const Products = ({allProducts}) => {
//...
}

Extend React forwardRef component with methods

I want to create an extended component with some additional functions added.
Let's say I have an ExtendedButton component which has a button that is forwardRef:ed, but which also has a doubleClick method. I know this is a silly example, but something like this:
const ExtendedButton = forwardRef<HTMLButtonElement, React.HTMLAttributes<HTMLButtonElement>>((props, ref) => {
const btnRef = useRef<HTMLButtonElement>(null);
useImperativeHandle(ref, () => btnRef?.current as HTMLButtonElement);
const doubleClick = () => {
btnRef.current?.click();
btnRef.current?.click();
};
return <button {...props} ref={btnRef}></button>;
});
I want to be able to get the doubleClick method, as well as all the methods on the button, from a consumer component like this:
export const Consumer = () => {
const ref = useRef<HTMLButtonElement>(null);
ref.current.doubleClick();
ref.current.click();
return <ExtendedButton ref={ref}></ExtendedButton>;
};
I feel I should probably remove the forwardRef so the ref is pointing to ExtendedButton instead of button, but how can I get the button methods then?
Thanks!
useImperativeHandle should expose all the methods you want to access:
type ExtendedButtonType = HTMLButtonElement & { doubleClick: () => void }
const ExtendedButton = forwardRef<ExtendedButtonType, React.HTMLAttributes<HTMLButtonElement>>(
(props, ref) => {
const btnRef = useRef<HTMLButtonElement>(null)
const doubleClick = (): void => {
btnRef.current?.click()
btnRef.current?.click()
}
useImperativeHandle(
ref,
() =>
({
...btnRef.current,
doubleClick,
} as ExtendedButtonType),
)
return <button {...props} ref={btnRef} />
},
)
export const Consumer: FC = () => {
const ref = useRef<ExtendedButtonType>(null)
ref.current?.doubleClick()
ref.current?.click()
return <ExtendedButton ref={ref} />
}
add the method inside the useImperativeHandle
const ExtendedButton = forwardRef<HTMLButtonElement, React.HTMLAttributes<HTMLButtonElement>>((props, ref) => {
const btnRef = useRef<HTMLButtonElement>( );
useImperativeHandle(ref, () => ({
...btnRef.current,
doubleClick: () => {
btnRef.current?.click();
btnRef.current?.click();
};
}));
return <button {...props} ref={btnRef}></button>;
});

What is the different between useImperativeHandle and useRef?

As I understand, useImperativeHandle helps parent component able to call function of its children component. You can see a simple example below
const Parent = () => {
const ref = useRef(null);
const onClick = () => ref.current.focus();
return <>
<button onClick={onClick} />
<FancyInput ref={ref} />
</>
}
function FancyInput(props, ref) {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
}
}));
return <input ref={inputRef} />;
}
FancyInput = forwardRef(FancyInput);
but it can be easy achieved by using only useRef
const Parent = () => {
const ref = useRef({});
const onClick = () => ref.current.focus();
return <>
<button onClick={onClick} />
<FancyInput ref={ref} />
</>
}
function FancyInput(props, ref) {
const inputRef = useRef();
useEffect(() => {
ref.current.focus = inputRef.current.focus
}, [])
return <input ref={inputRef} />;
}
FancyInput = forwardRef(FancyInput);
So what is the true goal of useImperativeHandle. Can someone give me some advices?. Thank you
Probably something similar to the relationship between useMemo and useCallback where useCallback(fn, deps) is equivalent to useMemo(() => fn, deps). Sometimes there is more than one way to accomplish a goal.
I'd say in the case of useImperativeHandle the code can be a bit more succinct/DRY when you need to expose out more than an single property.
Examples:
function FancyInput(props, ref) {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => inputRef.current.focus(),
property,
anotherProperty,
... etc ...
}), []); // use appropriate dependencies
...
}
vs
function FancyInput(props, ref) {
const inputRef = useRef();
useEffect(() => {
ref.current.focus = inputRef.current.focus;
ref.current.property = property;
ref.current.anotherProperty = anotherProperty;
... etc ...
}, []); // use appropriate dependencies
...
}
Not a big difference, but the useImperativeHandle is less code.
it can be easy achieved by using only useRef
No, you need at least another useEffect or probably better useLayoutEffect?
And even then it does a teeny tiny bit more than your code.
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
}
}));
is more likely equivalent to:
// using a function.
// no need to create this object over and over if there is no `ref`,
// or no need to update the `ref`.
const createRef = () => ({
focus: () => {
inputRef.current.focus();
}
});
useLayoutEffect(() => {
// refs can be functions!
if (typeof ref === "function") {
ref(createRef());
// when the ref changes, the old one is updated to `null`.
// Same on unmount.
return () => {
ref(null);
}
}
// and the same thing again for ref-objects
if (typeof ref === "object" && ref !== null && "current" in ref) {
ref.current = createRef();
return () => {
ref.current = null;
}
}
}, [ref]);

How to access history prop from a parent component using react?

i have a method showInfo that is accessed by two components Child1 and Child2.
initially i had the showInfo method within Child1 component like below
const Child1 = ({history}: RouteComponentProps) => {
type Item = {
id: string,
name: string,
value: string,
};
const showInfo = (item: item) => {
const id = item.id;
const value = item.value;
const handleRouteChange = () => {
const path = value === 'value1' ? `/value1/?item=${itemId}` : `/value2/?item=${itemId}`;
history.push(path);
}
return (
<Button onClick={handleRouteChange}> Info </Button>
);
}
return (
<SomeComponent
onDone = {({ item }) => {
notify({
actions: showInfo(item)
})
}}
/>
);
}
the above code works. but now i have another component child2 that needs to use the same method showInfo.
the component child2 is like below
const Child2 = () => {
return (
<Component
onDone = {({ item }) => {
notify({
actions: showInfo(item)
})
}}
/>
);
}
Instead of writing the same method showInfo in Child2 component i thought of having it in different file from where child1 and child2 components can share the method showInfo.
below is the file with name utils.tsx that has showInfo method
export const showInfo = (item: item) => {
const id = item.id;
const value = item.value;
const handleRouteChange = () => {
const path = value === 'value1' ? `/value1/?item=${itemId}` :
`/value2/?item=${itemId}`;
history.push(path); //error here push is not identified as no
//history passed
}
return (
<Button onClick={handleRouteChange}> Info </Button>
);
}
return (
<SomeComponent
onDone = {({ item }) => {
notify({
actions: showInfo(item)
})
}}
/>
);
}
With the above, i get the error where i use history.push in showInfo method. push not defined.
this is because history is not defined in this file utils.tsx
now the question is how can i pass history from child1 or child2 components. or what is the other way that i can access history in this case.
could someone help me with this. thanks.
EDIT:
notify in child1 and child2 is coming from useNotifications which is like below
const useNotifications = () => {
const [activeNotifications, setActiveNotifications] = React.useContext(
NotificationsContext
);
const notify = React.useCallback(
(notifications) => {
const flatNotifications = flatten([notifications]);
setActiveNotifications(activeNotifications => [
...activeNotifications,
...flatNotifications.map(notification => ({
id: notification.id,
...notification,
});
},
[setActiveNotifications]
)
return notify;
}
While you could potentially create a custom hook, your function returns JSX. There's a discussion about returning JSX within custom hooks here. IMO, this is not so much a util or custom hook scenario, as it is a reusable component. Which is fine!
export const ShowInfo = (item: item) => { // capitalize
const history = useHistory(); // use useHistory
// ... the rest of your code
}
Now in Child1 and Child2:
return (
<SomeComponent
onDone = {({ item }) => {
notify({
actions: <ShowHistory item={item} />
})
}}
/>
);
You may have to adjust some code in terms of your onDone property and notify function, but this pattern gives you the reusable behavior you're looking for.

How to pass string parameter to nested function, which has already parameters?

This is how my Categories react functional component looks like. For easier testing, I split up the handleClick and the react component itself - but that shouldn't be an issue for this question.
How do I pass the component string value from the map() to the handleClick()? handleClick already passes some operator parameter, which gets me struggling with this simple issue...
export const useCategories = () => {
const handleClick = (operator) => {
updateCategory({
variables: {
id: '123',
operator,
category // <-- this value is missing
}
})
}
return {
icon: {
onClick: handleClick('$pull') // <-- Here I add some operator value
}
}
}
export const Categories = () => {
const { icon } = useCategories()
return (
<div>
{categories.map((category) => <Icon onClick={icon.onClick} />)} {/* <-- how to pass category value to handleClick...? */}
</div>
)
}
In order to "add" a variable, you can use currying.
Hence, return a function that receives the new argument and use it internally:
export const useCategories = () => {
return (category) => {
const handleClick = (operator) => {
updateCategory({
variables: {
id: '123',
operator,
category // now this is the argument of the wrapper function
}
})
};
return () => handleClick('$pull');
};
}
And you can use it like this:
export const Categories = () => {
const getOnClick = useCategories();
return (
<div>
{categories.map((category) => {
const onClick = getOnClick(category);
return <Icon onClick={onClick} />;
})}
</div>
)
}

Categories

Resources