How to break down component and make sub component in reactjs? - reactjs

{
this.state.editMode
?
<Card.Body>
<Form>
<Form.Group>
<Form.Control id="des" as="textarea" rows={3} placeholder="Description"
value={this.state.description}
onChange={(des) => {
this.setState({description: des.target.value})
}}/>
</Form.Group>
<Form.Group>
<Form.Control id="link" type="text" placeholder="Link (Optional)"
value={this.state.link}
onChange={(link) => {
this.setState({link: link.target.value})
}}/>
</Form.Group>
</Form>
</Card.Body>
:
<Card.Body>
<p className="cardBody alignLeft">{this.state.description}</p>
<a href={"https://" + this.state.link}
target="#">{this.state.link}</a>
</Card.Body>
}
I want to make two sub component here. One for if editmode is true and another if editmode is false.
But problem is that, this subcomponent also need to use the state variable of parent class. And also, if i change something on sub component, parent components state need to be changed. How can I do that?

You need to pass the state of the parent as props for the child component, which is a standard practice as mentioned here - https://reactjs.org/docs/lifting-state-up.html
If you want to change the state of the parent within the child, you can also pass a function as a prop to the child. The call to the function in child component will trigger state change in parent.
https://dev.to/vadims4/passing-down-functions-in-react-4618
A better way for large component trees is through use of dispatch via React context. This is explained in
https://reactjs.org/docs/hooks-faq.html#how-to-avoid-passing-callbacks-down

As others suggested, you can pass state as props and setState as a prop as well to update parent from child.
Here is an example that might help you understand how to split components and passing values and setState using props.
Note: I have used React Hooks, you can modify it based on class based components.
import React, { useState } from "react";
const App = () => {
const [editMode, setEditMode] = useState(false);
const [data, setData] = useState({
description: "",
link: ""
});
const editModeHandler = (key, value) =>
{
setData((prevState) =>
{
return {...prevState, [key]: value}
})
//setEditMode(true)
};
return <div className="App">{editMode
? <GroupOne value = {description} change = {editModeHandler}/>
: <GroupTwo value = {link}/>}</div>;
};
export default App;
/*Edit Mode True*/
const GroupOne = (props) => {
const { value, change } = props;
return (
<Card.Body>
<Form>
<Form.Group>
<Form.Control
id="des"
as="textarea"
rows={3}
placeholder="Description"
value={value}
onChange={(des) => change("description",des.target.value)}
/>
</Form.Group>
<Form.Group>
<Form.Control
id="link"
type="text"
placeholder="Link (Optional)"
value={value}
onChange={(des) => change("link",des.target.value)}
/>
</Form.Group>
</Form>
</Card.Body>
);
};
/*Edit Mode False*/
const GroupTwo = (props) => {
const { value } = props;
return (
<Card.Body>
<p className="cardBody alignLeft">{value}</p>
<a href={"https://" + value} target="#">
{value}
</a>
</Card.Body>
);
};

You should create a second component and pass the state variables as to the second components. For more information read this documentation https://reactjs.org/docs/components-and-props.html

Related

Reset state without Effects using key

I came across exercise from beta.react docs concerning issue: Reset state without Effects.
You may find it in the bottom : Challenge 3 of 4: Reset state without Effects.
There's a component that receives object of person data to present it in editable form.
As for start it tells you that useEffect is redundant.
import React, { useState } from "react";
//ExportContact.jsx
export default function EditContact({ savedContact, onSave }) {
const [name, setName] = useState(savedContact.name);
const [email, setEmail] = useState(savedContact.email);
// useEffect(() => {
// setName(savedContact.name);
// setEmail(savedContact.email);
// }, [savedContact]);
return (
<section>
<label>
Name:{" "}
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
/>
</label>
<label>
Email:{" "}
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
</label>
<button
onClick={() => {
const updatedData = {
id: savedContact.id,
name: name,
email: email
};
onSave(updatedData);
}}
>
Save
</button>
<button
onClick={() => {
setName(savedContact.name);
setEmail(savedContact.email);
}}
>
Reset
</button>
</section>
);
}
Suggested solution is to split into another component that will receive key of contact id.
Like that where EditForm contains everything EditContact had so far.
export default function EditContact(props) {
return (
<EditForm
{...props}
key={props.savedContact.id}
/>
);
}
I'm just wondering how would it be different to add key prop with contact id value right into the parent component like this:
<EditContact
key={selectedContact.id}
savedContact={selectedContact}
onSave={handleSave}
/>
Instead of splitting EditContact into artificial subcomponent only to receive a key prop.
The difference is explained in the official link about spread attributes.
Below is the official summary:
Spread attributes can be useful but they also make it easy to pass unnecessary props to components that don’t care about them or to pass invalid HTML attributes to the DOM. We recommend using this syntax sparingly.

