change state in Grandparent Component with button - javascript

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).

Related

in reactjs want to update parent state based on child state?

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} />

React Passing Props to Second-level Children

I faced the problem of passing props while coding in React. Yes, I have seen this issue before, but this time it's a second level-children component and things are a bit weird. My code (comments along the way):
class EditForm extends React.Component {
handleSubmit = (event, idx) => {
event => event.preventDefault();
postData('/', {idx: idx})
.then(data => {if (data.success) {window.location = '/';}});
console.log(idx); // results printed from here
}
render() {
return (
<Form
onFinish={() => this.handleSubmit(idx)} // output 1
onFinish={() => this.handleSubmit(this.props.idx)} // output 2
>
</Form>
);
}
}
class UpdateModal extends React.Component {
render() {
return (
<Modal>
<EditForm idx={ this.props.idx } /> // value is still not undefined
</Modal>
);
}
}
Outputs:
// 1
useForm.js:766 ReferenceError: idx is not defined
// 2
undefined
Can anyone please explain why I can't pass the props two times in a row? As a matter of fact, the values are still valid when they are in UpdateModal but is gone somehow afterward.
Thanks in advance.
You should pass in the event object to your handlers:
class EditForm extends React.Component {
handleSubmit = (event, idx) => {
event => event.preventDefault();
postData('/', {idx: idx})
.then(data => {if (data.success) {window.location = '/';}});
console.log(idx); // results printed from here
}
render() {
return (
<Form
onFinish={(event) => this.handleSubmit(event, idx)} // output 1
onFinish={(event) => this.handleSubmit(event, this.props.idx)} // output 2
>
</Form>
);
}
}
class UpdateModal extends React.Component {
render() {
return (
<Modal>
<EditForm idx={ this.props.idx } /> // value is still not undefined
</Modal>
);
}
}
class EditForm extends React.Component {
constructor(props) {
super(props);
}
// ...
}
class UpdateModal extends React.Component {
constructor(props) {
super(props);
}
// ...
}
// <EditForm idx={this.state.idx}></EditForm>
// <UpdateModal idx={this.state.idx}></UpdateModal>

React - updating state to add/remove active class from tab navigation

