Unable to access child function via ref? - reactjs

Initially everything was working fine,I have a component something like.
this
class A extends React.Component {
constructor(props) {
super(props);
this.childRef = null
}
componentDidMount() {
this.childRef = this.refs.b
// now I can call child function like this
this.childRef.calledByParent()
}
render(){
<B ref = "b"/>
}
}
In other file
class B extends React.Component {
calledByParent(){
console.log("i'm called")
}
render(){
<div> hello </div>
}
}
export default B
till here it was working fine but when I do something like this in class B export default connect(mapStateToProps, mapDispatchToProps)(B)
It is not working. I have imported connect from react-redux

connect() accepts option as the forth parameter. In this option parameter you can set flag withRef to true. After this you can access functions to refs by using getWrappedInstance() like
class A extends React.Component {
constructor(props) {
super(props);
this.childRef = null
}
componentDidMount() {
this.childRef.getWrappedInstance().calledByParent()
}
render(){
<B ref = {ref => this.childRef = ref}/>
}
}
class B extends React.Component {
calledByParent(){
console.log("i'm called")
}
render(){
<div> hello </div>
}
}
export default connect(mapStateToProps, mapDispatchToProps, null, {withRef: true})(B)

Might be a little late but another (better) solution than using refs is to only give control to specific functions of the component.
class A extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
this.ctrl_B.calledByParent()
}
render(){
<B provideCtrl={ctrl => this.ctrl_B = ctrl} />
}
}
class B extends React.Component {
componentDidMount() {
this.props.provideCtrl({
calledByParent: () => this.calledByParent()
});
}
componentWillUnmount() {
this.props.provideCtrl(null);
}
calledByParent(){
console.log("i'm called")
}
render(){
<div> hello </div>
}
}
export default connect(mapStateToProps, mapDispatchToProps)(B)

I had similar problem but I didn't want to make my APIs dependent on getWrappedInstance() calls. In fact some components in your class hierarchy may use connect() and access the store and some others are just stateless components that don't need that additional Redux layer.
I have just written a small (maybe a bit hackish) method. Please note, it hasn't been fully tested yet so expect you may need to make some adjustments to get it working in your own scenario.
TypeScript (should be easy to convert to pure JavaScript syntax):
function exposeWrappedMethods(comp: React.ComponentClass<any>, proto?: any): any {
if (!proto) {
if (comp.prototype.constructor.name === 'Connect') {
// Only Redux component created with connect() is supported
proto = comp.prototype.constructor.WrappedComponent.prototype;
} else {
console.warn('Trying to extend an invalid component.');
return comp;
}
}
let prototypeName: string = proto.constructor.name;
if (prototypeName.search(/^React.*Component.*/) < 0 && proto.__proto__) {
for (let propertyName of Object.getOwnPropertyNames(proto)) {
if (!comp.prototype[propertyName]) {
let type: string = typeof proto[propertyName];
if (type === 'function') {
// It's a regular function
comp.prototype[propertyName] = function (...args: any[]) {
return this.wrappedInstance[propertyName](args);
};
} else if (type === 'undefined') {
// It's a property
Object.defineProperty(comp.prototype, propertyName, {
get: function () {
return (this as any).wrappedInstance[propertyName];
},
set: function (value: any) {
(this as any).wrappedInstance[propertyName] = value;
}
});
}
}
}
return exposeWrappedMethods(comp, proto.__proto__);
}
return comp;
}
Use it by simply wrapping your connect() call with exposeWrappedMethods. It will add all methods and properties from your own class (and subclasses) but will not overwrite already existing methods (i.e. methods from React.Component base class).
export default exposeWrappedMethods(
connect<any, any, Properties>(
(state: ApplicationState) => state.counter,
CounterState.actionCreators,
null,
{ pure: false, withRef: true } // It requires use of "withRef: true"
)(Counter)) as typeof Counter;
Hope you (or someone else) find it useful.
/Lukasz

Related

Setting state of a created but not mounted React component