How to pass state props to another component

I want to pass a state variable to another component but i don't get it what i'm doing wrong.
I have a component for radio inputs and I need to pass that taskLabel to Form component.
path is components/inputs/index.js
const TaskLabel = (props) => {
const [taskLabel, setTaskLabel] = useState('');
return (
<div label={props.taskLabel} >
<input
type='radio'
name='label'
value='urgent'
onChange={(e) => setTaskLabel(e.target.value)}
/>
<input
type='radio'
name='label'
value='not-urgent'
onChange={(e) => setTaskLabel(e.target.value)}
/>
</div>
);
};
i want to receive the taskLabel value, to use it in submitHandler function.
components/form/index.js
const Form = ({taskLabel}) => {
return (
<form onSubmit={submitHandler}>
<input
type='text'
placeholder='Text here'
className='form-input'
value={task}
onChange={(e) => {
setTask(e.target.value);
}}
/>
<TaskLabel taskLabel={taskLabel} />
</form>
)
}
This is what i tried, to pass taskLabel props from label={taskLabel}.
You need to move your state, to Form component, like this:
const [labelProp, setLabelProp] = useState("");
Your TaskLabel component should be
<TaskLabel label={{ labelProp, setLabelProp }} />
That means, you send label, as a prop to TaskLabel component.
In TaskLabel component you need to recive the prosp, by passing to component {label}.
Next, for every input use onChange={(e) => label.setLabelProp(e.target.value)}.
Edited sandbox => https://codesandbox.io/s/laughing-proskuriakova-dhi568?file=/src/components/task-label/index.js
Generally speaking, the concept is "data-down, actions-up". So if you want to pass data down to a lower-level component, you just pass a prop. If you want to update a value from a lower-level component to a higher-level component, you could pass a setter function down as a prop and call that.
Note I just call it taskLevelProp for a little more clarity. You should probably use a better name.
TaskLabel (lower level component)
/* It looks like TaskLabelProp gets passed in from something else.
Otherwise, you would use useState to get your setter function */
const TaskLabel = ({taskLabelProp, setTaskLabelProp}) => {
return (
<div label={props.taskLabel} >
<input
type='radio'
name='label'
value='urgent'
onChange={(e) => setTaskLabelProp(e.target.value)}
/>
<input
type='radio'
name='label'
value='not-urgent'
onChange={(e) => setTaskLabelProp(e.target.value)}
/>
</div>
);
};
Form (higher level component)
const Form = () => {
const [taskLabelProp, setTaskLabelProp] = useState('');
return (
<form onSubmit={submitHandler}>
<input
type='text'
placeholder='Text here'
className='form-input'
value={task}
onChange={(e) => {
setTask(e.target.value);
}}
/>
<TaskLabel taskLabel={taskLabelProp, setTaskLabelProp} />
</form>
)
}
Let me know if this answers your question.
EDIT: Made Form use useState. Based off your code, I was assuming you were using useState at the app level.
function App() {
const [task, setTask] = useState("");
const [taskLabelProp, setTaskLabelProp] = useState("");
const handleChange = (e) => {
setTaskLabelProp(e.target.value);
};
const submitHandler = (e) => {
e.preventDefault();
};
return (
<div className="App">
<form onSubmit={submitHandler}>
<input
type="text"
placeholder="Text here"
className="form-input"
value={task}
onChange={(e) => {
setTask(e.target.value);
}}
/>
<TaskLabel
onChange={handleChange}
taskLabelProp={taskLabelProp}
taskLabel="select"
/>
<button type="submit">fs</button>
</form>
</div>
);
}
export default App;
const TaskLabel = ({ taskLabel, onChange, taskLabelProp }) => {
return (
<div label={taskLabel}>
<input
type="radio"
name="label"
value="urgent"
onChange={(e) => onChange(e)}
checked={taskLabelProp === "urgent"}
/>
urgent
<input
type="radio"
name="label"
value="not-urgent"
onChange={(e) => onChange(e)}
checked={taskLabelProp === "not-urgent"}
/>
not-urgent
</div>
);
};
export default TaskLabel;

