How can I pass component object to container in React.js? - javascript

My goal is to reuse modal from bootstrap in multiple components. I have a question how can I pass a component to container?
<ModalContainer
title="Password recovery"
body="<LoginRecoverForm someprophere='' />"
/>
This gives an error:
Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: object. Check the render method of `ModalLoginRecover`.
Here's simple container:
import { Modal } from 'react-bootstrap/lib/'
var React = require('react')
var ModalContainer = React.createClass({
getInitialState() {
return { showModal: true };
},
close() {
this.setState({ showModal: false });
browserHistory.push('/login');
},
open() {
this.setState({ showModal: true });
},
render: function () {
return (
<div>
<Modal show onHide={this.close}>
<Modal.Header closeButton>
<Modal.Title>{this.props.title}</Modal.Title>
</Modal.Header>
<Modal.Body>
{this.props.body}
</Modal.Body>
</Modal>
{this.props.children}
</div>
)}});
module.exports = ModalContainer;
Here's simple component:
import {Modal, HelpBlock, FormGroup, Button, FormControl } from 'react-bootstrap/lib/'
var React = require('react');
import { Router, ReactRouter, browserHistory } from 'react-router';
var ModalContainer = require('../../containers/ModalDialog')
function FieldGroup({ id, help, type, placeholder }) {
return (
<FormGroup controlId={id}>
<FormControl type={type} placeholder={placeholder} />
{help && <HelpBlock>{help}</HelpBlock>}
</FormGroup>
);
}
function LoginRecoverForm(){
return (
<form method="POST" action="">
<FieldGroup
id="formControlsEmail"
type="email"
placeholder="Enter your E-mail"
/>
<Button type="submit">Recover!</Button>
</form>
)};
var ModalLoginRecover = React.createClass({
render: function () {
return (
<div>
<ModalContainer
title="Password recovery"
body="<LoginRecoverForm someprophere='' />"
/>
</div>
)}});
module.exports = ModalLoginRecover;

Try:
<ModalContainer
title="Password recovery"
body={(() => {return <LoginRecoverForm someprophere='' />})()}
/>

Since JSX is just javascript, you can pass it in as props, just like any JS expression:
<ModalContainer
title="Password recovery"
body={<LoginRecoverForm someprophere=""/>}
/>

What you can do is pass the component as a child like this:
<ModalContainer
title="Password recovery"
body=""><LoginRecoverForm someprophere='' />
</ModalContainer>
React will put LoginRecoveryForm component at the place of {this.props.children} in ModalContainer

You can simply insert components between the tag. The inserted component will treated as default children prop. Like in this example code, <Container> is parent my component and I am trying to add <Routes>...</Routes> as its children.
const RouterProfile = () => {
return(
<Container>
<Routes>
<Route path="/" element={<ProfilePage />} />
</Routes>
</Container>
)
}
Now, You can see in this below code that child component is called using children prop that we are recieving from parent. Or you can log in console and see what are the argument you are recieving.
const Container = ({children}) => {
return(
<div className="d-flex flex-column" style={{width:"100vw", height:"100vh"}}>
<div class="flex-grow-1" style={{overflow : "scroll"}}>
{children}
</div>
</div>
)
}

Related

Set value of prop in child component and pass value back to state of app.js, react

