What should I do if the Class component of React cannot obtain the updated state in time? - web3-react

I have a piece of code like this.
Modal.confirm({
tilte: "title",
content: (
<>
<input onChange={
() => this.setState({ A: true });
}></input>
this.state.A && <div>text</div>
</>
)
})
The purpose is that after the input changes, <div>text</div> will be displayed, but it is found that state A cannot be obtained in time after the change, resulting in the content in div not being displayed. How can I modify it? ?

Question:
You are using imperative (Modal.confirm) to display the Modal, and the state change does not trigger the Modal to re-render.
solution:
Modal can be displayed declaratively, for example:
class Test extends React.Component {
state = {
A: false,
visible: false
};
handleClick = () => {
this.setState({ visible: true });
};
render() {
const { visible } = this.state;
return (
<>
<Button onClick={this.handleClick}>Show Modal</Button>
<Modal
visible={visible}
onCancel={() => this.setState({ visible: false })}
>
<input onChange={() => this.setState({ A: true })}></input>
{this.state.A && <div>text</div>}
</Modal>
</>
);
}
}
Extract the content displayed by Modal.confirm into a component
class ConfirmContent extends React.Component {
state = {
A: false
};
render() {
return (
<>
<input onChange={() => this.setState({ A: true })}></input>
{this.state.A && <div>text</div>}
</>
);
}
}
//
Modal.confirm({
tilte: "title",
content: <ConfirmContent />
});
//
Root cause: The update granularity of React is a fiber tree, such an imperative method often recreates a fiber tree (internally it will call createRoot, or ReactDom.render), your question That's when the update operation is triggered with a fiber on fiber tree1 (the fiber tree you created yourself in App.tsx), not with fiber on fiber2 Update (the fiber tree you created with confirm), a component corresponds to a fiber, they belong to that fiber tree after they are passed a createRoot or ReactDom.renders

Related

Setting the state from a callback in the parent component doesn't work

I'm trying to set the state from the parent component using a callback. This callback gets passed down to the child component that renders a material ui datatable. The callback responds onClick and passes some values to the callback. The problem is that setting the state with the values from the callback arguments doesn't work.
My assumption is that when the user clicks the button from the child component, it should invoke the callback function and pass the values I needed to set the state.
Parent Component:
export default function ViewJobs() {
const [type, setType] = useState('');
const [params, setParams] = useState({});
const callback = ({ cellValues, componentType, path }) => {
setType(componentType);
setParams(cellValues) // Sets the params with an object.
console.log(cellValues) // Displays the data I need in the console
history.push(path);
};
console.log(params) // Displays undefine in the console.
return(
<React.Fragment>
<TabPanel value={value} index={0} dir={theme.direction} >
<DataTable
jobs={job}
title='All'
parentCallback={callback}
/>
</TabPanel>
</React.Fragment>
);
}
Child Component
import React, { useEffect } from 'react';
export default function DataTable(props) {
const { jobs, parentCallback } = props;
const rows = jobs.payload;
const handleDiaryClick = (event, cellValues) => {
const params = {
cellValues,
componentType: 'diary',
path: "/view/jobs/diary"
};
parentCallback(params);
};
const renderDiaryElement = params => {
return (
<Button
variant="contained"
color="primary"
style={{ backgroundColor: "#000000" }}
onClick={(event) => {
handleDiaryClick(event, params);
}}
>
<MenuBookIcon />
</Button>
);
}
return (
<div
className={classes.root}
style={{ height: 400, width: '100%' }}
>
<DataGrid
rows={rows}
columns={columns}
pageSize={5}
//checkboxSelection
disableSelectionOnClick
/>
</div>
);
}
Since the state has been lifted up to the parent component, I'm under the impression that the code above should be working.
I tried to reproduce the issue but I couldn't replicate it.
Any advice or inputs are appreciated. Thanks.
After further checking on my codebase, I found that the history.push(path) located in my callback is causing the issue. I had to remove this line of code for it to work.