I have a react component I'm creating as a local variable. I'd like to tweak its state before attaching it to the DOM. A super-simplified version of the code looks like this:
class Demo extends React.Component {
constructor(props) {
super(props);
this.state = {foo: 2};
}
render() {
return <p>{this.state.foo}</p>;
}
}
class App extends React.Component {
render() {
let elem = <Demo/>;
elem.setState({foo:4});
}
}
(The real code has a point, but I'm posting the simplified test case so you don't have to read long irrelevancies)
I'm getting the error
TypeError: elem.setState is not a function
What does this error mean? I'm checked that element is an instance of Demo.
Is there a way to set the state at this time?
ETA: I know what props is. I really want to modify the element after creating it.
You can't setstate like this. If you want to manipulate state in the child component you have to add props to do that.
import React, { Component } from "react";
class Demo extends Component {
state = {
foo: this.props.foo || 0
};
componentDidUpdate(prevState) {
console.log(prevState.foo, this.props.foo);
if (prevState.foo !== this.props.foo) {
this.setState({ foo: this.props.foo });
}
}
render() {
return <button {...this.props}>{this.state.foo}</button>;
}
}
export default class App extends Component {
state = {
count: 0
};
clickMe = () => {
this.setState({
count: this.state.count + 1
});
};
render() {
return <Demo onClick={() => this.clickMe()} foo={this.state.count} />;
}
}
Here is working example https://codesandbox.io/s/festive-rhodes-redk7

Pass dynamic value to HOC in react

