Converting a React Class Component to Function Component - reactjs

I have build a small application using class component and it's working fine also.
While I convert that application to function component to use react hooks, it's giving me error.
Please check and let me know where it went wrong.
function component
import React ,{useState} from 'react';
import './App.css';
import Counters from './component/counters';
import Navbar from './component/navbar';
function App(props) {
const initialState = [
{ id: 1, value: 0 },
{ id: 2, value: 10 },
{ id: 3, value: 20 },
{ id: 4, value: 30 },
];
const [counters, setCounters] = useState(initialState);
const handleIncrement = (counter) => {
// const counters = [...counters];
// const index = counters.indexOf(counter);
// counters[index] = { ...counter };
// counters[index].value++;
setCounters({ counter : counter.value +1 });
};
const handleDecrement = (counter) => {
// // const counters = [...counters];
// const index = counters.indexOf(counter);
// counters[index] = { ...counter };
// counters[index].value--;
// setCounters({ counters });
};
const handleDelete = (counterId) => {
// const counters = counters.filter((c) => c.id !== counterId);
// setCounters({ counters });
};
return (
<div className="container">
{/* <Navbar totalCounters={counters.reduce((a,c)=>a + c.value,0 )}/> */}
<Navbar totalCounters={counters.filter((c) => c.value > 0).count()}/>
<Counters
counters={counters}
onIncrement={handleIncrement}
onDecrement={handleDecrement}
onDelete={handleDelete}
/>
</div>
);
}
export default App;
class component
import './App.css';
import Counters from './component/counters';
import Navbar from './component/navbar';
import React from 'react';
class App extends React.Component {
state = {
counters: [
{ id: 1, value: 0 },
{ id: 2, value: 10 },
{ id: 3, value: 20 },
{ id: 4, value: 30 },
],
};
handleIncrement = (counter) => {
const counters = [...this.state.counters];
const index = counters.indexOf(counter);
counters[index] = { ...counter };
counters[index].value++;
this.setState({ counters });
};
handleDecrement = (counter) => {
const counters = [...this.state.counters];
const index = counters.indexOf(counter);
counters[index] = { ...counter };
counters[index].value--;
this.setState({ counters });
};
handleDelete = (counterId) => {
const counters = this.state.counters.filter((c) => c.id !== counterId);
this.setState({ counters });
};
render() {
return (
<div className="container">
<Navbar totalCounters={this.state.counters.reduce((a,c)=>a + c.value,0)}/>
<Counters
counters={this.state.counters}
onIncrement={this.handleIncrement}
onDecrement={this.handleDecrement}
onDelete={this.handleDelete} />
</div>
);
}
}
export default App;
In case, if you want to see the full code then just let me know.

Issue
The issue is your counters state is an array but you are mutating the state invariant to be an object in your handlers.
Solution
App - Use a functional state update to map the counters array from the previous state to the next state, using the id to match the specific counter you want to increment/decrement/delete. This shallow copies the previous array. Notice also that the counter being updated is also shallow copied into a new object reference. When deleting a counter you are correct to use .filter to remove the specific element and return a new array.
function App(props) {
const initialState = [
{ id: 1, value: 0 },
{ id: 2, value: 10 },
{ id: 3, value: 20 },
{ id: 4, value: 30 },
];
const [counters, setCounters] = useState(initialState);
const handleIncrement = (id) => {
setCounters(counters => counters.map(counter => counter.id === id
? {
...counter,
value: counter.value + 1,
}
: counter
));
};
const handleDecrement = (id) => {
setCounters(counters => counters.map(counter => counter.id === id
? {
...counter,
value: counter.value - 1,
}
: counter
));
};
const handleDelete = (id) => {
setCounters(counters => counters.filter((c) => c.id !== id));
};
return (
<div className="container">
<Navbar totalCounters={counters.filter((c) => c.value > 0).count()}/>
<Counters
counters={counters}
onIncrement={handleIncrement}
onDecrement={handleDecrement}
onDelete={handleDelete}
/>
</div>
);
}
Counters - pass the counter id to the handlers
class Counters extends Component {
render() {
const { onIncrement, onDecrement, onDelete, counters } = this.props;
return (
<div>
{counters.map((counter) => (
<Counter
key={counter.id}
counter={counter}
onIncrement={() => {
onIncrement(counter.id);
}}
onDecrement={() => {
onDecrement(counter.id);
}}
onDelete={() => {
onDelete(counter.id);
}}
/>
))}
</div>
);
}
}
Counter - since the id is closed in callback scope above just call the event handlers
class Counter extends Component {
...
render() {
return (
<div>
<span className={this.getBadge()}>{this.getCount()}</span>
<button
className="btn btn-secondary m-2"
onClick={this.props.onIncrement}
>
Increment
</button>
<button
className="btn btn-secondary m-2"
onClick={this.props.onDecrement}
>
Decrement
</button>
<button className="btn btn-danger m-2" onClick={this.props.onDelete}>
Delete
</button>
</div>
);
}
}