dynamically focus on input (children component) using useRef doesn't work

I have 'textRef' created in parent component and passed to children component using createRef().
I am trying to dynamically focus Input in children component on props change.
It does work when I test on localhost (chrome) but not on web view.
Any advice on this issue ?
Thanks !!
parent component
const UserAddressForm = ({ query }) => {
const textRef = useRef(null);
const {
state: { newAddress },
saveMultiData,
} = query;
useEffect(() => {
if (textRef && textRef.current) {
textRef.current.focus();
}
}, [newAddress.zipcode]);
const onAddressChange = (type, value) => {
const addressObj = {
...newAddress,
};
...
saveMultiData({ newAddress: addressObj });
};
return (
<InfoUl margin="21px 0px 0px;">
...
<TextField
ref={textRef}
label=""
name="addr2"
placeholder="상세주소 입력"
textField={newAddress}
onInputChange={e => onAddressChange('addr2', e.target.value)}
/>
</InfoUl>
);
};
children component
import React from 'react';
import PropTypes from 'prop-types';
import { LabelBox, InputBox } from './styles';
const TextField = React.forwardRef(
(
{
label = null,
name,
type,
placeholder,
textField,
onInputChange,
autoComplete,
pattern,
disabled,
width,
flex,
marginRight,
marginBottom,
},
ref,
) => (
<LabelBox width={width} marginBottom={marginBottom}>
{label !== null && <label htmlFor={label}>{label}</label>}
<InputBox flex={flex} marginRight={marginRight}>
<input
ref={ref}
type={type || 'text'}
name={name}
placeholder={placeholder}
value={textField[name] || ''}
onChange={onInputChange}
autoComplete={autoComplete || 'on'}
pattern={pattern || null}
disabled={!!disabled}
/>
</InputBox>
</LabelBox>
),
);
version
React v16.9.0
I resolved it by using input autoFocus attribute as well as ref attribute.
Since input appears dynamically on a button click, ref.focus won't work.
AutoFocus will get focus when the input appears.
Then Ref will get re-focus where the input is already placed on address re-search.

Draftjs mentions plugin with scroll

The issue is keydown/keyup aren't working when mention list popup has scroll , i can scroll using mouse but keyup/keydown aren't making the scroll move to the right position
This can be achieved by custom entry Component ->
const entryComponent = (props:any) => {
const { mention, isFocused, searchValue, ...parentProps } = props;
const entryRef = React.useRef<HTMLDivElement>(null);
useEffect(() => {
if (isFocused) {
if (entryRef.current && entryRef.current.parentElement) {
entryRef.current.scrollIntoView({
block: 'nearest',
inline: 'center',
behavior: 'auto'
});
}}
}, [isFocused]);
return (
<>
<div
ref={entryRef}
role='option'
aria-selected={(isFocused ? 'true' : 'false')}
{...parentProps}>
<div className={'mentionStyle'}>
{mention.name}
</div>
</div>
</> );
};

React Search Filter of Objects not filtering

I'm trying to create a search filter that will filter through facility names that lives in an array of objects.If I hard code an array into the state the filter works, but I need it to drab the info from props. The filtered list is being generated and showing all of the names on the screen but when I type it the textbox to filter nothing happens. What have I overlooked?
class FacilitySearch extends React.Component {
constructor(props) {
super(props);
this.state = {
search: ""
};
}
componentDidMount() {
this.props.dispatch(actions.getFacilitiesList());
}
//The subsr limits the # of characters a user can enter into the seach box
updateSearch = event => {
this.setState({ search: event.target.value.substr(0, 10) });
};
render() {
if (!this.props.facilityList) {
return <div>Loading...</div>
}
let filteredList = this.props.facilityList;
filteredList.filter(facility => {
return facility.facilityName.toLowerCase().indexOf(this.state.search.toLowerCase()) !== -1;
});
return (
<div>
<input
type="text"
value={this.state.search}
onChange={this.updateSearch.bind(this)}
placeholder="Enter Text Here..."
/>
<ul>
{filteredList.map(facility => {
return <li key={facility.generalIdPk}>{facility.facilityName}</li>;
})}
</ul>
</div>
);
}
}
const mapStateToProps = state => ({
facilityList: state.facilityList.facilityList
});
export default connect(mapStateToProps)(FacilitySearch)
The problem is that you are not storing the return value of filter in any variable.
You should do something like:
let filteredList = this.props.facilityList.filter(facility => {
return facility.facilityName.toLowerCase().indexOf(this.state.search.toLowerCase()) !== -1;
});
From MDN:
The filter() method creates a new array with all elements that pass the test implemented by the provided function.

