React Error boundary unexpected behavior with material ui popover - javascript

There is a notifications component within a popover that makes a fetch call.
The connection fails and cannot reach the resources.
The error boundary component wraps the notifications component.
It is expected that after the connection fails, will show a custom component that says there is a problem along with a button to retry the connection.
However, the behavior of the error boundary is: It pauses while the popover is open (nothing is shown) and then just a few moments before it closes, it displays the custom component to retry the connection and the popover finishes closing.
export default function PopOver() {
const [anchorNotificationEl, setAnchorNotificationEl] = React.useState(null);
const popoverNotiRef = React.useRef(null);
const isNotificationOpen = Boolean(anchorNotificationEl);
const handleClick = (event) => {
setAnchorNotificationEl(event.currentTarget);
};
const handleNotificationClose = () => {
setAnchorNotificationEl(null);
};
const notificationId = 'notification-popover';
return (
<>
<IconButtonCustom
aria-label="show 17 new notifications"
color="inherit"
onClick={handleClick}
id="notifications-icon"
>
<Badge
badgeContent={0}
color="secondary"
>
<NotificationsIcon
fontSize="large"
/>
</Badge>
</IconButtonCustom>
<Popover
ref={popoverNotiRef}
id={notificationId}
open={isNotificationOpen}
anchorEl={anchorNotificationEl}
onClose={handleNotificationClose}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'center',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'center',
}}
elevation={1}
>
<Notifications popoverRef={popoverNotiRef.current} />
</Popover>
</>
);
}
class ErrorBoundary extends React.Component <Props, State> {
constructor(props) {
super(props);
this.state = { error: null };
}
static getDerivedStateFromError(error): State {
return { error };
}
componentDidCatch(error) {
this.setState({ error });
}
render() {
const { children, fallback } = this.props;
const { error } = this.state;
if (error) {
if (fallback) {
return fallback;
}
return <p>Something is wrong</p>;
}
return children;
}
}
export default function NotificationWrapper({ popoverRef }: NWProps) {
const [queryReference, loadQuery] = useQueryLoader(NotificationsQuery);
useEffect(() => {
loadQuery({
count: 5,
}, { fetchPolicy: 'store-and-network' });
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<>
<ErrorBoundary fallback={<RetryComponent loadQuery={loadQuery}/>}>
{queryReference && (
<Suspense fallback={<Loading />}>
<NotificationsIntermediate queryRef={queryReference} popoverRef={popoverRef} />
</Suspense>
)}
</ErrorBoundary>
</>
);
}

Related

Function declared inside and outside of a component behaves differently in a Functional Component but works same in Class Component

I am using MaterialUi Snackbar with Slide transition.
Expected Behaviour: Snackbar should slide in the provided direction and retreat on close.
This code is working.
Here's the code:
import React, { useEffect, useState } from "react";
import Snackbar from "#mui/material/Snackbar";
import Slide from "#mui/material/Slide";
import Alert from "#mui/material/Alert";
const Transition = (newprops) => {
console.log("CustomMUISnackBar Transition props: ", newprops);
return <Slide {...newprops} direction="up" />;
};
function CustomMUISnackBar(props) {
const [open, setOpen] = useState(false);
useEffect(() => {
setOpen(props.open);
}, [props.open]);
return (
<Snackbar
autoHideDuration={1000}
TransitionComponent={Transition}
anchorOrigin={{ vertical: "top", horizontal: "center" }}
open={open}
onClose={() => {
setOpen(false);
props.onClose();
}}
key="Slide"
>
<Alert
onClose={() => {
setOpen(false);
props.onClose();
}}
severity="success"
sx={{ width: "100%" }}
>
<i style={{ color: "blue" }}>{props.updatedUserName}</i>'s details
updated!
</Alert>
</Snackbar>
);
}
export default CustomMUISnackBar;
But if I place Transition function inside the functional component's body then only the first part of the Slide works as in it shows the snackbar opening with sliding but disappears without sliding.
function CustomMUISnackBar(props) {
const [open, setOpen] = useState(false);
const Transition = (newprops) => {
console.log("CustomMUISnackBar Transition props: ", newprops);
return <Slide {...newprops} direction="up" />;
};
return ();
}
export default CustomMUISnackBar;
It only happens when I use the Snackbar in a separate Component.js and then use it somewhere. If I use it directly it works whether I place the Transition function inside the component or outside it.
Below code works for both buttons:
import React, { useState } from "react";
import Snackbar from "#mui/material/Snackbar";
import Slide from "#mui/material/Slide";
import Alert from "#mui/material/Alert";
const OutsideTransition = (tprops) => {
console.log("Outside Transition props: ", tprops);
return <Slide {...tprops} direction="up" />;
};
function CustomMUISnackBar(props) {
const [state, setState] = useState({
open: false,
transition: undefined
});
const InsideTransition = (tprops) => {
console.log("Outside Transition props: ", tprops);
return <Slide {...tprops} direction="up" />;
};
return (
<div>
<Snackbar
autoHideDuration={1000}
TransitionComponent={state.transition}
anchorOrigin={{ vertical: "top", horizontal: "center" }}
open={state.open}
onClose={() => setState({ ...state, open: false })}
key={state.transition?.name}
>
<Alert
onClose={() => setState({ ...state, open: false })}
severity="success"
>
<i style={{ color: "blue" }}>"Update Success!"</i>'s details updated!
</Alert>
</Snackbar>
<button
onClick={() => setState({ open: true, transition: OutsideTransition })}
>
SanckBar OutsideTransition
</button>
<button
onClick={() => setState({ open: true, transition: InsideTransition })}
>
SanckBar InsideTransition
</button>
</div>
);
}
export default CustomMUISnackBar;
If I use class Component in a separate component.js file, then also it works as expected.
This code works:
import React, { useEffect, useState } from "react";
import Snackbar from "#mui/material/Snackbar";
import Slide from "#mui/material/Slide";
import Alert from "#mui/material/Alert";
const Transition = (newprops) => {
console.log("CustomMUISnackBar Transition props: ", newprops);
return <Slide {...newprops} direction="up" />;
};
class CustomMUISnackBarClass extends React.Component {
constructor(props) {
super(props);
this.state = { open: props.open };
}
TransitionMethod = (newprops) => {
console.log("CustomMUISnackBar Transition props: ", newprops);
return <Slide {...newprops} direction="up" />;
};
render() {
return (
<Snackbar
autoHideDuration={1000}
TransitionComponent={this.TransitionMethod}
anchorOrigin={{ vertical: "top", horizontal: "center" }}
open={this.state.open}
onClose={() => {
this.setState({ open: false });
this.props.onClose();
}}
key="Slide"
>
<Alert
onClose={() => {
this.setState({ open: false });
this.props.onClose();
}}
severity="success"
sx={{ width: "100%" }}
>
<i style={{ color: "blue" }}>{this.props.updatedUserName}</i>'s
details updated!
</Alert>
</Snackbar>
);
}
}
export default CustomMUISnackBarClass;
Above code works even if TransitionComponent={this.TransitionMethod} is replaced with TransitionComponent={Transition}.
So, my question is how the functionality is getting differed in functional and class component. What is changing if a function is declared inside a functional component or outside as compared to behaviour when a functional component is defined in a component.js file?
Here's the code at codesandbox: https://codesandbox.io/s/transitionsnackbar-eillp. Wait for few seconds before clicking on other buttons.

How can I make a toggle hide all components on a page so that only a background image in visible in react-native? I

I have a camera view which shows the camera view as the background. On that page I have a joystick and some sliders to control exposure and move around the view. How can I make a toggle to disable the joystick and sliders so that the view is unobstructed?
Below is my code. What I need is a toggle which can hide the sliders and joystick which are in this view. So when the toggle is selected it just hides everything and only shows the mjpeg stream and when unselected it shows the sliders and joystick again.
import { Layout } from "components/common";
import { Box, Text, Icon, IconButton, VStack, Button } from "native-base";
import React, {
cloneElement,
useCallback,
useLayoutEffect,
useState,
} from "react";
import * as ScreenOrientation from "expo-screen-orientation";
import { useFocusEffect, useNavigation } from "#react-navigation/core";
import { Feather } from "#expo/vector-icons";
import { KorolJoystick, KorolSlider, Stepper } from "components/ui";
import { SettingsModal } from "components/CameraView";
import { WebView } from "react-native-webview";
import { useAppSelector } from "redux-store/store";
// import socket from "tools/poseCam";
var ip = "10.42.0.1";
let ws = new WebSocket(`ws://${ip}:2100/ws`);
ws.onopen = () => {
console.log("connection established");
};
const CameraView = () => {
const nav = useNavigation();
const [showSettings, setShowSettings] = useState(false);
const { controlType, step } = useAppSelector((state) => state.cameraSettings);
useLayoutEffect(() => {
ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.LANDSCAPE);
}, []);
const [x, setX] = useState(0);
useFocusEffect(
useCallback(() => {
return () => {
ScreenOrientation.unlockAsync();
};
}, [])
);
return (
<Layout>
<Box flex={1} position="relative">
<WebView
originWhitelist={["*"]}
scrollEnabled={false}
scalesPageToFit
style={{
backgroundColor: "transparent",
}}
containerStyle={{
flex: 1,
}}
source={{
uri: "http://10.42.0.1:2101/mjpeg",
// uri: "http://mjpeg.sanford.io/count.mjpeg",
}}
/>
<VStack
position="absolute"
display="flex"
width="full"
flexDirection="row"
padding="5"
alignItems="center"
>
<IconButton
marginRight="auto"
icon={<Icon as={Feather} color="primary.500" name="arrow-left" />}
colorScheme="primary"
onPress={() => nav.goBack()}
/>
<Button
size="md"
startIcon={<Icon size="xs" as={Feather} name="plus" />}
backgroundColor="primary.500:alpha.40"
>
Add Waypoint
</Button>
<IconButton
icon={<Icon as={Feather} color="primary.500" name="settings" />}
colorScheme="primary"
onPress={() => setShowSettings(true)}
/>
</VStack>
<VStack position="absolute" bottom="30" left="30" width="56" space={2}>
<VStack>
{/* <Text>Focus</Text>
<KorolSlider /> */}
</VStack>
<VStack>
{/* <Text>Zoom</Text>
<KorolSlider /> */}
</VStack>
<VStack>
<Text>Slide</Text>
<KorolSlider
value={x}
onChange={(slider) => {
setX(slider);
console.log(slider);
}}
onDragEnd={() => {
let message = {
from: "FromClient",
data: {
case: "joystick",
joystick: {
slider: x,
},
},
};
ws.send(JSON.stringify(message));
}}
/>
</VStack>
</VStack>
<Box position="absolute" right="30" bottom="30">
{controlType === "joystick" ? (
<KorolJoystick
onMove={(values) => {
console.log(values);
let message = {
from: "FromClient",
data: {
case: "joystick",
joystick: {
angle: values.angle.radian,
force: values.force,
},
},
};
ws.send(JSON.stringify(message));
}}
onStop={(values) => {
let message = {
from: "FromClient",
data: {
case: "joystick",
joystick: {
angle: 0,
force: 0,
},
},
};
ws.send(JSON.stringify(message));
}}
/>
) : (
<Stepper
onPress={(dir) => {
const vel = 5;
const tiltVel = 5;
// if (Math.abs(step)<=1){
// vel=1
// }
if (dir === "up") {
const message = {
from: "FromClient",
data: {
case: "controlCMDRel",
moveData: { tilt: [step, vel] },
},
};
console.log(message);
ws.send(JSON.stringify(message));
}
if (dir === "down") {
const message = {
from: "FromClient",
data: {
case: "controlCMDRel",
moveData: { tilt: [-step, vel] },
},
};
ws.send(JSON.stringify(message));
}
if (dir === "left") {
const message = {
from: "FromClient",
data: {
case: "controlCMDRel",
moveData: { pan: [-step, vel] },
},
};
ws.send(JSON.stringify(message));
}
if (dir === "right") {
const message = {
from: "FromClient",
data: {
case: "controlCMDRel",
moveData: { pan: [step, vel] },
},
};
ws.send(JSON.stringify(message));
}
}}
/>
)}
</Box>
</Box>
<SettingsModal
show={showSettings}
onClose={() => setShowSettings(false)}
/>
</Layout>
);
};
export default CameraView;
I cant see where (on which element) your background image is set,but you can render any component conditionally like this:
return(
<div>
I am wrapper
{isVisible && <div>I am visible if isVisible is true</div>}
</div>
)
and you can set background image on the parent div, so when the child div is not visible you can see the background image.

