How to pass data from parent to children with react hooks - javascript

I have a simple modal in which I have a form on submit I display errors now I want to pass these errors to the children component.
Here is a simplified parent component
import React from 'react';
import Input from 'components/Functional/Input';
function Parent() {
const [errors, setErrors] = useState([]);
const handleSubmit = async e => {
const formData = new FormData();
}
return (
<Modal handleSubmit={handleSubmit}>
<Input setErrors={errors} ></Input>
</Modal>
)
}
export default Parent
Here is my children component
import React from 'react'
function Input({errors}) {
const [field, setField] =useState('');
console.log('errors', errors)
return (
<div>
<input type="text" onChange={e => setField(e.target.value)} />
</div>
)
}
export default Input
Now when I submit the data with errors, the console.log('errors', errors) in the children component I get undefined
What is wrong here?

Pay attention to props name. You're passing from parent a property called setErrors while in child component you're looking for errors. Try to rename property from setErrors to errors or simply read setErrors from <Input /> component

You seem to have a typo in your input component. You set the prop setErrors but your component expects errors. If you use plain javascript you should use proptypes to ensure this doesn't happen.
import React from 'react'
import PropTypes from 'prop-types'
function Input({errors}) {
const [field, setField] =useState('');
console.log('errors', errors)
return (
<div>
<input type="text" onChange={e => setField(e.target.value)} />
</div>
)
}
Input.propTypes = {
errors: PropTypes.arrayOf(PropTypes.string)
};
export default Input
See PropTypes

Related

how do i pass a the input value of the textfield from some component to another component in reactjs?

I am trying to pass the value of the text area from some component in reactjs to be used in another react component. the component value is stored in the first component in a useState hook so I want to access it in another component and run map() function around it . Is this possible in reactjs ? I don't want to put the whole thing in app.js because that is just plain HTML which I don't want. I want to use reactjs function components instead ?
first component:
import React, { useState, useRef, useEffect } from "react";
function Firstcomp() {
const [quotes, setQuotes] = useState(["hi there", "greetings"]);
const reference = useRef();
function sub(event) {
event.preventDefault();
setQuotes((old) => [reference.current.value, ...old]);
console.log(quotes);
return;
}
return (
<>
<div>
<div>
<div>
<h4>jon snow</h4>
</div>
<form onSubmit={sub}>
<textarea
type="textarea"
ref={reference}
placeholder="Type your tweet..."
/>
<button type="submit">Tweet</button>
</form>
{quotes.map((item) => (
<li key={item}>{item}</li>
))}
{/* we can use card display taking item as prop where it
will do the job of filling the <p> in card entry */}
</div>
</div>
</>
);
}
export default Firstcomp;
second component
import React from "react";
function SecondComp(props) {
return (
<div>
<p>{props.message}</p>
</div>
);
}
export default Secondcomp;
Use a global management state like Recoil, Redux ot Context
import React from 'react';
export const UserContext = React.createContext();
export default function App() {
return (
<UserContext.Provider value="Reed">
<User />
</UserContext.Provider>
)
}
function User() {
const value = React.useContext(UserContext);
return <h1>{value}</h1>;
}
on the exemple above we used useContext hook to provide a global variable "value", even its not declared directly in User component, but you can use it by calling the useContext hook.
in this exemple the return value in the user component is "Reed"

Is there a way to pass data to Formik's HOC (Higher Order Component) withFormik() in React?

I have semi-legacy code. There's some Formik components that are being wrapped with withFormik().
The primary thing is, the variable/data is not part of the initial props of the wrapped component. A possible example is if React Context API is being used. That means it isn't part of the props. Quick simple React component and withFormik() below with React's useContext hook being used in the wrapped component, but would like access to it in the withFormik() HOC.
Also, the data can't be passed through [hidden] fields in the form. I realize that is an easy way to do it and grab it in values. Wondering if there is another way.
import React, { useContext } from 'react';
import { Form, Field, withFormik } from 'formik';
const MyForm = props => {
const { section } = useContext( SectionContext );
return (
<Form>
<Field
component="input"
name="name"
/>
{errors.name && touched.name && <div id="feedback">{errors.name}</div>}
<button type="submit">Submit</button>
</form>
);
};
const MyEnhancedForm = withFormik({
mapPropsToValues: () => ({ name: '' }),
handleSubmit: (values, { setSubmitting, props }) => {
// Want to use the context, section.
alert(JSON.stringify(values, null, 2));
setSubmitting(false);
}
})(MyForm);
Thanks for any help