I feel like this is pretty simple and it's just my lack of experience with react.js holding me up, but I have my app.js file which has a state value isUserLoggedIn and I'm passing this into my Login.js file as a prop.
The values is getting to the login page because I can use a conditional there to render or console.log accordingly.
However, I can't seem to figure out how I can (using my handleSubmit function in Login.js) set the value of loggedIn in props to true and pass back to the app.js file.
I'm using this to render conditionally in app.js so all I need to do is be able to change the value of loggedIn in login.js and pass that back to app.js.
How can I do that here?
App.js
class App extends React.Component {
state = {
isUserLoggedIn: false
};
render() {
return (
<Router>
<Row className={css(styles.container)}>
<Column flexGrow={1} className={css(styles.mainBlock)}>
<div className={css(styles.content)}>
<Switch>
<Route path="/" exact component={(props) => <Login {...props} loggedIn = {this.state.isUserLoggedIn}/>} />
</Switch>
</div>
</Column>
</Row>
</Router>
);
}
}
}
export default App;
Login.js
import React, { useState } from "react";
import { Button, FormGroup, FormControl, FormLabel } from "react-bootstrap";
import "./Login.css";
export default function Login(props) {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
function validateForm() {
return email.length > 0 && password.length > 0;
}
async function handleSubmit(event) {
event.preventDefault();
try{
props.history.push("/");
}catch (e) {
alert(e.message);
}
}
return(
<div className="Login">
<form onSubmit={handleSubmit}>
<FormGroup controlId="email" bsSize="large">
<FormLabel>Email</FormLabel>
<FormControl
autoFocus
type="email"
value={email}
onChange={e => setEmail(e.target.value)}
/>
</FormGroup>
<FormGroup controlId="password" bsSize="large">
<FormLabel>Password</FormLabel>
<FormControl
value={password}
onChange={e => setPassword(e.target.value)}
type="password"
/>
</FormGroup>
<Button bsSize="large" disabled={!validateForm()} type="submit">
Login
</Button>
</form>
</div>
);
}
You need to pass a function from app.js to Login.js. Also in app.js, the state should be handled by the component which means you can put it in the constructor.
App.js
class App extends React.component{
constructor(props){
super(props);
this.state = {
isLoggedIn: false
}
}
handleLogin = () => {
//add your logic here
this.setState({
isLoggedIn: true
})
}
render(){
<div>
<Route path="/" exact component={(props) => <Login {...props} handleLoggedIn = {this.handleLogin}/>} />
</div>
}
}
Login.js
export default function Login(props) {
async function handleSubmit(event) {
event.preventDefault();
this.props.handleLoggedIn()
try{
props.history.push("/");
}catch (e) {
alert(e.message);
}
}
// other parts of your code
}
Between these two components, rather you are passing the state to login.js, you are passing a function downward. After the button has been clicked, the function in app.js will be triggered. Because the function set a new state, the component will re-render and hence update the component. Therefore, you are able to get the latest state.

React: Invalid value for prop `savehere` on <div> tag. Either remove it from the element, or pass a string or number value to keep it in the DOM