React JS / Typescript passing an updated prop onChange from one component to another component

I'm using ReactJS and Typescript for my frontend application but have run into an issue passing a prop from one component to another component. I'm very new to ReactJS and I'm not sure if I'm doing this correctly. Here is my simple Search component:
const Search: NextPage = () => {
const [visible, setVisible] = useState(false)
let id = ""
const handleOnChange = (e: React.ChangeEvent<HTMLInputElement>)=> {
console.log(id)
id = e.target.value;
}
return (
<>
<Container>
<Card>
<Form>
<Row>
<Col>
<Form.Group className="mb-3" controlId="formBasicEmail">
<Form.Control onChange={handleOnChange} placeholder="Enter Number" />
</Form.Group>
</Col>
<Col>
<Button variant="primary" onClick = {() => {setVisible(true)}}>
<i className="fa fa-search" aria-hidden="true"></i>
</Button>
</Col>
</Row>
</Form>
</Card>
</Container>
{visible && <TransactionContent id={id} />}
</>
);
};
The idea is that given a number passed by the user, when the user clicks on Submit, that number is passed into another component via a prop (id) and then based on the id passed in, the component will either render more information about that number, or let the user know that number was not found in the mock json data I have.
There are 2 problems I'm currently facing:
Although the id changes via an onChange event, the id passed into the prop is always nothing.
Once onClick event triggers, the button is basically useless.
I would first like to fix the issue of the id prop not being passed in correctly via the prop. Not sure why it isn't working, since the console.log in handleOnChange function seems to update according to user. After that, I would like for the user to continue to input a number even if the first number doesnt work or if they found a number, they can search for other numbers and still render the TransactionContent but with different data.
Add id to the state will solve your problem
const [visible, setVisible] = useState(false);
const [id, setId] = useState("");
const handleOnChange = (e) => {
setId(e.target.value);
};
console.log("id", id);
Here the codesandbox for this: https://codesandbox.io/s/nostalgic-frost-7ktsuy?file=/src/App.js
Add id to the state.
And onClick function exchange.
const Search: NextPage = () => {
const [visible, setVisible] = useState(false);
const [id, setId] = useState("");
const handleOnChange = (e: React.ChangeEvent<HTMLInputElement>)=> {
setId(e.target.value);
}
return (
<>
<Container>
<Card>
<Form>
<Row>
<Col>
<Form.Group className="mb-3" controlId="formBasicEmail">
<Form.Control onChange={handleOnChange} placeholder="Enter Number" />
</Form.Group>
</Col>
<Col>
<Button variant="primary" onClick = {() => setVisible(true)}>
<i className="fa fa-search" aria-hidden="true"></i>
</Button>
</Col>
</Row>
</Form>
</Card>
</Container>
{visible && <TransactionContent id={id} />}
</>
);
};

How to pass input values from a child Form component to the state of its parent component for submission with react hooks?

