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>)
};
Related
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 convert a class component to a function component and struggling with assigning the refs to each rendered item in a Flatlist.
This is the original class component.
...
constructor(props) {
super(props);
this.cellRefs = {};
}
....
_renderItem = ({ item }) => {
return (
<Item
ref={ref => {
this.cellRefs[item.id] = ref;
}}
{...item}
/>
);
};
...
Assuming both your Item and the component rendering the FlatList need to be functional components, you need to take care of 2 things
Add dynamic refs to each Item component
Make sure that the Item component uses useImperativeHandle with forwardRef to expose functions
const App = () => {
const cellRefs = useRef({}) // Adding an object as we need more than one ref
const _renderItem = ({ item }) => {
return (
<Item
ref={ref => {
cellRefs.current[item.id] = ref;
}}
{...item}
/>
);
};
....
}
Post that you need to change your Item component like
const Item = React.forwardRef((props, ref) => {
...
const handleClick = () => {};
useImperativeHandle(ref, () => ({
// values that need to accessible by component using ref, Ex
handleClick,
}))
...
})
P.S. If Item is not a functional component, you can avoid the second step
do something like that (from react doc)
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
// `current` points to the mounted text input element
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
I'm new to react and struggling with how to transfer data from one component to another.
I referred some tutorials and blogs, but things aren't working for me.
I have two child components, Body-content.jsx and Profile.jsx and 1 parent component parent.jsx
I want to transfer some data from Body-content.jsx toProfile.jsx.
Here's my code
Body-content.jsx
class BodyContent extends React.Component {
componentDidMount() {
this.getUserList()
}
getUserList(){
fetch('https://jsonplaceholder.typicode.com/users')
.then(result => {
return result.json();
}).then(data =>{
this.setState({
users : data
})
})
}
render() {
const user = this.state.users.map((userData, i) => (
<CardBody>
...some code here
<Button color='primary' onClick={e => this.viewProfile(userData)}>View Profile</Button>
</CardBody>
</Card>
));
return (
<>
<div>{user}</div>
</>
)
}
viewProfile = function (data) {
}
}
export default BodyContent;
profile.jsx
class Profile extends React.Component {
componentDidMount() {
}
render() {
return (
<>
<TopNav />
<main className="profile-page" ref="main">
<section>
//code goes here
</section>
</main>
</>
);
}
}
export default Profile;
Store your data in parent component and send it as props to children.
If you have to change it in one of them, then send (also as prop) the function, which will change data in parent component.
Code sample:
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {someData: ''};
}
changeData(newData) {
this.setState({
someData: newData
});
}
render() {
return (
<>
<Child1 setData={this.changeData} data={this.state.someData} />
<Child2 setData={this.changeData} data={this.state.someData} />
</>
)
}
}
Both of them will be able to change data in parent component using this.props.setData('newData')
If you want to share your state across the child component then you may need to move that property in parent's state which you may able to share between two child components.
Sibling to Sibling
Parent Component
Any to Any
Observer Pattern
Global Variables
Context
How to make a shared state between two react components?
You can hoist state to parent component:
class Parent extends Component {
state = {
users
};
handleUsersChange = users => this.setState({ users });
render() {
const { users } = this.state;
return (
<React.Fragment>
<Body-content onUsersChange={ this.handleUsersChange } />
<Profile users={ users } />
</React.Fragment>
);
}
}
...
class BodyContent extends React.Component {
getUserList(){
fetch('https://jsonplaceholder.typicode.com/users')
.then(result => {
return result.json();
}).then(data =>{
this.props.handleUsersChange(data);
})
}
}
In ReactJs the data flow is uni-directional - Top-to-bottom. The parents passes the data to respective children.
Here, since you want to share the data between two siblings. The data should be first available to their parent.
In order to do so, your getUserList api call should be inside of your parent, i.e
your parent.jsx component.
From there you can pass the data as props to both the siblings.
Let me know if you need and further explanation to this. If needed, please share your parent.jsx code. I can help you further.
Hi and welcome to the world of React,
If you want to share data between siblings components, you should always keep in mind that you should store your data at the highest common component (doc).
In your case that would mean having a parent component that holds your users list and the current profile in its state, and then render accordingly your list and the current profile.
A little example to get you on the "right track" sandbox:
class Parent extends Component {
constructor(props) {
super(props);
this.state = {
users: [],
currentIndex: null
}
}
componentDidMount() {
this.getUserList()
}
getUserList(){
fetch('https://jsonplaceholder.typicode.com/users')
.then(result => result.json())
.then(data => {
this.setState(() => ({
users : data
}))
});
}
updateCurrent = (index) => {
this.setState(() => ({ currentIndex: index }));
}
render() {
return (
<div>
<UserList
users={this.state.users}
updateCurrent={this.updateCurrent}
/>
{this.state.currentIndex !== null && (
<Profile user={this.state.users[this.state.currentIndex]} />
)}
</div>
)
}
}
const UserList = (props) => (
<div>
{props.users.map((user, i) => (
<div key={user.id}>
<button color='primary' onClick={() => props.updateCurrent(i)}>View Profile</button>
</div>
))}
</div>
);
const Profile = ({ user }) => (
<div className="profile-page">
{user.name}
</div>
);
Feel free to ask clarification if needed.
Happy coding,
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
I have an array of objects stored in redux. I want to be able to filter that array based on user input. Should I create a state object that receives the array through props and modify that array, or is it bad practice to mix state and props? If it is alright to mix the two, should I set the state in componentWillReceiveProps?
Building state based on props can be somewhat complicated, which is acceptable, but you should consider all of your options.
The simplest to implement is to filter the props in your render method. If you have sufficiently small components which don't update for too many reasons, and especially if the number of elements in the list is low, this might be the preferred method:
class FilterList extends React.Component {
render () {
const { elements } = this.props;
const { filterStr } = this.state;
const filteredElements = elements
.filter(e => e.includes(filterStr))
.map(e => <li>{ e }</li>)
return (
<div>
<input
type="text"
value={ filterStr }
onChange={ e => this.setState({ filterStr: e.target.value }) } />
<ul>
{ filteredElements }
</ul>
</div>
);
}
}
The next option is to do what you're describing, and derive a computed state based of the component's filter state and props passed to it. This is good when you have a complicated component which recieves many props and is rendered often. Here, you're caching the viewable elements and only filtering the list when it needs to be filtered.
class FilterList extends React.Component {
constructor (props) {
this.state = {
viewableEls: props.elements
}
}
componentWillReceiveProps (nextProps) {
const { elements } = this.props;
const { filterStr } = this.state;
if (elements !== nextProps.elements) {
this.setState({
viewableEls: this.getViewableEls(nextProps.elements, filterStr)
})
}
}
getViewableEls (elements, filterStr) {
return elements.filter(el => el.includes(filterStr))
}
handleFilterChange = e => {
const { elements } = this.props;
this.setState({
filterStr: e.target.value,
viewableEls: this.getViewableEls(elements, filterStr)
})
}
render () {
const { viewableEls } = this.state;
return (
<div>
<input
type="text"
value={ filterStr }
onChange={ this.handleFilterChange } />
<ul>
{ viewableEls.map(e => <li key={ e }>{ e }</li>) }
</ul>
</div>
);
}
}
And finally, the redux 'way', which requires you to pass the action creator and filterStr as props to the component, probably passed in via connect somewhere else. The implementation below is using a stateless component since we're not keeping the fitlerStr in the component state at all.
const FilterTable = ({ elements, filterStr, changeFilterStr }) => {
return (
<div>
<input
type="text"
value={ filterStr }
onChange={ e => changeFilterStr(e.target.value) } />
<ul>
{
elements
.filter(e => e.includes(filterStr))
.map(e => <li key={ e }>{ e }</li>)
}
</ul>
</div>
)
}