I'm trying to do the following, currently I have 3 components:
Parent.js:
class Parent extends Component {
applyFilters = () => {console.log("applying original filters")}
setApplyFilters = (callback) => {
this.applyFilters = () => callback();
}
render(){
return(
<div>
<Filters applyFilters={this.applyFilters} />
<Screen1 setApplyFilters={this.setApplyFilters} />
</div>
)
}
}
Filters.js:
class Filters extends Component {
onButtonPress = () => {
this.props.applyFilters(),
}
render(){
...
}
}
Screen1.js:
class Screen1 extends Component {
applyFilter = () => {
console.log("Applying filters using the callback function in Screen1");
}
componentDidMount = () => {
this.props.setApplyFilters(() => this.applyFilters());
}
render(){
...
}
}
I have a Filters component that is common to all the screens. I have multiple screens of the type Screen1.
I want to on componentDidMount of the current screen pass the applyFilter function as a callback to the parent, and from the parent pass the applyFilter as a prop to the Filter component. When the onButtonPressed handler of the filter is called, it should execute the applyFilter callback for the mounted screen.
But for some reason it just prints to the console "applying original filters" which is the original string, as if the function is not being overwritten with the callback from the screen.
How do I do this correctly?
The this.applyFilters is resolved early during the render process to () => {console.log("applying original filters")}
<Filters applyFilters={this.applyFilters} />
In this scenario you only care for its value at the time that the function it references is invoked, so you want to set it lazily.
<Filters applyFilters={() => this.applyFilters()} />
This way when applyFilters invoked in Filters component, the value that is referenced in this.applyFilters is resolved and then invoked.
After changing applyFilters on the parent, rerender it i.e. with forceUpdate to make Filter component receive the changed function.
class Parent extends Component {
applyFilters = () => {console.log("applying original filters")}
setApplyFilters = (callback) => {
this.applyFilters = () => callback();
this.forceUpdate();
}
render(){
return(
<div>
<Filters applyFilters={this.applyFilters} />
<Screen1 setApplyFilters={this.setApplyFilters} />
</div>
)
}
}
Related
I was wondering if it were possible to call a child's function inside the parent?
For example, can I use useRef to create a reference to the child and then call the function like that? Or is there another way?
// Parent
const init = () => {
const childRef = useRef();
childRef.current.child();
}
// Child
const init = () => {
function child() {
}
}
So you need to use useImperativeHandle hook and wrap the child in forwardRef Hoc. useImperativeHandle gives access to functions which are written inside it to the parent component.
const Child = forwardRef((props,ref)=>{
useImperativeHandle(ref, () => ({
test: () => {
//do something
}
}));
});
in parent
const childRef= useRef();
<Child ref={childRef} />
Execute test function
childRef.current.test();
By using ref you can point it out to a function too,
parent component,
export default function App() {
const childFnRef = useRef(null);
const callChildFn = () => {
if (childFnRef?.current) {
childFnRef.current("valueOne", "valueTwo", "valueThree");
}
}
return (
<div>
<h1>Parent component</h1>
<ChildComponent ref={childFnRef} />
<button onClick={callChildFn}>call child fn</button>
</div>
);
}
child component
//Ref forwarding is an opt-in feature that lets some components take a ref they receive,
//and pass it further down (in other words, “forward” it) to a child.
const ChildComponent = forwardRef((props, ref) => {
const childFn = (paramOne, paramTwo, paramThree) => {
console.log("calling child fn... params are: ", paramOne, paramTwo, paramThree);
};
ref.current = childFn;
return (
<h2>child component</h2>
);
})
demo
When you use useRef you must have current after function. Like this
const childRef = useRef();
childRef.current.child();
An example in document: https://reactjs.org/docs/hooks-reference.html#useref
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>
</>
);
}
Yes, but only if the child component is class component.
Because functional component does not have an instance.
Let's say your child component is a class-based then you just need to have a createRef() inside parent and then pass that to the child.
Then, you can collect that instance from .current property of the ref and then trigger it.
Example,
Parent Component's constructor
this.childRef = React.createRef();
Parent Component's render
render() {
return <ChildComponent ref={this.childRef} />
}
Child Component
class ChildComponent extends React.Component {
constructor(props) {
super(props);
}
doSomeThing = () => {}
And then at any event handler, you can execute child function as this.childRef.current.doSomeThing();
Reference: https://reactjs.org/docs/refs-and-the-dom.html#accessing-refs
May be someone looking for functional component.
Here is heck without forwardRef and imperative Handler.
In Parent create child Ref like this,
const childRef = useRef()
In Child give ref passed from parent =>
ChildComponent ref={childRef} />
And finally in Parent use like this,
childRef.current.goToIndex({index:1)}
I am currently experiencing an issue when it comes to wrapping the child elements of a parent element in a higher order component and then rendering them.
Consider the following structure:
return (
<div className="App">
<FocusProvider>
<TestComponent testProp={'Foo'}/>
<TestComponent testProp={'Foo'}/>
<TestComponent testProp={'Foo'}/>
</FocusProvider>
</div>
);
Where FocusProvider is the parent element and TestComponent is the child element that needs to be wrapped in a higher order component that provides lifecycle methods to it as well as inject props.
And then the higher order component called hoc which overrides the prop for TestComponent and provides a lifecycle method to it as well looks like:
const hoc = (WrappedComponent, prop) => {
return class extends React.Component {
shouldComponentUpdate = (prevProps, prop) => {
return !prevProps === prop
}
render(){
return <WrappedComponent testProp={prop}/>
}
}
}
The render method of FocusProvider looks like :
render(){
return(
this.props.children.map(child => {
let Elem = hoc(child, 'bar')
return <Elem/>
})
)
}
When I try and render that I get Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: object.
When I try and change it to :
render(){
return(
this.props.children.map(child => {
let elem = hoc(child, 'bar')
return elem
})
)
}
Nothing is returned from render. I am confused because I can render the chil components directly, but not the child components wrapped in the HOC:
render(){
return(
this.props.children.map(child => {
return child //This works
})
)
}
I want to avoid using React.cloneElement as I don't want to trigger re-renders by cloning the child elements every time the parent updates.
Any help would be appreciated
hoc is a function which returns a Component not jsx. You cannot wrap the children in a HOC like that.
But you can wrap just FocusProvider and pass the prop down to it's children using cloneElement. There is no problem in use cloneElement like this. Is a common pattern actually.
function App() {
return (
<div className="App">
<FocusProvider bar="baz">
<Child />
<Child />
<Child />
</FocusProvider>
</div>
);
}
const withHOC = Component => props => {
return <Component foo="bar" {...props} />;
};
const FocusProvider = withHOC(({ children, foo, bar }) => {
return React.Children.map(children, child => {
return React.cloneElement(child, { foo, bar });
});
});
const Child = ({ foo, bar }) => (
<>
{foo}
{bar}
</>
);
How do I make sure I set a value in the context provider before components are mounted?
In the code example below, the console.log in the child component(Dashboard) will be logged first (as undefined). Why is that and is there any way for me to make sure the value is set before that component is mounted?
App.js
render() {
return (
<div className='App'>
<ContextProvider>
<Dashboard />
</ContextProvider>
</div>
);
}
ContextProvider.js
componentDidMount = () => {
this.setState({value: 1})
console.log(this.state.value);
}
Dashboard.js
componentDidMount = () => {
console.log(this.context.value);
}
Children are rendered first. Regardless of that, setState is asynchronous, so a context will be provided to consumers asynchronously.
In case there's a necessity for children to wait for a context, they should be either conditionally rendered:
render() {
this.context.value && ...
}
Or be wrapped with context consumer which can be written as a HOC for reuse:
const withFoo = Comp => props => (
<FooContext.Consumer>{foo => foo.value && <Comp {...props}/>}</FooContext.Consumer>
);
I have a wrapper component that creates a context consumer and passes the context value as a prop to a handler component. When the parent of the wrapper component updates, it's causing my handler component to remount instead of just update.
const Wrapper = forwardRef((props, ref) => {
class ContextHandler extends Component {
componentDidMount() {
// handle the context as a side effect
}
render() {
const { data, children } = this.props;
return (
<div ref={ref} {...data}>{children}</div>
);
}
}
return (
<Context.Consumer>
{
context => (
<ContextHandler
data={props}
context={context}
>
{props.children}
</ContextHandler>
)
}
</Context.Consumer>
);
});
I put the wrapper inside a parent component:
class Parent extends Component {
state = {
toggle: false
}
updateMe = () => {
this.setState(({toggle}) => ({toggle: !toggle}))
}
render() {
const { children, data } = this.props;
return (
<Wrapper
onClick={this.updateMe}
{...data}
ref={me => this.node = me}
>
{children}
</Wrapper>
)
}
}
When I click on the Wrapper and cause an update in Parent, the ContextHandler component remounts, which causes its state to reset. It should just update/reconcile and maintain state.
What am I doing wrong here?
Your ContextHandler class is implemented within the render function of the Wrapper component which means that an entirely new instance will be created on each render. To fix your issue, pull the implementation of ContextHandler out of the render function for Wrapper.
I'm trying to call my method onModelSelect in render()
If I call it this.onModelSelect(model.id)) I then get the error Warning: setState(...): Cannot update during an existing state transition (such as withinrender). Render methods should be a pure function of props and state. However it does output to console.log
So I am trying to call it like I would with an event handler ()=>this.onModelSelect(model.id)) but this doesn't output anything to console.log
what is the correct syntax to use to call my method??
export default class App extends Component {
render() {
onModelSelect = (modelId) => {
this.props.selectModel(modelId);
this.props.setModelSelected(true);
console.log('test')
console.log('modelId',modelId)
}
return(
<div>
{this.props.models.length === 1 && this.props.models.map(model => ()=>this.onModelSelect(model.id))}
</div>
)
}
}
You shouldn't call anything containing this.setState from your render method.
Actually, your this.onModelSelect(model.id) would return Undefined function error because you have to define onModalSelect function in your component not in your render() function
export default class App extends Component {
onModelSelect = (modelId) => {
this.props.selectModel(modelId);
this.props.setModelSelected(true);
console.log('test')
console.log('modelId',modelId)
}
render() {
return(
<div>
{this.props.models.length === 1 && this.props.models.map(model => ()=>this.onModelSelect(model.id))}
</div>
)
}
}
Try putting your onModelSelect method outside the render method
There is a issue when you write like this:
.map(model => ()=>this.onModelSelect(model.id))
This says that .map callback returns function which in turn needs to be called to execute your function. Instead it should be:
.map(model => this.onModelSelect(model.id))
Also .map callback should return some value which in turn will be appended to array and will be rendered inside element.
You should create onModelSelect method outside render function:
export default class App extends Component {
onModelSelect = (modelId) => {
this.props.selectModel(modelId);
this.props.setModelSelected(true);
console.log('test')
console.log('modelId',modelId)
}
render() {
return(
<div>
{this.props.models.length === 1 && this.props.models.map(model => this.onModelSelect(model.id))}
</div>
)
}
}
Or if there is a legit use case and you want to put it in render function call it directly and not with this. Consider below code:
export default class App extends Component {
render() {
let onModelSelect = (modelId) => {
this.props.selectModel(modelId);
this.props.setModelSelected(true);
console.log('test')
console.log('modelId',modelId)
};
return(
<div>
{this.props.models.length === 1 && this.props.models.map(model => onModelSelect(model.id))}
</div>
)
}
}