React focus div after it has loaded - javascript

I'm currently experimenting with React, and I've now run into an issue that I can't seem to solve.
In my application, I use a React library to handle hotkeys, these hotkeys have a scope, so when I want a certain set of hotkeys to be active in a div, I have to wrap that div with a <HotKeys> tag.
I have to toggle some of these divs, so I'll have something along the lines of
isActive ?
<HotKeys ...>
<div ...>...</div>
</HotKeys>
: <div ...>...</div>
I now need to figure out a way to focus the div when it's created. Pretty much every article on the web suggest something like this:
const focusRef = useRef(null);
useEffect(() => {
focusRef.current.focus()
})
return (
isActive ?
<HotKeys ...>
<div ref={focusRef} tabIndex={-1} ...>...</div>
</HotKeys>
: <div ...>...</div>
)
I've tried some variations, including having the div top level (without the <HotKeys> wrapping them), all to no avail.
When I print the focusRef object in the useEffect method, I do get the expected output, focusRef is correctly set and current is populated, but calling the focus method doesn't work. At one point I tried calling the focus method from a button and manually triggering it after the component had fully loaded, it seemed to work (document.activeElement was being changed), then for some reason it stopped working again. All this leads me to believe that somehow the component hasn't fully loaded, despite calling the useEffect hook, which, if I understand correctly, triggers when the element has rendered for the first time/after every change to state.
I'd really appreciate some help with this, since I basically started learning React yesterday.

You must to use an useCallback , because useRef don't notifies you when ref was created
https://reactjs.org/docs/hooks-reference.html#useref

I think I figured it out.
You were right about using the tabIndex but you needed to pass it in as a string like this:
tabIndex={"-1"}
When you first load it a dotted line box surrounds the div that has the ref attached.
check out this code sandbox:
https://codesandbox.io/s/nifty-wildflower-eqjtk?file=/src/App.js
grabbed from this accepted answer where they pass in a string:
Need to put focus on div in react

Related

DOM remove method in react js

I want to change remove a certain tag with an id in the html page, eg.<div id="theid"><p>sometext</p></div> Is there any ways to do it with react js? I know I can do it with javascript by document.getElementById("theid").remove();, how can I do it in the react way? I don't need a button or anything, just simply remove it when the page loads. I'd prefer methods without importing any modules or libraries if possible. Thank you.
You should likely use a ref:
https://reactjs.org/docs/refs-and-the-dom.html
So you attach the ref to the DOM element, then you can imperatively remove that element just like you specified.
So in the component function body:
const myRef = useRef();
Then in the component render:
<Component ref={myRef} />
Then you can use the code in your question to remove the element from the DOM.
ref.remove();
If you need to do it when the page loads look at using useEffect to achieve this. Although if you're removing an element on page load, I question why you even need the element there in the first place ;).
If it's rendered as part of React, the right way to do it would be to simply omit it from the source code:
const App = () => (
<div>
<div id="theid">foo</div>
<div>more content</div>
</div>
);
to
const App = () => (
<div>
<div>more content</div>
</div>
);
If it's not part of React, then remove it from whatever process generates the HTML.
If that's not an option - if it must be part of the HTML served to the client and it's not rendered as part of React - then you'll have to resort to doing what you're currently doing:
document.getElementById("theid").remove();
probably completely separate from your React script, since it's something you want to do only once, when the page loads, and not something that needs to be a part of the React lifecycles.

Page Jumps on Component Re-Render (ReactJS/Gatsby)