How to modify react useState from secondary File

I'm all new to react and am currently trying to modify an useState hook from another File. When one of the radio buttons from "Options.tsx" get's selected, the result should somehow be updated with the setResult function of useState hook so the Tag gets updated.
I think I almost got it, but I don't manage to pass the correct 'onSelect' Property to Options.tsx so it is updated.
Here's my code so far:
App.tsx
import React from 'react';
import './App.css';
import { useState } from 'react';
import { Result, ResultType } from './Result'
import { Options } from './Options'
function App() {
const [result, setResult] = useState<ResultType>('pending')
return (
<div className="App">
<header className="App-header">
<Options onSelect={props.onSelect} />
<Result result={result} />
</header>
</div>
);
}
export default App;
Options.tsx
import React from 'react'
interface Props {
onSelect: (correct: boolean) => void
}
export const Options = ({onSelect}: Props) => {
// TODO
const setWrong = () => setResult('wrong');
const setCorrect = () => setResult('correct');
return(
<div>
<fieldset>
<input type='radio' id='option1' onSelect={setWrong}/>
<label htmlFor='option1'>Label 1</label>
<input type='radio' id='option2' onSelect={setCorrect}/>
<label htmlFor='option2'>Label 2</label>
<input type='radio' id='option3' onSelect={setCorrect}/>
<label htmlFor='option3'>Label 3</label>
</fieldset>
</div>
)
}
Result.tsx (just for completion - works fine so far)
import React from 'react'
export type ResultType = 'pending' | 'correct' | 'wrong'
interface Props {
result: ResultType
}
export const Result = ({ result }: Props) => {
switch (result) {
case 'pending':
return <h2>Make a guess</h2>
case 'correct':
return <h2>Yay, good guess!</h2>
case 'wrong':
return <h2>Nope, wrong choice...</h2>
}
}
Any idea, how I can update the useState from Options.tsx?
Thank you in advance!
It's quite simple - you just need to propagate the setter via properties, to Options.
<Options setResult={setResult} />
Or, provide your own method which uses setResult, depending on circumstances.
I would note though that the value you're currently passing down to the onSelect, appears to be bound to an incorrect value. Typescript compiler is probably complaining about it?
You can pass the updater function to Options component:
<Options setResult={setResult} />
then in your Options Component you can use
props.setResult('blah')
Just pass setResult prop to Options component.
App.tsx:
function App() {
const [result, setResult] = useState<ResultType>('pending')
return (
<div className="App">
<header className="App-header">
<Options onSelect={props.onSelect} setResult={setResult} />
<Result result={result} />
</header>
</div>
);
}
Options.tsx:
export const Options = ({onSelect, setResult}: Props) => {
const setWrong = () => setResult('wrong');
const setCorrect = () => setResult('correct');
...
}

How to pass props to custom components in react-select

