Let's say I have this component:
class MyForm {
render() {
return (
<>
<h3>Remember my Setting</h3>
<Toggle
value={this.state.rememberMe}
onClick={() => {
this.setState(prevState => ({ rememberMe: !prevState.rememberMe }));
}}
/>
</>
)
}
}
How can I pass this prevState if its wrapped in child component? Is this acceptable?
class MyForm {
render() {
return (
<PartialForm
onClick={(v) => {
this.setState({ rememberMe: v });
}}
/>
)
}
}
const PartialForm = (props) => {
return (
<>
<h3>Remember my Setting</h3>
<Toggle
value={props.rememberMe}
onClick={() => {
props.onClick(!props.rememberMe);
}}
/>
</>
);
}
I want to know if accessing props.rememberMe is the same and safe as accessing prevState.rememberMe in parent component. Thanks
In my opinion, the way you're doing is safe and it has no conflicts at all.
I can simply demonstrate those re-rendering steps here:
Pass the current state to PartialForm
Trigger toggle click props.onClick for updating rememberMe in the upper component
The upper component gets re-rendered, and then your child component will have the latest state of rememberMe
By the way, you also forget to pass rememberMe in <PartialForm />
class MyForm {
render() {
return (
<PartialForm
rememberMe={rememberMe}
onClick={(v) => {
this.setState({ rememberMe: v });
}}
/>
)
}
}
const PartialForm = (props) => {
return (
<>
<h3>Remember my Setting</h3>
<Toggle
value={props.rememberMe}
onClick={() => {
props.onClick(!props.rememberMe);
}}
/>
</>
);
}
Related
This is my parent Component having state ( value and item ). I am trying to pass value state as a props to child component. The code executed in render method is Performing toggle when i click on button. But when i call the list function inside componentDidMount, Toggle is not working but click event is performed.
import React, { Component } from 'react'
import Card from './Components/Card/Card'
export class App extends Component {
state = {
values : new Array(4).fill(false),
item : [],
}
toggleHandler = (index) => {
console.log("CLICKED");
let stateObject = this.state.values;
stateObject.splice(index,1,!this.state.values[index]);
this.setState({ values: stateObject });
}
list = () => {
const listItem = this.state.values.map((data, index) => {
return <Card key = {index}
show = {this.state.values[index]}
toggleHandler = {() => this.toggleHandler(index)} />
})
this.setState({ item : listItem });
}
componentDidMount(){
// if this is not executed as the JSX is render method is executed everything is working fine. as props are getting update in child component.
this.list();
}
render() {
return (
<div>
{/* {this.state.values.map((data, index) => {
return <Card key = {index}
show = {this.state.values[index]}
toggleHandler = {() => this.toggleHandler(index)} />
})
} */}
{this.state.item}
</div>
)
}
}
export default App
This is my child Component where the state is passed as props
import React from 'react'
const Card = (props) => {
return (
<div>
<section>
<h1>Name : John Doe</h1>
<h3>Age : 20 </h3>
</section>
{props.show ?
<section>Skills : good at nothing</section> : null
}
<button onClick={props.toggleHandler} >Toggle</button>
</div>
)
}
export default Card
I know the componentDidMount is executed only once. but how to make it work except writing the JSX directly inside render method
make a copy of the state instead of mutating it directly. By using [...this.state.values] or this.state.values.slice()
toggleHandler = (index) => {
console.log("CLICKED");
let stateObject = [...this.state.values]
stateObject = stateObject.filter((_, i) => i !== index);
this.setState({ values: stateObject });
}
Also in your render method, this.state.item is an array so you need to loop it
{this.state.item.map(Element => <Element />}
Also directly in your Render method you can just do
{this.state.values.map((data, index) => {
return <Card key = {index}
show = {this.state.values[index]}
toggleHandler = {() => this.toggleHandler(index)} />
})}
In your card component try using
<button onClick={() => props.toggleHandler()}} >Toggle</button>
Value should be mapped inside render() of the class component in order to work
like this:
render() {
const { values } = this.state;
return (
<div>
{values.map((data, index) => {
return (
<Card
key={index}
show={values[index]}
toggleHandler={() => this.toggleHandler(index)}
/>
);
})}
</div>
);
}
check sandbox for demo
https://codesandbox.io/s/stupefied-spence-67p4f?file=/src/App.js
Just learning React, and I would like to add an onClick on the font awesome icon, and run the markTaskAsCompleted function. I'm having trouble because it's several components lower in the hierarchy. How would you ideally go about this? Bear in mind that I also have to pass the ID of the task in the function.
class TasksBase extends React.Component {
constructor(props) {
super(props);
this.state = {
loading: false,
tasks: [],
};
}
componentDidMount() {
this.onListenForTasks();
}
onListenForTasks() {
this.setState({ loading: true });
this.unsubscribe = this.props.firebase
.tasks()
.orderBy('created', 'desc')
.onSnapshot(snapshot => {
if (snapshot.size) {
let tasks = [];
snapshot.forEach(doc =>
tasks.push({ ...doc.data(), uid: doc.id }),
);
this.setState({
tasks: tasks,
loading: false
});
} else {
this.setState({ tasks: null, loading: false });
}
});
}
markTaskAsCompleted(){
console.log("Completed");
}
componentWillUnmount() {
this.unsubscribe();
}
render() {
const { tasks, loading } = this.state;
return (
<div>
{loading && <div>Loading ...</div>}
{tasks ? (
<TaskList tasks={tasks} />
):(
<div>There are no tasks ...</div>
)}
</div>
);
}
}
const Tasks = withFirebase(TasksBase);
const TaskList = ({ tasks }) => (
<ul className="tasks">
{tasks.map( task => (
<Task key={task.uid} task={task} />
))}
</ul>
);
const Task = ({ task }) => (
(!task.completed && !task.obsolete && !task.waitingForDependencies) &&
<li className="task">
<strong>{task.userId}</strong> {task.name}
<div className="icons">
<FontAwesomeIcon icon="check-circle"/>
<FontAwesomeIcon icon="times-circle" />
</div>
</li>
);
const condition = authUser => !!authUser;
export default compose(
withEmailVerification,
withAuthorization(condition),
)(Tasks);
Bind your class function in the constructor:
this.markTaskAsCompleted = this.markTaskAsCompleted.bind(this);
Pass the function into the child component with props:
<TaskList tasks={tasks} handleMarkCompleted={this.markTaskAsCompleted} />
Pass the function again to child component, this is prop drilling and is not the latest greatest approach but it works:
const TaskList = ({ tasks, handleMarkCompleted }) => (
<ul className="tasks">
{tasks.map( task => (
<Task key={task.uid} task={task} handleMarkCompleted={handleMarkCompleted} />
))}
</ul>
);
Trigger the function with onClick:
inside <Task>...
<FontAwesomeIcon icon="check-circle" onClick={() => handleMarkCompleted(task.uid)} />
If passing data into the function (ex. task.uid) make it a param in the function definition as well so you can use it:
markTaskAsCompleted(id){
console.log("Completed", id);
}
you will need to pass it down the tree as
markTaskAsCompleted={this.props.markTaskAsCompleted}
and make sure that the function is bound to the parent in the constructor.
u can use refs or document.getElementById to get the ID.
I have subdivided my components and I want to change state of text using deleteName function from child component. However I have used onPress={this.props.delete(i)} in my child component which is not working. The error that occurs for me is:
undefined variable "I"
Here is my code:
App.js
export default class App extends Component {
state = {
placeName: '',
text: [],
}
changeName = (value) => {
this.setState({
placeName: value
})
}
deleteName = (index) => {
this.setState(prevState => {
return {
text: prevState.text.filter((place, i) => {
return i!== index
})
}
}
}
addText = () => {
if (this.state.placeName.trim === "") {
return;
} else {
this.setState(prevState => {
return {
text: prevState.text.concat(prevState.placeName)
};
})
}
}
render() {
return (
<View style={styles.container}>
<View style={styles.inputContainer}>
<Input changeName={this.changeName}
value={this.state.placeName} />
<Button title="Send" style={styles.inputButton}
onPress={this.addText} />
</View>
<ListItems text={this.state.text} delete={this.deleteName}/>
{/* <View style={styles.listContainer}>{Display}</View> */}
</View>
);
}
}
and child component ListItems.js
const ListItems = (props) => (
<View style={styles.listitems}>
<Text>{this.props.text.map((placeOutput, i) => {
return (
<TouchableWithoutFeedback
key={i}
onPress={this.props.delete(i)}>
onPress={this.props.delete}
<ListItems placeName={placeOutput}/>
</TouchableWithoutFeedback>
)
})}
</Text>
</View>
);
You need to bind the index value at the point of passing the props to the child.
delete = index => ev => {
// Delete logic here
}
And in the render function, you can pass it as
items.map((item, index) => {
<ChildComponent key={index} delete={this.delete(index)} />
})
In your child component, you can use this prop as
<button onClick={this.props.delete}>Click me</button>
I have created a Sandbox link for your reference
Instead of onPress={this.props.delete(i)}, use onPress={() => this.props.delete(i)}
In order to have the cleaner code, you can use a renderContent and map with }, this);like below. Also you need to use: ()=>this.props.delete(i) instead of this.props.delete(i) for your onPress.
renderContent=(that)=>{
return props.text.map((placeOutput ,i) => {
return (
<TouchableWithoutFeedback key={i} onPress={()=>this.props.delete(i)}>
onPress={this.props.delete}
</TouchableWithoutFeedback>
);
}, this);
}
}
Then inside your render in JSX use the following code to call it:
{this.renderContent(this)}
Done! I hope I could help :)
i have created three components Aboutus,AboutusChild & GrandChild and now i want to pass grandchild state value in my grandparent component that is "Aboutus" component but without using intermediate component(AboutusChild), is it possible in react js without using redux state management library.
i dont' want to use redux right now until some data-communication concept are not clear.
class AboutUs extends Component {
constructor(props) {
super(props)`enter code here`
this.state = {
childState: false
}
}
myMehtod(value) {
this.setState({
childState: value
})
}
render() {
console.log('AboutUs', this.state.childState)
return (
<div>
<h1 className={`title ${this.state.childState ? 'on' : ''}`}>
About us {this.props.aboutusProp}
</h1>
<AboutUsChild />
</div>
)
}
};
class AboutUsChild extends Component {
myMehtod(value) {
this.setState({
childState: value
},
this.props.myMehtodProp(!this.state.childState)
)
}
render() {
console.log('AboutUsChild', this.state.childState)
return (
<div>
<h1 className={`title ${this.state.childState ? 'on' : ''}`}>About Us Child</h1>
<GrandChild>
without function
</GrandChild>
<h1>About Us Child</h1>
<GrandChild Method={true} myMehtodProp={this.myMehtod.bind(this)} />
</div>
)
}
};
class GrandChild extends Component {
constructor() {
super()
this.state = {
TitleClick: false
}
}
TitleClick() {
this.setState(
{
TitleClick: !this.state.TitleClick
},
this.props.myMehtodProp(!this.state.TitleClick)
)
}
render() {
console.log('GrandChild', this.state.TitleClick)
return (
<div>
{this.props.Method ?
<h1 onClick={this.TitleClick.bind(this)} className={`title ${this.state.TitleClick ? 'on' : ''}`}>Grand Child</h1>
: null}
<h1>{this.props.children}</h1>
</div>
)
}
};
There's really no way to pass child state up without passing it to some callback. But frankly speaking this does not seem like a good design choice to me. You should always have common state on top of all components consuming that state. What you can do, is to pass stuff as children:
class SomeComponent extends React.Component {
...
render() {
const { value } = this.state;
return (
<Child value={value}>
<GrandChild value={value} onChange={this.handleValueChange} />
</Child>
);
}
}
const Child = ({ value, children }) => (
<div>
<h1 className={`title ${value ? 'on' : ''}`}>About Us Child</h1>
{children}
</div>
)
const GrandChild = ({ value, onChange }) => (
<div>
<h1 onClick={onChange} className={`title ${value ? 'on' : ''}`}>Grand Child</h1>
</div>
);
This way you got control from parent component of everything. If this is not the way, because you are already passing children, and for some reason you want to keep it this way, you can pass "render" prop:
// JSX in <SomeComponent /> render function:
<Child
value={value}
grandChild=(<GrandChild value={value} onChange={this.handleValueChange} />)
>
Some other children
</Child>
...
const Child = ({ value, grandChild, children }) => (
<div>
<h1 className={`title ${value ? 'on' : ''}`}>About Us Child</h1>
{grandChild}
{children}
</div>
)
If you want to be more fancy and there will more than few levels of nesting, you can always use context (highly recommend reading docs before using):
const someContext = React.createContext({ value: true, onChange: () => {} });
class SomeComponent extends React.Component {
...
render() {
const { value } = this.state;
return (
<someContext.Provider value={{ value: value, onChange: this.handleValueChange }}>
<Children>
</someContext.Provider>
);
}
}
...
const SomeDeeplyNestedChildren = () => (
<someContext.Consumer>
{({ value, onChange }) => (
<h1 onClick={onChange}>{value}</h1>
)}
</someContext.Consumer>
)
I would pick first two, if your structure is not that complex, but if you are passing props deeply, use context.
The only way to do something of this sort without external library would be leveraging React's Context API, although bare in mind that it is not nearly as robust as redux or mobX.
I'm using Gatsby for a static website.
My page is composed of two parts. Section 1 and Section 2.
I want to hide an image in Section 1, when a button is hovered in Section 2.
If I clean up a bit my .js, it looks like that :
<section>
<SomeText/>
<DefaultImage />
<ImageOne />
<ImageTwo />
</section>
<section>
<Button1/>
<Button2/>
</section>
What I want to achieve:
By default, <DefaultImage/> is shown.
If I hover <Button1>, I want to hide <DefaultImage/> and display <ImageOne/> instead.
Same goes for <Button2/>, which, when hovered, should hide <DefaultImage/> and display <ImageTwo/>.
I've read about onMouseEnter and onMouseLeave, and I think that the answer lies there but couldn't make it work for now.
Thank you for your ideas!
Maybe I can also pass a prop (like a css class) on the "to be hidden" component when the other is hovered
I managed to do it (check the accepted answer).
Here is my edited code:
class Parent extends Component {
state = {
isHoveringImage1: false
}
state = {
isNotHovering: false
}
state = {
isHoveringImage2: false
}
startHoverMasque = () => this.setState({ isHoveringMasque: true, isNotHovering: true})
stopHoverMasque = () => this.setState({ isHoveringMasque: false, isNotHovering: false })
startHoverMains = () => this.setState({ isHoveringMains: true, isNotHovering: true})
stopHoverMains = () => this.setState({ isHoveringMains: false, isNotHovering: false })
render() {
return (
<>
<Global
styles={globalStyles}/>
<section>
{
this.state.isNotHovering
? <ImageDefaultHidden />
: <ImageDefault/>
}
{
this.state.isHoveringImage1
? <Image1 />
: <ImageDefaultHidden />
}
{
this.state.isHoveringImage2
? <Image2 />
: <ImageDefaultHidden />
}
</section>
<section>
<Button1
onMouseEnter={ this.startHoverImage1}
onMouseLeave={ this.stopHoverImage1 }
>Bouton1</Button1>
<Button2
onMouseEnter={ this.startHoverImage2}
onMouseLeave={ this.stopHoverImage2 }
>Bouton 2</Button2>
</section>
</>
)
}
}
export default Parent```
You can annotate when the mouse enter and leaves the target Button in the state of your parent component:
class Parent extends Component {
state = {
isHovering: false
}
startHover = () => this.setState({ isHovering: true })
stopHover = () => this.setState({ isHovering: false })
render() {
return (
<>
<section>
<SomeText/>
{
this.state.isHovering
? <ImageOne />
: <DefaultImage />
}
<ImageTwo />
</section>
<section>
<Button1
onMouseEnter={ this.startHover }
onMouseLeave={ this.stopHover }
/>
<Button2/>
</section>
</>
)
}
}
The solution is to include the variable saying whether or not your image should be rendered in your parent component's state.
To set this variable, pass down a function to the component containing the button and bind it to the events you gave in your question : onMouseEnter and onMouseLeave.
Working example :
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
hideImage: false
}
}
toggleImage = hideImage => ev => {
this.setState({ hideImage })
}
render = () => {
return(
<div>
<ButtonComponent hovered={this.toggleImage}/>
<ImageComponent isHidden={this.state.hideImage}/>
</div>
)
}
}
const ButtonComponent = ({ hovered }) => <button onMouseEnter={hovered(true)} onMouseLeave={hovered(false)}>Hover me :)</button>
const ImageComponent = ({ isHidden }) => <img hidden={isHidden} src='https://reactjs.org/logo-og.png'/>
ReactDOM.render(<App />, document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.5.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.5.1/umd/react-dom.production.min.js"></script>
<div id='root'>