In my React component, I need to read data from the DOM when it's available for later parts of my application to work. I need to persist the data into state, as well as send it through Flux by dispatching an action. Is there a best practice for doing this?
To be more specific, I need the data from the DOM in at least two cases:
Visually immediate, as in the first thing the user sees should be "correct," which involves calculations based on data read from the DOM.
Data used at a later time (such as the user moving the mouse), but based on the initial DOM state.
Please consider the following:
I need to read from the DOM and save the data. This DOM is available in componentDidMount, however I cannot dispatch actions in componentDidMount because this will case a dispatch during a dispatch error. Dispatching in component*mount is an anti-pattern in React for this reason.
The usual workaround (hack) for the above is to put the dispatch inside componentDidMount in a setTimeout( ..., 0 ) call. I want to avoid this as it seems purely like a hack to bypass Flux's errors. If there truly is no better answer I will accept it, but reluctantly.
Don't answer this question with don't read from the DOM. That's not related to what I'm asking. I know this couples my app to the DOM, and I know it's not desired. Again, the question I'm asking is unrelated to whether or not I should be using this method.
Here is the pattern I use in reflux.
export default class AppCtrl extends AppCtrlRender {
constructor() {
super();
this.state = getAllState();
}
componentDidMount = () => {
// console.log('AppCtrl componentDidMount');
this.unsubscribeApp = AppStore.listen(this.appStoreDidChange);
this.unsubscribeGS = GenusSpeciesStore.listen(this.gsStoreDidChange);
Actions.setWindowDefaults(window);
}
componentWillUnmount = () => { this.unsubscribeApp(); this.unsubscribeGS(); }
appStoreDidChange = () => { this.setState(getAppState()); }
gsStoreDidChange = () => { this.setState(getGsState()); }
shouldComponentUpdate = (nextProps, nextState) => {
return nextState.appStoreChanged && nextState.gsStoreChanged;
}
}
In app.store.js
function _windowDefaults(window) {
setHoverValues();
let deviceTyped = 'mobile';
let navPlatform = window.navigator.platform;
switch (navPlatform) {
case 'MacIntel':
case 'Linux x86_64':
case 'Win32': deviceTyped = 'desktop'; break;
}
//deviceTyped = 'mobile';
_appState.deviceType = deviceTyped;
_appState.smallMobile = (deviceTyped == 'mobile' && window.screen.width < 541);
//_appState.smallMobile = (deviceTyped == 'mobile');
if (deviceTyped == 'desktop') {
let theHeight = Math.floor(((window.innerHeight - 95) * .67));
// console.log('_windowDefaults theHeight: ', theHeight);
_appState.commentMaxWidth = theHeight;
_appState.is4k = (window.screen.width > 2560);
Actions.apiGetPicList();
} else {
React.initializeTouchEvents(true);
_bodyStyle = {
color: "white",
height: '100%',
margin: '0',
overflow: 'hidden'
};
AppStore.trigger();
}
}
Related
I am trying to find an item from a collection, from the code below, in order to update my react component, the propertState object isnt empty, it contains a list which i have console logged, however I seem to get an underfined object when i console log the value returned from my findProperty function... I am trying update my localState with that value so that my component can render the right data.
const PropertyComponent = () => {
const { propertyId } = useParams();
const propertyState: IPropertiesState = useSelector(
propertiesStateSelector
);
const[property, setProperty] = useState()
const findProperty = (propertyId, properties) => {
let propertyReturn;
for (var i=0; i < properties.length; i++) {
if (properties[i].propertyId === propertyId) {
propertyToReturn = properties[i];
break;
}
}
setProperty(propertyReturn)
return propertyReturn;
}
const foundProperty = findProperty(propertyId, propertyState.properties);
return (<>{property.propertyName}</>)
}
export default PropertyComponent
There are a few things that you shall consider when you are finding data and updating states based on external sources of data --useParams--
I will try to explain the solution by dividing your code in small pieces
const PropertyComponent = () => {
const { propertyId } = useParams();
Piece A: Consider that useParams is a hook connected to the router, that means that you component might be reactive and will change every time that a param changes in the URL. Your param might be undefined or an string depending if the param is present in your URL
const propertyState: IPropertiesState = useSelector(
propertiesStateSelector
);
Piece B: useSelector is other property that will make your component reactive to changes related to that selector. Your selector might return undefined or something based on your selection logic.
const[property, setProperty] = useState()
Piece C: Your state that starts as undefined in the first render.
So far we have just discovered 3 pieces of code that might start as undefined or not.
const findProperty = (propertyId, properties) => {
let propertyReturn;
for (var i=0; i < properties.length; i++) {
if (properties[i].propertyId === propertyId) {
propertyToReturn = properties[i];
break;
}
}
setProperty(propertyReturn)
return propertyReturn;
}
const foundProperty = findProperty(propertyId, propertyState.properties);
Piece D: Here is where more problems start appearing, you are telling your code that in every render a function findProperty will be created and inside of it you are calling the setter of your state --setProperty--, generating an internal dependency.
I would suggest to think about the actions that you want to do in simple steps and then you can understand where each piece of code belongs to where.
Let's subdivide this last piece of code --Piece D-- but in steps, you want to:
Find something.
The find should happen if you have an array where to find and a property.
With the result I want to notify my component that something was found.
Step 1 and 2 can happen in a function defined outside of your component:
const findProperty = (propertyId, properties) => properties.find((property) => property.propertyId === propertyId)
NOTE: I took the liberty of modify your code by simplifying a little
bit your find function.
Now we need to do the most important step, make your component react at the right time
const findProperty = (propertyId, properties) => properties.find((property) => property.propertyId === propertyId)
const PropertyComponent = () => {
const { propertyId } = useParams();
const propertyState: IPropertiesState = useSelector(
propertiesStateSelector
);
const[property, setProperty] = useState({ propertyName: '' }); // I suggest to add default values to have more predictable returns in your component
/**
* Here is where the magic begins and we try to mix all of our values in a consistent way (thinking on the previous pieces and the potential "undefined" values) We need to tell react "do something when the data is ready", for that reason we will use an effect
*/
useEffect(() => {
// This effect will run every time that the dependencies --second argument-- changes, then you react afterwards.
if(propertyId, propertyState.properties) {
const propertyFound = findProperty(propertyId, propertyState.properties);
if(propertyFound){ // Only if we have a result we will update our state.
setProperty(propertyFound);
}
}
}, [propertyId, propertyState.properties])
return (<>{property.propertyName}</>)
}
export default PropertyComponent
I think that in this way your intention might be more direct, but for sure there are other ways to do this. Depending of your intentions your code should be different, for instance I have a question:
What is it the purpose of this component? If its just for getting the property you could do a derived state, a little bit more complex selector. E.G.
function propertySelectorById(id) {
return function(store) {
const allProperties = propertiesStateSelector(store);
const foundProperty = findProperty(id, allProperties);
if( foundProperty ) {
return foundProperty;
} else {
return null; // Or empty object, up to you
}
}
}
Then you can use it in any component that uses the useParam, or just create a simple hook. E.G.
function usePropertySelectorHook() {
const { propertyId } = useParams();
const property = useSelector(propertySelectorById(propertyId));
return property;
}
And afterwards you can use this in any component
functon AnyComponent() {
const property = usePropertySelectorHook();
return <div> Magic {property}</div>
}
NOTE: I didn't test all the code, I wrote it directly in the comment but I think that should work.
Like this I think that there are even more ways to solve this, but its enough for now, hope that this helped you.
do you try this:
const found = propertyState.properties.find(element => element.propertyId === propertyId);
setProperty(found);
instead of all function findProperty
I'm trying to update state variable when button click.but my issue is,it's update once with correct data then again it updated with constructor defined data.
constructor(props) {
super(props);
this.state = {
popupshow: [{ check: false, id: '' }]
}
}
componentDidUpdate(prevProps, prevState) {
console.log("this.state.popupshow",this.state.popupshow)
}
Details(type){
this.state.popupshow[i].id = type
this.state.popupshow[i].check = true;
this.setState({ popupshow: this.state.popupshow });
}
render() {
return (
<a onClick={() => this.Details("Tv Series")}>Update </>
)
}
my console.log is like bellow
You should not update React state directly. You should always update/set React state via setState method.
These lines are against React principal
this.state.popupshow[i].id = type
this.state.popupshow[i].check = true;
Update your Details as follows
Details(type){
let { popupshow } = this.state;
let i = 0;
popupshow[i].id = type
popupshow[i].check = true;
this.setState({ popupshow });
}
Note I dont have idea of variable i so assumed that as 0
I think you should rewrite details functions like :
Details(type, i){
const popupDetail = Object.assign([], this.state.popupshow);
popupDetail[i].id = type
popupDetail[i].check = true;
this.setState({ popupshow: popupDetail });
}
you are setting popupshow: this.state.popupshow this is causing forceupdate which re renders the component hence its value gets reset.
I totally agree with the other answers have given for the question, however there are few things worth noting is you might wanna add the function to the context.
The argument in favour of adding these lines to the constructor is so that the new bound functions are only created once per instance of the class. You could also use
onClick={this.Details.bind(this, "Tv Series")}
or (ES6):
onClick={() => this.Details("Tv Series")}
but either of these methods will create a new function every time the component is re-rendered.
Then change the function to arrow fucntion too like
Details(type, i){
const popupDetail = Object.assign([], this.state.popupshow);
popupDetail[i].id = type
popupDetail[i].check = true;
this.setState({ popupshow: popupDetail });
}
This questions is actually React JS related. Is it OK to define internal class variables inside one of the class methods and then use it in other methods? I mean to do something like this:
class Something extends React.Component {
state = {
value: 'doesnt matter'
};
something = () => {
//a lot is happening in here and at some point I define this.thing
this.thing = 1;
}
increase = () => {
if(this.thing) {
this.thing += 1;
}
}
decrease = () => {
if(this.thing && this.thing > 0) {
this.thing -= 1;
}
}
render() {
return (
<span>this.state.value</span>
);
}
}
thing is that I don't need to put that this.thing as a state value, because I only need it internally. Please be aware that this code is just an example, real code is a bit more complicated, but the main question, is it OK to define class internal variables(this.thing) like I do in my example? Or maybe I should do this differently? What would be the best practice?
It's not a problem to use the constructor to do such a thing but based on the react theory and UI rendering this kind of usage will not re-render or follow the react pattern of trigger and re-render, it will just server as a storage for a value that has nothing to do with the react life cycle.
class Something extends React.Component {
constructor(props) {
super(props);
// Your thing
this.thing = 0;
this.state = {
value: "doesnt matter."
};
}
something = () => {
//a lot is happening in here and at some point I define this.thing
this.thing = 1;
};
increase = () => {
if (this.thing) {
this.thing += 1;
}
};
decrease = () => {
if (this.thing && this.thing > 0) {
this.thing -= 1;
}
};
render() {
this.something();
console.log(this.thing); // will equal 1.
return <span>{this.state.value}</span>;
}
}
I don't need to put that this.thing as a state value, because I only need it internally.
A React component's state should also only be used internally.
What would be the best practice?
You can use instance variables (ivars) instead of state to increase performance because you may reduce the burden on the event queue. Aesthetically, ivars often require less code. But state updates are usually preferred because they will trigger a re-render; this guarantee makes your code easier to think about, as the render is never stale. In your case, the render function is independent of this.thing, so it's okay to store it in an ivar.
Generally, it's best to initialize ivars in the constructor because it runs first, so this.thing is guaranteed to be ready for consumption by other methods:
constructor(props) {
super(props);
this.thing = 0;
}
let's say I have a react component like this:
class App extends Component {
print = () => {
const { url } = this.props
const frame = document.createElement('iframe')
frame.addEventListener('load', () => {
const win = frame.contentWindow
win.focus()
win.print()
win.addEventListener('focus', () => document.body.removeChild(frame))
})
Object.assign(frame.style, {
visibility: 'hidden',
position: 'fixed',
right: 0,
bottom: 0
})
frame.src = url
document.body.appendChild(frame)
}
}
Basically, clicking a button calls the print function in the browser for the user. In a situation like this, do I still make this into a redux action like DO_PRINT that doesn't actually do anything to my redux state or do I just not bother with it?
For your particular example, I would avoid creating a Redux action as there is no need for that DO_PRINT to update any state if it is only calling window.print().
In fact, assuming you're creating a "Print button" component, I would redefine this as a dumb component. (See differences between presentationl and container components.)
import React from ‘react’;
const PrintButton = () => {
const onClick = () => {
window.print();
};
return <button onClick={onClick}>Click Me</button>
};
export default PrintButton;
FYI, the code above might not be the most efficient way of declaring event handlers for stateless components as the function is potentilly called each time the component is rendered. There might be better (more efficient) ways (described in another SO question) but that's beyond this question.
I'm trying to create a basic "Toast" like service in my React app using Alt.
I've got most of the logic working, I can add new items to the array which appear on my view when triggering the add(options) action, however I'm trying to also allow a timeout to be sent and remove a toast item after it's up:
onAdd(options) {
this.toasts.push(options);
const key = this.toasts.length - 1;
if (options.timeout) {
options.timeout = window.setTimeout(() => {
this.toasts.splice(key, 1);
}, options.timeout);
}
}
On add, the toast appears on my page, and the timeout also gets triggered (say after a couple of seconds), however manipulating this.toasts inside of this setTimeout does not seem to have any effect.
Obviously this is missing the core functionality, but everything works apart from the setTimeout section.
It seems that the timeout is setting the state internally and is not broadcasting a change event. It might be as simple as calling forceUpdate(). But the pattern I use is to call setState() which is what I think you might want in this case.
Here is an example updating state and broadcasting the change event.
import alt from '../alt'
import React from 'react/addons'
import ToastActions from '../actions/ToastActions'
class ToastStore {
constructor() {
this.toasts = [];
this.bindAction(ToastActions.ADD, this.add);
this.bindAction(ToastActions.REMOVE, this.remove);
}
add(options) {
this.toasts.push(options);
this.setState({toasts: this.toasts});
if (options.timeout) {
// queue the removal of this options
ToastActions.remove.defer(options);
}
}
remove(options) {
const removeOptions = () => {
const toasts = this.toasts.filter(t => t !== options);
this.setState({toasts: toasts});
};
if (options.timeout) {
setTimeout(removeOptions, options.timeout);
} else {
removeOptions();
}
}
}
module.exports = alt.createStore(ToastStore, 'ToastStore');