React-Native Router Flux with Netwoking and Sceenchooser

I have a problem.
My App work right now but not like how I want.
App.js
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
data: [],
isLoading: true,
};
}
componentDidMount() {
fetch('https://reactnative.dev/movies.json')
.then((response) => response.json())
.then((json) => {
this.setState({ data: json.movies });
})
.catch((error) => console.error(error))
.finally(() => {
this.setState({ isLoading: false });
});
}
render() {
const { data, isLoading } = this.state;
const goToPageTwo = () => Actions.sw({text: 'Hello World!'});
return (
<View style={{ flex: 1, padding: 24 }}>
{isLoading ? <ActivityIndicator/> : (
<FlatList
data={data}
keyExtractor={({ id }, index) => id}
renderItem={({ item }) => (
<TouchableOpacity onPress={() => { Actions.hp({text: item.id}) }}>
<Text>{item.title}</Text>
</TouchableOpacity>
)}
/>
)}
</View>
);
}
};
index.js / Route.js in my case sm.js
import app from './App';
import sw from './StarWars'
import bttf from './BacktotheFuture'
import hP from './handlePage';
import tM from './theMatrix';
import iN from './Inception';
const SceneManager = () => (
<Router>
<Scene>
<Scene key='home'
component={app}
title='BTour'
initial
/>
<Scene key='sw'
component={sw}
title='star wars'
/>
<Scene key='hp'
component={hP}
title='BTour'
/>
<Scene key='bttf'
component={bttf}
title='Back to the future'
/>
<Scene key='tM'
component={tM}
title='MAtrix'
/>
<Scene key='iN'
component={iN}
title='Inception'
/>
</Scene>
</Router>
)
export default SceneManager;
and my Handlepage.js
import StarWars from './StarWars';
import BTTF from './BacktotheFuture';
import TM from './theMatrix';
import IN from './Inception';
export default class handlePage extends Component {
renderElement() {
if(this.props.text ==1) {
return <StarWars/>
}
if (this.props.text ==2) {
return <BTTF/>
}
if (this.props.text ==3) {
return <TM/>
}
if(this.props.text == 4) {
return <IN/>
}
return null;
}
render(){
return(
this.renderElement()
)
}
};
NOW MY PROBLEM:
I want to use the Scene what I have defined in my Router class. For example when I press the first button in the Home Screen then "Star Wars" will be open but not the Component what I write in the route class.
Can I take the Component Scene from Route.js to the HandlePage.js in the If-Statemant OR can I put the If Statemant in the Router class.
Right now only the script in the IF-Statemant will open.
I don't think defining handlePage as class is necessary, you can just declare a function to navigate to your scene depending on item.id
Handlepage.js
import { Actions } from 'react-native-router-flux';
export const navigateByItemId = (id) => {
switch (id) {
case 1:
Actions.jump('sw');
break;
case 2:
Actions.jump('bttf');
break;
case 3:
Actions.jump('tM');
break;
default:
Actions.jump('iN');
}
};
And in App.js you can call this utility function to decide where to navigate
import { navigateByItemId } from './Handlepage';
...
<TouchableOpacity onPress={() => { navigateByItemId(item.id) }}>
<Text>{item.title}</Text>
</TouchableOpacity>
You can delete handlePage declaration in your router:
<Scene key='hp' //you can delete this code
component={hP}
title='BTour'
/>

