When I work with JS I tend to whip out a console for the browser and manipulate values on the fly.
I have a page where I use React to render some components and I had the idea that it would be great to be able to manipulate it's state from the console to debug a design quirk which is only visible if the component is in a corner-case state.
I ran into problem that I was unable to get hold of a reference to my component.
I figured there might be a list of active components currently being rendered somewhere, but I was not able to find one on the React global object or anywhere else.
Is there an exposed reference to the components being rendered?
I'm rendering the component like:
<script>React.render(React.createElement(Comp, domElem))</script>
I could store a reference to the result of React.createElement() but it seems to be an antipattern. Also I'm using the ReactJS.NET library to handle server-side rendering for me so the whole React.render line is generated and is hard to modify.
My other idea was to create a mixin that makes the component explicitly expose itself on mount, like:
var ActiveComponents = [];
var debugMixin = {
componentDidMount: function () {
var id = this.getDOMNode().id;
ActiveComponents[id] = {
id: id,
getState: () => { return this.state; },
setState: (state) => { this.setState(state); },
comp: this
};
}
};
Are there drawbacks for an approach like this? Is this the same antipattern mentioned above?
Although being much cleaner than entangling these test hooks in the component code directly, adding a mixin is still a modification, and I would like to avoid that if possible.
The questions I hope to get answers for are bolded.
A workaround for this is to assign your object to the window object:
window.myStateObject = myStateObject
and then you can inspect it in the console:
window.myStateObject
There is a ReactJS extension for Chrome that may meet your needs https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi
If that isn't good enough, React keeps track of all the mounted components in a private variable instancesByReactID. If you just want to access these for debugging, you could modify the React code and expose that variable as a global.
Related
I am using a 3rd party library to generate a sortable grid that expects to be fed JSON data, is there a way to send part of the redux store to this non-react grid other than as a react component, attribute or innerHtml since it does expect JSON? The challenges are that all my redux scripts are modules which is a scoping issue since, in this case, the 3rd party element is wrapped in asp.net so I can't expose it to a function (though it can call a javascript function to get the data), and that I need to be able to subscribe to changes, this has proven unnecessarily difficult so far.
You can just call store.getState() to get the current state of the store.
You may want to attach the store on to the window to give it global scope when you create it,
window.redux = { store }
or if using modern js, create a wrapper export function in the file that you create it
export const getStore = () => store
So that you can then do
getStore().getState()
If I understand your question correctly, you are using some third-party element in a react app that takes data in the form of JSON? Without more details on the third-party library its hard to give a proper answer but I'll give it a shot.
Whatever this library is, you have to render it somehow at some point. Since we don't know what you are using I am going to pick jQuery.
Assume the following usage
$("#grid").initializeGrid(data);
You can expect the react component to be something like
const GridWrapper = (props) => {
const tableData = useSelector(selectTableJson);
useEffect(() => {
$("#grid").initializeGrid(data);
}, [tableData]);
return (
<div id="grid" />
);
}
Basically what is happening is you select the data from the redux store, and then initialize the third party component with that data.
I have a component that has complex rendering logic.
I try to carry out this logic to helper classes, for simplifying.
To do this, in the data section (for reactivity), I create class references as follows:
export default {
data: () => ({
state: new InitialState(this),
query: new QueryController(this)
})
}
As I understand it, at this point the context of this is not yet defined.
So, I have two questions.
1) Is there a way to pass the this component context in the data section (without lifecycle hooks)?
2) Is the approach with references to external classes of vuejs philosophy contrary?
Component instance is already available when data function runs, this is one of reasons why it has been forced to be a function.
Due to how lexical this works with arrow functions, it's incorrect to use them to access dynamic this. It should be:
data() {
return {
state: new InitialState(this),
query: new QueryController(this)
};
})
The problem with InitialState(this) is that the entire component instance is passed instead of relevant data, this breaks the principle of least privilege.
Despite Vue isn't focused on OOP, there's nothing wrong with using classes. One of possible pitfalls is that classes may not play well with Vue reactivity because it puts restrictions on the implementation. Another pitfall is that classes cannot be serialized to JSON and back without additional measures, this introduces limitations to how application state can be handled.
As I understand it, at this point the context of this is not yet defined.
Only because of the way you've written the code. The component instance does exist and is available. It is sometimes used to access the values of props for determining the initial values of data properties.
For example, here is an example from the documentation:
https://v2.vuejs.org/v2/guide/components-props.html#One-Way-Data-Flow
export default {
props: ['initialCounter'],
data: function () {
return {
counter: this.initialCounter
}
}
}
The reason why your code doesn't work is because you are using an arrow function. If you change it to the following then this will be available:
export default {
data () {
return {
state: new InitialState(this),
query: new QueryController(this)
}
}
}
See also the note here:
https://v2.vuejs.org/v2/api/#data
Note that if you use an arrow function with the data property, this won’t be the component’s instance, but you can still access the instance as the function’s first argument
As to your other question about whether using classes like this is contrary to Vue...
I don't think the use of classes like this is encouraged but they can be made to work so long as you understand the limitations. If you have a clear understanding of how Vue reactivity works, especially the rewriting of properties, then it is possible to write classes like this and for them to work fine. The key is to ensure that any properties you want to be reactive are exposed as properties of the object so Vue can rewrite them.
If you don't need reactivity on these objects then don't put them in data. You'd be better off just creating properties within the created hook instead so the reactivity system doesn't waste time trying to add reactivity to them. So long as they are properties of the instance they will still be accessible in your templates, there's nothing special about using data from that perspective.
I think computed is a better way to do what you want
export default {
computed:{
state(){
return new InitialState(this);
},
query(){
return new QueryController(this);
}
}
}
I've been trying out React Hooks and they do seem to simplify things like storing state. However, they seem to do a lot of things by magic and I can't find a good article about how they actually work.
The first thing that seems to be magic is how calling a function like useState() causes a re-render of your functional component each time you call the setXXX method it returns?
How does something like useEffect() fake a componentDidMount when functional components don't even have the ability to run code on Mount/Unmount?
How does useContext() actually get access to the context and how does it even know which component is calling it?
And that doesn't even begin to cover all of the 3rd party hooks that are already springing up like useDataLoader which allows you to use the following...
const { data, error, loading, retry } = useDataLoader(getData, id)
How do data, error, loading and retry re-render your component when they change?
Sorry, lots of questions but I guess most of them can be summed up in one question, which is:
How does the function behind the hook actually get access to the functional/stateless component that is calling it so that it can remember things between re-renders and initiate a re-render with new data?
React hook makes use of hidden state of a component, it's stored inside a fiber, a fiber is an entity that corresponds to component instance (in a broader sense, because functional components don't create instances as class components).
It's React renderer that gives a hook the access to respective context, state, etc. and incidentally, it's React renderer that calls component function. So it can associate component instance with hook functions that are called inside of component function.
This snippet explains how it works:
let currentlyRenderedCompInstance;
const compStates = new Map(); // maps component instances to their states
const compInstances = new Map(); // maps component functions to instances
function useState(initialState) {
if (!compStates.has(currentlyRenderedCompInstance))
compStates.set(currentlyRenderedCompInstance, initialState);
return [
compStates.get(currentlyRenderedCompInstance) // state
val => compStates.set(currentlyRenderedCompInstance, val) // state setter
];
}
function render(comp, props) {
const compInstanceToken = Symbol('Renderer token for ' + comp.name);
if (!compInstances.has(comp))
compInstances.set(comp, new Set());
compInstances.get(comp).add(compInstanceToken);
currentlyRenderedCompInstance = compInstanceToken;
return {
instance: compInstanceToken,
children: comp(props)
};
}
Similarly to how useState can access currently rendered component instance token through currentlyRenderedCompInstance, other built-in hooks can do this as well and maintain state for this component instance.
Dan Abramov created a blog post just a couple days ago that covers this:
https://overreacted.io/how-does-setstate-know-what-to-do/
The second half specifically goes into details regarding hooks like useState.
For those interested in a deep dive into some of the implementation details, I have a related answer here: How do react hooks determine the component that they are for?
I would recommend reading https://eliav2.github.io/how-react-hooks-work/
It includes detailed explanations about what is going on when using react hooks and demonstrate it with many interactive examples.
Note - the article does not explain in technical terms how React schedule calls for later phases, but rather demonstrates what are the rules that react uses to schedule calls for later phases.
The URL in another answer given by Eliav Louski is so far the best React explaination I have come across. This page should replace React's official tutorial as it removes all the magic from hooks and friends.
I have a global data object I update and then I call React.renderComponent() again on the top/main component.
Is this the right pattern for triggering this update?
You should generally pass the data object into the component as a prop, even if it's a global variable. This lets you test the component, and also use it elsewhere without being tied to that global.
As Mike said, there's nothing wrong with using React.renderComponent to update it.
He also mentioned flux, but that's overkill for this. A simple event emitter where you do something like .emit('change', newData), and the component is listening for the change event tends to be better for simpler cases. See my answer to this question for an example of how that can be done.
This is the correct pattern. React.renderComponent will either mount a component for the first time, or get an already mounted component to update.
If you're using a global object though, you might want to look in to the Flux architecture here:
http://facebook.github.io/flux/docs/overview.html
I had the same problem and asked myself if I really needed to re-render the component.
You can do so with this.forceUpdate() but it's not advisable. As React docs states:
You should try to avoid all uses of forceUpdate() and only read from this.props and this.state in render(). This makes your component "pure" and your application much simpler and more efficient.
So what I did was create a data property like exists and test it:
// renderDeleteButton() is being called on render()
renderDeleteButton () {
if (!this.props.store.exists) {
return;
}
return(
<DeleteButton
...
deleteAction={this.delete} />
);
}
Whenever I delete/save, I toggle exists and component will show up or hide based on that. React handles that for me.
I'd like to know if there is a way to get a component by using some type of id, or by type, similar as you would do in DOM manipulation. Something like:
var Avatar = React.createClass({
render: function () {
...
}
});
React.renderComponent(Avatar({id:'avatar'}), ...);
...
...
var avatar = React.getComponentById('avatar');
avatar.setProps({url = 'http://...'});
// or
var avatars = React.getComponentByType('Avatar');
if (avatars.length) {
avatars[0].setProps({url = 'http://...'});
}
I don't want to keep references of components instances...
setProps is something that you should use sparingly. In fact storing references to "rendered" components in general might indicate that you can structure your code differently. We also limit your uses of setProps to top level components.
var avatar = React.renderComponent(<Avatar .../>, node);
avatar.setProps({ foo: '1' });
is equivalent to this, which fits in a bit better with the declarative model:
React.renderComponent(<Avatar .../>, node);
React.renderComponent(<Avatar ... foo="1" />, node);
You could wrap that render up inside a function call so you could call it at will.
Sorry, there's no (publicly exposed) global registry of mounted React components. If you need to send messages to a component after mounting it, the best way is to save a reference to it. Iterating through the list of all Avatar components seems like the wrong solution to me anyway because it wrecks the composability aspect of components, where each parent component can specify its child's props and trust that outside forces won't change them -- changing this makes your page harder to reason about.
If you provide a jsfiddle of what you're trying to do, perhaps I can be of more help.