I'm setting up a multi-step form to handle sign in with Next.js and Material-UI. In the signup.js page located in pages/signup.js. My multi-step form is created. Within this page, the child components to be displayed as the step content are defined in getStepContent(step) and the main component is called SignInSide below it. The steps for forward and back is also defined on the signup.js page. The child form components only contain inputs. I am trying to pass the data entered into the child component form inputs to the parent component's state within the sign up page, signup.js so I can submit it. Both the parent and the child component are functional components using react hooks for state.
I've already tried to pass props from the parent to the child component and then define the 'value' of the TextField components to {props.value} and that worked when I tested it with a console.log of value in the parent component I saw the letters coming through, however there was no name: attached to it, it was just coming through as just letters so I tried to add [name]: event.target.value but then when I try to write text in the input field it just says [Object object] however, in the console I can see [Object object](insert letter typed here) so its logging the text but not properly and the text typed is not visible in the input box.
This is where the step content is.
const steps = ['Welcome', 'Information', 'Creative', 'Confirm'];
function getStepContent(step) {
const [value, setValue] = React.useState('');
//set state for returned input values
function handleChange(newValue) {
setValue(newValue);
}
switch (step) {
case 0:
return (
<div style={{ padding: '8em 0 4em' }}>
<Hero title='Welcome' paragraph="Let's get you signed up." />
</div>
);
case 1: // Form Components
return <CredentialsForm value={value} onChange={handleChange} />;
case 2:
return <CreativeForm />;
case 3:
return <Review />;
default:
throw new Error('Unknown step');
}
}
// Sign In Page
export default function SignInSide() {
const classes = useStyles();
const [activeStep, setActiveStep] = React.useState(0);
const [form, setForm] = React.useState(false);
// Forward and Back button functions, the buttons are defined here, in the parent page component.
const handleNext = () => {
setActiveStep(activeStep + 1);
};
const handleBack = () => {
setActiveStep(activeStep - 1);
};
const handleFormExtend = () => {
setForm(true);
setActiveStep(activeStep + 1);
};
return (
...
)
}
The parent component is SignInSide. Im using material-ui. An example of the stepper form im using can be seen here: https://material-ui.com/getting-started/templates/checkout/ its similar code, I just changed the textfields to outlined and set up the logic for the props passed from the parent.
Inside <CredentialsForm value={value} onChange={handleChange} /> component.
export default function CredentialsForm(props) {
const classes = useStyles();
// Floating Form Label
const inputLabel = React.useRef(null);
const [labelWidth, setLabelWidth] = React.useState(0);
React.useEffect(() => {
setLabelWidth(inputLabel.current.offsetWidth);
}, []);
//Send input value to parent state
const handleChange = name => event => {
props.onChange({
[name]: event.target.value
});
};
return (
<div className={classes.paper}>
<Avatar className={classes.avatar}>
<LockOutlinedIcon />
</Avatar>
<Typography component='h1' variant='h5'>
Who Are U?
</Typography>
<form className={classes.form} noValidate>
<Grid container spacing={2}>
<Grid item xs={2} md={2}>
<FormControl
margin='normal'
variant='outlined'
fullWidth
required
className={classes.formControl}
>
<InputLabel
ref={inputLabel}
htmlFor='outlined-userTitle-native-simple'
>
Title
</InputLabel>
<Select
native
value={props.value}
onChange={handleChange('title')}
labelWidth={labelWidth}
required
inputProps={{
name: 'title'
}}
>
<option value='' />
<option value={10}>Mr.</option>
<option value={20}>Ms.</option>
<option value={20}>Mrs.</option>
<option value={30}>Non-Binary</option>
</Select>
</FormControl>
</Grid>
<Grid item xs={5} md={5}>
<TextField
variant='outlined'
margin='normal'
required
fullWidth
value={props.value}
onChange={handleChange('firstName')}
id='first'
label='First Name'
name='firstname'
/>
</Grid>
</Grid>
</form>
</div>
);
}
Expected: On user input the child component saves the input in state and passes it to the parent component so it can submit the data for sign up.
Actual: input sucessfully being passed to parent, but in the wrong format. It is passing as "[Object object](user typed letters here)" and in the input field the user can only see [Object object] as seen in the Console. No errors.
Your problem is here:
const handleChange = name => event => {
props.onChange({
[name]: event.target.value
});
};
You're giving your props.onChange function an object, but that function is updating a state hook that is initialized with a string value. Then you set the text value to an object, when it used to be a string giving your odd looking object text.
In addition, you are setting all your form values to the same string from the parent state.
To resolve this, make your state an object instead of a string.
In parent:
const [values, setValues] = React.useState({title: '', firstName: ''});
....
function handleChange(newValue) {
setValues({...values, ...newValue});
}
....
return <CredentialsForm values={values} onChange={handleChange} />;
In child:
<Select
native
value={props.values.title} // Change to this
onChange={handleChange('title')}
labelWidth={labelWidth}
required
inputProps={{
name: 'title'
}}
>
....
<TextField
variant='outlined'
margin='normal'
required
fullWidth
value={props.values.firstName} // Change to this
onChange={handleChange('firstName')}
id='first'
label='First Name'
name='firstname'
/>
Recommended but not required: I'd get rid of the handleChange function in the form component since it doesn't really add value. Then call props.onChange directly from the input, and change your parent handleChange to:
const handleChange = name => event => {
setValues({...values, [name]: event.target.value});
}