In your handleIncrement() function, you're setting an object, when the setCounters method is expecting an array of objects.
You can use ES6 destructuring. Also note the object key should be the id not the object.
I expect the below to fix the error.
setCounters([...counters, { counter.id : counter.value +1 }]);
And I believe below is the implementation you're looking for.
otherCounters = counters.filter(c => c.id != counter.id)
setCounters([...otherCounters, { counter.id : counter.value +1 }])
EDIT: One thing I noticed in your counters.jsx and counter.jsx is that you're calling a function with variables that do not expect one.
From counters.jsx
<Counter
...
// onIncrement of "Counter" (the 1st onIncrement on the left)
// is a function that do not expect a variable.
onIncrement={() => {onIncrement(counter);}}
/>
From counter.jsx
<button>
...
// "onIncrement" is called with "this.props.counter". However this "onIncrement" is not expecting a variable.
onClick={() => this.props.onIncrement(this.props.counter)}
</button>
For the correct syntax, you can change either, but as example you can change counter.jsx to
<button>
...
onClick={() => this.props.onIncrement()}
</button>

In functional component on react when you wanna make state you must be use useState, you can follow my code here:
import './App.css';
import Counters from './component/counters';
import Navbar from './component/navbar';
import { useState } from 'react';
const App = () => {
const [countersState, setCountersState] = useState([
{ id: 1, value: 0 },
{ id: 2, value: 10 },
{ id: 3, value: 20 },
{ id: 4, value: 30 },
]);
const handleIncrement = (counter) => {
const counters = countersState
const index = counters.indexOf(counter);
counters[index] = { ...counter };
setCountersState(counters);
};
const handleDecrement = (counter) => {
const counters = countersState
const index = counters.indexOf(counter);
console.log(index)
counters[index] = { ...counter };
counters[index].value--;
setCountersState(counters);
};
const handleDelete = (counterId) => {
const counters = countersState.filter((c) => c.id !== counterId);
setCountersState(counters)
}
return (
<div className="container">
<Navbar totalCounters={this.state.counters.reduce((a,c)=>a + c.value,0)}/>
<Counters
counters={countersState}
onIncrement={handleIncrement}
onDecrement={handleDecrement}
onDelete={handleDelete} />
</div>
);
};
export default App;

Related

react memoize using React.memo

