Can't reach refs of react component - reactjs

I created canvas element with ref="canvas" property.
When i try to get them in componentDidMount:
componentDidMount = () => {
console.log(this.refs);
}
There is empty Object.
BUT, then i do
componentDidMount = () => {
console.log(this);
}
I see React element with non-empty 'refs' Object containing my canvas!
How can this happen?

I think the problem is that you are setting the value of componentDidMount to be a function that will evaluate in order to set the result of componentDidMount. At the time, this.refs isn't populated because the component is firing that function pre-render. However, by just logging this, the console will eventually pick up the updated component so you can see refs inside of it. Here is how you should structure the component:
componentDidMount() {
console.log(this.refs)
}
Does that make sense? You're doing the equivilent of this...
class Foo {
bar = () => {
console.log('I get called when foo loads to determine the value of bar')
}
}
instead of this
class Foo {
bar() {
console.log('I get called with bar()')
}
}
I'm not able to actually test my code as this syntax is invalid, so I'm not sure how it's working for you...

Related

Where is the best place to make calculations outside the render method in React?

I have a render method in my container component like this:
render() {
const { validationErrors } = this.state
const { errorsText, errorsFields} = validationErrors.reduce(
(acc, error) => {
acc.errorsText.push(error.text)
acc.errorsFields[error.field.toLowerCase()] = true
return acc
},
{
errorsText: [],
errorsFields: {},
},
)
return (
<MyViewComponent
errorsText={errorsText}
errorsFields={errorsFields}
/>
)
}
As you can see every render there are some computations happens (returned array and object with the new values), then I pass it into my child component as a props. I have a feeling that this is a wrong pattern. We should keep render function 'pure'. Isn't it? The question is: Where is the best place for making such computations outside the render?
If this were a functional component (which I highly recommend you use in the future, by the way), you'd be able to use the 'hook' useEffect to recalculate errorsText and errorsField whenever this.state.validationErrors changes, and only when it changes.
For your Class Component, however, I assume at some point you set this.state.validationErrors. What you should do is create a method that runs your reducer and stores errorsText and errorsField to state, then place a call to this method after each point you set this.state.validationErrors. Then, remove the logic in the render method and replace errorsText and errorsField with this.state.errorsText and this.state.errorsField respectively.
Doing this will ensure you only ever run your reducer when necessary (i.e. when this.state.validationErrors changes).
Your component would end up looking something like this:
class MyComponent extends Component {
...
someCallback() {
const validationErrors = someFunctionThatReturnsErrors();
// We do the logic here, because we know that validationErrors
// could have changed value
const { errorsText, errorsFields } = validationErrors.reduce(
(acc, error) => {
acc.errorsText.push(error.text);
acc.errorsFields[error.field.toLowerCase()] = true;
return acc;
}, {
errorsText: [],
errorsFields: {},
},
);
// Put everything in the state
this.setState({
validationErrors, // you may not even need to set this if it's not used elsewhere`
errorsText,
errorsFields
});
}
...
render() {
const {
errorsText,
errorsFields
} = this.state;
return (
<MyViewComponent
errorsText={errorsText}
errorsFields={errorsFields}
/>
);
}
}
It is pure, as it has no side effects.
As long as this does not create performance issues I see no problem with this. If it does create performance issues, you should look into memoizing the reduce. If you were using hooks you could use the built-in React.useMemo for this. While using class version you could look into something like https://www.npmjs.com/package/memoize-one

How do I call a function only when a React property changes?

