How to organize actions that don't modify state? - javascript

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.

Related

Can i setState of a react functional component from a external gloabal function present inside script tag?

I have an android app which calls the a function present inside the head of my react
page. All i want is to allow the the function to set a state inside the react component
<head>
<script>
webViewAndriodInteraction(parm1)
{
//Here i want this function to change the state of my react functional component
//setSomeState(pram)
}
</script>
</head>;
function LiveScreen(props) {
const [somedata, setSomeState] = useState();
return <h1>someData</h1>;
}
It's probably against some React best practices, but you could do this:
In your component, define an effect that puts your state setter on the window object:
useEffect(() => {
window.setSomeState = setSomeState;
return () => {
window.setSomeState = undefined;
};
}, []);
And in your webViewAndriodInteraction function:
if (window.setSomeState !== undefined) {
// call window.setSomeState here
}
You also need to ensure that your call to window.setSomeState is deferred until the function gets defined. So if you're sure it's going to get defined, you could set a timeout or retry the if check a few times with a given delay.

How to force new state in React (Hooks) for textarea when clicking on another element?

codesandbox.io sandbox
github.com repository
I am creating this small Wiki project.
My main component is Editor(), which has handleClick() and handleModeChange() functions defined.
handleClick() fires when a page in the left sidebar is clicked/changed.
handleModeChange() switches between read and write mode (the two icon buttons in the left sidebar).
When in read mode, the clicking on different pages in the left sidebar works properly and changes the main content on the right side.
However, in write mode, when the content is echoed inside <TextareaAutosize>, the content is not changed when clicking in the left menu.
In Content.js, I have:
<TextareaAutosize
name="textarea"
value={textareaValue}
minRows={3}
onChange={handleMarkdownChange}
/>
textareaValue is defined in Content.js as:
const [textareaValue, setTextareaValue] = useState(props.currentMarkdown);
const handleMarkdownChange = e => {
setTextareaValue(e.target.value);
props.handleMarkdownChange(e.target.value);
};
I am unsure what is the correct way to handle this change inside textarea. Should I somehow force Editor's child, Content, to re-render with handleClick(), or am I doing something completely wrong and would this issue be resolved if I just changed the definition of some variable?
I have been at this for a while now...
You just need to update textareaValue whenever props.currentMarkdown changes. This can be done using useEffect.
useEffect(() => {
setTextareaValue(props.currentMarkdown);
}, [props.currentMarkdown]);
Problem:
const [textareaValue, setTextareaValue] = useState(props.currentMarkdown);
useState will use props.currentMarkdown as the initial value but it doesn't update the current state when props.currentMarkdown changes.
Unrelated:
The debounce update of the parent state can be improved by using useRef
const timeout = useRef();
const handleMarkdownChange = (newValue) => {
if (timeout.current) {
clearTimeout(timeout.current);
}
timeout.current = setTimeout(function () {
console.log("fire");
const items = [];
for (let value of currentData.items) {
if (value["id"] === currentData.active) {
value.markdown = newValue;
value.unsaved = true;
}
items.push(value);
}
setCurrentData({ ...currentData, items: items });
}, 200);
};
using var timeout is bad because it declares timeout on every render, whereas useRef gives us a mutable reference that is persisted across renders

How to provide window width to all React components that need it inside my app?