I am trying to use a custom component as an input field in react-select. Since I need validation I am trying to use HTML5 oninvalid (onInvalid in JSX) for my input tag and set the custom message for oninvalid. However I am unable to pass the message as a prop to the component that I am setting in select. Below is my code.
Input.js
import React from "react";
import { components } from "react-select";
export default class Input extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
console.log("component mounted");
}
setInvalidMessage = event => {
event.target.setCustomValidity("Custom Message");
};
render() {
if (this.props.isHidden) {
return <components.Input {...this.props} />;
}
return (
<components.Input
{...this.props}
required
onInvalid={this.setInvalidMessage}
/>
);
}
}
example.js
import React from "react";
import Input from "./Input";
import Select from "react-select";
import { colourOptions } from "./docs/data";
const InputBoxWithText = props => {
return <Input {...props} />;
};
export default () => (
<form>
<Select
closeMenuOnSelect={true}
components={{ InputBoxWithText }}
options={colourOptions}
/>
<input type="submit" />
</form>
);
If I pass Input in components attribute I get the hard coded message in Input.js. If I pass InputBoxWithText I don't see Input mounting at all.
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import Example from './example';
ReactDOM.render(
<Example />,
document.getElementById('root')
);
Here is CodeSandBox.io URL.
Can any one let me know if what am I doing wrong.
It's better to pass custom props via select:
props.selectProps
To avoid re-creating of Custom component each time Select updates, what may cause unexpected bugs.
In my case I was passing errors in such way:
<Select
defaultValue={values}
selectProps={{ errors }}
isMulti
options={inventoryList}
onChange={changeTreeElement}
// #ts-ignore
styles={colourStyles}
/>
Then access it like selectProps.selectProps.errors in colourStyles methods.
I managed to pass my custom props using an arrow function
See docs for defining components
const Input = (inputProps: InputProps) => (
<components.Input {...inputProps} />
);
<Select
closeMenuOnSelect={true}
options={colourOptions}
components={{Input}}
/>
I don't have the solution (i'm looking for the same thing as well), but you example has multiple errors.
To load the input you have to write components={{ Input: InputBoxWithText }}, since the component name for Input is not InputBoxWithText.
Also the onInvalid does not seem to be part of the Input API, so it will never trigger. Are you trying to use the <input oninvalid="" />..?
In version 5 the only way to use custom props with typescript is to use module augmentation.
So in my project I opened react-app-env.d.ts and added there this:
import { GroupBase } from 'react-select'
declare module 'react-select/dist/declarations/src/Select' {
export interface Props<Option, IsMulti extends boolean, Group extends GroupBase<Option>> {
customOnClear: () => void;
}
}
You pass the prop to the select like this:
import Select from "react-select";
<Select customOnClear={() => {/* Your custom clear */} />
And use it in your custom component like this:
const ClearIndicator = ({ selectProps }: ClearIndicatorProps<Option, false>) => {
const { customOnClear } = selectProps
return <InputClear onClick={customOnClear} />
}
Docs:
https://react-select.com/typescript#custom-select-props

React-Loadable re-rendering causing input to lose focus

I'm having an issue where react-loadable is causing one of my input components to re-render and lose focus after a state update. I've done some digging and I can't find anyone else having this issue, so I think that I'm missing something here.
I am attempting to use react-loadable to dynamically include components into my app based on a theme that the user has selected. This is working fine.
./components/App
import React from 'react';
import Loadable from 'react-loadable';
/**
* Import Containers
*/
import AdminBar from '../../containers/AdminBar';
import AdminPanel from '../../components/AdminPanel';
import 'bootstrap/dist/css/bootstrap.css';
import './styles.css';
const App = ({ isAdmin, inEditMode, theme }) => {
const MainContent = Loadable({
loader: () => import('../../themes/' + theme.name + '/components/MainContent'),
loading: () => (<div>Loading...</div>)
});
const Header = Loadable({
loader: () => import('../../themes/' + theme.name + '/components/Header'),
loading: () => (<div>Loading...</div>)
});
return (
<div>
{
(isAdmin) ? <AdminBar
className='admin-bar'
inEditMode={inEditMode} /> : ''
}
<Header
themeSettings={theme.settings.Header} />
<div className='container-fluid'>
<div className='row'>
{
(isAdmin && inEditMode) ? <AdminPanel
className='admin-panel'
theme={theme} /> : ''
}
<MainContent
inEditMode={inEditMode} />
</div>
</div>
</div>
);
};
export default App;
./components/AdminPanel
import React from 'react';
import Loadable from 'react-loadable';
import './styles.css';
const AdminPanel = ({ theme }) => {
const ThemedSideBar = Loadable({
loader: () => import('../../themes/' + theme.name + '/components/SideBar'),
loading: () => null
});
return (
<div className='col-sm-3 col-md-2 sidebar'>
<ThemedSideBar
settings={theme.settings} />
</div>
);
};
export default AdminPanel;
This is what my <ThemedSideBar /> components looks like:
./themes/Default/components/SideBar
import React from 'react';
import ThemeSettingPanel from '../../../../components/ThemeSettingPanel';
import ThemeSetting from '../../../../containers/ThemeSetting';
import './styles.css';
const SideBar = ({ settings }) => {
return (
<ThemeSettingPanel
name='Header'>
<ThemeSetting
name='Background Color'
setting={settings.Header}
type='text'
parent='Header' />
<ThemeSetting
name='Height'
setting={settings.Header}
type='text'
parent='Header' />
</ThemeSettingPanel>
);
};
export default SideBar;
./components/ThemeSettingPanel
import React from 'react';
import { PanelGroup, Panel } from 'react-bootstrap';
const ThemeSettingPanel = ({ name, children }) => {
return (
<PanelGroup accordion id='sidebar-accordion-panelGroup'>
<Panel>
<Panel.Heading>
<Panel.Title toggle>{name}</Panel.Title>
</Panel.Heading>
<Panel.Body collapsible>
{children}
</Panel.Body>
</Panel>
</PanelGroup>
);
};
export default ThemeSettingPanel;
./containers/ThemeSetting
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { themeSettingChange } from '../App/actions';
import ThemeSetting from '../../components/ThemeSetting';
class ThemeSettingContainer extends Component {
constructor(props) {
super(props);
this.handleOnChange = this.handleOnChange.bind(this);
}
handleOnChange(name, parent, value) {
const payload = {
name: name,
parent,
value: value
};
this.props.themeSettingChange(payload);
}
render() {
return (
<ThemeSetting
name={this.props.name}
setting={this.props.setting}
parent={this.props.parent}
type={this.props.type}
handleOnChange={this.handleOnChange} />
);
}
}
//----Redux Mappings----//
const mapStateToProps = (state) => ({
});
const mapDispatchToProps = {
themeSettingChange: (value) => themeSettingChange(value)
};
export default connect(mapStateToProps, mapDispatchToProps)(ThemeSettingContainer);
./component/ThemeSetting
import React from 'react';
import TextField from '../common/TextField';
import './styles.css';
const ThemeSetting = ({ name, setting, type, parent, handleOnChange }) => {
return (
<div className='row theme-setting'>
<div className='col-xs-7'>
{name}
</div>
<div className='col-xs-5'>
{
generateField(type, setting, name, parent, handleOnChange)
}
</div>
</div>
);
};
function generateField(type, setting, name, parent, handleOnChange) {
const value = setting ? setting[name] : '';
switch (type) {
case 'text':
return <TextField
value={value}
name={name}
parent={parent}
handleOnChange={handleOnChange} />;
default:
break;
}
}
export default ThemeSetting;
./components/common/TextField
import React from 'react';
import { FormControl } from 'react-bootstrap';
const TextField = ({ value, name, parent, handleOnChange }) => {
return (
<FormControl
type='text'
value={value}
onChange={(e) => {
handleOnChange(name, parent, e.target.value);
}} />
);
};
export default TextField;
When a field inside of my Admin Panel is updated, a state change is triggered. It seems like this triggers react-loadable to re-render my <ThemedSideBar /> components which destroys my input and creates a new one with the updated value. Has anyone else had this issue? Is there a way to stop react-loadable from re-rendering?
EDIT: Here is the requested link to the repo.
EDIT: As per conversation in the comments, my apologies, I misread the question. Answer here is updated (original answer below updated answer)
Updated answer
From looking at the react-loadable docs, it appears that the Loadable HOC is intended to be called outside of a render method. In your case, you are loading ThemedSideBar in the render method of AdminPanel. I suspect that the change in your TextEdit's input, passed to update your Redux state, and then passed back through the chain of components was causing React to consider re-rendering AdminPanel. Because your call to Loadable was inside the render method (i.e. AdminPanel is a presentational component), react-loadable was presenting a brand new loaded component every time React hit that code path. Thus, React thinks it needs to destroy the prior component to appropriately bring the components up to date with the new props.
This works:
import React from 'react';
import Loadable from 'react-loadable';
import './styles.css';
const ThemedSideBar = Loadable({
loader: () => import('../../themes/Default/components/SideBar'),
loading: () => null
});
const AdminPanel = ({ theme }) => {
return (
<div className='col-sm-3 col-md-2 sidebar'>
<ThemedSideBar
settings={theme.settings} />
</div>
);
};
export default AdminPanel;
Original answer
It seems that your problem is likely related to the way you've built TextField and not react-loadable.
The FormControl is taking value={value} and the onChange handler as props. This means you've indicated it is a controlled (as opposed to uncontrolled) component.
If you want the field to take on an updated value when the user types input, you need to propagate the change caught by your onChange handler and make sure it gets fed back to the value in the value={value} prop.
Right now, it looks like value will always be equal to theme.settings.Height or the like (which is presumably null/empty).
An alternative would be to make that FormControl an uncontrolled component, but I'm guessing you don't want to do that.

Categories