How to use useState in a child Component - javascript

I'm not sure why I can't use my pass on useState from Parent component <AppMain /> to the child component <GroupPage /> and <CreateGroupForm />
What I'm trying to do:
I'm working on an update functionality, where on clicking the edit button in <GroupPage />, I want the content of GroupPage to fill on the form fields of <CreateGroupForm />. So for that, I have created states currentId and setCurrentId in <AppMain /> since it's the parent component of both, and I can pass on these to its child components assuming they both share the states.
const AppMain = () => {
const [ currentId, setCurrentId ] = useState(null)
return (
<div>
<Switch>
<Route path="/groupMain" exact> <GroupMain /> </Route>
<Route path="/groupMain/:id" exact> <GroupPage setCurrentId={setCurrentId} /> </Route>
<Route path="/createGroup" exact> <CreateGroupForm currentId={currentId} setCurrentId={setCurrentId} /> </Route>
</Switch>
</div>
)
}
export default AppMain
const GroupPage = ({setCurrentId}) => {
const { group } = useSelector((state) => state.groups)
// the reason for this condition is to prevent rendering something before data is actually fetched
if(!group) return null
return (
<div>
<EditButton onClick= {() => {
setCurrentId(group._id)
history.push(`/createGroup/`)
}} />
<h1>{group.groupName}</h1>
</div>
)
}
export default GroupPage
Now when clicking on the edit button of <GroupPage /> I'm setting the current group Id in setCurrentId and directing it to the <CreateGroupForm />. In <CreateGroupForm /> I'm checking if currentId matches the one with the already existed group. And by useEffect I'm populating those values in form fields.
const CreateGroupForm = ({currentId, setCurrentId}) => {
const [groupData, setGroupData] = useState({
groupName: ''
})
const group = useSelector((state) => currentId ? state.groups.groups.find((grp) => grp._id === currentId) : null)
console.log(group) // null
console.log(currentId) // undefined
useEffect(() => {
if(group) setGroupData(group)
}, [group])
return (
<div>
<MainForm>
<form autoComplete="off" onSubmit={handleSubmit}>
<h1>{ currentId ? 'Editing' : 'Creating'} a Group:</h1>
<label htmlFor="Group Name">Your Group Namee: </label>
<input type="text" value={groupData.groupName} onChange={(e) => setGroupData({ ...groupData, groupName: e.target.value })} />
<button type="submit">Submit</button>
</form>
</MainForm>
</div>
)
}
export default CreateGroupForm
What is happening:
On clicking the Edit button, the form fields are not populating with the group content.
Please any help would be appreciated.

It's not a good practice to pass setStates between its children. I recommend you to create callback functions in your appMain, and pass that functions to your GroupPage and CreateGroupForm components. When you call those functions inside the components your functions will change your currentIdState in the appMain. Changing the state of currentId in your appMain the components will recive the new state of currentId

Related

React: Can we pass 2 forms from 2 different child components to a parent, and submit them with a button which is inside the parent component?