I Try not to Rerender Persons Component When ShowCockpit State Changes In MainAssignment Component. Like when i do in Cockpit Component, it doesn't rerender When Persons state change.
In This Case We Have 3 Components MainAssignment Component [parnt] , Cockpit Component [child] , Persons Component [child].
/********************************************************/
/*** MainAssignment Component ***/
import React, { useCallback, useState } from 'react';
import Persons from './persons';
import Coockpit from './cockpit';
const MainAssignment = () => {
// All State
const [persons, setPersons] = useState([
{ id: '1', name: 'mustafa', age: 24 },
{ id: '2', name: 'ahmed', age: 25 },
{ id: '3', name: 'saad', age: 26 },
]);
const [showPersons, setShowPersons] = useState(true);
const [showCoockpit, setShowCoockpit] = useState(true);
const togglePersonHandler = useCallback(() => {
setShowPersons(!showPersons);
}, [showPersons]);
// change name in specific object in persons state
const nameChangeHandler = (e, id, personIndex) => {
let newPersons = [...persons];
let person = { ...newPersons[personIndex] };
person.name = e.target.value;
newPersons[personIndex] = person;
setPersons(newPersons);
};
// delete object from persons state
const deletePersonHandler = (personIndex) => {
let newPersons = [...persons];
newPersons.splice(personIndex, 1);
setPersons(newPersons);
};
// Main Render
return (
<>
<button
onClick={() => {
setShowCoockpit((prev) => !prev);
}}
>
remove Coockpit
</button>
{showCoockpit ? (
<div style={{ border: '1px solid' }}>
<Coockpit clicked={togglePersonHandler} personsLength={persons.length} showPersons={showPersons} />
</div>
) : null}
{showPersons ? <Persons persons={persons} clicked={deletePersonHandler} changed={nameChangeHandler} /> : null}
</>
);
};
export default MainAssignment;
/********************************************************/
/*** Cockpit Component ***/
/********************************************************/
/*** Cockpit Component ***/
import React, { useRef } from 'react';
const Cockpit = ({ clicked }) => {
let toggleBtnRef = useRef(null);
console.log('render => Cockpit');
return (
<div>
<h1>hi i'm a main assin from cockpit</h1>
<button className="toggle-persons" onClick={clicked} ref={toggleBtnRef}>
toggle persons
</button>
</div>
);
};
// in Cockpit i use React.memo and it work
export default React.memo(Cockpit);
/********************************************************/
/*** Persons Component ***/
import React, { useEffect, useRef } from 'react';
import Person from './person';
const Persons = ({ persons, clicked, changed }) => {
console.log('render => personssss');
const mainRef = {
allInputPersonRef: useRef([]),
};
return (
<>
{persons?.map((person, idx) => (
<Person
key={idx}
name={person.name}
age={person.age}
position={idx}
index={idx}
ref={mainRef}
click={() => {
clicked(idx);
}}
changed={(e) => {
changed(e, person.id, idx);
}}
/>
))}
</>
);
};
// in Persons i use React.memo and it doesn't work
export default React.memo(Persons);
/********************************************************/
/*** Person Component ***/
import React from 'react';
const Person = React.forwardRef((props, ref) => {
const { allInputPersonRef } = ref;
// value of props
const { name, age, click, changed, children, index } = props;
return (
<div>
<p onClick={click}>
i'm {name} and i'm {age} years old
</p>
<p> i'am props children: {children}</p>
<input type="text" onChange={changed} value={name} ref={(el) => (allInputPersonRef.current[index] = el)} />
<button onClick={click}>delete this person</button>
</div>
);
});
export default Person;
React.memo can prevent children from rerendering when the parent component rerenders.
It compares (by reference) each previous and next prop. When one of them is different React will rerender the child normally.
In your case you are always passing new function to changed prop
const nameChangeHandler = (e, personIndex) => {
let newPersons = [...persons];
let person = { ...newPersons[personIndex] };
person.name = e.target.value;
newPersons[personIndex] = person;
setPersons(newPersons);
};
How to avoid this?
Make sure that nameChangeHandler is the same function each time you need to rerender and you don't want to rerender the Person component. https://reactjs.org/docs/hooks-reference.html#usecallback
const nameChangeHandler = useCallback((e, personIndex) => {
setPersons((persons) => {
let newPersons = [...persons];
let person = { ...newPersons[personIndex] };
person.name = e.target.value;
newPersons[personIndex] = person;
return newPersons
});
}, []);
Similarly you should memorize deletePersonHandler function
const deletePersonHandler = useCallback((personIndex) => {
setPersons((persons)=>{
let newPersons = [...persons];
newPersons.splice(personIndex, 1);
return newPersons
});
}, []);
using useCallback with togglePersonHandler and deletePersonHandler
const nameChangeHandler = useCallback((e, id, personIndex) => {
let newPersons = [...persons];
let person = { ...newPersons[personIndex] };
person.name = e.target.value;
newPersons[personIndex] = person;
setPersons(newPersons);
}, []);
const deletePersonHandler = useCallback((personIndex) => {
let newPersons = [...persons];
newPersons.splice(personIndex, 1);
setPersons(newPersons);
}, []);