Describe the Problem
I am making a very simple ReactJS/Gatsby website for someone and I am having an issue with one of my functional styled components when it re-renders. The problem is that it is causing the window to jump (scroll) after the re-render is complete.
The re-render is triggered by the user clicking on a span element (enclosed in an li element) which fires a function.
The list of li elements is determined by the state of the component. The overall parent component has a fixed height which is why I am having trouble diagnosing the issue.
What I Expect to Happen
The component to re-render and the window's scroll position to remain where it was when the user initiated it.
What Actually Happens
When the user clicks the element the page appears to jump (scroll). Sometimes it does so and remains in the new position, sometimes it does so and then returns to the original scroll position.
What I've Tried
I've tried following advice from other questions which suggest using event.preventDefault() and others which suggest moving the styling out of the component itself and, instead, opting for using classes.
Neither of these solutions worked.
I have managed to definitively find that the issue is due to setActiveTabs -- which causes the re-render of the ul element -- as logging window.scrollY both prior to it firing and after it completes displays a different value.
Edit 2:
I have managed to figure out that the issue is with making the list items targetable. It seems that either adding the tabIndex="0" attribute or making the li child an interactive element causes this bug.
Does anyone know a way around this?
Edit
The full frontend source code can be found in the following GitHub repo: https://github.com/MakingStuffs/resinfusion
In order to solve the issue I needed to prevent the clicked element from being targeted on the re-render. In order to do this I edited the clickHandler so that it uses element.blur() after setting the state.
The click handler is as follows:
const forwardClickHandler = event => {
setLoading(true)
const clickedSlug =
event.target.closest("button") !== null
? event.target.closest("button").getAttribute("data-slug")
: event.target.children[0].getAttribute("data-slug")
const categoryObject = getNeedle(clickedSlug, categories, "slug")
const subCatObject = getNeedle(clickedSlug, subCategories, "slug")
const serviceObject = getNeedle(clickedSlug, services, "slug")
const associatedChildren = getAssociatedChildren(
categoryObject
? categoryObject
: subCatObject
? subCatObject
: serviceObject
)
setBgImage(associatedChildren[0].thumb.localFile.childImageSharp.fluid)
setActiveTabs(associatedChildren)
event.target.blur()
return setTimeout(() => setLoading(false), 1000)
}

My React component does not update in the Safari browser