I write some HOC and I need to pass to this HOC a dynamic object that I create on some life cycle level and I did not get him as a prop.
If I try to pass some static value ( for example initialize myObj from start) it works as expected and I get the correct value.
Let's say this is my component class :
let myObj = {};
class Test extends React.Component
{
constructor(props) {
super(props);
.....
}
render() {
myObj = {test:'test'};
return ( ... )
}
}
export default withHOC(Test, myObj);
And this is my HOC:
const withHOC = (Component, test) => {
class Hoc extends React.Component
{
constructor(props)
{
super(props);
const s = test; // ---->test is empty object always !!
...
}
}
return Hoc;
}
My 'Dynamic' object that I create on my 'test' class is always empty on my HOC class.
It's happend also when I try to pass some value from my props directly, in this case the page is stuck(without errors in console).
Does someone have any idea how to resolve that? Thanks!
When you compose a component that way, composition only happens at compile time (static composition). This means that withHOC runs only once and is receiving an empty myObj argument, as it is using the one defined on declaration.
export default withHOC(Test, myObj); //myObj = {}
If you want that value to be dynamic, the withHOC composition should be runned when that value changes.
You can't send data up from the WrappedComponent (Test) to the HOC (withHOC), so even if you change myObj value in Test.render, the HOC would never know.
What you could do, if you really need it, is do the composition on the Test.render
render(){
const Hoc = withHOC(this.state.myObj, WrappedComponent);//WrappedComponent can be anything
return(
<Hoc/>
)
}
This way, every time the component renders, Hoc is composed using as myObj a value from the component state, wich is not the preferable way to do it, because this.state.myObj might have the same value as it did at the previous render, and you would be re-composing with the same data as before.
A better way to do it is checking for changes in myObj at Test.componentDidUpdate, and if it did change, then compose Hoc again.
You are passing an empty object to the withHOC function
let myObj = {}; // <- your myObj is empty
class Test extends React.Component
{
constructor(props) {
super(props);
.....
}
render() {
myObj = {test:'test'}; // <- You're doing this in the render method of your Test component, so until the component is rendered myObj is empty
return ( ... )
}
}
export default withHOC(Test, myObj);
Some explanation about what's happening here, by order:
import Comp from '.../Test.js'
the withHOC function is triggered, with the params of Test (which is defined above the call) and myObj (which is defined above the call but is empty)
Test component is returned, and nobody used the logic of myObj = {test:'test'}
Suggested solution:
Make the HOC get the logic from the props with another hoc:
const withProps = newProps => BaseComponent => props => {
const propsToAdd = typeof newProps === 'function' ? newProps(props) : newProps
return <BaseComponent {...props} {...propsToAdd} />
}
Usage:
class Test extends React.Component
{
constructor(props) {
super(props);
.....
}
render() {
return ( ... )
}
}
export default withProps({test:'test'})(withHOC(Test));
// or: export default withProps(props => {test:'test'})(withHOC(Test));
const withHOC = (Component) => {
class Hoc extends React.Component
{
constructor(props)
{
super(props);
const s = this.props.test;
...
}
}
return Hoc;
}
you can use recompose, a library which has many hocs and utils, and for better readability:
import { compose, withProps } from "recompose"
class Test extends React.Component {...}
const enhance = compose(
withProps({test:'test'}),
withHOC
)
export default enhance(Test);
I can't say with confidence this is optimal but I solved a similar problem by having a function within the HOC that updates state that you can then invoke with any data in the wrapped component.
HOC:
func = (a, b) => {
this.setState({
stateA: a,
stateB: b
)}
}
return ({ <WrappedComponent func={this.func} /> })
Wrapped Component:
this.props.func(anythingA, anythingB);
You can then access the data through state in the HOC.
To elaborate:
const withHOC = (WrappedComponent) => {
class withHOC extends React.Component {
constructor(props) {
super(props)
this.state = {
stateA: 1,
stateB: 2
}
*use state however you want in this HOC, including pass it through to another component*
*the following is just a function*
*when it's invoked in the wrapped component state will update here in the
HOC*
changeState = (a, b) => {
this.setState({
stateA: a,
stateB: b
)}
}
render() {
return (
<div>
<p>this.state.stateA</p>
<p>this.state.stateB</p>
<WrappedComponent changeState={this.changeState} />
</div>
)
}
}
}
}
In wrappedComponent, after importing:
class aComponent extends Component {
constructor(props) {
super(props)
this.state = {
}
*you can now invoke the function from the HOC in this wrapped component*
}
}
You can use react-redux and store your object in redux state. Change the object wherever you need (in your case it's in Test) and access it in component inside your HOC from redux state, it'll be always up to date.

Reactjs: Parent function call triggered by a child

So I am building my first react project and stumbled upon following problem:
In my App.js (main application) I got a function and render my components:
class App extends Component {
constructor(props) {
super(props);
this.candidateCounter = 0;
this.setCandidateVote = this.setCandidateVote.bind(this);
}
...
setCounter (name) {
this.candidateCounter++;
console.log(this.candidateCounter);
}
render() {
...
<Candidates setCounter={this.setCounter} />
}
}
The child component Candidates.jsx has another function and thus calls another component:
export class Candidates extends React.Component {
constructor(props) {
super(props);
this.AppProps = props;
}
...
registerVote(name) {
...
this.AppProps.setCounter(name);
}
render() {
...
<MyButton id={this.state.candidates[i].name} register={this.registerVote} />
}
And the last component MyButton.jsx looks like this:
export class MyButton extends React.Component {
constructor(props) {
super();
this.ParentProps = props;
this.state = { active: false }
}
buttonActiveHandler = () => {
this.setState({
active: !this.state.active
});
if (this.state.active === false) {
this.ParentProps.register(this.ParentProps.id);
}
else {
...
}
}
render() {
return (
<Button content='Click here' toggle active={this.state.active} onClick={this.buttonActiveHandler} />
);
}
}
I have successfully debugged that all functions calls are working except when the grandchild MyButton has triggered the registerVote() function in my Candidates module. Logging in this method gets printed but it cannot call this.AppProps.setCounter() from the parent App. I receive the following error:
TypeError: Cannot read property 'setCounter' of undefined
I hope this wasn't too complicated explained, any help is appreciated :)
Simply bind the function in the constructor of the class as #qasimalbaqali stated in his comment.
constructor(props) {
super();
this.registerVote = this.registerVote.bind(this);
}

React/TypeScript: Creating a unified "notification" function that ties to a single component

I am trying to design my app so that all notifications tie in to a single "snackbar" style component (I'm using the material UI snackbar component) that wraps the app:
example
class App extends React.Component {
public render() {
return (
<MySnackbar >
<App />
<MySnackbar />
}
}
truncated example snackbar class:
class MySnackbar extends React.Component<object, State> {
public state = {
currentMessage: { message: "", key: 0},
open: false
};
private messageQueue = [];
public openAlert = (message: string) => {
this.queue.push({ key: new Date().getTime(), message})
if (!this.state.open) {
this.setState({ open: true });
}
}
// other class methods...
public render () {
// render component here...
}
}
I am trying to figure out how I can make it so that I can simply export a function that when called, has access to the "openAlert" function referencing the parent snackbar.
hypothetical child component:
import notificationFunction from "MySnackbar";
class Child extends React.Component {
public notifyUser = () => {
notificationFunction("Hi user!")
}
}
I know there are libraries that do this, but its important for me to understand how they work before I use one. I have seen a few examples using global state (redux, react-context), but I'm looking to avoid using global state for this.
I have tried following some guides on creating HOC patterns, but I can't seem to design something that works how I want this to work. Is what I'm trying to do even technically possible? I know that I could make this work by passing the function down to every child as a prop, but that requires adding a field to every single interface and intermediate component, and is not very DRY.
Stable React's way of doing that is Context (https://reactjs.org/docs/context.html).
interface IContext {
updateMessage: (newMessage: string) => void;
}
interface IProps {}
interface IState {
message: string;
}
const SnackbarContext = React.createContext<IContext>({
updateMessage: () => {},
});
class Snackbar extends React.Component<IProps, Partial<IState>> {
constructor(props) {
super(props);
this.state = {
message: "",
};
this.updateMessage = this.updateMessage.bind(this);
}
render() {
return (
<SnackbarContext.Provider value={{ updateMessage: this.updateMessage }}>
<div>
<strong>MESSAGE:</strong> {this.state.message}
</div>
<hr />
{this.props.children}
</SnackbarContext.Provider>
);
}
updateMessage(newMessage: string) {
this.setState({ message: newMessage });
}
}
class Child extends React.Component {
static contextType: React.Context<IContext> = SnackbarContext;
context: IContext;
constructor(props) {
super(props);
this.onButtonClick = this.onButtonClick.bind(this);
}
render() {
return (
<div>
<button onClick={this.onButtonClick}>Create message</button>
</div>
);
}
onButtonClick() {
this.context.updateMessage(`Message with random number at the end ${Math.random()}`);
}
}
<Snackbar>
<Child />
</Snackbar>
There is also hooks experiment (https://reactjs.org/docs/hooks-intro.html) which may or may not be a future.
I have tried following some guides on creating HOC patterns, but I can't seem to design something
HOC will not work here. Or all JSX.Elements will need to be wrapped in HOC. And it is more easy to pass callback function down the whole element tree instead of using HOCs.

What is right way to design an component class with private properties in react native

Is there any recommended way to have private properties/Variables for the component and update the values in Constructor and use those properties throughout the components
Example:
export default class Workarea extends React.Component {
constructor(props) {
super(props);
MenuProps = this.props.navigation.getParam('MenuProps', {})
}
MenuProps= {};
getCurrentForm = () => {
if(MenuProps.Type == "Type1") {
this.RenderType1();
}
if(MenuProps.Type == "type2") {
this.RenderType2();
}
}
render() {
return (
<View>
{
this.getCurrentForm()
}
</View>
)
}
}
Is this the right way of doing class and property based approach.
Or i should always set the value and props to state please recommend.
I ain't familiar with ReactNative, but es6 classes do not support private properties. The way to achieve them though, is to just declare the variable outside the class, but in the same file, like this:
let _somePrivateProperty='hey';
export default class Workarea extends React.Component {
constructor(props) {
super(props);
MenuProps = this.props.navigation.getParam('MenuProps', {})
}
MenuProps= {};
getCurrentForm = () => {
if(MenuProps.Type == "Type1") {
this.RenderType1();
}
if(MenuProps.Type == "type2") {
this.RenderType2();
}
}
render() {
return (
<div>
<p>{_somePrivateProperty}</p>
<View>
{
this.getCurrentForm()
}
</View>
</div
)
}
}
Hope that this is what you meant.

Resources