I'm calling a parent method from child component using props and I'm getting this error:
The way I'm passing props to the AddGuest child component is like this:
import React from 'react';
import globalService from '../services/globalService';
import '../styles/chairqueue.css';
import {buttonText,endPoint} from '../constants/global.constants';
import Modal from 'react-bootstrap/Modal'
import ModalDialog from 'react-bootstrap/ModalDialog'
import ModalHeader from 'react-bootstrap/ModalHeader'
import ModalTitle from 'react-bootstrap/ModalTitle'
import ModalBody from 'react-bootstrap/ModalBody'
import ModalFooter from 'react-bootstrap/ModalFooter'
import Button from 'react-bootstrap/Button'
import { useState, useEffect } from 'react';
import DatePicker from "react-datepicker";
import "react-datepicker/dist/react-datepicker.css";
import AddGuest from './addGuest'
class CreateMeeting extends React.Component {
constructor(props){
super(props)
this.state={
guestModalShow:false
}
}
as=(a)=>{
console.log('saasa')
this.setState({guestModalShow:a});
}
asd=(a)=>{
console.log(a) // works perfectly
}
render(){
return (
<Modal
{...this.props}
size="lg"
aria-labelledby="contained-modal-title-vcenter"
centered
>
<Modal.Header >
<label >Cancel</label>
<Modal.Title id="contained-modal-title-vcenter">
New Meeting
</Modal.Title>
<label>Create</label>
</Modal.Header>
<Modal.Body>
<h4><input type="text" className="form-control" placeholder="Meeting title"/></h4>
{/* <DatePicker className="form-control"
selected={startDate}
onChange={setStartDate}
/> */}
<label variant="primary" onClick={()=>this.as(true)}>
Add Guest
</label>
</Modal.Body>
<Modal.Footer>
<Button onClick={this.props.onHide}>Close</Button>
</Modal.Footer>
<AddGuest
show={this.state.guestModalShow}
savehere={(a)=>this.asd(a)}
onHide={() => this.as(false)}
/>
</Modal>
)
}
}
export default CreateMeeting;
My child component is implemented as:
import React from 'react';
import '../styles/chairqueue.css';
import {buttonText,endPoint} from '../constants/global.constants';
import Modal from 'react-bootstrap/Modal'
import ModalDialog from 'react-bootstrap/ModalDialog'
import ModalHeader from 'react-bootstrap/ModalHeader'
import ModalTitle from 'react-bootstrap/ModalTitle'
import ModalBody from 'react-bootstrap/ModalBody'
import ModalFooter from 'react-bootstrap/ModalFooter'
import Button from 'react-bootstrap/Button'
import { useState, useEffect } from 'react';
import DatePicker from "react-datepicker";
import "react-datepicker/dist/react-datepicker.css";
class AddGuest extends React.Component {
constructor(props){
super(props)
this.state={
startDate:new Date(),
formControls: {
email: '',
name: ''
},
}
}
changeHandler = event => {
const name = event.target.name;
const value = event.target.value;
this.setState({
formControls: {
...this.state.formControls,
[name]:
value
}
});
}
sendData = () => {
console.log('hhhh--')
this.props.savehere("Hey Popsie, How’s it going?");
}
render(){
return (
<Modal {...this.props} >
<Modal.Header closeButton>
<Modal.Title>Add Guest</Modal.Title>
</Modal.Header>
<Modal.Body>
<h4><input type="text" name="name" value={this.state.formControls.name}
onChange={this.changeHandler} required className="form-control" placeholder="Guest Name"/></h4>
<h4><input type="text" className="form-control" name="email" value={this.state.formControls.email}
onChange={this.changeHandler} required placeholder="Guest Email"/></h4>
</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={this.props.onHide}>
Close
</Button>
<Button variant="primary" onClick={()=>this.sendData()}>
Save
</Button>
</Modal.Footer>
</Modal>
);
}
}
export default AddGuest;
Im using react boostrap modals and calling another modal. What could be problem causing this error?
The problem here is that a non-stardard input prop savehere of your <AddGuest/> component which is being directly spread into the <Modal/> component when AddGuest is rendered:
render(){
return (
<Modal {...this.props} > {/*<-- This is problematic, as all props
of AddGuest are spread into Modal
including those not supported by
Modal such as "savehere"*/}
...
</Modal>)
}
Rather than spread the props directly to Modal, consider only applying the props that the Modal component supports. A simple solution for your case would be to explictly specify the show and onHide props passed to AddGuest:
render(){
return (
<Modal show={this.props.show} onHide={this.props.onHide}>
...
</Modal>)
}
Hope that helps!
I also met this problem and solved it with Object Destructuring
const {savehere, ...others} = props
return (
<Modal ...others/>
)
use variable savehere to store the callback function, and use variable other to store the propertys which will be passed to <Modal/>
In general this is caused by accidentally adding a non-dom property to a dom element (in this case a div).
If you checkout the spec for div, I suspect that you will not find 'savehere' defined. As such, 'savehere' will have no effect on the div element.
You can see what attributes you can pass to such elements via the mdn pages (linked below but you can just go there and search for them).
https://developer.mozilla.org/en-US/docs/Web/HTML/Element/div
https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes
As such, it could be argued that this is a mistake on the part of react bootstrap's Modal component which appears to be passing on all props to ModalDialog and somewhere down the line that passes your prop to a .
On you side you can 'fix' this by only passing the props that you wish to give and by reformulating your approach.
The general, and quite clean, practice that seems to be ubiquitous in the React world (pushed by linter defaults, used in example code in docs and guides etc) would be to:
refactor this quite simple set of components into functions
destructure your props at the component definition
pass only the variables you need
keep your handlers in the parent
So the parent component would look sth like:
const CreateMeeting = ({modalProp1, modalProps2, whatever}) => {
const [guestModalShow, setGuestModalShow] = useState(false)
const handleSaveClick = () => {
console.log('clicked')
}
const closeModal = () => setGuestModalShow(false)
return (
<Modal
modalProp1={modalProp1}
modalProps2={modalProp2}
size="lg"
aria-labelledby="contained-modal-title-vcenter"
centered
>
<Modal.Header >
<label >Cancel</label>
<Modal.Title id="contained-modal-title-vcenter">
New Meeting
</Modal.Title>
<label>Create</label>
</Modal.Header>
<Modal.Body>
<label variant="primary" onClick={()=>this.as(true)}>
Add Guest
</label>
</Modal.Body>
<Modal.Footer>
<Button onClick={this.props.onHide}>Close</Button>
</Modal.Footer>
<AddGuest
show={guestModalShow}
savehere={handleSaveClick}
onHide={closeModal}
/>
</Modal>
)
}
}```
for react-bootstrap you might still spread out the props ( i'm using functional components, React 18.1.0 and typescript)
<CustomModal handleshow={setCustomModalShow}/> <-- from the parentcomponent with customprop (here a ReactSetAction from usestate())
custom Modal component:
CustomModal = (props:
JSX.IntrinsicAttributes &
Omit<Pick<DetailedHTMLProps<HTMLAttributes<HTMLDivElement>,
HTMLDivElement>, "key" | keyof HTMLAttributes<HTMLDivElement>> &
{ ref?: ((instance: HTMLDivElement | null) => void) |
RefObject<HTMLDivElement> | null | undefined; },
BsPrefixProps<"div"> & ModalProps> &
BsPrefixProps<"div"> &
ModalProps &
{ children?: ReactNode; }, //all the types from a React-Bootstrap-Modal
handleshow: (e: boolean) => void // followed by the customprop and its type
) => {
return(
<Modal {...{ ...props, handleshow: handleshow }} >
...modalstuff here
<Modal/>)
}
and the error from the OP went away (it worked as intended before, yet the error popped up).
this.asd this is a function, you don’t need to wrap it in the function again.
<AddGuest
show={this.state.guestModalShow}
savehere={this.asd}
onHide={() => this.as(false)}
/>

Passing props to children in React

I'm trying to make a Formik wrapper which takes children as props and would render anything put inside. There are a couple forms to make which take different initial values and validation schema etc. The only thing in common thing is the grid layout. The goal is to have the access to Formik props like values, errors etc. in the child component and I have no idea how to pass it to its child. The form fields don't even show up.
The wrapper:
import React from 'react';
import { Formik, FormikConfig, FormikValues } from "formik";
import { Col, Layout, Row } from "antd";
const FormContainer: React.FC<FormikConfig<FormikValues>> = ({ children, ...props }) => {
return <Formik
{...props}
>
{props => (
<Layout>
<Row style={{ height: "100vh", display: "flex", alignItems: "center" }}>
<Col span={12}>
<Layout>
{/*this will be replaced with some background image*/}
<pre>{JSON.stringify(props.values, null, 2)}</pre>
<pre>{JSON.stringify(props.errors, null, 2)}</pre>
</Layout>
</Col>
<Col span={12}>
<Layout>
{/*here goes goes a Form from a different components*/}
{children}
</Layout>
</Col>
</Row>
</Layout>
)}
</Formik>
};
export default FormContainer;
I must be doing something wrong. I am unable to get any Formik props/values from anywhere else when I wrap FormContainer around anything.
My form example (so far):
import React from "react";
import { Field, Form } from "formik";
import { Col, Form as AntForm, Icon, Input, Row } from "antd";
import { initialValues, validationSchema } from "./fieldValidation";
import FormContainer from "../../../containers/FormContainer/FormContainer";
const RegisterPage: React.FC = () => {
return (
<FormContainer
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={(data, { setSubmitting }) => {
setSubmitting(true);
setTimeout(() => {
alert(JSON.stringify(data, null, 2));
setSubmitting(false);
}, 5000);
}}
>
{({touched, errors}) => (
<Form>
<Row gutter={[8, 8]}>
<Col span={12}>
<AntForm.Item
help={touched.firstName && errors.firstName ? errors.firstName : ""}
validateStatus={touched.firstName && errors.firstName ? "error" : undefined}
>
<Field
name="firstName"
prefix={<Icon type="solution" style={{ color: "rgba(0,0,0,.25)" }} />}
placeholder="First name"
as={Input}
/>
</AntForm.Item>
</Col>
<Col span={12}>
<AntForm.Item
help={touched.lastName && errors.lastName ? errors.lastName : ""}
validateStatus={touched.lastName && errors.lastName ? "error" : undefined}
>
<Field
name="lastName"
prefix={<Icon type="solution" style={{ color: "rgba(0,0,0,.25)" }} />}
placeholder="Last name"
as={Input}
/>
</AntForm.Item>
</Col>
</Row>
</Form>
)}
</FormContainer>
);
};
export default RegisterPage;
I'm stuck. What am I doing wrong here?
Here's how to pass the prop "propsToPass" from the parent to all his direct children:
const Parent = props => {
const { children } = props;
const childrenWithExtraProp = React.Children.map(children, child =>
React.cloneElement(child, { propsToPass: "toChildren" })
);
return <div>{childrenWithExtraProp}</div>;
};
export default Parent;
So in this case, both children will have the prop "propsToPass"
<Parent>
{/* this.props.propsToPass will be available in this component */}
<Child></Child>
{/* this.props.propsToPass will be available in this component */}
<AnotherChild></AnotherChild>
</Parent>
You could do the same for your form.
I don't see like rendering Formik as children is good idea here, especially that you are supposed to render one form in such FormWrapper. I would use render props here, so here is basic example how you can do it.
Anyway, I still can't get your concept of re-inventing FormWrapper if Formik provides its own wrapper:
https://jaredpalmer.com/formik/docs/api/formik
interface FormWrapperProps extends FormikConfig<FormikValues> {
renderForm(props: FormWrapperProps): React.ReactNode
}
export const RegisterForm = (props: FormWrapperProps) => (
<form>
<input type="text"/>
<input type="text"/>
</form>
)
const FormWrapper: React.FC<FormWrapperProps> = (props) => {
return (
<div className="layout">
{/*here goes goes a Form from a different components*/}
{props.renderForm(props)}
</div>
)
}
const FormPage = () => {
const props = {} as FormWrapperProps
return (
<FormWrapper
{...props}
renderForm={(props: FormWrapperProps) => <RegisterForm {...props} />}
/>
)
}

Warning: Failed prop type: The prop open is marked as required in Snackbar, but its value is undefined

I'm trying to introduce jest snapshot tests to my app.
LoginForm component
render() {
return (
...
<DynamicSnack
dialogOpen={this.props.dialogOpen}
snackOpen={this.props.snackOpen}
snackTimer={this.props.snackTimer}
snackMessage={this.props.snackMessage}
/>
)
}
DynamicSnack component
import Snackbar from 'material-ui/Snackbar';
render() {
let { snackOpen, snackTimer, snackMessage } = this.props
return (
<Snackbar
open={snackOpen}
message={snackMessage}
autoHideDuration={snackTimer}
onRequestClose={this.closeSnack}
/>
)
}
LoginForm.spec.js
import React from 'react'
import renderer from 'react-test-renderer'
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import LoginForm from '../../app/components/loginComponents/loginForm'
describe('LoginForm', () => {
it('should render snapshot', () => {
const component = renderer.create(
<MuiThemeProvider>
<LoginForm />
</MuiThemeProvider>
)
const tree = component.toJSON()
expect(tree).toMatchSnapshot()
})
})
Warnings:
Warning: Failed prop type: The prop message is marked as required in Snackbar, but its value is undefined.
Warning: Failed prop type: The prop open is marked as required in Snackbar, but its value is undefined.
I have tried importing the DynamicSnack component and even the Snackbar directly and manually add the properties open={false} message={'w00f'} but nothing changes.
I'm new to unit testing and trying to start by learning jest.
How can i get rid of these warnings?
The solution is very simple, when testing the LoginForm you are not passing the props that the Snackbar requires. Pass them as
const component = renderer.create(
<MuiThemeProvider>
<LoginForm snackOpen={true}
snackMessage={'Wrong info'}/>
</MuiThemeProvider>
)
In a component the required props if we misspelled then we will get this error, In my case I had same problem
Error: <Dialog in={props.open}>
<Dialog in={props.open}>
<Alert
action={
<IconButton
aria-label='close'
color='inherit'
size='small'
onClick={useCallback(() => props.closeAlert({ msg: '', open: false }))}
>
<CloseIcon fontSize='inherit' />
</IconButton>
}
>
{props.msg}
</Alert>
</Dialog>
Solution: <Dialog open={props.open}>
<Dialog open={props.open}>
<Alert
action={
<IconButton
aria-label='close'
color='inherit'
size='small'
onClick={useCallback(() => props.closeAlert({ msg: '', open: false }))}
>
<CloseIcon fontSize='inherit' />
</IconButton>
}
>
{props.msg}
</Alert>
</Dialog>

Material-UI BottomNavigationItem URL

UI on a react component. I have a <BottomNavigationItem /> component. This actually renders as an <button>. How can I actually make it render/navigate to a URL?
class FooterNavigation extends Component {
state = {
selectedIndex: 0,
};
select = (index) => this.setState({selectedIndex: index});
render() {
return (
<footer className="mdl-mini-footer">
<Paper zDepth={1}>
<BottomNavigation selectedIndex={this.state.selectedIndex}>
<BottomNavigationItem
label="Reviews"
icon={reviewIcon}
onClick={() => this.select(0)}
/>
</BottomNavigation>
</Paper>
</footer>
);
}
}
Simply you can just add containerElement={<Link to="/home"/>} don't forget to import Link from react-router-dom
So it will be like this:
<BottomNavigationItem
containerElement={<Link to="/home"/>}
label="Reviews"
icon={reviewIcon}
onClick={() => this.select(0)}
/>

Categories