Thought everything was going great. Works perfectly in Chrome, FF, Edge, even IE 11!
The parent component holds all the sate. I pass the bets object to the child component which calculates a count to pass to the grandchild component to display.
The parent state 'bets' is an object with the keys as an ID and the value as an object.
The parent state is correctly changing when I interact with the app. Why will only Safari not update when the parent state changes? (on iOS and MacOS)
Parent
class ParentComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
bets: {}
};
}
}
Child
getBadgeCount = (league) => {
const bets = this.props.bets;
let count = 0;
Object.keys(bets).map((bet) => bets[bet].event.league === league && count++);
return count;
};
// ...
<ChildItem count={this.getBadgeCount(league)} />
GrandChild
class GrandChildComponent extends React.Component {
render() {
const { count } = this.props;
return (
<div>
<div>
{count > 0 && <div>{count}</div>}
</div>
</div>
);
}
}
I console.log the count inside the grandchild render and in the componentDidUpdate and it shows the right number. Is there something apple/safari specific with react I am missing?
In the case where there are variables being changed based on which you can switch between the styles (opacity: 1 or opacity: 0.99) You can try adding a key to the element which is not being updated in Safari/iOS.
<div key={new Date()} className={'myComponent'}>{Count}</div>
I ran into the same problem, this seems to work for now.
So I did solve the very bizarre issue after a few days.
What was happening was I guess the engine in the Safari browser was not re-rendering the little badge icon correctly. I would print out the value inside the DOM, OUTSIDE of the styled badge I was using, and the number would update as expected... Leading me to believe the issue was related to styles.
THEN after removing what I thought was the CSS causing the issue, I noticed that it looked like safari was 'partially' updating the value in the badge. It appears to have half re-rendered, and the previous number collides with the new value... Not sure if this is some obscure problem with the render engine?
After adding and removing all the CSS one by one, the issue remained so I decided to trick the browser to 'force' render with a simple calculation inside the grandchild where it was being rendered:
const safariRenderHack = { opacity: count % 2 ? 1 : 0.99 };
<div style={safariRenderHack}>{count}</div>
Not a great solution but oh well I guess it's fixed. ha
I just run into similar problem.
Safari didn't re-render some texts implemented as:
<span>{someValue}</span>
On 'someValue' field update, reactJS worked fine (element was requested to render) but Safari re-renders only area of new value (shorter than previous). UI glitches :-/
If I done anything to CSS via Developer tools, element has been rendered again and looks fine :-/
After some tries, I luckily used a 'display: block;' style property and it starts to re-rendering absolutely fine. Also 'display: inline-block;' will fix that problem too, if it is needed to be used.
I had a similar problem where a center positioned span using flexbox inside a div wasn't updating correctly in Safari.
Restyling this div to use display:block solved this rendering issue so this might be worth looking into if anyone runs into the same problem in the future.
P.S. Looking at the styling code now, making the change was actually a much cleaner solution as it reduced the amount of lines from 4 to 1 but this might not be the case for all scenarios of course.
before:
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
after: (is a div so display:block is already used)
text-align: center;
When using map function , React looks at the keys :
To identify which items have changed, are added, or are removed.
Solution :
Now, As you use map function u must Add Key to the div inside map .
Use a key variable that is connected to the logic behind clicking , so that when clicking the key , the variable is updated and so React will rerender this grandchild div .
As the following code :
<div key={Count} className={'myComponent'}>{Count}</div>
PS : I discourage to use Date as a key , because I f u are clicking quickly it glitches , while if u are connecting it with a state with the logic behind clicking , it won't .
this may help you:
will-change: transform;
I know it may be late but what helped me with Safari not re-rendering reused components was:
will-change: opacity;
You may have to experiment which element in your HTML hierarchy to apply this to.
It makes no sense because no opacity was being changed in my case but it did fix the problem. Thank you, Apple...
We also ran into this problem. So, I did a short research about it and got the gist as follows:
It works by providing the unique key to the element(key={new Date()} works bcz unique even with seconds) which is not rerendering properly say a description as text inside it that changes by button or an event.
So what I got as a gist basically, providing the individual key make new dom element to react during reconcilation process rather than changing same value inside the element so basically that element showing object is made unique instead of changing value inside that dom element so I think safari specific it is as it is holding state of the element rendering dynamically. So after providing the key prop you are basically rendering the unique different element(obviously with different text/object inside it). This basically utilizes here the concept of keys while we render a list in react component.

How to make rehydrate update slightly changed style props