I wish to show a modal dialog box (such as an alert()) every time a Meteor subscription, tracked in React with withTracker, changes.
I have tried using Tracker.autorun to track changes but cannot work out where in the code to place it. It does not seem to work in the Component constructor and runs every time if placed in render().
This is an outline of what my code looks like:
class Foo extends Component {
render() {
return (
<h1>Example Header</h1>
{ this.maybeShowAlert() }
);
}
maybeShowAlert() {
// ONLY if bar has been updated
alert('bar has changed');
}
}
export default withTracker(() => {
Meteor.subscribe('bar')
return {
bar: Bar.findOne({})
};
})(Foo);
Haven't used Meteor before, but if you want to do things in response to state/prop changes then componentDidUpdate() is the lifecycle method for it. E.g.
componentDidUpdate(prevProps) {
if (this.props.bar !== prevProps.bar {
// bar prop has changed
alert("bar changed);
}
}
If you're going to use Tracker.autorun, then the best place to call that is in componentDidMount, because it's called only once after the component has been mounted. You only need to call the tracker function once since the tracker function will rerun whenever the reactive data sources that it depends on ever changes. In the tracker function is where you will call maybeShowAlert depending on the value of bar like so,
componentDidMount() {
Tracker.autorun(() => {
let bar = this.props.bar;
if (bar) {
this.maybeShowAlert();
}
}
}

Where to move setState() callback from deprecated componentWillReceiveProps()?

React 16 deprecates componentWillReceiveProps() lifecycle method. The preferred replacement is new getDerivedStateFromProps() or componentDidUpdate(). Now let's suppose I have some code like that:
componentWillReceiveProps(newProps) {
if (this.props.foo !== newProps.foo) {
this.setState({foo: newProps.foo}, () => this.handleNewFoo()});
}
}
I could try moving the code to getDerivedStateFromProps() like that:
getDerivedStateFromProps(newProps, prevState) {
if (this.props.foo !== newProps.foo) {
return {foo: newProps.foo};
} else {
return null;
}
}
but what do I do with the callback from setState()? Is moving it to componentDidUpdate the only option? I'd rather have this.handleNewFoo() invoked before render() to avoid visual and performance cost of two renders.
If you haven't read this blog post you should
https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html#when-to-use-derived-state
If you are simply assigning props.foo to state.foo without any calculation / processing then you need to ask yourself should you really need that field in state. Because you can always use this.props.foo instead of this.state.foo.
what is this.handleNewFoo method does?
are you doing another setState inside handleNewFoo? if you are then you are triggering multiple render calls.
eg.
this.setState({foo: this.props.foo} // one render call, () => this.handleNewFoo())
const handleNewFoo = () => {
this.setState({ }) // another render call
}
If you are not doing any setState inside handleNewFoo then ask yourself does handleNewFoo has to be called on prop change?
If you are using setState inside the handleFoo i would suggest something like this
getDerivedStateFromProps(newProps, prevState) {
if (this.props.foo !== newProps.foo) {
return this.handleNewFoo(this.props.foo, prevState);
} else {
return prevState;
}
}
handleNewFoo(foo, state) {
// do some calculation using foo
const derivedFoo = state.foo + foo;
// if your are using immutability_helper library.
const derivedState = update(state, { foo: {$set: derivedFoo}});
return derivedState
}
It all depends on what you are doing inside this.handleNewFoo
EDIT:
Above code won't work as #Darko mentioned getDerivedStateFromProps is static method and you cannot access this inside it. And you cannot get the previous props inside getDerivedStateFromPropslink. So this answer is completely wrong.
one solution is to use useEffect
useEffect(() => {
setState(...)
}, [props.foo])
this useEffect will call whenever the props.foo is changed. And also the component will be rendered twice one for prop change and another for setState inside useEffect. Just be careful with setState inside useEffect as it can cause infinite loop.

Dispatched and re-rendered callback?

It's easiest to explain what I'm trying to accomplish with an example:
addContact = ev => {
ev.preventDefault();
this.props.setField('contacts', contacts => update(contacts, {$push: [{name: 'NEW_CONTACT'}]}));
this.props.setFocus(`contacts.${this.props.data.contacts.length-1}.name`);
};
In this example, this.props.setField dispatches an action which causes an extra field to be added to my form.
this.props.setFocus then attempts to focus this new field.
This won't work because the form hasn't re-rendered yet when setFocus is called.
Is there any way to get a callback for when my component has been re-rendered after a dispatch call?
If you need to see it, setField looks like this:
setField(name, value) {
if(_.isFunction(value)) {
let prevValue = _.get(data, name);
if(prevValue === undefined) {
let field = form.fields.get(name);
if(field) {
prevValue = field.props.defaultValue;
}
}
value = value(prevValue);
}
dispatch(actions.change(form.id, name, value));
},
I would put
this.props.setFocus(`contacts.${this.props.data.contacts.length-1}.name`);
in componentDidUpdate and I would call it on some condition. Like let's say, prevProps.data.contact.length < this.props.data.contacts.
UPDATE
You should keep this:
addContact = ev => {
ev.preventDefault();
this.props.setField('contacts', contacts => update(contacts, {$push: [{name: 'NEW_CONTACT'}]}));
};
In a parent component, and in that component you will render all the sub components:
render() {
return {
<div>
{contacts.map(c => <ContactComponent key='blah' contact={c}>)}
<a onClick={addContact}>Add Contact</a>
</div>
};
}
Then your contact component, will be as you like, the same goes for all the other elements you want to accommodate with this functionality.
At that point, you're asking:
Where is the focus thingy?
What you need for this abstraction-ish is higher order composition. I will give you an example, but please make time to read about HOCs.
This will be you HOC:
function withAutoFocusOnCreation(WrappedComponent) {
// ...and returns another component...
return class extends React.Component {
componentDidMount() {
// contacts string below can be changed to be handled dynamically according to the wrappedComponent's type
// just keep in mind you have access to all the props of the wrapped component
this.props.setFocus(`contacts.${this.props.data.contacts.length-1}.name`);
}
render() {
return <WrappedComponent {...this.props} />;
}
};
}
And then in each child component you can use it as a decorator or just call it with your HOC and that's all. I won't write more, but do make the time to read more about HOCs, here is the official documentation's page
official documentation's page. But you can check Dan Abramov's video on egghead as well. I hope my answer helps you, please accept it if it does :) Take care!

Refs in dynamic components with ReactJS

I'm facing a issue and I haven't found any documentantion related.
This is my component's render method:
render()
{
return (
<div refs="myContainer">
</div>
);
}
Additionally I have a method to get data from my Java server:
getAsyncData()
{
$.get('...URL...')
.done(function(response) {
var list = JSON.parse(response); // This is an objects array
var elementsList = [];
list.forEach(function(item) {
elementsList.push(<div ref={item.id}>{item.name}</div>);
});
React.render(elementsList, React.findDOMNode(this.refs.myContainer));
});
}
In my componentDidMount method I just start the async method:
componentDidMount()
{
this.getAsyncData();
}
So I'm getting this error from ReactJS:
Only a ReactOwner can have refs. This usually means that you're trying
to add a ref to a component that doesn't have an owner (that is, was
not created inside of another component's render method). Try
rendering this component inside of a new top-level component which
will hold the ref.
So this means that I'm not able to use my dynamic elements, additionally think that instead of a simple DIV I would have a complex component with methods within.
How can I deal this?
Thank you!
How can I deal this?
By writing the component how it is supposed to. Keep the data in the state. .render() is called when the state changes, updating the output.
Example:
var Component = React.createClass({
getInitialeState() {
return {items: []};
},
getAsyncData() {
$.get(...).done(
response => this.setState({items: JSON.parse(response)})
);
},
render() {
var list = this.state.items.map(
item => <div key={item.id} ref={item.id}>{item.name}</div>
);
return <div>{list}</div>;
}
});
That's what React is all about: Describing the output based on the data. You are not adding or removing elements dynamically, you update the state, rerender and let React figure out how reconcile the DOM.
However, it's unclear to me why you need a ref in this situation.
I recommend to read https://facebook.github.io/react/docs/thinking-in-react.html

Resources