I have two components. The first of them looks like this
class App extends Component {
constructor(props) {
super(props);
this.state = {
change: false
};
this.handleSwitch = this.handleSwitch.bind(this);
}
handleSwitch = () => {
const { change } = this.state;
this.setState({ change: !change })
console.log(this.state.change)
}
render() {
const { change } = this.state;
return (
<>
<UserProfilPanel handleSwitch={this.handleSwitch}/>
{
change ? <UserProfilGallery /> : <UserProfilContent />
}
</>
);
}
}
To the UserProfile Panel component, it passes the function which is to be responsible for changing the state.
const UserProfil = (handleSwitch) => {
return (
<Container>
<div>
<button onClick={() => handleSwitch}>
gallery
</button>
<button onClick={() => handleSwitch}>
info
</button>
</div>
</Container>
)
}
When I press some buttons, nothing happens. The console also does not appear an error.
How to fix this problem? I want to change content after clicking the button
First argument in UserProfil() is props. To destructure only a specific property of the props object you need to do:
const UserProfil = ({handleSwitch}) => {...
Then inside your onClick anonymous function you need to call handleSwitch()
<button onClick={() => handleSwitch()}>
// ^^ call function
Related
I have a basic increment app in react with the following code. Passing in the handleClick function into the first button works fine, however passing it to a child component IcrementButton that returns the exact same button gives the error:
React: Expected `onClick` listener to be a function, instead got a value of `object. Why is it working for the first button but not the child component button and how can it be fixed? thanks in advance.
import { useState } from "react";
import "./styles.css";
const IncrementButton = (handleClick) => {
return (
<button onClick={handleClick}>+</button>
)
}
export default function App() {
const [num, setNum] = useState(0)
const handleClick = (e) => {
e.preventDefault()
console.log('clicked')
setNum(prev => prev += 1)
}
return (
<div className="App">
<div>{num}</div>
<button onClick={handleClick}>+</button>
<IncrementButton handleClick={handleClick} />
</div>
);
}
Since the IncrementButton is a custom component all props passed to it is sent as an object and to access it you need to use props.property. There are 2 ways to get your code to work.
Use props.property
const IncrementButton = (props) => {
return (
<button onClick={props.handleClick}>+</button>
)
}
Destructure the props object
const IncrementButton = ({handleClick}) => {
return (
<button onClick={handleClick}>+</button>
)
}
You didn't destructure handle click. Try the code below
const IncrementButton = ({handleClick}) => {
return (
<button onClick={handleClick}>+</button>
)
}
That does not work because React passes every props on a Component as a single Object.
You have two ways to get handleClick function reference.
Destructure props (es6)
const IncrementButton = ({handleClick}) => {
return (
<button onClick={handleClick}>+</button>
)
}
Use props.propertyName
const IncrementButton = (props) => {
return (
<button onClick={props.handleClick}>+</button>
)
}
Let's say I have a component tree as follows
<App>
</Header>
<Content>
<SelectableGroup>
...items
</SelectableGroup>
</Content>
<Footer />
</App>
Where SelectableGroup is able to select/unselect items it contains by mouse. I'm storing the current selection (an array of selected items) in a redux store so all components within my App can read it.
The Content component has set a ref to the SelectableGroup which enables me to clear the selection programatically (calling clearSelection()). Something like this:
class Content extends React.Component {
constructor(props) {
super(props);
this.selectableGroupRef = React.createRef();
}
clearSelection() {
this.selectableGroupRef.current.clearSelection();
}
render() {
return (
<SelectableGroup ref={this.selectableGroupRef}>
{items}
</SelectableGroup>
);
}
...
}
function mapStateToProps(state) {
...
}
function mapDispatchToProps(dispatch) {
...
}
export default connect(mapStateToProps, mapDispatchToProps)(Content);
I can easily imagine to pass this clearSelection() down to Contents children. But how, and that is my question, can I call clearSelection() from the sibling component Footer?
Should I dispatch an action from Footer and set some kind of "request to call clear selection" state to the Redux store? React to this in the componentDidUpdate() callback in Content and then immediately dispatch another action to reset this "request to call clear selection" state?
Or is there any preferred way to call functions of siblings?
You can use ref to access the whole functions of Content component like so
const { Component } = React;
const { render } = ReactDOM;
class App extends Component {
render() {
return (
<div>
<Content ref={instance => { this.content = instance; }} />
<Footer clear={() => this.content.clearSelection() } />
</div>
);
}
}
class Content extends Component {
clearSelection = () => {
alert('cleared!');
}
render() {
return (
<h1>Content</h1>
);
}
}
class Footer extends Component {
render() {
return (
<div>Footer <button onClick={() => this.props.clear()}>Clear</button>
</div>
);
}
}
render(
<App />,
document.getElementById('app')
);
<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="app"></div>
I think the context API would come handy in this situation. I started using it a lot for cases where using the global state/redux didn't seem right or when you are passing props down through multiple levels in your component tree.
Working sample:
import React, { Component } from 'react'
export const Context = React.createContext()
//***************************//
class Main extends Component {
callback(fn) {
fn()
}
render() {
return (
<div>
<Context.Provider value={{ callback: this.callback }}>
<Content/>
<Footer/>
</Context.Provider>
</div>
);
}
}
export default Main
//***************************//
class Content extends Component {
render() {
return (
<Context.Consumer>
{(value) => (
<div onClick={() => value.callback(() => console.log('Triggered from content'))}>Content: Click Me</div>
)}
</Context.Consumer>
)
}
}
//***************************//
class Footer extends Component {
render() {
return (
<Context.Consumer>
{(value) => (
<div onClick={() => value.callback(() => console.log('Triggered from footer'))}>Footer: Click Me</div>
)}
</Context.Consumer>
)
}
}
//***************************//
Assuming content and footer and in there own files (content.js/footer.js) remember to import Context from main.js
According to the answer of Liam , in function component version:
export default function App() {
const a_comp = useRef(null);
return (
<div>
<B_called_by_a ref={a_comp} />
<A_callB
callB={() => {
a_comp.current.f();
}}
/>
</div>
);
}
const B_called_by_a = forwardRef((props, ref) => {
useImperativeHandle(ref, () => ({
f() {
alert("cleared!");
}
}));
return <h1>B. my borther, a, call me</h1>;
});
function A_callB(props) {
return (
<div> A I call to my brother B in the button
<button onClick={() => { console.log(props); props.callB();}}>Clear </button>
</div>
);
}
you can check it in codesandbox
One way I use to call the sibling function is to set a new date.
Let me explain more:
In their parent we have a function that set new date in a state (the state's name is something like "refresh date" or "timestamp" or something similar).
And you can pass state to sibling by props and in sibling component you can use useEffect for functional components or componentDidUpdate for class components and check when the date has changed, call your function .
However you can pass the new date in redux and use redux to check the date
const Parent = () => {
const [refreshDate, setRefreshDate] = useState(null);
const componentAClicked = () => setRefreshDate(new Date())
return (
<>
<ComponentA componentAClicked={componentAClicked}/>
<ComponentB refreshDate={refreshDate}/>
</>
}
const ComponentA = ({ componentAClicked}) => {
return (
<button onClick={componentAClicked}>click to call sibling function!!<button/>
)
}
const ComponentB = ({ refreshDate }) => {
useEffect(()=>{
functionCalledFromComponentA()
},[refreshDate]
const functionCalledFromComponentA = () => console.log("function Called")
return null
}
Functional components & TypeScript
Note 1: I've swapped useRef for createRef.
Note 2: You can insert the component's prop type in the second type parameter here: forwardRef<B_fns, MyPropsType>. It's confusing because the props & ref order are reversed.
type B_fns = {
my_fn(): void;
}
export default function App() {
const a_comp = createRef<B_fns>();
return (
<div>
<B_called_by_a ref={a_comp} />
<A_callB
callB={() => {
a_comp.current?.my_fn();
}}
/>
</div>
);
}
const B_called_by_a = forwardRef<B_fns>((props, ref) => {
useImperativeHandle(ref, () => ({
my_fn() {
alert("cleared!");
}
}));
return <h1>B. my borther, a, call me</h1>;
});
function A_callB(props) {
return (
<div> A I call to my brother B in the button
<button onClick={() => { console.log(props); props.callB();}}>Clear </button>
</div>
);
}
I am trying to use both context and static approach for more flexible dropdown so i can use any element to show in the dropdown title(may be button or icon or just text or anything that is relevant). However, my dropdown is not toggled. I mean toggleMenu function is not triggered so that it can ensure whether to show the dropdown item or not.
Here is how i have done
class Title extends React.Component {
render() {
return (
<Dropdown.Consumer>
{({ showMenu, toggleMenu }) => {
return (
<React.Fragment>
<DropdownTitle>
{React.cloneElement(this.props.children, {
showMenu,
onClick: () => toggleMenu()
})}
</DropdownTitle>
</React.Fragment>
);
}}
</Dropdown.Consumer>
);
}
}
class Item extends React.Component {
render() {
return (
<Dropdown.Consumer>
{({ showMenu }) => {
return (
<React.Fragment>
{showMenu && <DropdownItem>{this.props.children}</DropdownItem>}
</React.Fragment>
);
}}
</Dropdown.Consumer>
);
}
}
const DropdownContext = React.createContext();
class Dropdown extends Component<Props> {
static Title = Title;
static Item = Item;
static Consumer = DropdownContext.Consumer;
state = {
showMenu: false,
toggleMenu: () => {}
};
toggleMenu = () => {
console.log('did not trigger this function when clicking')
this.setState(
prevState => ({
showMenu: !prevState.showMenu
}),
() => this.props.toggleState(this.state.showMenu)
);
};
render() {
const { children } = this.props;
return (
<React.Fragment>
<DropdownContext.Provider value={{ ...this.state }}>
{children}
</DropdownContext.Provider>
</React.Fragment>
);
}
}
export default Dropdown;
I have created a sandbox either. Here it is
https://codesandbox.io/s/k3lw98jlov
I think there are some issues with your code. First how you use the context value. There is no way of getting to the function because you don't provide it. I'd do it like so:
<DropdownContext.Provider value={{ ...this.state, toggleMenu: this.togglemenu }}>
{children}
</DropdownContext.Provider>
otherwise the toggelmenu function is just an empty function (you need to remove it from the state).
Second I took a quick look at the sandbox and I don't really see where the context is used, i.d. where the function should be called. Make sure you use the context at the right place. I think what you need to do is to connect the index.js to the context, because here you are listening for a button-click. Correct me if I am wrong or if I didn't understand something properly.
Hope that helps.
Regards
I'm just starting to learn React.js (and Javascript) and I have a very basic question for you guys.
This is a working example of a small component that creates 3 buttons for which a value is incremented each time they are clicked.
class Button extends React.Component {
handleClick = () => {
this.props.onClickFunction(this.props.incrementValue);
}
render(){
return(
<button onClick={this.handleClick}>
{this.props.incrementValue}
</button>
);
}
}
const Result = (props) => {
return(
<div>{props.counter}</div>
);
};
class App extends React.Component {
state = {counter: 0};
incrementCounter = (incrementValue) => {
this.setState((prevState) => ({
counter: prevState.counter + incrementValue
}));
};
render() {
return (
<div>
<Button incrementValue={2} onClickFunction={this.incrementCounter}/>
<Button incrementValue={10} onClickFunction={this.incrementCounter}/>
<Button incrementValue={99} onClickFunction={this.incrementCounter}/>
<Result counter={this.state.counter}/>
</div>
);
}
}
ReactDOM.render(<App />, mountNode);
While experiencing with the code, I tried to change the handleClick function.
class Button extends React.Component {
handleClick(){
this.props.onClickFunction(this.props.incrementValue);
}
render(){
return(
<button onClick={this.handleClick}>
{this.props.incrementValue}
</button>
);
}
}
const Result = (props) => {
return(
<div>{props.counter}</div>
);
};
class App extends React.Component {
state = {counter: 0};
incrementCounter = (incrementValue) => {
this.setState((prevState) => ({
counter: prevState.counter + incrementValue
}));
};
render() {
return (
<div>
<Button incrementValue={2} onClickFunction={this.incrementCounter}/>
<Button incrementValue={10} onClickFunction={this.incrementCounter}/>
<Button incrementValue={99} onClickFunction={this.incrementCounter}/>
<Result counter={this.state.counter}/>
</div>
);
}
}
ReactDOM.render(<App />, mountNode);
I am now getting: Uncaught TypeError: Cannot read property 'props' of undefined
at handleClick (eval at
From my understanding, the anonymous function handleClick = () =>... can access the props from the parent because of a closure but why does the magic stop when I replace it with a class method?
Your issue does not seem to be about closures, but rather about how this works in JS. In your working example
handleClick = () => {
this.props.onClickFunction(this.props.incrementValue);
}
You have an arrow function, and as a result, your this always points to your instance, which is why you can access this.props.
In your non working example
handleClick(){
this.props.onClickFunction(this.props.incrementValue);
}
You no longer have an arrow function, so now the this no longer refers to your instance when the function is called. This is why you cannot access this.props.
You can fix this by using an arrow function like you have in the working case, or you can bind your function to this current instance in order to make sure this always points to your instance.
I have a problem with React.
When I press the "+" button, this console message appears and nothing happens:
Cannot update during an existing state transition (such as within `render` or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved to `componentWillMount`
I found several questions with similar titles, but common thing among them is that there were calls of functions with setState inside render method.
My render method has no calls, but error appears.
Why?
Thank you for reading.
Code:
import React from 'react';
const TodoForm = ({addTodo}) => {
let input;
return (
<div>
<input
ref={node => {
input = node;
}}
/>
<button onClick={() => {
addTodo(input.value);
input.value = '';
}}>
+
</button>
</div>
);
};
const Todo = ({todo, remove}) => {
// Each Todo
return (<li onClick={remove(todo.id)}>{todo.text}</li>)
};
const TodoList = ({todos, remove}) => {
// Map through the todos
const todoNode = todos.map((todo) => {
return (<Todo todo={todo} key={todo.id} remove={remove}/>)
});
return (<ul>{todoNode}</ul>);
};
const Title = () => {
return (
<div>
<div>
<h1>to-do</h1>
</div>
</div>
);
};
window.id = 0;
class TodoApp extends React.Component {
constructor(props) {
// Pass props to parent class
super(props);
// Set initial state
this.state = {
data: []
}
}
// Add todo handler
addTodo(val) {
// Assemble data
const todo = {text: val, id: window.id++}
// Update data
this.state.data.push(todo);
// Update state
console.log('setting state...');
this.setState({data: this.state.data});
}
// Handle remove
handleRemove(id) {
// Filter all todos except the one to be removed
const remainder = this.state.data.filter((todo) => {
if (todo.id !== id) return todo;
});
// Update state with filter
this.setState({data: remainder});
}
render() {
// Render JSX
return (
<div>
<Title />
<TodoForm addTodo={
(val)=>{
this.addTodo(val)
}
}/>
<TodoList
todos={this.state.data}
remove={this.handleRemove.bind(this)}
/>
</div>
);
}
}
export default TodoApp;
In your render method for Todo you invoke remove, which is where your erroneous state update happens.
To fix this, return a function from the handleRemove method of TodoApp that updates the state. Simplified version:
handleRemove(id) {
return () => {
...
this.setState({ data: remainder });
}
}
Also worth noting here that because you're using the current state, it's best to use the setState callback (which gets prevState as an argument), and not rely on this.state.
setState docs
Andy_D very helped and my answer has two solutions:
First in render function change
<TodoList
todos={this.state.data}
remove={this.handleRemove.bind(this)}
/>
to
<TodoList
todos={this.state.data}
remove={() => this.handleRemove.bind(this)}
/>
or change code
const Todo = ({todo, remove}) => {
// Each Todo
return (<li onClick={remove(todo.id)}>{todo.text}</li>)
};
to that:
const Todo = ({todo, remove}) => {
// Each Todo
return (<li onClick={() => remove(todo.id)}>{todo.text}</li>)
};