So I've got this hook to return the windowWidth for my App components. I'll call this Option #1.
import {useEffect, useState} from 'react';
function useWindowWidth() {
const [windowWidth,setWindowWidth] = useState(window.innerWidth);
useEffect(() => {
function handleResize() {
setWindowWidth(window.innerWidth);
}
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
return windowWidth;
}
export default useWindowWidth;
And right now I'm basically using it on every component that depends on the window width to render, like:
function Component(props) {
const windowWidth = useWindowWidth();
return(
// RETURN SOMETHING BASED ON WINDOW WIDTH
);
}
And since the hook has an event listener for the resize events, the component stays responsive even after window resizes.
But I'm worried that I'm attaching a new listener for every component that uses that hook and it might slow things down at some point. And I've though of other approach:
Option #2
I use the useWindowWidth() hook only one time, inside a top level component like <App/> and I'll provide the windowWidth value down the chain via context.
Like:
function App() {
const windowWidth = useWindowWidth();
return(
<WindowWidthContext.Provider value={windowWidth}>
<Rest_of_the_app/>
</WindowWidthContext.Provider>
);
}
And then, every component that needs it could get it via:
function Component() {
const windowWidth = useContext(WindowWidthContext);
return(
// SOMETHING BASED ON WINDOW WIDTH
);
}
QUESTION
Am I right in being bothered by that fact that I'm setting up multiple resize listeners with Option #1 ? Is Option #2 a good way to optmize that flow?
If your window with is used by so many components as you mentioned, you must prefer using context. As it reads below:
Context is for global scope of application.
So, #2 is perfect choice here per react.
First approach #1 might be good for components in same hierarchy but only up-to 2-3 levels.
I'm not sure if adding and removing event listeners is a more expensive operation than setting and deleting map keys but maybe the following would optimize it:
const changeTracker = (debounceTime => {
const listeners = new Map();
const add = fn => {
listeners.set(fn, fn);
return () => listeners.delete(fn);
};
let debounceTimeout;
window.addEventListener('resize', () => {
clearTimeout(debounceTimeout);
debounceTimeout = setTimeout(
() => {
const width=window.innerWidth;
listeners.forEach(l => l(width))
},
debounceTime
);
});
return add;
})(200);
function useWindowWidth() {
const [windowWidth, setWindowWidth] = useState(
() => window.innerWidth
);
useEffect(
() =>//changeTracker returns a remove function
changeTracker((width) =>
setWindowWidth(width)
),
[]
);
return windowWidth;
}
As HMR said in an above thread, my solution was to use redux to hold the width value. With this strategy you only need one listener and you can restrict how often you update with whatever tool you like. You could check if the width value is within the range of a new breakpoint and only update redux when that is true. This only works if your components dont need a steady stream of the window width, in that case just debounce.

Using ResizeObserver in React class component

I am using React 15 on Chrome and want to hook up an event listener to detect changes to a parent container. After looking around for options, I came across ResizeObserver and am not sure how to get it to work in my project.
Currently, I am putting it in my constructor but it does not seem to print any text and I am not sure what to put in the observe call.
class MyComponent extends React.Component {
constructor(props) {
super(props);
const resizeObserver = new ResizeObserver((entries) => {
console.log("Hello World");
});
resizeObserver.observe(somethingGoesHere);
}
render() {
return (
<AnotherComponent>
<YetAnotherComponent>
</YetAnotherComponent>
<CanYouBelieveIt>
</CanYouBelieveIt>
<RealComponent />
</AnotherComponent>
);
}
}
Ideally, I also don't want to wrap RealComponent in a div and give that div an id. Is there a way to the RealComponent directly?
My goal is to observe any resize changes to the RealComponent but MyComponent is fine too. What should I put in the somethingGoesHere slot?
EDIT:
For the sake of getting something to work, I bit the bullet and wrapped a div tag around RealComponent. I then gave it an id <div id="myDivTag"> and changed the observe call:
resizeObserver.observe(document.getElementById("myDivTag"));
However, when running this, I get:
Uncaught TypeError: resizeObserver.observe is not a function
Any help would be greatly appreciated.
ComponentDidMount would be the best place to set up your observer but you also want to disconnect on ComponentWillUnmount.
class MyComponent extends React.Component {
resizeObserver = null;
resizeElement = createRef();
componentDidMount() {
this.resizeObserver = new ResizeObserver((entries) => {
// do things
});
this.resizeObserver.observe(this.resizeElement.current);
}
componentWillUnmount() {
if (this.resizeObserver) {
this.resizeObserver.disconnect();
}
}
render() {
return (
<div ref={this.resizeElement}>
...
</div>
);
}
}
EDIT: Davidicus's answer below is more complete, look there first
ResizeObserver can't go in the constructor because the div doesn't exist at that point in the component lifecycle.
I don't think you can get around the extra div because react components reduce to html elements anyway.
Put this in componentDidMount and it should work:
componentDidMount() {
const resizeObserver = new ResizeObserver((entries) => {
console.log("Hello World");
});
resizeObserver.observe(document.getElementById("myDivTag"));
}
I was fighting a the similar problem recently with the difference that my app is predominantly using hooks and functional components.
Here is an example how to use the ResizeObserver within a React functional component (in typescript):
const resizeObserver = React.useRef<ResizeObserver>(new ResizeObserver((entries:ResizeObserverEntry[]) => {
// your code to handle the size change
}));
const resizedContainerRef = React.useCallback((container: HTMLDivElement) => {
if (container !== null) {
resizeObserver.current.observe(container);
}
// When element is unmounted, ref callback is called with a null argument
// => best time to cleanup the observer
else {
if (resizeObserver.current)
resizeObserver.current.disconnect();
}
}, [resizeObserver.current]);
return <div ref={resizedContainerRef}>
// Your component content here
</div>;

React: How to read from the DOM after mount?

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();
}
}

Categories