Objects are not valid as a React child (object with keys {id, value, name}). If you meant to render a collection of children, use an array instead

I don't know what is the error in this, since I am a beginner, please help me out. When I added handleIncrement, this error showed up.
import React, { Component } from 'react';
import Counter from './counter';
class Counters extends Component {
state = {
counters: [
{ id: 1, value: 4, name:'Cow Milk' },
{ id: 2, value: 0, name:'Buffalo Milk' },
{ id: 3, value: 0, name:'Almond Milk' },
{ id: 4, value: 0, name:'Cashew Milk' }
]
};
handleIncrement = counter => {
const counters = [...this.state.counters]; //cloning the state array with id, value and name.
const index = counters.indexOf(counter);
counters[index] = {...counter};
counters[index].value++;
this.setState({counters});
}
handleDelete = (counterId) => {
const counters = this.state.counters.filter(c => c.id !== counterId); //gets all the counters except the deleted one. now we have a new array.
this.setState({ counters });//because the key and value are the same({ counters: counters }), we can simplify
};
handleReset = (counterId) => {
const counters = this.state.counters.map(c => {
c.value = 0;
return c;
});
this.setState({ counters });
}; //map all the counters, reset its value to 0 and then return it.
render() {
return (
<div className="container col-12">
<button
onClick={this.handleReset}
className="btn-primary btn-sm m-2">
Reset
</button>
{ this.state.counters.map(counter => (
<Counter
key={counter.id}
onIncrement={this.handleIncrement}
onDelete={this.handleDelete}
counter={counter}
name={counter.name}
>
<h5>{counter.name}</h5>
</Counter>
))}
<div className="row justify-content-center">
<div className="col-3 mt-3">
<button className="btn btn-dark btn-block" >Order</button>
</div>
<div className="col-3 mt-3">
<button className="btn btn-dark btn-block" >Cancel</button>
</div>
</div>
</div>
);
}
}
export default Counters;`enter code here`
Objects are not valid as a React child (object with keys {id, value, name}). If you meant to render a collection of children, use an array instead
In practice, I have seen this error, when I was trying to use object as a child, e.g. this snippet will raise this error:
function User() {
const user = {
name: 'name',
age: 10
}
return <>{user}</>
}
I don't see the actual solution. But, the better way to write the handleIncrement function is following:
handleIncrement = (id) => {
const counter = this.state.counter.map((c) => {
if (c.id === id) {
return {
...c,
value: c.value + 1
}
}
return {...c}
})
this.setState({ counter })
}
When you will call the function in the Counter component you need to pass counter.id to it. This solution is better, because you work with immutable data structure and it easier to debug.

React and state

