I'm using the Segmented component by Ant Design. My application has some component like this:
...
import EmployeeCard from './EmployeeCard';
import ContractorCard from './ContractorCard';
function MyComponent() {
const [cardType, setCardType] = useState('Employee');
...
function changeCard(value) {
setCardType(value);
}
return (
...
<Segmented
options={['Employee', 'Contractor']}
onChange={changeCard)
value={cardType}
tabIndex={-1}
/>
{cardType === 'Employee'
? <EmployeeCard />
: <ContractorCard />
}
...
);
}
The EmployeeCard and ContractorCard have some input components in them that I want to be part of the overall tab flow on the page. However, I don't want the Segmented buttons themselves to be part of it. I set tabIndex={-1} on the Segmented component in order to take it out of the tab-flow, but apparently, internally, that component includes an Input component, which still has the default tabIndex={0}. I cannot figure out how to reach that component in order to set its tab index to -1.
Related
I am developing an UI framework based on antd (I call it Yooooo UI)
The design is a little different with the default style of antd and I did something to set the different color, size, margin and blablabla with setting less variables - follow the official solution.
Also, there is something new, such as I extend the type of Buttons - add 'atype', 'btype', 'ctype' to Button Component. That means I can use it like
import { Button } from 'yooooo-ui';
<Button type="atype" />
The solution for supporting this is here below
import { ButtonType } from 'antd/lib/button';
import { BaseButtonProps } from 'antd/lib/button/button';
const ExtendedButtonTypes = tuple(
'atype',
'btype',
'ctype'
);
type ExtendButtonType = typeof ExtendedButtonTypes[number];
type MyButtonType = ButtonType | ExtendButtonType;
interface MyBaseButtonProps extends Omit<BaseButtonProps, 'type'> {
type?: MyButtonType;
}
// ...something else codes
But there is a condition that Modal component used the Button component with a relative path in antd.
// codes in antd
import { ButtonProps } from './button'
// ...something else
that means, if I do nothing in Yoooo UI and use the Modal component, the button type 'atype' is invalid since Modal component doesn't use my customized Button component
import { Modal } from 'yoooo-ui';
const buttonProps = {
type: 'atype' // <-- error happened
}
<Modal
okButtonProps={buttonProps}
/>
I wanna figure out a way to handle this condition with a commonly way.
I have tried finding the answer to this on StackOverflow and there are some related posts (e.g. React Child Component Not Updating After Parent State Change) but I want to understand why this is not working...
I have a React application that will display a layout of character cards (that is, each card displays a different character). It uses a child component, CharacterBoard, that lays out the CharacterCards, which would be a grandchild component. I pass the characters down from the App to the CharacterBoard as props, and CharacterBoard in turn maps these out the CharacterCards.
The problem is that I want the state of the character to change when I click on one of them. Specifically, I want the revealed field to change. However, even though the state change is reflected in the array of characters in the App (that is, the revealed field changes correctly), and the change is reflected in the array of characters in CharacterBoard, but not in CharacterCard. In fact, my mapping does not seem to be called at all in CharacterBoard when the props change.
Do I need to use something like getDerivedStateFromProps in CharacterBoard and set the state of that component and then use the state to map the values down to CharacterCard? If so, why?
In short (tl;dr), can you pass props on down through the component chain and map them out along the way and still have all changes reflected automatically?
Thanks for any guidance.
If it helps, the render method of my App is
render() {
const {state: {characters}} = this
return (
<div>
<header>
</header>
<main>
<CharacterBoard
onCardSelected={this.onCardSelected}
rowSize={logic.ROW_SIZE}
characters={characters}
cardSize={this.CARD_SIZE}/>
</main>
</div>
);
}
that of CharacterBoard is
render() {
const {props: {characters, rowSize, cardSize,onCardSelected}} = this
const rowUnit = 12 / rowSize
const cardLayout = characters
.map((character, i) => (
<Col xs={6} sm={rowUnit} key={character.name}>
<CharacterCard
onCardSelected = {onCardSelected}
key={i + Math.random()}
character={character}
cardSize={cardSize}
/>
</Col>
)
)
return (
<div>
<Container>
<Row>
{cardLayout}
</Row>
</Container>
</div>
)
}
and finally CharacterCard has this render method
render() {
const {props: {character, cardSize}} = this
const {thumbnail, revealed} = character
const imgURL = `${thumbnail.path}/${cardSize}.${thumbnail.extension}`
const topCardClass = classNames('characterCard__card-back', {'characterCard__card-back--hidden': revealed})
console.log(revealed)
return < a href="/#" onClick={this.onCardSelected}>
<div className='characterCard__card'>
<div className={topCardClass}>
<img src="/images/card_back.png" alt=""/>
</div>
< div className='characterCard__card-front'>< img alt=''
src={imgURL}/>
</div>
</div>
</a>
}
Doh! A simple forgetting to setState in App. Knowing that it should work made me go back through the code one more time and see that, indeed, it was a stupid error on my part.
I am starting my adventure with React so it is a hard time for me, however I prepared such pen for you to test. Here is a portion of code:
class App extends React.Component {
constructor() {
super();
this.state = {
settings: true,
next: false,
};
}
toggler(abc) {
console.log(">>", abc)
this.setState({
next: !this.state.next
/* {abc}: this.state.{abc} */
})
console.log(this.state.next)
}
render() {
return (
<div className="kalreg">
<MyButton name='settings' isActive={this.state.settings} type="settings" toggle={this.toggler.bind(this)}/>
<MyButton name='settings2' isActive={this.state.settings} type="settings" toggle={this.toggler.bind(this)}/>
<MyButton name='next' isActive={this.state.next} type="next" toggle={this.toggler.bind(this)}/>
</div>)
}
}
class MyButton extends React.Component {
constructor(props) {
super(props);
}
onChangeName(){
console.log(this.props.type)
if ( this.props.isActive ) { console.log("this one is active"); } else { console.log("ouch! it is not active, ignoring!"); return;}
this.props.toggle(this.props.type);
}
render () {
if ( this.props.isActive ) {
return ( <div className="button notVisible" onClick={this.onChangeName.bind(this)}>{this.props.name}</div>)
} else {
return ( <div className="button visible" onClick={this.onChangeName.bind(this)}>{this.props.name}</div>)
}
}
}
ReactDOM.render(<App />, document.getElementById("app"));
What I am trying to achieve is that when i press one of "settings" buttons (yellow) the "next" button becomes unclickable (green). There is a toggle function that every time I click settings button it turns on and off "next" button.
It works quite good, however it is just a draft of bigger project and i want to automate it a little bit.
As you can see I create my <MyButton> with both "isActive" and "type" props. But isActive holds what's inside this.state.settings while type is "settings". Instead of using two variables it would be great to pass only type of button to its component and component, depending on its type would check its parent's this.state.{type}. I used {type} because i would like to check it dynamically. Is that possible?
If so - how to do it?
My first attempt is to pass type from <MyButton> to <App> via toggler function. I named the variable "abc". I commented the way I wanted to do it because it doesn't work:
{abc}: !this.state.{abc}
Any idea to solve this problem would be more than appreciated.
Kalreg.
It is somewhat unclear what you are trying to achieve here. If you want to wire the state dynamically based on type, as you wrote in code: {abc}: !this.state.{abc} each button would toggle itself, not the next button. In this case your syntax is a little incorrect, it will work if you write it like:
[abc]: !this.state[abc]
However as I said, in your example, this makes the settings button change the state for this.state.settings disabling itself instead of the next button.
Another note would be, that if it is not necessary for the MyButton component to know its own type for other reasons, it is unnecessary to pass it as a prop and than make the component pass it back as an argument (this.props.toggle(this.props.type);). You can simply define the toggle function in the parent as:
toggle={() => this.toggler("settings")}
without passing type as a prop.
So basically we want to have the settings and settings2 buttons, and when we click on them, they toggle the state of the next button by making it un-clickable (green).
So if that is our goal, then
we don't need an isActive prop for the settings button. (Because it's always going to be active no matter what)
We also don't need to have a toggle prop on the Next button. (Because clicking the next button isn't supposed to toggle anything)
Instead of having two variables in the state why not just have one and then use that to determine the isActive prop of the next button?
The component would look like this:
constructor() {
super();
this.state = {
nextIsActive: false,
};
}
toggler() {
this.setState({
nextIsActive: !this.state.nextIsActive
})
console.log(this.state);
}
render() {
const {nextIsActive} = this.state
return (
<div className="kalreg">
<MyButton name='settings' isActive={true} type="settings" toggle={this.toggler.bind(this)}/>
<MyButton name='settings2' isActive={true} type="settings" toggle={this.toggler.bind(this)}/>
<MyButton name='next' isActive={nextIsActive}/>
</div>
)
}
That way you don't have to have 2 state properties that you have to dynamically update because it adds more complexity to your application.
You can see the finished product here: Codepen
I'm relative new to react and I need to create a character counter component (CharacterCounter) for a input field.
This question is not specific to this component (character counter) - but is a generic question about components best practices in general.
I can implement in two ways (as far I know - if there are better ways I'm happy to hear about it):
1) Wrapping the component and have the input field as a child - The component will insert the span (for showing the counter) after the input field
<CharacterCounter maxLength={50}>
<input type="text">
</CharacterCounter>
and
const input = this.container.firstChild
input.addEventListener('keyup', function() { ... });
advantage: I can have multiple components for the same input - if I need extra functionality (components) for this input.
disadvantage: If the input for some reason stop being the first child of this component - stop working/ fragile
2) To create a generic component which will render the input and the counter on the render() function
like:
<CharacterCounter />
render() {
return (
<input type="text">
<span>{this.state.count}</span>
)
advantage: Not fragile - not relying on the first child
disadvantage: Not sure is possible to have other component for the same input - let's say I need another component for tracking every time the user type/ or focus/ or blur the field
What is the best practices?
Surely the second approach is better as it is not directly interfering with DOM elements.
If you wanted to have access to DOM elements, still it's better to use refs.
disadvantage: Not sure is possible to have other component for the
same input - let's say I need another component for tracking every
time the user type/ or focus/ or blur the field
You will get around that easily just with props.
You could use second approach with components with state and then use composition to extend that component or create more “special cases” of that component.
let {Component} = React;
class Input extends Component {
constructor(props) {
super(props);
this.state = {count: 0}
}
render() {
return <div>
<input
{...this.props}
onChange={() => {
let count = this.refs.inputCount.value.length;
this.setState({count})
}}
type="text"
maxLength="50"
ref="inputCount" />
<span> {this.state.count} / 50</span>
</div>
}
}
class FocusInput extends Component {
render() {
return <Input
onFocus={() => {
console.log('Focus')
}} />
}
}
class App extends Component {
render() {
return <div>
<Input />
<FocusInput />
</div>
}
}
ReactDOM.render(
<App />,
document.getElementById('app')
);
<script src="https://unpkg.com/react#16.1.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16.1.0/umd/react-dom.development.js"></script>
<div id="app"></div>
Given the very simple page (having assumed React and react-router#4 have been imported):
// Current location: example.com/about
<Link to="/about/#the-team">See the team</Link>
// ... loads of content ... //
<a id="the-team"></a>
I would expect the above, upon clicking "See the team" would scroll down to the id'ed team anchor. The url correctly updates to: example.com/about#the-team, but it doesn't scroll down.
I have tried alternatives such as <a name="the-team"></a> but I believe this is no longer spec (nor does it work).
There are plenty of work arounds on github for react-router#v2 but they rely on the update callback present on BrowserRouter that is no longer present in v4.
Given a <ScrollIntoView> component which takes the id of the element to scroll to:
class ScrollIntoView extends React.Component {
componentDidMount() {
this.scroll()
}
componentDidUpdate() {
this.scroll()
}
scroll() {
const { id } = this.props
if (!id) {
return
}
const element = document.querySelector(id)
if (element) {
element.scrollIntoView()
}
}
render() {
return this.props.children
}
}
You could either wrap the contents of your view component in it:
const About = (props) => (
<ScrollIntoView id={props.location.hash}>
// ...
</ScrollIntoView>
)
Or you could create a match wrapper:
const MatchWithHash = ({ component:Component, ...props }) => (
<Match {...props} render={(props) => (
<ScrollIntoView id={props.location.hash}>
<Component {...props} />
</ScrollIntoView>
)} />
)
The usage would be:
<MatchWithHash pattern='/about' component={About} />
A fully fleshed out solution might need to consider edge cases, but I did a quick test with the above and it seemed to work.
Edit:
This component is now available through npm. GitHub: https://github.com/pshrmn/rrc
npm install --save rrc
import { ScrollIntoView } from 'rrc'
The react-router team seem to be actively tracking this issue (at the time of writing v4 isn't even fully released).
As a temporary solution, the following works fine.
EDIT 3 This answer can now be safely ignored with the accepted answer in place. Left as it tackles the question slightly differently.
EDIT2 The following method causes other issues, including but not limited to, clicking Section A, then clicking Section A again doesn't work. Also doesn't appear to work with any kind of animation (have a feeling with animation starts, but is overwritten by a later state change)
EDIT Note the following does screw up the Miss component. Still looking for a more robust solution
// App
<Router>
<div>
<Match pattern="*" component={HashWatcher} />
<ul>
<li><Link to="/#section-a">Section A</Link></li>
<li><Link to="/#section-b">Section B</Link></li>
</ul>
<Match pattern="/" component={Home} />
</div>
</Router>
// Home
// Stock standard mark up
<div id="section-a">
Section A content
</div>
<div id="section-b">
Section B content
</div>
Then, the HashWatcher component would look like the following. It is the temp component that "listens" for all route changes
import { Component } from 'react';
export default class HashWatcher extends Component {
componentDidMount() {
if(this.props.location.hash !== "") {
this.scrollToId(this.hashToId(this.props.location.hash));
}
}
componentDidUpdate(prevProps) {
// Reset the position to the top on each location change. This can be followed up by the
// following hash check.
// Note, react-router correctly sets the hash and path, even if using HashHistory
if(prevProps.location.pathname !== this.props.location.pathname) {
this.scrollToTop();
}
// Initially checked if hash changed, but wasn't enough, if the user clicked the same hash
// twice - for example, clicking contact us, scroll to top, then contact us again
if(this.props.location.hash !== "") {
this.scrollToId(this.hashToId(this.props.location.hash));
}
}
/**
* Remove the leading # on the hash value
* #param string hash
* #return string
*/
hashToId(hash) {
return hash.substring(1);
}
/**
* Scroll back to the top of the given window
* #return undefined
*/
scrollToTop() {
window.scrollTo(0, 0);
}
/**
* Scroll to a given id on the page
* #param string id The id to scroll to
* #return undefined
*/
scrollToId(id) {
document.getElementById(id).scrollIntoView();
}
/**
* Intentionally return null, as we never want this component actually visible.
* #return {[type]} [description]
*/
render() {
return null;
}
}
I've created a library called react-scroll-manager that addresses this issue and the other issues around scroll position with React Router. It uses this technique to navigate to hash links anywhere in the document without the need to wrap them individually. Simply wrap your Router component in a ScrollManager component:
class App extends React.Component {
constructor() {
super();
this.history = createHistory();
}
render() {
return (
<ScrollManager history={this.history}>
<Router history={this.history}>
...
</Router>
</ScrollManager>
);
}
}
You can link to any component with an id property:
<MyComponent id="mycomp">...</MyComponent>
Just include the id as a fragment in your Link target:
<Link to="#mycomp">...</Link>
The library is based on HTML5 and React 16, and it supports React Router 4 (and possibly earlier versions).