I m trying to use React Context API to pass state data from a Parent component to a deep nested Child.
The parent is a class component and the child is a function.
When from the child i update the parent state with a function passed through the Context the parent state is updated successfully but then the local state of the child is reset to the initial value.
Parent:
export class ObjectLinking extends Component {
constructor(props) {
super(props);
this.state = {
setCurrentlyDisplayed: this.setCurrentlyDisplayed,
currentlyDisplayed: []
};
}
[...]
render() {
return (
<ObjectLinkingContext.Provider value={this.state}>
{//Panel body will contain deep nested child that is connected to context}
<PanelBody />
</ObjectLinkingContext.Provider>
);
}
}
Child:
import ObjectLinkingContext from '../../context/ObjectLinkingContext';
const AssetListFilters = ({ assets, filtersModel }) => {
const [searchByNameVal, setSearchByNameVal] = useState([]);
const panelContext = useContext(ObjectLinkingContext);
useEffect(() => {
filterBySearchNameVal();
}, [searchByNameVal]);
const filterBySearchNameVal = () => {
if (searchByNameVal.length) {
const { setCurrentlyDisplayed } = panelContext;
const { value: searchedId } = searchByNameVal[0];
const searchedAsset = assets.filter(asset => asset.assetId === searchedId) || [];
setCurrentlyDisplayed(searchedAsset);
}
};
return (
<Autocomplete
onChange={val => setSearchByNameVal(val)}
/>
);
};
Find here the full Parent.js and VeryDeepChild.js component
Related
I want to create a HOC that have a event trigger when a certain key is pressed. When this key is pressed it should provide an event to the parent component. In this case, the key is "#".
Child HOC
import React, { Component } from 'react';
const withMention = WrappedComponent => {
return class extends Component {
state = {
mentionStart: false,
textInput: '',
selection: 0,
};
handleOnKeyPress = key => {
if (key === '#') {
this.setState({ mentionStart: true });
}
};
render() {
const { onMentionStart } = this.state;
return (
<WrappedComponent
onChangeText={text => {
this.setState({ textInput: text });
}}
onKeyPress={event => this.handleOnKeyPress(event.nativeEvent.key)}
onSelectionChange={event =>
this.setState({ selection: event.nativeEvent.selection })
}
onMentionStart={onMentionStart}
{...this.props}
/>
);
}
};
};
export default withMention;
Parent component
const UserComment = withMention(TextInput);
<UserComment onMentionStart={(event) => console.log(event)} />
I know the implementation is wrong, because whenever I assign a function to onMentionStart prop of child component in parent, child's function is overridden by parent. In this case, how to create a custom event trigger from child component and pass event into it so that the parent can use it accordingly?
I actually solved it by removing onMentionStart prop from HOC and passed onMentionStart function from parent to child as a callback, handled it in onKeyPress handler function.
import React, { Component } from 'react';
const withMention = WrappedComponent => {
return class extends Component {
state = {
mentionStart: false,
textInput: '',
selection: 0,
};
handleOnKeyPress = key => {
if (key === '#') {
this.setState({ mentionStart: true }, () =>
this.props.onMentionStart(this.state.mentionStart),
);
}
};
render() {
return (
<WrappedComponent
onChangeText={text => {
this.setState({ textInput: text });
}}
onKeyPress={event => this.handleOnKeyPress(event.nativeEvent.key)}
onSelectionChange={event =>
this.setState({ selection: event.nativeEvent.selection })
}
{...this.props}
/>
);
}
};
};
export default withMention;
I have attached an event listener to the parent element to listen for a non-synthetic-event and I wonder if there is a nice way to get reference to the component which triggers the event to use it's properties
I need to postpone the rendering of item.component until the nonSyntheticEvent occurs
const items = [
{
name: "click me",
component: function First() {
return <strong>asd</strong>;
}
},
{
name: "click me 2",
component: function Second() {
return <b>oasd</b>;
}
}
];
class Component extends React.Component {
componentDidMount() {
this.el.addEventListener("nonSyntheticEvent", event =>
this.nonSyntheticEventHandler(event)
);
}
nonSyntheticEventHandler(event) {
// how to get reference to item
// from event.target to render it's item.component
const el = React.createElement(item.component);
ReactDOM.render(el, event.target);
}
render() {
return (
<div ref={ref => { this.el = ref; }}>
{this.props.items.map(item => <Child {...item} />)}
</div>
);
}
}
<Component items={items} />
With React 16.3 React.createRef() is introduced which can be used in Component to create reference before the Child component is rendered.
for example in Component.constructor a reference to each child can be created in the state
this.state = {
items: items.map(item => ({
...item,
reference: React.createRef()
}))
};
and then in the Child component can be used from props:
function Child(props){
return (
<div ref={props.reference}>
<span>{props.name}</span>
</div>
);
}
and then in the nonSyntheticEventHandler the item can be obtained like so:
const found = this.state.items.find(item => {
return item.reference.current === event.target;
});
working example in Codesandbox.io
I have this constructor in my parent component
constructor (props) {
super(props)
this.state = {
owner,
name: '',
projectToAdd: this.props.defaultProject,
}
}
And the property projectToAdd is set to the value passed by props, I need to change this property of the state with the following function located in my child component:
handleProjectSelection = (project) => () => {
this.setState({
projectToAdd: project.get('id')
})
}
This function is called when I click an element of a <li> tag in my child component:
renderDropdown () {
const { projects } = this.props
const projectsList = projects.map((project) => (
<li className='u-cursor--pointer u-padding-tiny u-font-size--12px'
key={project.get('id')}
onClick={this.handleProjectSelection(project)} >
{project.get('name')}
</li>
))
return (
<div>
<ul className='c-folder-dropdown'
name='projectList'
form='start-retro-form'>
{projectsList}
</ul>
</div>
)
}
How can I change the state from the child component to the parent component?
Pass a change handler function prop from your parent to the child.
class Parent{
handleChange = () => {
this.setState({
foo: 'bar'
})
}
render(){
return <Child onChange={this.handleChange} {...this.state}/>
}
}
I need to always intercept when React unmounts a Component, no matter if that is a Functional or Class based component.
Here is my case:
function observe(component) {
const p = component.type.prototype;
const delegate = p.componentWillUnmount || function noop() {};
if(!delegate.__decorated) {
p.componentWillUnmount = function() {
console.log('I am going to be unmounted');
return delegate.apply(this, arguments);
}
p.componentWillUnmount.__decorated = true;
}
return component;
}
class Comp extends React.Component {
render() {
return (<h1>Hello World</h1>);
}
}
class App extends React.Component {
render() {
const active = this.state && this.state.active;
const toggle = () => this.setState({
active: !active,
});
return (
<div>
<button onClick={toggle}>Toggle</button>
<hr />
{active && observe(<Comp />)}
</div>
);
}
}
Now, as you can easily see, I am able to hook on every time <Comp /> gets unmounted. That is just what I need.
Things will dramatically change when that <Comp /> is a functional component:
function observe(component) {
const p = component.type.prototype;
const delegate = p.componentWillUnmount || function noop() {};
if(!delegate.__decorated) {
p.componentWillUnmount = function() {
console.log('I am going to be unmounted');
return delegate.apply(this, arguments);
}
p.componentWillUnmount.__decorated = true;
}
return component;
}
function Comp() {
return (<h1>Hello World</h1>);
}
class App extends React.Component {
render() {
const active = this.state && this.state.active;
const toggle = () => this.setState({
active: !active,
});
return (
<div>
<button onClick={toggle}>Toggle</button>
<hr />
{active && observe(<Comp />)}
</div>
);
}
}
So, my question is:
How can I hook on functional components?
I can change approach (or use React internal Apis), I just need to always intercept changes on a component passed as arguments for observe.
You can't. Functional components don't have lifecycles (yet).
Instead of messing with the functional component directly, why don't you just wrap the functional component in a class with a HOC. You could use recompose toClass for this.
function observe(component) => {
const classComponent = toClass(component):
const p = classComponent.type.prototype;
const delegate = p.componentWillUnmount || function noop() {};
if(!delegate.__decorated) {
p.componentWillUnmount = function() {
console.log('I am going to be unmounted');
return delegate.apply(this, arguments);
}
p.componentWillUnmount.__decorated = true;
}
return classComponent;
}
Or just copy the code from here.
So I am trying to use the ref callback pattern explained here: https://facebook.github.io/react/docs/refs-and-the-dom.html, but continue to get undefined in the Parent's componentDidMount method
const Child = ({ createFormHeight, getChildRef }) => (
<CreateForm createFormHeight={createFormHeight}>
// getting reference from here
<div ref={getChildRef}>
// form markup here
</div>
</CreateForm>
);
class Parent extends Component {
constructor(props) {
super(props);
this.state = {
createFormHeight: '',
}
}
componentDidMount() {
const createFormHeight = this.formWrapper.offsetHeight; // returns
error that this.formWrapper is undefined
this.setFormHeight(createFormHeight);
}
getChildRef = (el) => {
// if I console.log(el) it returns the div here
this.formWrapper = el;
}
setFormHeight = (height) => {
this.setState(() => ({ createFormHeight: height }));
};
render() {
const { createIsOpen, createFormHeight } = this.state;
return (
<CreateMember createFormHeight={createFormHeight} getChildRef=
{this.getChildRef} />
);
}
}
I guess the problem is you didn't mount your getChildRef to the context. Try add this.getChildRef = tihs.getChildRef.bind(this) to the constructor of Parent.