I would like your take on a specific implementation. I have a react app (no redux), the app has a shopping cart. The shopping cart is defined in the state in the App component and it is passed and used further down the tree in several components. E.g. I have a component called ShoppingCart, it displays the shopping cart, plus it has actions to add/remove/clear the cart.
My problem is updating the shopping cart state after performing an action on the shopping cart. E.g. when I call a function to clear the shopping cart, the state should be updated in the App component thus updating my component which is further down the tree. How would one implement these action functions (without redux)?
Code:
const App = () => {
const [cart, setCart] = useState({ lines: [], total: 0 });
return <ShoppingCart cart={cart} />;
}
const ShoppingCart = ({ cart }) => {
const onAddOne = l => {
// not sure how to update cart and update state
}
const onRemoveOne = l => {
// not sure how to update cart and update state
}
return (
<table>
{
cart.lines.map(l => <tr><td>{l.name}</td><td><button onClick={() => onAddOne(l)}>+</button><button onClick={() => onRemoveOne(l)}>-</button></td></tr>)
}
</table>
);
}
Thanks in advance for any tip.
Here you can use the useContext hook.
The idea is similar to redux.
So, what you can do is, first create a StateProvider, like in the example
import React, { createContext, useReducer, useContext } from "react";
export const StateContext = createContext();
export const StateProvider = ({ reducer, initialState, children }) => (
<StateContext.Provider value={useReducer(reducer, initialState)}>
{children}
</StateContext.Provider>
);
export const useStateValue = () => useContext(StateContext);
Similarly, create a Reducer for that, you can add more reducers, the example shown is to ADD ITEMS IN BASKET and REMOVE ITEMs FROM BASKET
export const initialState = {
basket: [],
user: null,
};
export const getBasketTotal = (basket) =>
basket?.reduce((amount, item) => item.price + amount, 0);
function reducer(state, action) {
switch (action.type) {
case "ADD_TO_BASKET":
return { ...state, basket: [...state.basket, action.item] };
case "REMOVE_ITEM":
let newBasket = [...state.basket];
const index = state.basket.findIndex(
(basketItem) => basketItem.id === action.id
);
if (index >= 0) {
newBasket.splice(index, 1);
} else {
console.warn("Cant do this");
}
return { ...state, basket: newBasket };
default:
return state;
}
}
export default reducer;
Go to your index.js file and wrap your file like this
<StateProvider initialState={initialState} reducer={reducer}>
<App />
</StateProvider>
And voila, while adding items to the basket use following code
const addtobasket = () => {
dispatch({
type: "ADD_TO_BASKET",
item: {
id: id,
title: title,
price: price,
rating: rating,
color: color,
},
});
};
I found a solution, however, I am not sure it is the correct way to do things:
const App = () => {
const onUpdateCart = (cart) => {
setCart({ ...cart });
}
const [cart, setCart] = useState({ lines: [], total: 0, onUpdateCart });
return <ShoppingCart cart={cart} />;
}
const ShoppingCart = ({ cart }) => {
const onRemoveLine = l => {
cart.lines = cart.lines.filter(l2 => l2 !== l);
cart.onUpdateCart(cart);
}
const onAddOne = l => {
l.amount++;
cart.onUpdateCart(cart);
}
const onRemoveOne = l => {
l.amount--;
cart.onUpdateCart(cart);
}
return (
<table>
{
cart.lines.map(l => (
<tr>
<td>{l.name}</td>
<td>
<button onClick={() => onAddOne(l)}>+</button>
<button onClick={() => onRemoveOne(l)}>-</button>
<button onClick={() => onRemoveLine(l)}>x</button>
</td>
</tr>)
)
}
</table>
);
};
The straight forward way to implement this is to pass down props to the child component that when called update the state.
Notice how all state business logic is in a central place .e.g in App component. This allows ShoppingCart to be a much simpler.
const App = () => {
const [cart, setCart] = useState({ lines: [], total: 0 });
const updateLineAmount = (lineIdx, amount) => {
// update the amount on a specific line index
setCart((state) => ({
...state,
lines: state.lines.map((line, idx) => {
if (idx !== lineIdx) {
return line;
}
return {
...line,
amount: line.amount + amount,
};
}),
}));
};
const onAddOne = (lineIdx) => {
updateLineAmount(lineIdx, 1);
};
const onRemoveOne = (lineIdx) => {
updateLineAmount(lineIdx, -1);
};
return (
<ShoppingCart cart={cart} onAddOne={onAddOne} onRemoveOne={onRemoveOne} />
);
};
const ShoppingCart = ({ cart, onAddOne, onRemoveOne }) => {
return (
<table>
{cart.lines.map((line, idx) => (
<tr key={idx}>
<td>{line.name}</td>
<td>
<button onClick={() => onAddOne(idx)}>+</button>
<button onClick={() => onRemoveOne(idx)}>-</button>
</td>
</tr>
))}
</table>
);
};

