In reactjs I want to update parent state based on child state.
My parent component in Login.
I want child state in Login component role function
//this function to show link //
function GetLink(props) {
const { role } = props
let admin = <Link to='/Admin'>Admin</Link>
let f = <Link to='/Finance'>Finance</Link>
let s = <Link to='/Sales'>Sales</Link>
switch (role) {
default:
case "admin": return (
<>
{admin}
{f}
{s}
</>
)
case "finance": return (
<>
{f}
{s}
</>
)
case "sales": return (
<>
{s}
</>
)
}
}
//this is the parent component //
class Login extends Component {
constructor(props) {
super(props);
this.state = {
role: ""
}
}
//want this state to be update when child state is updated
role = () => {
this.setState({ role: });
}
render() {
return (
<>
{ this.state.role === "admin" }
<GetLink role={localStorage.getItem("role")} />
</>
);
}
}
now this is my child component where the state is updating in componentDidMount
//this is child component //
//the state is updating in this component //
class Sales extends Component {
constructor(props) {
super(props);
this.state = {role: "" }
}
componentDidMount() {
if (localStorage.getItem("role") === null) {
this.props.setState({ role: localStorage.setItem('role', 'sales') })
}
}
logout() {
localStorage.removeItem('role');
}
render() {
return (
<>
<h1>Sales</h1>
<button onClick={this.logout}>logout</button>
</>
);
}
}
export default Sales;
can anyone help me out with this problem?
I think it is the best way that you send a function as props.
<GetLink role={this.role} />
Related
Hello i'm new on react and have to do a Tutorial where i have to change the state of the Child Component with a button onClick function.
currently i'm use a button in my Parent component to do it and it works but now i have to use call this button in other child components and not directly in the Parent to restart my Tutorial.
but i dont know how i can do it.
ill happy about every suggestion.
class Child extends React.Component {
constructor (props) {
super(props)
this.state = {
run: this.props.run,
stepIndex: this.props.stepIndex
}
}
componentWillReceiveProps (props) {
this.setState({ run: props.run, stepIndex: props.stepIndex })
}
callback = (tour) => {
const { action, index, type } = tour
// if you delete action === 'skip', the Tutorial will not start on other pages after skipped once.
if (action === 'close' || action === 'skip' || type === 'tour:end') {
this.setState({ run: false })
} else if ([EVENTS.STEP_AFTER, EVENTS.TARGET_NOT_FOUND].includes(type)) {
this.setState({ stepIndex: index + (action === ACTIONS.PREV ? -1 : 1) })
}
}
render () {
let { run, stepIndex, steps } = this.state
if (this.props.location.pathname === '/') {
steps = []
run = false
} else if (this.props.location.pathname === '/matches/' || this.props.location.pathname.length === '/matches') {
steps = matchSiteSteps
} else if (this.props.location.pathname.startsWith('/matches/') && this.props.location.pathname.endsWith('/edit/')) {
steps = matchEditorSteps
} else if (this.props.location.pathname.startsWith('/matches/') && this.props.location.pathname.includes('sequence')) {
steps = matchSequenceSteps
} else if (this.props.location.pathname.startsWith('/matches/') && this.props.location.pathname.match('\\d+')) {
steps = matchSteps
}
return (
<>
<Joyride
callback={this.callback}
run={run}
stepIndex={stepIndex}
steps={steps}
continuous
disableOverlayClose
spotlightClicks
showSkipButton
locale={{
back: <span>Zurück</span>,
last: (<span>Beenden</span>),
next: (<span>Weiter</span>)
}}
/>
</>
)
}
}
class Parent extends Component {
constructor (props) {
super(props)
this.handler = this.handler.bind(this)
this.state = {
run: true,
stepIndex: 0,
}
}
handler () {
this.setState({ run: true, stepIndex: 0 })
}
render () {
return (
//some other stuff
<RestartButton handler={this.handler} />
<Tutorial run={this.state.run} stepIndex={this.state.stepIndex} />
//some other stuff
)
}
}
class RestartButton extends React.Component {
render () {
return (
<button className='restartButton' onClick={() => this.props.handler()}>click</button>
)
}
}
You shouldn't store props in the child component state if state.run and state.stepIndex are always going to be the same as props.run and props.stepIndex. Instead you should just use those props directly when you need to use them in the render method. stepIndex={this.props.stepIndex} will pass exactly the same values to the Joyride component as setting the child component's state.stepIndex equal to props.stepIndex and then passing stepIndex={this.state.stepIndex}.
If you want to change the value of the parent component's state from a child component, you can pass the handler method (bound to the parent component) through as many layers of components as you want, or to as many different children as you want.
class Tutorial extends React.Component {
constructor(props) {
super(props)
}
render() {
return (
<>
<RestartButton handler={this.props.handler}/>
<Joyride
callback={this.callback}
run={this.props.run}
stepIndex = {this.props.stepIndex}
steps={steps}
continuous
disableOverlayClose
spotlightClicks
showSkipButton
locale={{
back: < span > Zurück < /span>,
last: ( < span > Beenden < /span>),
next: ( < span > Weiter < /span>)
}}
/>
</>
)
}
}
class Parent extends Component {
constructor(props) {
super(props)
this.handler = this.handler.bind(this)
this.state = {
run: true,
stepIndex: 0,
}
}
handler() {
this.setState({run: true, stepIndex: 0})
}
render() {
return (
<Tutorial
run={this.state.run}
stepIndex={this.state.stepIndex}
/>
)
}
}
class RestartButton extends React.Component {
render() {
return (
<button
className='restartButton'
onClick={() => this.props.handler()}
> click </button>
)
}
}
(Also, componentWillReceiveProps is deprecated and you should use componentDidUpdate instead, if you do need to do something on component update).
I have a component where when I click on an icon, I execute a function that modify a state and then i can check the state and modify the icon. In that comonent, I am mapping datas and it renders several items.
But when I click on one icon all the icons of the components change too.
Here is the code for the component
export default class DiscoveryComponent extends Component {
constructor(props) {
super(props)
this.state = {
starSelected: false
};
}
static propTypes = {
discoveries: PropTypes.array.isRequired
};
onPressStar() {
this.setState({ starSelected: !this.state.starSelected })
}
render() {
return (
this.props.discoveries.map((discovery, index) => {
return (
<Card key={index} style={{flex: 0}}>
<CardItem>
<TouchableOpacity style={[styles.star]}>
<Icon style={[styles.iconStar]} name={(this.state.starSelected == true)?'star':'star-outline'} onPress={this.onPressStar.bind(this)}/>
</TouchableOpacity>
</CardItem>
</Card>
)
})
);
}
}
And here is the code for my screen that uses the component
export default class DiscoveryItem extends Component {
constructor(props) {
super(props);
this.state = {
discoveries: [],
loading: true
};
}
componentDidMount() {
firebase.database().ref("discoveries/").on('value', (snapshot) => {
let data = snapshot.val();
let discoveries = Object.values(data);
this.setState({discoveries: discoveries, loading: false});
});
}
render() {
return (
<Container>
<Content>
<DiscoveryComponent discoveries={this.state.discoveries} />
</Content>
</Container>
)
}
}
Your initiation is correct but you are missing INDEX of each item. Inside this.onPressStar() method check if item's index = currentItem. Also don't forget to set item id = index onpress.
I hope this has given you idea how to handle it.
You have to turn your stars into an Array and index them:
change your constructor:
constructor(props) {
super(props)
this.state = {
starSelected: []
};
}
change your onPressStar function to :
onPressStar(index) {
this.setState({ starSelected[index]: !this.state.starSelected })
}
and your icon to
<Icon style={[styles.iconStar]} name={(this.state.starSelected[index] == true)?'star':'star-outline'} onPress={()=>this.onPressStar(index)}/>
Well, the problem is that you have a single 'starSelected' value that all of your rendered items in your map function are listening to. So when it becomes true for one, it becomes true for all.
You should probably maintain selected state in the top level component, and pass down the discovery, whether its selected, and how to toggle being selected as props to a render function for each discovery.
export default class DiscoveryItem extends Component {
constructor(props) {
super(props);
this.state = {
discoveries: [],
selectedDiscoveries: [] // NEW
loading: true
};
}
toggleDiscovery = (discoveryId) => {
this.setState(prevState => {
const {selectedDiscoveries} = prevstate
const discoveryIndex = selectedDiscoveries.findIndex(id => id === discoveryId)
if (discoveryIndex === -1) { //not found
selectedDiscoveries.push(discoveryId) // add id to selected list
} else {
selectedDiscoveries.splice(discoveryIndex, 1) // remove from selected list
}
return {selectedDiscoveries}
}
}
componentDidMount() {
firebase.database().ref("discoveries/").on('value', (snapshot) => {
let data = snapshot.val();
let discoveries = Object.values(data);
this.setState({discoveries: discoveries, loading: false});
});
}
render() {
return (
<Container>
<Content>
{
this.state.discoveries.map(d => {
return <DiscoveryComponent key={d.id} discovery={d} selected={selectedDiscoveries.includes(d.id)} toggleSelected={this.toggleDiscovery} />
//<DiscoveryComponent discoveries={this.state.discoveries} />
</Content>
</Container>
)
}
}
You can then use your DiscoveryComponent to render for each one, and you're now maintaining state at the top level, and passing down the discovery, if it is selected, and the toggle function as props.
Also, I think you may be able to get snapshot.docs() from firebase (I'm not sure as I use firestore) which then makes sure that the document Id is included in the value. If snapshot.val() doesn't include the id, then you should figure out how to include that to make sure that you use the id as both key in the map function as well as for the selectedDiscoveries array.
Hope that helps
It works now, thanks.
I've made a mix between Malik and Rodrigo's answer.
Here is the code of my component now
export default class DiscoveryComponent extends Component {
constructor(props) {
super(props)
this.state = {
tabStarSelected: []
};
}
static propTypes = {
discoveries: PropTypes.array.isRequired
};
onPressStar(index) {
let tab = this.state.tabStarSelected;
if (tabStar.includes(index)) {
tabStar.splice( tabStar.indexOf(index), 1 );
}
else {
tabStar.push(index);
}
this.setState({ tabStarSelected: tab })
}
render() {
return (
this.props.discoveries.map((discovery, index) => {
return (
<Card key={index} style={{flex: 0}}>
<CardItem>
<Left>
<Body>
<Text note>{discovery.category}</Text>
<Text style={[styles.title]}>{discovery.title}</Text>
</Body>
</Left>
<TouchableOpacity style={[styles.star]}>
<Icon style={[styles.iconStar]} name={(this.state.tabStarSelected[index] == index)?'star':'star-outline'} onPress={()=>this.onPressStar(index)}/>
</TouchableOpacity>
</CardItem>
</Card>
)
})
);
}
}
I'm trying to update more than 5 components' status after a button is clicked. I'm now putting all the status and event handlers in App.js. For example,
// MyButton.js
export const MyButton = (props) => {
<button type='button' onClick={props.onClick}>Click Me!</button>
}
// Component1.js
export const Component1 = ({ name }) => {
return (
<h1>{name}</h1>
);
}
// Component2.js
export const Component2 = ({ name }) => {
return (
<h1>{name}</h1>
);
}
App.js
class App extends React.Component {
constructor(props) {
super(props);
this.state = { name1: '', name2: '' };
this.changeNames = this.changeNames.bind(this);
}
changeNames() {
this.changeComponent1Name();
this.changeComponent2Name();
}
changeComponent1Name() {
this.setState({ name1: 'name1' });
}
changeComponent2Name() {
this.setState({ name2: 'name2' });
}
render() {
return (
<MyButton onClick={this.changeName} />
<Component1 name={this.state.name1} />
<Component2 name={this.state.name2} />
);
}
}
ReactDOM.render(<App />, document.getElementById('app'));
The code becomes more complicated as more components I have to update when the button is clicked. I'd like to put the status and event handlers in each components so the code can be more readable. Any suggestions?
just loop over state:
changeNames() {
let newState = {...this.state}
for (var stateProp in newState) {
//add here your logic depending on prop name/for every prop
}
}
I have a simple component who show element onClick:
class MyComponent extends React.Component {
state = {
isVisible : false
}
render() {
const { isVisble } = this.state
return(
<div>
{isVisble ?
<div onClick={() => this.setState({isVisble: false})}>Hide</div> :
<div onClick={() => this.setState({isVisble: true})}>Show</div>}
</div>
)
}
}
I use this component three times in other component :
class MySuperComponent extends React.Component {
render() {
return(
<div>
<MyComponent />
<MyComponent />
<MyComponent />
</div>
)}
}
I need to pass isVisible at false for all other component if one of have isVisible to true
How to do that ?
Thanks
You should have your component controlled, so move isVisble to props and and then assign it from MySuperComponent.
Also pass MyComponent a callback so it can inform the parent if it wants to change the state.
You'd want some data structure to store that states.
https://codepen.io/mazhuravlev/pen/qxRGzE
class MySuperComponent extends React.Component {
constructor(props) {
super(props);
this.state = {children: [true, true, true]};
this.toggle = this.toggle.bind(this);
}
render() {
return (
<div>
{this.state.children.map((v, i) => <MyComponent visible={v} toggle={() => this.toggle(i)}/>)}
</div>
)
}
toggle(index) {
this.setState({children: this.state.children.map((v, i) => i !== index)});
}
}
class MyComponent extends React.Component {
render() {
const text = this.props.visible ? 'visible' : 'hidden';
return (<div onClick={this.props.toggle}>{text}</div>);
}
}
React.render(<MySuperComponent/>, document.getElementById('app'));
You can check your code here, is this what you want.
example
I am struggling with successfully removing component on clicking in button. I found similar topics on the internet however, most of them describe how to do it if everything is rendered in the same component. In my case I fire the function to delete in the child component and pass this information to parent so the state can be changed. However I have no idea how to lift up the index of particular component and this is causing a problem - I believe.
There is a code
PARENT COMPONENT
export class BroadcastForm extends React.Component {
constructor (props) {
super(props)
this.state = {
numberOfComponents: [],
textMessage: ''
}
this.UnmountComponent = this.UnmountComponent.bind(this)
this.MountComponent = this.MountComponent.bind(this)
this.handleTextChange = this.handleTextChange.bind(this)
}
MountComponent () {
const numberOfComponents = this.state.numberOfComponents
this.setState({
numberOfComponents: numberOfComponents.concat(
<BroadcastTextMessageForm key={numberOfComponents.length} selectedFanpage={this.props.selectedFanpage}
components={this.state.numberOfComponents}
onTextChange={this.handleTextChange} dismissComponent={this.UnmountComponent} />)
})
}
UnmountComponent (index) {
this.setState({
numberOfComponents: this.state.numberOfComponents.filter(function (e, i) {
return i !== index
})
})
}
handleTextChange (textMessage) {
this.setState({textMessage})
}
render () {
console.log(this.state)
let components = this.state.numberOfComponents
for (let i = 0; i < components; i++) {
components.push(<BroadcastTextMessageForm key={i} />)
}
return (
<div>
<BroadcastPreferencesForm selectedFanpage={this.props.selectedFanpage}
addComponent={this.MountComponent}
textMessage={this.state.textMessage} />
{this.state.numberOfComponents.map(function (component) {
return component
})}
</div>
)
}
}
export default withRouter(createContainer(props => ({
...props
}), BroadcastForm))
CHILD COMPONENT
import React from 'react'
import { createContainer } from 'react-meteor-data'
import { withRouter } from 'react-router'
import { BroadcastFormSceleton } from './BroadcastForm'
import './BroadcastTextMessageForm.scss'
export class BroadcastTextMessageForm extends React.Component {
constructor (props) {
super(props)
this.handleChange = this.handleChange.bind(this)
this.unmountComponent = this.unmountComponent.bind(this)
}
handleChange (e) {
this.props.onTextChange(e.target.value)
}
unmountComponent (id) {
this.props.dismissComponent(id)
}
render () {
console.log(this.props, this.state)
const textMessage = this.props.textMessage
return (
<BroadcastFormSceleton>
<div className='textarea-container p-3'>
<textarea id='broadcast-message' className='form-control' value={textMessage}
onChange={this.handleChange} />
</div>
<div className='float-right'>
<button type='button'
onClick={this.unmountComponent}
className='btn btn-danger btn-outline-danger button-danger btn-small mr-3 mt-3'>
DELETE
</button>
</div>
</BroadcastFormSceleton>
)
}
}
export default withRouter(createContainer(props => ({
...props
}), BroadcastTextMessageForm))
I am having problem with access correct component and delete it by changing state. Any thoughts how to achieve it?
Please fix the following issues in your code.
Do not mutate the state of the component. Use setState to immutably change the state.
Do not use array index as the key for your component. Try to use an id field which is unique for the component. This will also help with identifying the component that you would need to unmount.
Try something like this. As mentioned before, you don't want to use array index as the key.
class ParentComponent extends React.Component {
constructor() {
this.state = {
// keep your data in state, as a plain object
textMessages: [
{
message: 'hello',
id: '2342334',
},
{
message: 'goodbye!',
id: '1254534',
},
]
};
this.handleDeleteMessage = this.handleDeleteMessage.bind(this);
}
handleDeleteMessage(messageId) {
// filter by Id, not index
this.setState({
textMessages: this.state.textMessages.filter(message => message.id !== messageId)
})
}
render() {
return (
<div>
{this.state.textMessages.map(message => (
// Use id for key. If your data doesn't come with unique ids, generate them.
<ChildComponent
key={message.id}
message={message}
handleDeleteMessage={this.handleDeleteMessage}
/>
))}
</div>
)
}
}
function ChildComponent({message, handleDeleteMessage}) {
function handleClick() {
handleDeleteMessage(message.id)
}
return (
<div>
{message.message}
<button
onClick={handleClick}
>
Delete
</button>
</div>
);
}