Open only one React-Bootstrap Popover at a time

I'm using React-Bootstrap Popover and I was wondering if there is any builtin property that I can add either to Popover itself or to OverlayTrigger so only one popover will display at a time.
You can try rootClose props which will trigger onHide when the user clicks outside the overlay. Please note that in this case onHide is mandatory. e.g:
const Example = React.createClass({
getInitialState() {
return { show: true };
},
toggle() {
this.setState({ show: !this.state.show });
},
render() {
return (
<div style={{ height: 100, position: 'relative' }}>
<Button ref="target" onClick={this.toggle}>
I am an Overlay target
</Button>
<Overlay
show={this.state.show}
onHide={() => this.setState({ show: false })}
placement="right"
container={this}
target={() => ReactDOM.findDOMNode(this.refs.target)}
rootClose
>
<CustomPopover />
</Overlay>
</div>
);
},
});
I managed to do this in a somewhat unconventional manner. You can create a class which tracks the handlers of all of your tooltips:
export class ToolTipController {
showHandlers = [];
addShowHandler = (handler) => {
this.showHandlers.push(handler);
};
setShowHandlerTrue = (handler) => {
this.showHandlers.forEach((showHandler) => {
if (showHandler !== handler) {
showHandler(false);
}
});
handler(true);
};
}
Then in your tooltip component:
const CustomToolTip = ({
children,
controller,
}: CustomToolTipProps) => {
const [showTip, setShowTip] = useState(false);
useEffect(() => {
if (!controller) return;
controller.addShowHandler(setShowTip);
}, []);
return (
<OverlayTrigger
onToggle={(nextShow) => {
if (!nextShow) return setShowTip(false);
controller ? controller.setShowHandlerTrue(setShowTip) : setShowTip(true);
}}
show={showTip}
overlay={(props: any) => <Overlay {...props}/>}
>
<div className={containerClassName}>{children}</div>
</OverlayTrigger>
);
};
It's not really a 'Reacty' solution but it works quite nicely. Note that the controller is completely optional here so if you wanted you could not pass that in and it would then behave like a normal popover allowing multiple tooltips at once.
Basically to use it you can create another component and instantiate a controller which you pass into CustomToolTip. Then for any tooltips which are rendered using that component, only 1 will appear at a time.
STEP 1: we declare a currentPopover variable that contain current popover id, so we are sure that there is only one popover at a time.
const [currentPopover, setCurrentPopover] = useState(null);
STEP 2: the OverlayTrigger from react-bootstrap has properties to set popover state manually. If the currentPopover variable is equal to popover id then we show the popover.
show={currentPopover === `${i}`}
STEP 3: the OverlayTrigger from react-bootstrap has properties to handle popover click manually. On click we update the currentPopover variable with the new id, except if we clicked on the current.
onToggle={() => {
if( currentPopover === `${i}` )
setCurrentPopover(null)
else
setCurrentPopover(`${i}`)
}}
RESULT:
const [currentPopover, setCurrentPopover] = useState(null);
<OverlayTrigger
trigger="click"
show={currentPopover == `${i}`}
onToggle={() => {
if( currentPopover == `${i}` )
setCurrentPopover(null)
else
setCurrentPopover(`${i}`)
}}
>
(I use ${i} as id cause my OverlayTrigger is in a loop where i is the index)

Resources