react native redux state update but not rendering in a component

I have 2 components named A and B, In B I have a list of Languages to be selected and updated in component A. I am using Redux for state management when I change the Language from the list I can see that the states are updated(using redux-logger to get logs). But the problem is when I go back to Component A using react-navigation the updated state value is not updated I can see only the old value of state only
ScreenA.js
class ScreenA extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedLanguage: this.props.state.defaultLangName
}
}
render(
return (
<Container>
<Content style={{ backgroundColor: item.backgroundColor }}>
<View style={styles.logoContainer}>
<Text>test page</Text>
</View>
<View style={styles.cardParent}>
<View style={styles.card}>
<Item style={styles.listItem}>
<Button transparent style={styles.contentChecked} onPress={() => this._openLang()}>
<Text style={styles.listbtn}>{this.state.selectedLanguage}</Text>
<Icon name='ios-arrow-forward' style={styles.iconChecked}/>
</Button>
</Item>
</View>
</View>
</Content>
</Container>
);
)
}
export default connect(
state => ({ state : state.introauthenticate }),
dispatch => ({
actions: bindActionCreators(introActions, dispatch)
})
)(ScreenA);
ScreenB.js
class ScreenB extends Component {
constructor(props){
super(props);
this.state = {
langChecked: '0'
};
}
FlatListItemSeparator = () => {
return (
<View
style={{
height: 1,
width: "100%",
backgroundColor: "#999",
}}
/>
);
}
_selectLanguage (val, name){
this.setState({ langChecked: val });
this.props.actions.changeLanguage(val,name, this.props.navigation.navigate);
//this.props.navigation.navigate('Intro');
}
renderItem = (item )=> {
return(
<TouchableHighlight
style={styles.boxSelect}
underlayColor="transparent"
onPress={() => this._selectLanguage(item.Value, item.Name)}
>
<View style={styles.contentChecked}>
<Text style={styles.item} > {item.Name} </Text>
{this.state.langChecked === item.Value && <Icon name="ios-checkmark-circle" style={styles.iconChecked}/>}
</View>
</TouchableHighlight>
)
}
render() {
return (
<Container>
<Header>
<Left>
<Button transparent onPress={() => this.props.navigation.goBack()}>
<Icon name='ios-arrow-back' />
</Button>
</Left>
<Body>
<Title>Languages</Title>
</Body>
<Right />
</Header>
<Content>
<FlatList
data={ langs }
keyExtractor={(item) => item.Value}
ItemSeparatorComponent = {this.FlatListItemSeparator}
renderItem={({item}) => this.renderItem(item)}
/>
</Content>
</Container>
);
}
}
export default connect(
state => ({ state: state.introauthenticate }),
dispatch => ({
actions: bindActionCreators(introActions, dispatch)
})
)(ScreenB);
reducer.js
export const CHANGE_LANGUAGE = "CHANGE_LANGUAGE";
export function changeLanguage(langValue,langName,navigateTo) { // Fake authentication function
return async dispatch => {
try {
if (langValue && langName) { //If the email and password matches
const session = { langValue : langValue,langName:langName } // Create a fake token for authentication
setTimeout(() => { // Add a delay for faking a asynchronous request
dispatch(setLanguage(session)) // Dispatch a successful sign in after 1.5 seconds
navigateTo('Intro') // If successfull login navigate to the authenticated screen
}, 1500)
}
} catch (err) { // When something goes wrong
console.log(err)
}
};
}
function setLanguage(lang){
return {
type: types.CHANGE_LANGUAGE,
data: {
lang: lang
}
};
}
const initialsliderState = {
defaultLang:'en',
defaultLangName:'English',
};
export default function introauthenticate(state = initialsliderState, action = {}) {
switch (action.type) {
case types.CHANGE_LANGUAGE:
return {
...state,
defaultLang: action.data.lang.langValue,
defaultLangName: action.data.lang.langName,
};
default:
return state;
}
}
Logger:
LOG %c prev state "introauthenticate": {"defaultLang": "nl", "defaultLangName": "Deutsch", "isAuthSlider": false, "requestingsliderRestore": false}
LOG %c action {"data": {"lang": {"langName": "English", "langValue": "en"}}, "type": "CHANGE_LANGUAGE"}
LOG %c next state "introauthenticate": {"defaultLang": "en", "defaultLangName": "English", "isAuthSlider": false, "requestingsliderRestore": false}}
You are initializing the state of ScreenA with a value passed as a prop and never update it. As you are using redux to store the current language you do not need any state in ScreenA. When you connect a component you pass it the relevant data from your store as props. It seems like you are trying to "override" the state by passing it in as state but that does not update the state as it will be in this.props.state rather then in this.state. What you need to do is to just pass the language as a prop to ScreenA:
export default connect(
state => ({ selectedLanguage : state.introauthenticate.defaultLang }),
dispatch => ({
actions: bindActionCreators(introActions, dispatch)
})
)(ScreenA);
and then read the selected language from props:
<Text style={styles.listbtn}>{this.props.selectedLanguage}</Text>
When you update your store the component will re-render with the new language. You do not need any additional state in the component itself for data that you have in your redux store.