How to set state for text box in functional component

I am working on React JS. I have one text-box component and I want to show some default value in it. After that, the user should be allowed to change the value. Now I am unable to change the value. The text box is behaving like read-only. Below is my code
const EditStyleFormComponent = ({
submitting,
invalid,
}) => (
<form className={className} onSubmit={handleSubmit}>
<h2>LSPL (Low Stock Presentation Level)</h2>
<Line />
<InputGroup>
<TextFieldWithValidation name="lsplMan" label="LSPL Manual" input={{ onChnage:'', value: 'Current' }} />
</InputGroup>
</form>
);
Below is my TextFieldWithValidation code.
export const TextFieldWithValidationComponent = ({
meta,
input,
noStyles,
...otherProps
}) => (
<TextField
state={noStyles ? textFieldStates.DEFAULT : getState(meta)}
errorMessage={meta.touched ? meta.error : null}
{...input}
{...otherProps}
/>
);
Below is my TextField code.
const TextField = ({
className,
label,
description,
state,
errorMessage,
isEditable,
spaceAtBottom, // Not used, but we don't want it in otherProps
...otherProps
}) => {
const inputId = _.uniqueId();
return (
<div className={className}>
{label &&
<label htmlFor={inputId}>{label}</label>
}
<div className="input-group" id={isEditable ? 'editable' : 'readonly'}>
<input
id={inputId}
readOnly={!isEditable}
{...otherProps}
/>
{getStatusIcon(state)}
{errorMessage &&
<Error>{errorMessage}</Error>
}
{description &&
<Description>{description}</Description>
}
</div>
</div>
);
};
Can someone help me to fix this issue? Any help would be appreciated. Thanks
You can use State Hook for manage state in functional component.
Example :
const Message = () => {
const [message, setMessage] = useState( '' );
return (
<div>
<input
type="text"
value={message}
placeholder="Enter a message"
onChange={e => setMessage(e.target.value)}
/>
<p>
<strong>{message}</strong>
</p>
</div>
);
};
Yu defined onChange as empty string in EditStyleFormComponent component. So on any change input component just do nothing.
onChange should be some function that will update value.
If you want to use functional components there are two possible solutions:
Lift state up to parent component of EditStyleFormComponent (in case parent is class based component)
Use React Hooks like so (just example!)
const EditStyleFormComponent = ({
submitting,
invalid,
}) => {
const [inputValue, setInputValue] = useState ('Current'); // default value goes here
return <form className={className} onSubmit={handleSubmit}>
<h2>LSPL (Low Stock Presentation Level)</h2>
<Line />
<InputGroup>
<TextFieldWithValidation name="lsplMan" label="LSPL Manual" input={{ onChnage: (e) => { setInputValue(e.target.value); }, value: inputValue }} />
</InputGroup>
</form>
};

Resources