React hooks: How do I update state with object

I have 3 components like this, how I update state in App component
How I update state onclick in Counter component
import React, { useState } from 'react'
import Header from './components/Header'
import Counters from './components/Counters'
const App = () => {
const initialCounters = [
{ id: 1, value: 0 },
{ id: 2, value: 0 },
{ id: 3, value: 0 },
{ id: 4, value: 0 },
]
const [counters, setCounters] = useState(initialCounters)
const onIncrement = (counter) => {
console.log(counter)
}
return (
<>
<Header totalCounters={counters.length} />
<main className='container'>
<Counters counters={counters} onIncrement={onIncrement} />
</main>
</>
)
}
export default App
In your Counters component when you call the OnIncrement method you need to pass it's id as reference.
Then in your OnIncrement you do this
const onIncrement = (counterId) => {
const updatededCounters = counters.map((counter) => {
if(counter.id === counterId){
counter.value++
return counter
}
return counter
})
setCounters(updatededCounters)
}
Just to be clear in your Counters component
import React from "react";
const Counters = ({ counters, onIncrement }) => {
return (
<>
{counters.map((counter) => (
<div key={counter.id}>
<p>My counter : {counter.value}</p>
<button onClick={() => onIncrement(counter.id)}>Increment</button>
</div>
))}
</>
);
};
Full code for the parent component
import React, { useState } from "react";
import Counters from "./Counters";
const App = () => {
const initialCounters = [
{ id: 1, value: 0 },
{ id: 2, value: 0 },
{ id: 3, value: 0 },
{ id: 4, value: 0 }
];
const [counters, setCounters] = useState(initialCounters);
const onIncrement = (counterId) => {
const updatededCounters = counters.map((counter) => {
if (counter.id === counterId) {
counter.value++;
return counter;
}
return counter;
});
setCounters(updatededCounters);
};
return (
<>
<main className="container">
<Counters counters={counters} onIncrement={onIncrement} />
</main>
</>
);
};
export default App;
Codesandbox link: https://codesandbox.io/s/sad-chebyshev-l4hez?file=/src/App.js:0-725
Explanation
What i do the onIncrement method is simple:
i will create a new array with values i want to edit inside of it, then i'll set the state with this new array.
In the .map()
Each instance of counter will be looped, so for each instance i check if one of them is the one im looking for to update (with the same id as the counterId i receive as parameter)
If so, i edit the value of the current counter and then return the counter instance to allow the loop to continue to the next one.
When the counter id is not the same as the counterId received, i just return the counter without any modification.
So at the end, you will get the same array of values, exepct for the specific counter you incremented where you will see it's value updated by one count
I advice you to read some documentation about .map() function since it's really used in react : https://fr.reactjs.org/docs/lists-and-keys.html
And aswell you coud look into Object.keys() beceause it's often used with .map() aswell if you need to loop through object properties : https://reedbarger.com/how-to-transform-javascript-objects-the-power-of-objectkeys-values-entries/

React - How to send a custom function from a child to parent component

