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>
)
}
Related
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}) => {
//...
}
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 ]);
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.
Faced a problem with the navigate() in Gatsy function, using the navigate function, you can pass an object as a second parameter to another page. I did as in the documentation, but when getting data I get undefined.
const Panel = () => {
const [name, setName] = useState('')
useEffect (() => {
if (name !== undefined) {
setName('Hello World')
}
}, [name])
const handleRedirect = () => {
navigate('/cabinet/', { state: { name }})
}
return (
<div>
<button onClick={handleRedirect}>Redirect</button>
<div>
)
}
const Cabinet = ({location}) => {
console.log(location.state.name) // undefined
return (
<div>
<h1>{location.state.name}</h1>
</div>
)
}
Your problem is a matter of timings/asynchronously. Your navigate built-in function looks good, however, you are setting the name (with your setName setter) after the navigation occurs. In addition, you are missing the key for the state. Try something like:
const handleRedirect = () => {
if(name) navigate('/cabinet', {state: {'name': name}}); // Try hardcoding Hello World for debugging purposes {'name'; 'Hello World'}
}
Then in your Cabinet component:
const Cabinet = ({location}) => {
console.log(location.state.name) // will be your Hello World
return (
<div>
<h1>{location.state.name}</h1>
</div>
)
}
Alternatively, you can do an async/await approach, something like:
const setNameFunction = ()=>{
setName('Hello World')
return name
}
const handleRedirect = async () => {
let nameFromFunction= await setNameFunction;
navigate('/cabinet/', { state: { 'name': nameFromFunction }}) // use if(nameFromFunction) navigate('/cabinet/', { state: { name: nameFromFunction }}) alternatively
}
This second approach will make the ecosystem dynamic, allowing you to pass a custom name based on some logic that you will need to add (passing the name as a parameter through those functions).
Alternatively, you can look for your state in your Cabinet component using window object in window.history.state.
I am trying to render some data using 'ListItem' component in ReactJS. But the component is not getting the data. I also tried to load the data without using 'ListItem' component. In that case it is successfully rendering.
So, How can i load the data using 'ListItem' component?
import React, { Component } from 'react';
function ListItem(props) {
console.log(props);
return <li>{props.value}</li>;
}
class NumberList extends Component {
constructor(props) {
super(props);
}
render() {
const numbers = this.props.numbers;
const listItems = numbers.map( (number) => {
<ListItem key={number.toString()} value={number} />
});
console.log(listItems);
return(
<ul>
{
// numbers.map( n => {
// return <li>{n}</li>
// } )
listItems
}
</ul>
);
}
}
export default NumberList;
You are returning undefined in your map-function.
Try changing to this:
const listItems = numbers.map( (number) => {
return <ListItem key={number.toString()} value={number} />
});
or:
// By omitting the {} around the callback function body
// you can utilise the implicit-returning feature of arrow functions.
const listItems = numbers.map( (number) => (
<ListItem key={number.toString()} value={number} />
));
// The above example is the same as:
const listItems = numbers.map( (number) => <ListItem key={number.toString()} value={number} />);
You are using the arrow function notation inside the map. When you use it with braces ({ and }), you should add a return to the statement you wish to return.
const add = (a, b) => {
return a + b;
};
When your function has just one statement, you can omit the braces and place only the statement at the right side of the arrow, like this:
const add = (a, b) => a + b;
But if you add the braces, but does not write a return statement, then the function will return undefined. So, this would return undefined:
const add = (a, b) => {
const sum = a + b;
};
You have to return your listitem component from map. You are returning li items in your commented code. But looks like you missed adding return when converting it to use ListItem.