I have a tab navigation at the top of my page, and I want to add an 'active' class to the tab when it's clicked and make sure it's not on any of the other tabs.
So far what I have adds the 'active' class to the first tab, but doesn't update if you click on any of the other tabs. So the first tab is always the active tab regardless of what you click on.
import React from 'react'
import { string } from 'prop-types'
class TabNav extends React.Component {
constructor(props) {
super(props)
this.state = {
currentTab: ''
}
this.handleClick = this.handleClick.bind(this)
this.createTabItems = this.createTabItems.bind(this)
}
shouldComponentUpdate (nextProps, nextState) {
return nextProps.navItems !== this.props.navItems
}
handleClick (currentTab) {
this.setState({
currentTab: currentTab
})
this.createTabItems()
}
createTabItems () {
const { navItems = false } = this.props
if (!navItems) return false
const splitItems = navItems.split(',')
if (!splitItems.length) return false
return splitItems.map((item, currentTab) => {
const items = item.split('_')
if (items.length !== 3) return null
const itemLink = items[1]
return (
<li key={currentTab} className={this.state.currentTab == currentTab ? 'side-nav-tab active' : 'side-nav-tab'} onClick={this.handleClick.bind(this, currentTab)}>
<a href={itemLink}>
<p>{ items[0] }</p>
</a>
</li>
)
})
}
render () {
const tabItems = this.createTabItems()
if (!tabItems) return null
return (
<div>
<ul id='tabNavigation'>
{tabItems}
</ul>
</div>
)
}
}
TabNav.propTypes = {
navItems: string.isRequired
}
export default TabNav
I have also tried calling this.createTabItems asynchronously in setState to try and force an update but that didn't work:
handleClick (currentTab) {
this.setState({
currentTab: currentTab
}, () => this.createTabItems)
}
I think your shouldComponentUpdate is causing the issue. Can you try removing it? Also, you don't need to call this.createTabItems in your handleClick()
I feel like a mindset shift is required here to think more declaratively, you don't need to tell React when you want to create the tabs, it will determine this itself from the render method and the data that's passed to it.
I've removed your componentShouldUpdate function because React will already do the comparison of those props for you. I've also tracked the selected item by index because it simplifies the logic in my mind.
Try something like this:
import React from 'react';
import { string } from 'prop-types';
class TabNav extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedIndex: 0
};
}
handleClick = index => {
this.setState({
selectedIndex: index
});
};
render() {
const { navItems = [] } = this.props;
return (
<div>
<ul id="tabNavigation">
{navItems.map((navItem, index) => {
if (navItem.length !== 3) return;
const [text, link] = navItem;
return (
<li
className={
this.state.selectedIndex === index
? 'side-nav-tab active'
: 'side-nav-tab'
}
onClick={this.handleClick.bind(null, index)}
>
<a href={link}>
<p>{text}</p>
</a>
</li>
);
})}
}
</ul>
</div>
);
}
}
TabNav.propTypes = {
navItems: string.isRequired
};
export default TabNav;
There are a couple other changes in there, like destructuring from the array rather than using indexes so it's clearer what the properties you're pulling out of a navItem array are.
I also used a fat arrow function for the handleClick function because it means you don't have to bind to this in your constructor.
You got two problems, first the way you are using this in your function and secondly your shouldUpdateComponent, just like the other mentioned before me. If you want to reference your class using this you need to use arrow functions. remember that the ES6 arrow function uses lexical scoping which means that this references the code that contains the function. I made the changes in the code below with a working example.
class TabNav extends React.Component {
constructor(props) {
super(props)
this.state = {
currentTab: ''
}
this.handleClick = this.handleClick.bind(this)
this.createTabItems = this.createTabItems.bind(this)
}
handleClick (currentTab) {
this.setState({
currentTab: currentTab
})
}
createTabItems = () => {
const { navItems = false } = this.props
if (!navItems) return false
const splitItems = navItems.split(',')
if (!splitItems.length) return false
return splitItems.map((item, currentTab) => {
const items = item.split('_')
if (items.length !== 3) return null
const itemLink = items[1]
return (
<li key={currentTab} className={this.state.currentTab == currentTab ? 'side-nav-tab active' : 'side-nav-tab'} onClick={this.handleClick.bind(null, currentTab)}>
<a href={itemLink}>
<p>{ items[0] }</p>
</a>
</li>
)
})
}
render () {
const tabItems = this.createTabItems()
if (!tabItems) return null
return (
<div>
<ul id='tabNavigation'>
{tabItems}
</ul>
</div>
)
}
}
class Nav extends React.Component {
render() {
return(
<TabNav navItems={'Test1_#_Test1,Test2_#_Test2'} />)
}
}
ReactDOM.render(<Nav />, document.getElementById('root'))
.active {
font-size: 20px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
.

How to fire a function in child from parent in react?

My React app has three components. Two of them are child components and the other is parent. I need to pass a data (projectId) from one child component to the other child through the parent component and after receiving the data, fire a function. As my example, I'm sending projectId from ChildOne to Parent and then send projectId from Parent to ChildTwo. ChildTwo has a function called setProject(projectId) and I need to fire it once the projectID is received. The problem is I can't get the function getProjectId fired in ChildTwo by clicking on the button in ChildOne. I also tried with componentDidMount and componentWillReceiveProps which are not working for me. How can I do this?
Here what I tried
ChildOne :
class ChildOne extends React.Component {
constructor(props) {
super(props);
this.state = {
projectId: 3,
};
}
sendProjectId = (projectId) => {
this.props.sendId(projectId)
}
render() {
return(
<button onClick={() => this.sendProjectId(this.state.projectId)}>
Click
</button>
)
}
}
Parent:
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {
projectId: '',
};
}
getId = (proId) => {
this.setState({
projectId : proId
})
}
render() {
return(
<div>
<CildOne sendId={this.getId} />
<CildTwo sendOneId={this.state.projectId} />
</div>
)
}
}
ChildTwo:
class ChildTwo extends React.Component {
constructor(props) {
super(props);
this.state = {
projectId: '',
};
}
getProjectId = (this.props.sendOneId) => {
//Do something with this.props.sendOneId
}
render() {
return(
<div></div>
)
}
}
This would depend on what ChildTwo wants to accomplish with the said data.
Case 1:
ChildTwo intends to fetch some data with the corresponding projectId and display it in the component. Then, you can easily fetch this data in the parent component and pass the data down as props.
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {
projectId: '',
dataForChildTwo: null,
};
}
getId = (proId) => {
this.setState({
projectId : proId,
dataForChildTwo: fetchData(proId)
})
}
render() {
return(
<div>
<CildOne sendId={this.getId} />
<CildTwo data={this.state.dataForChildTwo} />
</div>
)
}
}
Case 2:
ChildTwo intends to make some change to something inside it when projectId changes. Then you can use componentDidUpdate hook to see if prop changed and respond to it.
class ChildTwo extends React.Component {
constructor(props) {
super(props);
this.state = {
projectId: '',
};
}
getProjectId = (this.props.sendOneId) => {
//Do something with this.props.sendOneId
}
componentDidUpdate(prevProps) {
if(this.props.projectId!==prevProps.projectId) {
// do something
}
}
render() {
return(
<div></div>
)
}
}
Case 3:
If none of the above cases work for you, then you can manually reload the complete component when the projectId changes using a key attribute:
<CildTwo key={this.state.projectId} sendOneId={this.state.projectId} />
Note: This reloads the whole component quite unnecessarily.
You did a mistake in getProjectId function of ChildTwo component.
Your function cannot receive anything as a parameter from prop.
So, your function should look like:
getProjectId = (sendOneId) => {
//Do something with this.props.sendOneId
}
Then you should use componentWillReceiveProps like this:
componentWillReceiveProps(nextProps) {
if (this.props.sendOneId !== nextProps.sendOneId) {
this.getProjectId(nextProps.sendOneId);
}
}
Here is a working codesandbox example that I created to fix your problem:
https://codesandbox.io/s/5v4rn7qnll
You should probably use componentDidUpdate with a condition to check to see whether the projectId in state needs to be updated when sendOneId changes. You can then use setStates callback to call getProjectId:
componentDidUpdate() {
const { projectId: currentProjectId } = this.state;
const { sendOneId: projectId } = this.props;
if (projectId !== currentProjectId) {
this.setState({ projectId }, () => this.getProjectId());
}
}
Full working example:
class ChildOne extends React.Component {
constructor(props) {
super(props);
this.state = {
projectId: 3,
};
}
sendProjectId = (projectId) => {
this.props.sendId(projectId)
}
render() {
return (
<button onClick={() => this.sendProjectId(this.state.projectId)}>
Click
</button>
);
}
}
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {
projectId: '',
};
}
getId = (projectId) => {
this.setState({ projectId });
}
render() {
return (
<div>
<ChildOne sendId={this.getId} />
<ChildTwo sendOneId={this.state.projectId} />
</div>
)
}
}
class ChildTwo extends React.Component {
constructor(props) {
super(props);
this.state = {
projectId: '',
};
}
componentDidUpdate() {
const { projectId: currentProjectId } = this.state;
const { sendOneId: projectId } = this.props;
if (projectId !== currentProjectId) {
this.setState({ projectId }, () => this.getProjectId());
}
}
getProjectId = () => {
console.log(this.state.projectId);
}
render() {
return (
<div></div>
);
}
}
ReactDOM.render(
<Parent />,
document.getElementById('container')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="container"></div>
Our you can try a functional component or hooks if you want to set some state
function ChildOne(props) {
const [projectId, setProjectId] = useState(3);
function sendProjectId(data){
props.sendId(projectId)
}
return(
<button onClick={() => sendProjectId(projectId)}>
Click
</button>
)
}
function ChildTwo(props) {
const [state, setState] = useState('')
function getProjectId(data) {
//Do something with this.props.sendOneId
console.log(`data here ${data}`)
return false;
}
getProjectId(props.sendOneId)
return (
<div>
</div>
)
}
function Parent(){
const [projectId, setProjectId] = useState('');
function getId(proId) {
setProjectId(proId)
}
return(
<div>
<ChildOne sendId={getId} />
<ChildTwo sendOneId={projectId} />
</div>
)
}

Unmount component on click in child component button // React

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>
);
}

Categories