I'm creating a custom steps wizard, please find the implementation below:
export const Wizard: React.FC<Props> = props => {
const {
steps,
startAtStep = 0,
showStepsNavigation = true,
prevButtonText = 'Back',
nextButtonText = 'Next',
onStepChange,
nextButtonTextOnFinalStep,
onNextClicked,
onPreviousClicked
} = props;
const [currentStep, setCurrentStep] = useState(startAtStep);
let CurrentStepComponent = steps[currentStep].Component;
const nextStep = () => {
setCurrentStep(currentStep + 1);
};
const previousStep = () => {
setCurrentStep(currentStep - 1);
};
const goToStep = (stepId: number) => {
const stepIndex = steps.findIndex(step => step.id == stepId);
if (stepIndex != -1) {
setCurrentStep(stepIndex);
}
};
const handlePreviousClick = () => {
if (onPreviousClicked && typeof onPreviousClicked == 'function') {
onPreviousClicked();
}
previousStep();
};
const handleNextClick = () => {
if (onNextClicked && typeof onNextClicked == 'function') {
onNextClicked();
}
nextStep();
};
return (
<article>
<section>
<CurrentStepComponent {...props} goToStep={goToStep} nextStep={nextStep} previousStep={previousStep} />
</section>
<footer>
<div className="wizard-buttons-container back-buttons">
<Button
className="wizard-button wizard-button--back"
secondary
onClick={handlePreviousClick}
disabled={steps[currentStep - 1] == null}
>
<i className="fas fa-chevron-left"></i>
{prevButtonText}
</Button>
</div>
<div className="wizard-buttons-container next-buttons">
<Button
className="wizard-button wizard-button--next"
onClick={handleNextClick}
disabled={steps[currentStep + 1] == null}
>
{steps[currentStep + 1] == null && nextButtonTextOnFinalStep ? nextButtonTextOnFinalStep : nextButtonText}
<i className="fas fa-chevron-right"></i>
</Button>
</div>
</footer>
</article>
);
};
The way I use it is as follows:
const steps = [
{
id: 1,
label: 'Directors and Owners',
Component: DirectorsAndOwnersStep
},
{
id: 2,
label: 'Bank Account',
Component: BankAccountStep
},
{
id: 3,
label: 'Company Documents',
Component: CompanyDocumentsStep
},
{
id: 4,
label: 'Review and Submit',
Component: ReviewAndSubmitStep
}
];
type Props = RouteComponentProps<MatchParams>;
export const EnterpriseOnboardingPage: React.FC<Props> = () => {
const onNext = () => {
console.log('Next Clicked');
};
const onPrevious = () => {
console.log('Previous Clicked');
};
return (
<section>
<Wizard
steps={steps}
nextButtonTextOnFinalStep="Submit"
onNextClicked={onNext}
onPreviousClicked={onPrevious}
/>
</section>
);
};
Now here is my problem, within the child components I want to handle what should happen when the user clicks Next, something like onNextClick execute this custom function in the child component rather than performing the default behaviour implemented in the Wizard component.
I've tried setting State in the wizard, "nextClbRegistered", and through a send the "setNextClbRegistered" to children to pass the custom function and execute it, then in the wizard in the "handleNextClick" if there is a function defined execute it. but it's always undefined.
Any ideas what is the best way to do it?
Thanks in Advance!
React is all about data flowing down in the components tree. If you want your Child to be able to show and/or modify a shared state between Child and Parent you should lift your state up and pass it down via props to it's children
const Parent = () =>{
const [title, settitle] = useState('foo')
return <Child title={title} setTitle={setTitle} />
}
const Child = ({ title, setTitle}) =>{
return <input value={title} onChange={e => setTitle(e.target.value)} />
}
In class based components
class Parent extends React.Component{
state = { title: '' }
setTitle = title => this.setState({ title })
render(){
const { title } = this.state
return <Child title={title} setTitle={this.setTitle} />
}
}
class Child extends React.Component{
render(){
const { title, setTitle } = this.props
return <input value={value} setTitle={e => setTitle(e.target.value)} />
}
}

Resources