Given a parent component renders the following:
const handleButtonClick = (e) => {
//code omitted
}
<ChildComponent
handleButtonClick={handleButtonClick}
/>
And the child component which adds this handler to a button:
const ChildComponent = (props) => {
const handleButtonClick = props.handleButtonClick
return (
<button onClick={handleButtonClick}>Click me!</button>
)
}
How would I transform handleButtonClick in the parent in a Rxjs Stream?
I have thought of one way, which is creating a subject:
const clickStream = new Subject()
const handleButtonClick = (e) => {
clickStream.next(e)
}
I would also be interested in how to do that in a scenario where the parent renders multiple, dynamic children, e.g.:
return(
<>
{
children.map((child, index) => {
<ChildComponent handleButtonClick={handleButtonClick}/>
})
}
</>
)
Related
I have a child component that has some states which change in value when you check or un-check some boxes; I want to be able to pass those state values to the parent component. I've tried a few things on the internet but nothing seem to work.
I've tried to (Parent Component File):
const ParentComponent = () => {
const [ data, setData ] = useState();
const childToParent = (childData) => {
setData(childData);
};
return(
<Child childToParent={childToParent} />
);
}
and on the Child Component:
const ChildComponent = ({ childToParent }) => {
const [ childData, setChildData ] = useState(false);
const handleChange = () => {
setChildData(!childData);
childToParent(childData);
};
return(
<div>
<Checkbox value={childData} callback{() => handleChange()} />
</div>
);
}
Basically the problem is I dont get how to move data from a child component to a parent component.
I have a React HOC that hides a flyover/popup/dropdown, whenever I click outside the referenced component. It works perfectly fine when using local state. My HOC looks like this:
export default function withClickOutside(WrappedComponent) {
const Component = props => {
const [open, setOpen] = useState(false);
const ref = useRef();
useEffect(() => {
const handleClickOutside = event => {
if (ref?.current && !ref.current.contains(event.target)) {
setOpen(false);
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => setOpen(false);
}, [ref]);
return <WrappedComponent open={open} setOpen={setOpen} ref={ref} {...props} />;
};
return Component;
}
When in use I just wrap up the required component with the HOC function
const TestComponent = () => {
const ref = useRef();
return <Wrapper ref={ref} />;
}
export default withClickOutside(TestComponent);
But I have some flyover containers that are managed from Redux when they are shown, or when they are hidden. When the flyover is shown, I want to have the same behavior, by clicking outside the referenced component to hide it right away. Here's a example of a flyover:
const { leftFlyoverOpen } = useSelector(({ toggles }) => toggles);
return (
<div>
<Wrapper>
<LeftFlyoverToggle
onClick={() => dispatch({ type: 'LEFT_FLYOVER_OPEN' })}
>
...
</Wrapper>
{leftFlyoverOpen && <LeftFlyover />}
{rightFlyoverOpen && <RightFlyover />}
</div>
);
Flyover component looks pretty straightforward:
const LefFlyover = () => {
return <div>...</div>;
};
export default LefFlyover;
Question: How can I modify the above HOC to handle Redux based flyovers/popup/dropdown?
Ideally I would like to handle both ways in one HOC, but it's fine if the examples will be only for Redux solution
You have a few options here. Personally, I don't like to use HOC's anymore. Especially in combination with functional components.
One possible solution would be to create a generic useOnClickOutside hook which accepts a callback. This enables you to dispatch an action by using the useDispatch hook inside the component.
export default function useOnClickOutside(callback) {
const [element, setElement] = useState(null);
useEffect(() => {
const handleClickOutside = event => {
if (element && !element.contains(event.target)) {
callback();
}
};
if (element) {
document.addEventListener('mousedown', handleClickOutside);
}
return () => document.removeEventListener('mousedown', handleClickOutside);
}, [element, callback]);
return setElement;
}
function LeftFlyOver() {
const { leftFlyoverOpen } = useSelector(({ toggles }) => toggles);
const dispatch = useDispatch();
const setElement = useOnClickOutside(() => {
dispatch({ type: 'LEFT_FLYOVER_CLOSE' });
});
return (
<Dialog open={leftFlyoverOpen} ref={ref => setElement(ref)}>
...
</Dialog>
)
}
I am trying to convert a class component to a function component and struggling with assigning the refs to each rendered item in a Flatlist.
This is the original class component.
...
constructor(props) {
super(props);
this.cellRefs = {};
}
....
_renderItem = ({ item }) => {
return (
<Item
ref={ref => {
this.cellRefs[item.id] = ref;
}}
{...item}
/>
);
};
...
Assuming both your Item and the component rendering the FlatList need to be functional components, you need to take care of 2 things
Add dynamic refs to each Item component
Make sure that the Item component uses useImperativeHandle with forwardRef to expose functions
const App = () => {
const cellRefs = useRef({}) // Adding an object as we need more than one ref
const _renderItem = ({ item }) => {
return (
<Item
ref={ref => {
cellRefs.current[item.id] = ref;
}}
{...item}
/>
);
};
....
}
Post that you need to change your Item component like
const Item = React.forwardRef((props, ref) => {
...
const handleClick = () => {};
useImperativeHandle(ref, () => ({
// values that need to accessible by component using ref, Ex
handleClick,
}))
...
})
P.S. If Item is not a functional component, you can avoid the second step
do something like that (from react doc)
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
// `current` points to the mounted text input element
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
const Child = (props) => {
const [val, setVal] = useState(props.val);
const handleCreate = (newData) => new Promise((resolve, reject) => {
setTimeout(() => {
{
const transactions = JSON.parse(JSON.stringify(tableData));
const clean_transaction = getCleanTransaction(newData);
const db_transaction = convertToDbInterface(clean_transaction);
transactions.push(clean_transaction);
// The below code makes post-request to 2 APIs synchronously and conditionally updates the child-state if calls are successful.
**categoryPostRequest(clean_transaction)
.then(category_res => {
console.log('cat-add-res:', category_res);
transactionPostRequest(clean_transaction)
.then(transaction_res => {
addToast('Added successfully', { appearance: 'success'});
**setVal(transactions)**
}).catch(tr_err => {
addToast(tr_err.message, {appearance: 'error'});
})
}).catch(category_err => {
console.log(category_err);
addToast(category_err.message, {appearance: 'error'})
});**
}
resolve()
}, 1000)
});
return (
<MaterialTable
title={props.title}
data={val}
editable={{
onRowAdd: handleCreate
}}
/>
);
}
const Parent = (props) => {
// some other stuff to generate val
return (
<Child val={val}/>
);
}
I am struggling to achieve this:
I'd like to move the post-request part of the function in handleCreate (bold-section), over to the Parent-component that can be called by the Child-class.
The idea is to make the Component abstract and re-usable by other similar Parent-classes.
Create the function in the parent, and pass it to the child in the props:
const Parent = (props) => {
// The handler
const onCreate = /*...*/;
// some other stuff
return (
<Child ...props onCreate={onCreate}/>
);
}
Then have the child call the function with any parameters it needs (there don't seem to be any in your example, you're not using val in it for instance):
return (
<MaterialTable
title={props.title}
data={val}
editable={{
onRowAdd: props.onCreate // Or `onRowAdd: () => props.onCreate(parameters, go, here)`
}}
/>
);
Side note: There's no reason to copy props.val to val state member within the child component, just use props.val.
Side note 2: Destructuring is often handy with props:
const Child = ({val, onCreate}) => {
// ...
};
Side note 3: You have your Parent component calling Child with all of its props, via ...props:
return (
<Child ...props onCreate={onCreate}/>
);
That's generally not best. Only pass Child what it actually needs, in this case, val and onCreate:
return (
<Child val={props.val} onCreate={onCreate}/>
);
I have a set of buttons in a child component where when clicked set a corresponding state value true or false. I have a useEffect hook in this child component also with dependencies on all these state values so if a button is clicked, this hook then calls setFilter which is passed down as a prop from the parent...
const Filter = ({ setFilter }) => {
const [cycling, setCycling] = useState(true);
const [diy, setDiy] = useState(true);
useEffect(() => {
setFilter({
cycling: cycling,
diy: diy
});
}, [cycling, diy]);
return (
<Fragment>
<Row>
<Col>
<Button block onClick={() => setCycling(!cycling)}>cycling</Button>
</Col>
<Col>
<Button block onClick={() => setdIY(!DIY)}>DIY</Button>
</Col>
</Row>
</Fragment>
);
};
In the parent component I display a list of items. I have two effects in the parent, one which does an initial load of items and then one which fires whenever the filter is changed. I have removed most of the code for brevity but I think the ussue I am having boils down to the fact that on render of my ItemDashboard the filter is being called twice. How can I stop this happening or is there another way I should be looking at this.
const ItemDashboard = () => {
const [filter, setFilter] = useState(null);
useEffect(() => {
console.log('on mount');
}, []);
useEffect(() => {
console.log('filter');
}, [filter]);
return (
<Container>..
<Filter setFilter={setFilter} />
</Container>
);
}
I'm guessing, you're looking for the way to lift state up to common parent.
In order to do that, you may bind event handlers of child components (passed as props) to desired callbacks within their common parent.
The following live-demo demonstrates the concept:
const { render } = ReactDOM,
{ useState } = React
const hobbies = ['cycling', 'DIY', 'hiking']
const ChildList = ({list}) => (
<ul>
{list.map((li,key) => <li {...{key}}>{li}</li>)}
</ul>
)
const ChildFilter = ({onFilter, visibleLabels}) => (
<div>
{
hobbies.map((hobby,key) => (
<label {...{key}}>{hobby}
<input
type="checkbox"
value={hobby}
checked={visibleLabels.includes(hobby)}
onChange={({target:{value,checked}}) => onFilter(value, checked)}
/>
</label>))
}
</div>
)
const Parent = () => {
const [visibleHobbies, setVisibleHobbies] = useState(hobbies),
onChangeVisibility = (hobby,visible) => {
!visible ?
setVisibleHobbies(visibleHobbies.filter(h => h != hobby)) :
setVisibleHobbies([...visibleHobbies, hobby])
}
return (
<div>
<ChildList list={visibleHobbies} />
<ChildFilter onFilter={onChangeVisibility} visibleLabels={visibleHobbies} />
</div>
)
}
render (
<Parent />,
document.getElementById('root')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script><div id="root"></div>
Yes, you can, useEffect in child component which depends on the state is also how you typically implement a component which is controlled & uncontrolled:
const NOOP = () => {};
// Filter
const Child = ({ onChange = NOOP }) => {
const [counter, setCounter] = useState(0);
useEffect(() => {
onChange(counter);
}, [counter, onChange]);
const onClick = () => setCounter(c => c + 1);
return (
<div>
<div>{counter}</div>
<button onClick={onClick}>Increase</button>
</div>
);
};
// ItemDashboard
const Parent = () => {
const [value, setState] = useState(null);
useEffect(() => {
console.log(value);
}, [value]);
return <Child onChange={setState} />;
};