How to call child component method from parent

In my Reactjs app, I need to have a parent component (a wizard) named Wizard.js and a number of child components (steps of the wizard) named PrimaryForm.js, SecondaryForm.js etc. They all are Class based components with some local validation functions.
Previous and Next buttons to advance the steps, reside in the Wizard.js.
To advance the next step of the wizard, I'm trying to call a method from PrimaryForm. I checked similar questions in Stackoverflow; tried using ref or forwardRef, but I could not make it work. I currently receive "TypeError: Cannot read property 'handleCheckServer' of null" error.
Below are my parent and child classes. Any help about what I would be doing wrong is appreciated.
Wizard.js:
import React, { Component } from 'react';
...
const getSteps = () => {
return [
'Info',
'Source Details',
'Target Details',
'Configuration'
];
}
class Wizard extends Component {
constructor(props) {
super(props);
this.firstRef = React.createRef();
this.handleNext = this.handleNext.bind(this);
this.state = {
activeStep: 1,
}
}
componentDidMount() {}
handleNext = () => {
if (this.state.activeStep === 1) {
this.firstRef.current.handleCheckServer(); <<<<<<<<<<<<<<<<< This is where I try to call child method
}
this.setState(state => ({
activeStep: state.activeStep + 1,
}));
};
handleBack = () => {
this.setState(state => ({
activeStep: state.activeStep - 1,
}));
};
handleReset = () => {
this.setState({
activeStep: 0,
});
};
render() {
const steps = getSteps();
const currentPath = this.props.location.pathname;
const { classes } = this.props;
return (
<React.Fragment>
<CssBaseline />
<Topbar currentPath={currentPath} />
<div className={classes.root}>
<Grid container spacing={2} justify="center" direction="row">
<Grid container spacing={2} className={classes.grid} justify="center" direction="row">
<Grid item xs={12}>
<div className={classes.topBar}>
<div className={classes.block}>
<Typography variant="h6" gutterBottom>Wizard</Typography>
<Typography variant="body1">Follow the wizard steps to create a configuration.</Typography>
</div>
</div>
</Grid>
</Grid>
<Grid container spacing={2} alignItems="center" justify="center" className={classes.grid}>
<Grid item xs={12}>
<div className={classes.stepContainer}>
<div className={classes.bigContainer}>
<Stepper classes={{ root: classes.stepper }} activeStep={this.state.activeStep} alternativeLabel>
{steps.map(label => {
return (
<Step key={label}>
<StepLabel>{label}</StepLabel>
</Step>
);
})}
</Stepper>
</div>
<PrimaryForm ref={this.firstRef} />
</div>
</Grid>
</Grid>
<Grid container spacing={2} className={classes.grid}>
<Grid item xs={12}>
<div className={classes.flexBar}>
<Tooltip title="Back to previous step">
<div>
<Button variant="contained"
disabled={(this.state.activeStep === 0)}
className={classes.actionButton}
onClick={this.handleBack}
size='large'>
<BackIcon className={classes.rightIcon} />Back
</Button>
</div>
</Tooltip>
<Tooltip title="Proceed the next step">
<div>
<Button
variant="contained" className={classes.actionButton}
color="primary"
size='large'
disabled={!(!this.state.isFormValid || this.state.isTestWaiting)}
onClick={this.handleNext}>
<ForwardIcon className={this.props.classes.rightIcon}/>Next</Button>
</div>
</Tooltip>
<Tooltip title="Cancel creating new configuration">
<Button variant="contained" color="default" className={classes.actionButton}
component={Link} to={'/configs'} style={{ marginLeft: 'auto' }}>
<CancelIcon className={classes.rightIcon} />Cancel
</Button>
</Tooltip>
</div>
</Grid>
</Grid>
</Grid>
</div>
</React.Fragment>
)
}
}
export default withRouter(withStyles(styles)(Wizard));
PrimaryForm.js:
import React, { Component } from 'react';
...
class PrimaryForm extends Component {
constructor(props) {
super(props);
this.handleCheckServer = this.handleCheckServer.bind(this);
this.state = {
hostname: {
value: "localhost",
isError: false,
errorText: "",
},
serverIp: {
value: "127.0.0.1",
isError: false,
errorText: "",
},
isFormValid: true,
isTestValid: true,
testErrorMessage: "",
isTestWaiting: false,
};
}
componentDidMount() { }
handleCheckServer() {
alert('Alert from Child. Server check will be done here');
}
evaluateFormValid = (prevState) => {
return ((prevState.hostname.value !== "" && !prevState.hostname.isError) &&
(prevState.serverIp.value !== "" && !prevState.serverIp.isError));
};
handleChange = event => {
var valResult;
switch (event.target.id) {
case 'hostname':
valResult = PrimaryFormValidator.validateHostname(event.target.value, event.target.labels[0].textContent);
this.setState({
...this.state,
hostname:
{
value: event.target.value,
isError: valResult.isError,
errorText: valResult.errorText,
},
});
break;
case 'serverIp':
valResult = PrimaryFormValidator.validateIpAddress(event.target.value, event.target.labels[0].textContent);
this.setState({
...this.state,
serverIp:
{
value: event.target.value,
isError: valResult.isError,
errorText: valResult.errorText,
}
});
break;
default:
}
this.setState(prevState => ({
...prevState,
isFormValid: this.evaluateFormValid(prevState),
}));
}
render() {
const { classes } = this.props;
return (
<React.Fragment>
<div className={classes.bigContainer}>
<Paper className={classes.paper}>
<div>
<div>
<Typography variant="subtitle1" gutterBottom className={classes.subtitle1} color='secondary'>
Primary System
</Typography>
<Typography variant="body1" gutterBottom>
Information related with the primary system.
</Typography>
</div>
<div className={classes.bigContainer}>
<form className={classes.formArea}>
<TextField className={classes.formControl}
id="hostname"
label="FQDN Hostname *"
onChange={this.handleChange}
value={this.state.hostname.value}
error={this.state.hostname.isError}
helperText={this.state.hostname.errorText}
variant="outlined" autoComplete="off" />
<TextField className={classes.formControl}
id="serverIp"
label="Server Ip Address *"
onChange={this.handleChange}
value={this.state.serverIp.value}
error={this.state.serverIp.isError}
helperText={this.state.serverIp.errorText}
variant="outlined" autoComplete="off" />
</form>
</div>
</div>
</Paper>
</div>
</React.Fragment>
)
}
}
export default withRouter(withStyles(styles)(PrimaryForm));
(ps: I would like to solve this without another framework like Redux, etc if possible)
Example in Typescript.
The idea is that the parent passes its callback to the child. The child calls the parent's callback supplying its own e.g. child callback as the argument. The parent stores what it got (child callback) in a class member variable and calls it later.
import * as React from 'react'
interface ICallback {
(num: number): string
}
type ChildProps = {
parent_callback: (f: ICallback) => void;
}
class Child extends React.Component {
constructor(props: ChildProps) {
super(props);
props.parent_callback(this.childCallback);
}
childCallback: ICallback = (num: number) => {
if (num == 5) return "hello";
return "bye";
}
render() {
return (
<>
<div>Child</div>
</>
)
}
}
class Parent extends React.Component {
readonly state = { msg: "<not yet set>" };
letChildRegisterItsCallback = (fun: ICallback) => {
this.m_ChildCallback = fun;
}
callChildCallback() {
const str = this.m_ChildCallback? this.m_ChildCallback(5) : "<callback not set>";
console.log("Child callback returned string: " + str);
return str;
}
componentDidMount() {
this.setState((prevState) => { return {...prevState, msg: this.callChildCallback()} });
}
render() {
return (
<>
<Child {...{ parent_callback: this.letChildRegisterItsCallback }} />
<div>{this.state.msg}</div>
</>
)
}
m_ChildCallback: ICallback | undefined = undefined;
}
P.S.
The same code in Javascript. The only difference is that interface, type, readonly and type annotations are taken out. Pasting into here confirms it's a valid ES2015 stage-2 code.
class Child extends React.Component {
constructor(props) {
super(props);
props.parent_callback(this.childCallback);
}
childCallback = (num) => {
if (num == 5) return "hello";
return "bye";
}
render() {
return (
<>
<div>Child</div>
</>
)
}
}
class Parent extends React.Component {
state = { msg: "<not yet set>" };
letChildRegisterItsCallback = (fun) => {
this.m_ChildCallback = fun;
}
callChildCallback() {
const str = this.m_ChildCallback? this.m_ChildCallback(5) : "<callback not set>";
console.log("Child callback returned string: " + str);
return str;
}
componentDidMount() {
this.setState((prevState) => { return {...prevState, msg: this.callChildCallback()} });
}
render() {
return (
<>
<Child {...{ parent_callback: this.letChildRegisterItsCallback }} />
<div>{this.state.msg}</div>
</>
)
}
m_ChildCallback = undefined;
}

Categories