Let's say I have a very simple app with the following render method.
render(){
return (<div style={{height:window.innerHeight}}/>);
}
Resulting in the following DOM:
<div data-reactroot="">
<div style="height:938px">
</div>
</div>
Now when I try to rehydrate the DOM I get a warning saying that style prop does not match but otherwise everyhing still works allright, except for the fact that the height of the div does not get updated to what it should be.
What would be the best way to either fix the warning or ignore it (as everything else work) and make react update the DOM element.
Note that:
- I think this worked fine w. React 15.x. Using 16.0.0 now;
- In this toy example I could obviously just use height:"100%" but in the real life case I do not have this luxury and have to calculate the dimensions in a way that depends ultimately on viewport size.
Did a McGyver fix here, reading the console for those warnings and then if I get those:
setTimeout(function(){
ReactDOM.unmountComponentAtNode(RoboReact.rootParent);
ReactDOM.render(RoboReact.rootElement,RoboReact.rootParent);
},100);
(doesn't seem to work w/o the setTimeout for some reason)

How to make a React.js inline portal for a jQuery Datepicker

I've watched,
http://youtu.be/z5e7kWSHWTg?t=15m17s
and read,
https://github.com/ryanflorence/react-training/blob/gh-pages/lessons/05-wrapping-dom-libs.md
https://github.com/ryanflorence/react-training/tree/gh-pages/code/Dialog
http://jsbin.com/dutuqimawo/edit?js,output
How to create a React Modal(which is append to `<body>`) with transitions?
and I get the concept of the Portal, that you're tricking React into ceasing its rendering for one piece of the DOM, then continuing the rendering afterward, so you can tinker with that piece of the DOM without confusing React by making its virtual DOM get out of sync.
My problem is that the examples all address a Dialog that is rendered at the end of the page, but appears inline when you're reviewing your code. It's a cool trick for using a jQuery modal, but I need a jQuery datepicker whose div actually remains where I put it. (As an aside, I'm also curious about GetDOMNode's presence in the examples when it's deprecated? I suppose you use FindDOMNode, although you call it slightly differently, plus the documentation says "In most cases, use of this escape hatch is discouraged because it pierces the component abstraction", which makes me a little gunshy to use it.)
To isolate the jQuery datepicker from React, I originally created one React component to handle everything above the datepicker, and another to handle everything below the datepicker, and used event listeners in each component to listen for updates. However, I prefer the design of a single parent component that passes everything down to its children; it seems like a cleaner design.
I redesigned it with a single parent and it seems to work, but I have no idea if my portal is really isolated from React's virtual DOM or not; it's my first crack at a portal so I'm really muddling through. (I am using React-Bootstrap for my navbar and it works great; I just couldn't find an equivalent to jQuery's datepicker and I like how it looks and operates, which is why I'm sticking with it.)
Here's my top-level component (I removed the props/componentDidMount/etc for clarity). The <CalendarControl /> is going to be the portal:
var ReactApp = React.createClass({
render: function() {
return (
<div>
<BootstrapNavbar division={this.state.division} dps={this.state.dps} sections={this.state.sections} />
<div className="container">
<br />
<br />
<br />
<div className="row">
<div className="col-md-4" id="calendarPortal">
<CalendarControl />
</div>
<div className="col-md-8">
<h3>{this.state.dp}</h3>
<h4>{this.state.dpStartDate} - {this.state.dpEndDate}</h4>
</div>
</div>
<TimebookTableRecords timebookRecords={this.state.timebookRecs} />
</div>
</div>
);
}
});
Here's the code for my CalendarControl portal. When the CalendarControl mounts, I'm creating a new div calendarControl as a child of calendarPortal. I then use jQuery to create the datepicker on the calendarControl div.
var CalendarControl = React.createClass({
render: function () {
return null;
},
componentDidMount() {
var portalLocation = document.getElementById("calendarPortal");
var newElement = document.createElement('div');
newElement.id = "calendarControl";
portalLocation.appendChild(newElement);
},
componentWillUnmount() {
var portalLocation = document.getElementById("calendarPortal");
document.body.removeChild(portalLocation);
},
});
Here's the jQuery code that creates a datepicker on the calendarControl div:
$("#calendarControl").datepicker({
numberOfMonths: monthDiff,
defaultDate: dpStartDate,
showButtonPanel: false,
beforeShowDay: formatCalendarDays, //formatter function
onSelect: dateClicked //handles click on calendar date
The final product seems to work fine, and doesn't generate any "the DOM was unexpectedly mutated" errors like when you manipulate part of the DOM that's under React's purview. I can update the state of the parent and see the changes propagate down nicely, and use jQuery to update the calendar.
However, I just don't know if this is the correct approach? That is to say, have I achieved a true portal here? I used the Google Chrome React Developer Tools add-in to inspect the component hierarchy, and it does look like from React's perspective there's a null in the CalendarControl div:
Thanks for bearing with me in this lengthy post. I have to say that so far I'm really loving the React approach to web development; it's so radically different that it took a number of readings and tinkering just to understand its concepts, but now it seems so much more elegant than the ways I've done it in the past.
From my understanding of portals, you are doing this mostly correct. But if it had any other children, you would have to reconnect with react after the jquery stuff, but I assume that is not the case here.
The reason you are seeing a "null" inside calendar control is because you return a null in your CalendarControl render function.
Why don't you just change your render function in calendarControl to:
render: function () {
return (
<div id="calendarControl"></div>
)
and do all your funky jQuery rendering inside componentDidMount function?

Categories

Resources