I am trying to somehow pass the data from 2 forms and 2 different components to the parent component and then somehow console.log all of this data with a button that is inside the parent component. Then I will simply send these data to a JSON file or a dummy database.
When I press the submit button of course nothing is triggered right now because I simply don't know how to pass the function from the children to the parent. I have tried many ways, but I would appreciate it if you could show me a way to lift the state and combine the forms.
For the input, in order to pass refs, I have used React.forwardRef()
It would be easy to just have 1 big component with 1 form and then the button inside this component, but since it is a fun project, I want to learn how to implement this functionality in case I will use it in the future. You can find a screenshot on this link:
[]
[1]: https://i.stack.imgur.com/myV0N.jpg
Here we go:
1. Parent component
const BookingComponent = () => {
return (
<div>
<CRContainer className="booking-crcontainer">
<CRColumn>
<PickUpCarComponent />
</CRColumn>
<CRColumn>
<CustomerInfo />
</CRColumn>
</CRContainer>
<CRContainer className="booking">
<Button type="submit" btnText="hello there" />
</CRContainer>
</div>
);
};
export default BookingComponent;
2. Child 1
const CustomerInfo = (props) => {
const firstlRef = useRef();
const lastNameRef = useRef();
const onTrigger = (e) => {
e.preventDefault();
//console.log(first1Ref.current.value)
console.log("heaheaheah");
};
return (
<>
<Subtitle stitle={SubtitleLabels.customerInfo} />
<div className="customer-info-container">
<form onSubmit={onTrigger}>
<div>
<LabeledInput
labelText={CustomerInfoLabels.firstName}
type="text"
inputPlaceholder={GeneralLabels.placeholder}
ref={firstlRef}
></LabeledInput>
<LabeledInput
labelText={CustomerInfoLabels.lastName}
type="text"
inputPlaceholder={GeneralLabels.placeholder}
ref={lastNameRef}
></LabeledInput>
</div> ...................
3. Child 2
Haven't put the refs here yet.
const PickUpCarComponent = () => {
return (
<div>
<Subtitle stitle={SubtitleLabels.pickUp} />
<form>
<div className="booking-inner-container">
<div>
<LabeledInput labelText={"Pick-up date*"} type="date"></LabeledInput>
<LabeledInput labelText={"Pick-up time*"} type="time"></LabeledInput>
</div>
<DropDown type="CarGroup" labeltext="Car Group*" attribute="name" />
<DropDown type="RentalOffice" labeltext="Region*" attribute="region" />
</div>
</form>
</div>
);
};
export default PickUpCarComponent;
4. Input Component
const LabeledInput = React.forwardRef((props, ref) => {
const { labelText, type, inputPlaceholder, onChange, className } = props;
return (
<div className={`input-container ${className}`}>
<label htmlFor="labelText">{labelText}</label>
<input
type={type}
placeholder={inputPlaceholder}
onChange={onChange}
ref={ref}
/>
</div>
);
});
export default LabeledInput;
you can use context to pass form handlers to child component then in the child component you can useContext and get value and handlers of parent form and use them.
const FormContext = React.createContext({});
const BookingComponent = () => {
const [values, setValues] = useState();
const handleChange = useCallback((e) => {
//handle child event in parent and save child state in
//parent to use later in submit button
}, []); //set dependency if it's needed
const contextValue = useMemo(() => ({ handleChange }), [handleChange]);
return (
<FormContext.Provider value={contextValue}>
<div>
<CRContainer className="booking-crcontainer">
<CRColumn>
<PickUpCarComponent />
</CRColumn>
<CRColumn>
<CustomerInfo />
</CRColumn>
</CRContainer>
<CRContainer className="booking">
<Button type="submit" btnText="hello there" />
</CRContainer>
</div>
</FormContext.Provider>
);
};
const LabeledInput = (props) => {
const formContext = useContext(FormContext);
const { labelText, type, inputPlaceholder, className } = props;
return (
<div className={`input-container ${className}`}>
<label htmlFor="labelText">{labelText}</label>
<input
type={type}
placeholder={inputPlaceholder}
onChange={formContext.handleChange}
ref={ref}
/>
</div>
);
};

How can I update the value instead of re render of complete app in react (Hooks)?

Here I am trying to update the value from header component to search Component using Context-API. In my case I have a dropdown in header component I want to pass the dropdown value to search page without re rendering the search component. Here is my sample code
import React,{useContext,useState} from "react";
import { RepoContext } from "../../App";
const App = ()=>{
const [RepoUrn, setRepoUrn] = useState("");
const handleRepo = (value) => { // Callback from Header to update the dropdown value and I am sending this context so the state is updated and rendering mutliple times
return setRepoUrn(value);
};
}
const Search =()=>{
const context = useContext(RepoContext);
console.log(context);
React.useEffect(()=>{
console.log('context',context) -> Found here re rendering multiple times
},[context]);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.4.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.4.2/umd/react-dom.production.min.js"></script>
**APP.js**
<RepoContext.Provider value={RepoUrn}>
<div className="app">
<>
<Header
locale={locale}
repoValues={RepoValues}
setLocale={setLocale}
onSelectRepo={handleRepo}
/>
<Route path="/" component={Search} />
</>
</RepoContext.Provider>
**Header.js**
<Dropdown isOpen={repoOpen} toggle={(e) => toggle(e, "Repo")}>
<DropdownToggle
tag="span"
data-toggle="dropdown"
aria-expanded={repoOpen}
data-value={dataUrn}
id="test"
>
{TruncateMessage(RepoValue)}
<img src={downarrow} className="downarrow" />
</DropdownToggle>
<DropdownMenu
style={dropDownStyles}
id="scroll_styles"
onClick={(e) => changeDropdownValue(e, "Repo")}
>
{props.repoValues.length > 0
? props.repoValues.map((item, index) => {
return (
<div
key={index}
className="subMenu"
data-value={item.urn}
data-shows={item.name}
>
<h6>{item.name}</h6>
</div>
);
})
: ""}
</DropdownMenu>
</Dropdown>
How can I pass dropdown value from header to context and subscribe in search without search component re rendering even if I get the new value from header?
When define the value of Context you need to define values and callback function to change that value. I think you should read and follow this react doc
https://reactjs.org/docs/context.html#updating-context-from-a-nested-component

Passing data from parent to child using react.State but State reset to initial state

I am facing an issue while passing the state to the child component, so basically I am getting customer info from child1(Home) and saving in the parent state(App) and it works fine.
And then I am passing the updated state(basketItems) to child2(Basket). But when I click on the Basket button the basket page doesn't show any info in console.log(basketItems) inside the basket page and the chrome browser(console) looks refreshed too.
Any suggestion why it is happening and how can I optimize to pass the data to child2(basket) from main (APP).
update:2
i have tired to simulated the code issue in sand box with the link below, really appreciate for any advise about my code in codesandbox (to make it better) as this is the first time i have used it
codesandbox
Update:1
i have made a small clip on youtube just to understand the issue i am facing
basketItems goes back to initial state
Main (APP)___|
|_Child 1(Home)
|_Child 2 (Basket)
Snippet from Parent main(App) component
function App() {
const [basketItems, setBasketItems] = useState([]);
const addBasketitems = (product, quantity) => {
setBasketItems(prevItems => [...prevItems, { ...product, quantity }])
}
console.log(basketItems) // here i can see the updated basketItems having customer data as expected [{...}]
return (
<Router>
<div className="App">
<header className="header">
<Nav userinfo={userData} userstatus={siginalready} />
</header>
<Switch>
<Route path="/" exact render={(props) => (
<Home {...props} userData={userData} userstatus={siginalready}
addBasketitems={addBasketitems}
/>
)}
/>
<Route path="/basket" exact render={(props) =>
(<Basket {...props} basketItems={basketItems} />
)}
/>
</Switch>
</div>
</Router>
Snippet from the child(basket)
function Basket({basketItems}) {
console.log(basketItems) // here i only get the [] and not the cusotmer data from parent component
return (
<div>
{`${basketItems}`} // here output is blank
</div>
);
}
export default Basket;
Snippet from the child(Home)
... here once the button is pressed it will pass the userselected details to parent
....
<Button name={producNumber} value={quantities[productName]} variant="primary"
onClick={() => {
addBasketitems(eachproduct, quantities[productName])
}}>
Add to Basket
</Button >
Your function works fine, the reason your output in addbasketItem does not change is the when using setState it takes some time to apply the changes and if you use code below you can see the result.
useEffect(()=>{
console.log('basket:',basketItems)
},[basketItems])
Your Basket component only renders once so replace it with this code and see if it works:
function Basket({ basketItems }) {
const [items, setItems] = useState([]);
useEffect(() => {
setItems(basketItems);
}, [basketItems]);
return <div>{`${items}`}</div>;
}
but for passing data between several components, I strongly suggest that you use provided it is much better.

react-responsive: cannot pass props to children of a Responsive component

I am using the common use case indicated in the ReadMe:
const Desktop = props => (
<Responsive {...props} minWidth={1680} maxWidth={2560} />
)
const LaptopL = props => (
<Responsive {...props} minWidth={1440} maxWidth={1679} />
)
...
and I have a prop (bottleState) that I am trying to pass to specific components inside the Responsive component (eg Desktop):
const WineHeader = ({ bottleState }) => (
<HeaderCard>
<Desktop>
<WineBox />
<WineBackgroundBox />
<VineyardBackgroundBox />
<WineInfoContainer>
<LeftContainer>
<WineTypeBox bottleState={bottleState} />
<WineTitleBox bottleState={bottleState} />
<WineDescriptionBox bottleState={bottleState} />
</LeftContainer>
<WineProperties />
</WineInfoContainer>
</Desktop>
<LaptopL>
<WineBox />
<WineBackgroundBox />
<VineyardBackgroundBox />
<WineInfoContainer>
<LeftContainer>
<WineTypeBox />
<WineTitleBox />
<WineDescriptionBox />
</LeftContainer>
<WineProperties />
</WineInfoContainer>
</LaptopL>
...
</HeaderCard>
)
WineHeader.propTypes = Object.freeze({
bottleState: PropTypes.object, //eslint-disable-line
})
export default WineHeader
When logging the bottleState prop in one of the above child components in which I am trying to access it - it is not available (the log returns undefined) :
const WineTypeBox = ({ bottleState }) => (
<WineTypeStyle>{console.log(bottleState)}</WineTypeStyle>
)
> undefined ---- WineTypeBox.jsx?a13c:36
and when I simply remove the Responsive component, I can access the bottleState prop as expected:
const WineHeader = ({ bottleState }) => (
<HeaderCard>
<WineBox />
<WineBackgroundBox />
<VineyardBackgroundBox />
<WineInfoContainer>
<LeftContainer>
<WineTypeBox bottleState={bottleState} />
<WineTitleBox bottleState={bottleState} />
<WineDescriptionBox bottleState={bottleState} />
</LeftContainer>
<WineProperties />
</WineInfoContainer>
...
</HeaderCard>
)
returns the bottleState object when logged to the console:
{wineCollection: "Classics", wineType: "Semi Sweet Red", bottleName: "Khvanchkara", bottleImage: Array(1), bottleNoteText: "<p>test</p>", …}
bottleImage: ["http://localhost/uploads/bottle.png"]
bottleName: "Khvanchkara"
bottleNoteText: "<p>test</p>"
temperatureHigh: null
vintage: null
volume: null
wineCollection: "Classics"
wineType: "Semi Sweet Red"
__proto__: Object ---- WineTypeBox.jsx?a13c:36
Any ideas why this is the case? I have tried defining the desktop function inside the WineHeader functional component, because that is the function where I am pulling off the bottleState prop from this.props but this doesn't change the behaviour; when throwing a debugger before the return statement of the Desktop component, I can clearly see the bottleState prop being passed in, I do not even need it to be passed in as I am directly passing it into other components nested further down the DOM tree without any issue when the Desktop Component is not wrapping them, but the fact that my other components that need to access this prop are nested inside the Desktop component is causing the props to be blocked for some reason. Any help is greatly appreciated. Thanks,
Corey
So, it's possible that you're expecting to look at the <Desktop> and actually looking at the <LaptopL> screen:
const WineHeader = ({ bottleState }) => (
<HeaderCard>
<Desktop>
...
<WineTypeBox bottleState={bottleState} />
<WineTitleBox bottleState={bottleState} />
<WineDescriptionBox bottleState={bottleState} />
...
</Desktop>
<LaptopL>
...
<WineTypeBox /> // <- pass props here
<WineTitleBox /> // <- pass props here
<WineDescriptionBox /> // <- pass props here
...
</LaptopL>
</HeaderCard>
)
Which you mentioned fixed it in the comments above. Kudos!
For reference:
It's also good practice with react-responsive to add a <Default ...> wrapper.
This can happen with other libraries, and require a different fix.
Eg. The initial destructure of props into ({ bottleState }) might mean that react-responsive can't access props in order to pass them onto their child components. When this happens with external libraries, it can be fixed in several ways:
Pass the props with the spread syntax
const WineHeader = props => (
...
...
...
)
OR
const WineHeader = ({bottleState, foo, bar}) => (
...
<Desktop>
...
<WineTypeBox {...{bottleState, foo, bar}} />
...
</Desktop>
)
Pass a single component, or wrap the libraries JSX inner components because the library can't handle multiple child components
const WineHeader = ({bottleState}) => (
...
OR
const WineHeader = ({bottleState}) => (
<HeaderCard>
<Desktop>
<Fragment>
<WineTitleBox bottleState={bottleState} />
<WineTitleBox bottleState={bottleState} />
...
</Fragment>
</Desktop>
Passing props using React Context

How to pass two functions to onClick event in react

I want to pass two functions to onClick event which is handleSubmit and handleDelete to the HomePage.js from the HomeItem.js
Here is my Error:
No duplicate props allowed react/jsx-no-duplicate-props.
Here is my HomePage.js:
const HomePage = props => {
const tvshow = props.item;
let res;
if (tvshow.length > 0) {
res = tvshow.map(res=> (
<Content item={res} onClick={props.onClick}/>
));
}
return (
<div>
<Container>
<Row>{res}</Row>
</Container>
</div>
);
};
export default HomePage;
Here is my HomeItem.js:
const HomeItem = props => {
function handleSubmit() {
props.onClick({
name: props.item.name,
id: props.item.id
});
}
function handleName() {
props.onClick({
name: props.item.name
});
}
<Button onClick={handleSubmit}></Button>
<Button onClick={handleName}></Button>
Here is my App.js:
handleSubmit(newFavorite) {}
handleName(newFavorite) {}
render() {
<Route
exact
path="/"
render={() => (
<HomePage
item={this.state.SaveFavorite}
onClick={this.handleSubmit}
onClick={this.handleName}
/>
)}
/>
}
So my question is how to put 2 onClick function to the Hompage.js
How about this:
<HomePage
item={this.state.SaveFavorite}
onClick={(favorite)=>{
this.handleSubmit(favorite);
this.handleName(favorite);
}
}
/>
This assumes your goal is to call both functions one at a time. If they should be called in different situations give one function a different name, eg onSubmit or onNameChange.
Try This:
<HomePage
item={this.state.SaveFavorite}
onClick={(newFavorite) => this.handleSubmit(newFavorite);this.handleName(newFavorite)}
/>
you can pass multiple functions to events in react, let say changeEvent, to do follow those steps.
1- create your function two or the number of function you like.
2- create an object that contains those functions
3- pass the object as a props to where it would be consumed
4- choose the correspondant function to each form or whatever you need.
here is an example, this sample is with typescript.
const onChangeFunctions = {
onChangeForm1: handleChangeForm1,
onChangeForm2: handleChangeForm2,
};
<MainForm
onChange={onChangeFunctions} // here is your function
datas={yourData}
otherProps={otherProps}
/>
Now you use the fucntion on the child components
interface PropsFrom {
model1: Model1;
model2: Model2;
onChange: any;
}
export default function ProductForm(props: PropsForm) {
return (
<Container maxWidth="lg">
<Grid item md={6}>
<FormOne
model={props.model1} // the model that bind your form
onChange={props.onChange.onChangeForm1} // here you can use your first function
/>
</Grid>
<Grid item md={6}>
<FormTwo
model={props.model2} // the model that bind your form
onChange={props.onChange.onChangeForm2} // here you can use your second function
/>
</Grid>
</Grid>
</Container>
for javascript just pass the functions as props and delete the interface from the child components.

Categories

Resources