When I use React+Redux+Immutable, I get an issue: the component created by dynamic way, when the props change, component not rerender.
Is it React bug?
I deleted business code, just React code here: http://codepen.io/anon/pen/GoMOEZ
or below:
import React from 'react'
import ReactDOM from 'react-dom'
class A extends React.Component {
constructor(props) {
super(props);
this.state = {
name: 'tom'
}
}
dynamic() {
ReactDOM.render(<B name={this.state.name} changeName={this.changeName.bind(this)} type={false}/>, document.getElementById('box'))
}
changeName() {
this.setState({
name: 'tom->' + Date.now()
});
}
render() {
return <div>
top name: {this.state.name}
<B name={this.state.name} changeName={this.changeName.bind(this)} type={true}/>
<div id="box"></div>
<button onClick={this.dynamic.bind(this)}>dynamic add component</button>
</div>
}
}
class B extends React.Component {
render() {
return <div>
{this.props.type ? '(A)as sub component' : '(B)create by ReactDOM.render'}
- name:【{this.props.name}】
<button onClick={this.props.changeName}>change name</button>
</div>
}
}
ReactDOM.render(
<A/>,
document.getElementById('example')
);
It is not a bug, it is just not React-way to do what you want. Each call to A.render will overwrite <div id="box">...</div> deleting elements added by A.dynamic.
More idiomatic way is to add some flag, set it in onClick handler and use it in A.render to decide if <div id="box"> should be empty or not.
See edited code on codepen: http://codepen.io/anon/pen/obGodN
Relevant parts are here:
class A extends React.Component {
constructor(props) {
super(props);
this.state = {
name: 'tom',
showB: false // new flag
}
}
changeName() {
this.setState({
name: 'tom->' + Date.now()
});
}
// changing flag on button click
showB() {
this.setState({showB: true})
}
render() {
// `dynamic` will contain component B after button is clicked
var dynamic;
if(this.state.showB) {
dynamic = <B
name = {this.state.name}
changeName = {this.changeName.bind(this)}
type = {false} />
}
return <div>
top name: {this.state.name}
<B name = {this.state.name}
changeName = {this.changeName.bind(this)}
type = {true}/>
<div>{dynamic}</div>
<button onClick = {this.showB.bind(this)}>
dynamic add component
</button>
</div>
}
}
Update
You can still use your approach, but you need to manually update dynamically created component.
E.g. you can manually rerender it in changeName function.
changeName() {
this.setState({
name: 'tom->' + Date.now()
}, this.dynamic.bind(this));
}
Note, that this.dynamic is passed as a second argument to this.setState this ensures that it will be called when state is really updated. Just adding this.dynamic() after this.setState({...}) will use not-yet-updated state.
Codepen here: http://codepen.io/anon/pen/EPwovV
Related
I"ve been trying my hands on learning react and just hit a snag. Having issue understanding passing props across child components as relates to the problem being encountered. I'm having problems deleting an item from my todo list and can for the life of me figure out wat's wrong.
import React, { Component } from 'react';
import Form from './components/Form';
import Todo from './components/Todo';
class App extends Component {
constructor(props){
super(props);
this.state = {
userInput: "",
todoList: []
}
this.onChange= this.onChange.bind(this);
this.addItem=this.addItem.bind(this);
this.handleDelete=this.handleDelete(this);
}
//update input state
onChange=(e)=>{
this.setState({
userInput: e.target.value
})
}
//update todo list
addItem=(e)=>{
e.preventDefault();
if(this.state.userInput!==""){
const userData={
//create a specific user id for each input
id: Math.random(),
value: this.state.userInput
}
const list=[...this.state.todoList];
list.push(userData);
//Reset userInput after inputing data
this.setState({
userInput: "",
todoList: list
})
}
}
//Deleting an item
handleDelete=(id)=>{
const list=[...this.state.todoList];
const updatedList=list.filter(item=>item.id!==id)
this.setState({
todoList:updatedList
})
}
render() {
return(
<div>
<h1>
My Todo List
</h1>
<Form value={this.state.userInput} onChange={this.onChange} onSubmit={this.addItem} />
<Todo list={this.state.todoList} onDelete={this.handleDelete}/>
</div>
)
}
}
export default App;
Above is the parent component
import React, { Component } from 'react';
class Form extends Component {
render() {
return (
<div>
<form >
<input type="text" value={this.props.value} onChange={this.props.onChange}/><button className="btn btn-primary" type="submit" onClick={this.props.onSubmit}>add</button>
</form>
</div>
);
}
}
export default Form;
import React, { Component } from 'react';
class Todo extends Component {
render() {
const todos=this.props.list.map((item,index)=><li key={item.id}>{item.value} <i onClick={this.props.onDelete(item.id)} class="fas fa-trash"></i></li>)
return (
<div>
<ul>
{todos}
</ul>
</div>
);
}
}
export default Todo;
You almost got it right. You need to provide a callback to your onClick handler.
The way you have it now you are providing the onClick handler with the result of running that function. In other words onDelete is being called on each render and onClick is given the return value of onDelete (which is undefined).
You want to give it the function itself. Just change it to:
onClick={() => this.props.onDelete(item.id)}
EDIT: You also made a mistake in your binding of this.handleDelete. It should be:
this.handleDelete = this.handleDelete.bind(this);
but you did:
this.handleDelete = this.handleDelete(this);
which is why you got the error.
Apparently, your Todo component isn't providing the click handler properly:
<i onClick={this.props.onDelete(item.id)} class="fas fa-trash"></i>
should be:
<i onClick={() => this.props.onDelete(item.id)} class="fas fa-trash"></i>
The reason is because onClick expects a function and passing it this way onClick={this.props.onDelete(item.id)} provides the return of the onDelete function.
I have a React.Component with render() declared this way:
render(){
return <div>
<button id="butt" onClick={()=> $("#noti").change("test") }>click me</button>
<Notification id="noti" onMounted={() => console.log("test")}/>
</div>
}
And this is my Notification class:
class Notification extends React.Component {
constructor(props) {
super(props)
this.state = {
message: "place holder",
visible: false
}
}
show(message, duration){
console.log("show")
this.setState({visible: true, message})
setTimeout(() => {
this.setState({visible: false})
}, duration)
}
change(message){
this.setState({message})
}
render() {
const {visible, message} = this.state
return <div>
{visible ? message : ""}
</div>
}
}
As the class name suggests, I am trying to create a simple notification with message. And I want to simply display the notification by calling noti.show(message, duration).
However, when I try to find noti by doing window.noti, $("#noti") and document.findElementById("noti"), they all give me undefined, while noti is displayed properly. And I can find the butt using the code to find noti.
How should I find the noti? I am new to front end so please be a little bit more specific on explaining.
It's not a good idea using JQuery library with Reactjs. instead you can find a appropriate react library for notification or anything else.
Also In React we use ref to to access DOM nodes.
Something like this:
constructor(props) {
super(props);
this.noti = React.createRef();
}
...
<Notification ref={this.noti} onMounted={() => console.log("test")}/>
more info: https://reactjs.org/docs/refs-and-the-dom.html
I have hardcoded the id to 'noti' in the render method. You can also use the prop id in the Notification component.I have remodelled the component so that you can achieve the intended functionality through React way.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
messageContent: 'placeholder'
}
}
setMessage = (data) => {
this.setState({messageContent : data});
}
render() {
return (
<div className="App">
<button id='butt' onClick= {() => this.setMessage('test')} />
<Notification message = {this.state.messageContent} />
</div>
);
}
}
class Notification extends React.Component {
render () {
const {message} = this.props;
return (
<div id='noti'>
{message}
</div>
)
}
}
Before beginning: Using id/class to reach DOM nodes is not suggested in React.js, you need to use Ref's. Read more at here.
In your first render method, you give id property to Notification component.
In react.js,
if you pass a property to some component, it becomes a props of that
component. (read more here)
After you give the id to Notification, you need to take and use that specific props in your Notification component.
You see that you inserted a code line super(props) in constructor of Notification? That means, take all the props from super (upper) class and inherit them in this class.
Since id is HTML tag, you can use it like:
class Notification extends React.Component {
constructor(props) {
// inherit all props from upper class
super(props);
this.state = {
message: "place holder",
visible: false,
// you can reach all props with using this.props
// we took id props and assign it to some property in component state
id: this.props.id
}
}
show(message, duration){
// code..
}
change(message){
// code..
}
render() {
const {visible, message, id} = this.state
// give that id to div tag
return <div id={id}>
{message}
</div>
}
}
You can't pass id/class to a React Component as you would declare them in your normal HTML. any property when passed to a React Component becomes a props of that component which you have to use in the component class/function.
render() {
const {visible, message} = this.state
// give your id props to div tag as id attr
return <div id={this.props.id}>
{message}
</div>
}
This answer does not provide the exact answer about selecting a component as you want. I'm providing this answer so you can see other alternatives (more React way maybe) and improve it according to your needs.
class App extends React.Component {
state = {
isNotiVisible: false
};
handleClick = () => this.setState({ isNotiVisible: true });
render() {
return (
<div>
<button onClick={this.handleClick}>Show Noti</button>
{this.state.isNotiVisible && (
<Noti duration={2000} message="This is a simple notification." />
)}
</div>
);
}
}
class Noti extends React.Component {
state = {
visible: true
};
componentDidMount() {
setTimeout(() => this.setState({ visible: false }), this.props.duration);
}
render() {
return this.state.visible && <div>{this.props.message}</div>;
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<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" />
So I know this question has been asked a couple of times and the general concession is that props cant be changed when it has already passed down to a child. The situation I have here is that basically i have a different onClick function in a different file that updates the the id="movie-header" with an innerHTML, the DOMSubtreeModified and componentDidUpdatedetects the change and pass down the new props to Child "Ebay".
So the question here is how do I get the Ebay component to update its state and make use of the new value with every change to the state in the moviemodalwindow(the parent of the Ebay)
MovieModalWindow.js
import React from "react";
import "../MovieGo.css";
import Ebay from "../Store/Ebay";
class MovieModalWindow extends React.Component {
constructor() {
super();
this.state = {
name: 1
};
}
componentDidMount() {
var element = document.getElementById("movie-header");
element.addEventListener("DOMSubtreeModified", this.myFunction(element));
var name = this.state.name + 1;
this.setState({ name: [...this.state.name, name] });
}
myFunction = input => event => {
this.setState({ name: input.innerHTML });
};
componentDidUpdate(prevProps, prevState) {
if (prevState.name != this.state.name) {
window.localStorage.setItem("keyword", this.state.name);
}
}
render() {
return (
<div id="myModal" class="modal">
<div class="modal-content">
<span onClick={onClose} class="close">
×
</span>
<h1 id="movie-header" />
<div className="middle-window">
<div className="left">
<Ebay id="ebay" keyword={this.state.name} />
</div>
</div>
<h3>PLOT</h3>
<p id="moviedetails" />
</div>
</div>
);
}
}
export default MovieModalWindow;
Ebay.js File
import React from "react"
class Ebay extends React.Component{
constructor(){
super();
this.state={
data:[],
}
}
componentWillUpdate(prevProps, prevState){
if (prevProps.keywords!=this.props.keywords){
console.log(window.localStorage.getItem("keyword"))
}
render(){
const{newInput} =this.props
return(
<div>
</div>
)
}
}
export default Ebay
I'm unsure if I'm answering the question you're asking, so apologies if this isn't what you're asking.
Step 1. Make Ebay's prop's change when you need this update to happen. (I think you stated you already have this occurring?)
Step 2: Make Ebay's state update when the props change. Here you can just watch for prop changes with componentWillReceiveProps and update the state accordingly.
class Ebay extends React.Component {
constructor() {
super();
this.state = { data: [] };
}
componentWillRecieveProps(nextProps) {
if (nextProps.keyword !== this.props.keyword) {
this.setState({ data: ['something new'] });
}
}
render() { ... }
}
Given some items stored in state, I want to be able to click a button and display a random item from that array. So far it only works on the first click and then it displays the same one letter after the first click.
What exactly is going on?
import React, { Component } from 'react'
export default class App extends Component {
constructor(props) {
super(props)
this.state = {
notes: ['hey', 'yo', 'sup'],
clicked: false
}
}
handleClick = () => {
this.setState({
clicked: true,
notes: this.state.notes[Math.floor(Math.random() *
this.state.notes.length)]
})
}
render() {
return (
<div className="App">
<button onClick={this.handleClick}>Random Note</button>
<h1>{this.state.clicked ? this.state.notes : ''}</h1>
</div>
)
}
}
Add selected note handling
import React, { Component } from 'react'
export default class App extends Component {
constructor(props) {
super(props)
this.state = {
notes: ['hey', 'yo', 'sup'],
selectedNote: null,
clicked: false
}
}
handleClick = () => {
this.setState({
clicked: true,
selectedNote: this.state.notes[Math.floor(Math.random() *
this.state.notes.length)]
})
}
render() {
return (
<div className="App">
<button onClick={this.handleClick}>Random Note</button>
<h1>{this.state.clicked && this.state.selectedNote}</h1>
</div>
)
}
}
You're overwriting the notes array in state in your handleClick method. Try using a different key (something like activeNote) in handleClick, then use that in your render method rather than this.state.notes.
So I think I solved it.
I ended up adding another state key randomWord and setting it equal to ''.
I then set the state of randomWord to that of this.state.notes when randomized.
Finally, I rendered this.state.randomWord
No clue if that's the correct approach but it's a working solution, lol.
Learning React so might be a bit nooby question. Consider this code:
class Application extends React.Component {
render() {
return <Container />
}
}
class Container extends React.Component {
constructor(props) {
super(props)
this.state = {
isOn: false
}
this.handleToggle = this.handleToggle.bind(this);
}
handleToggle(on) {
this.setState({
isOn: on
});
}
render() {
return (
<div>
<p>
{this.state.isOn ? 'on' : 'off'}
</p>
<MyButton handleToggle={this.handleToggle} />
</div>
);
}
}
class MyButton extends React.Component {
constructor(props) {
super(props);
this.state = {
pressed: false
}
this.handleButtonToggle = this.handleButtonToggle.bind(this);
}
handleButtonToggle() {
const on = !this.state.pressed
this.setState({
pressed: on
});
this.props.handleToggle(on);
}
render() {
return (
<div>
<button onClick={this.handleButtonToggle}>
{this.state.pressed ? "pressed" : "depressed"}
</button>
</div>
);
}
}
ReactDOM.render(<Application />, document.getElementById('app'));
As you can see currently when the button inside the Container is clicked handleButtonToggle() is fired which changes the state of the button itself and then calls a function to change the state of the parent Container. Is this the React way to do it? At the moment Container state is changed when the function handleButtonToggle is fired. Ideally I would want Container state to be dependent on MyButton state directly (cuz maybe in future there will be ways to set button state other than through handleButtonToggle, and I don't want to manually call this.props.handleToggle every time the state changes.). In other words is there a way to do something like this.props.handleToggle(this.state.pressed) in the button component when its state changes.
Codepen
After reviewing the code, a better way to write the Button component is to make it a controlled component. If the container component requires to maintain the pressed state, a controlled button component would receive the pressed state from the container component as props.
<MyButton pressed={this.state.pressed} onToggle={this.handleToggle} />
And the render method of the Button component should be:
render() {
return (
<div>
<button onClick={this.props.onToggle}>
{this.props.pressed ? "pressed" : "depressed"}
</button>
</div>
);
}
The actual button toggle will be done in the handleToggle method of the container component:
handleButtonToggle() {
let { pressed } = this.state;
pressed = !pressed;
this.setState({
pressed
});
}
You can either pass a callback as the second argument to setState or implement componentDidUpdate on your component to wait for state changes. The smallest change would be the former:
handleButtonToggle() {
const on = !this.state.pressed
this.setState({
pressed: on
}, () => {
this.props.handleToggle(on);
});
}
or with componentDidUpdate:
componentDidUpdate(prevProps, prevState) {
if (prevState.on !== this.state.on) {
this.props.handleToggle(this.state.on);
}
}
Here is a working codepen: https://codepen.io/damien-monni/pen/XRwewV.
I tried to keep as much as I can of your code so you can focus on really needed changes.
You need to create a controlled component. The state of your button will be stored in the container and pass by props to the child MyButton component.
/*
* A simple React component
*/
class Application extends React.Component {
render() {
return <Container />
}
}
class Container extends React.Component {
constructor(props) {
super(props)
this.state = {
isOn: false
}
this.handleToggle = this.handleToggle.bind(this);
}
handleToggle() {
this.setState({
isOn: !this.state.isOn
});
}
render() {
return (
<div>
<p>
{this.state.isOn ? 'on' : 'off'}
</p>
<MyButton pressed={this.state.isOn} handleToggle={this.handleToggle} />
</div>
);
}
}
class MyButton extends React.Component {
constructor(props) {
super(props);
this.state = {
pressed: this.props.pressed
}
this.handleButtonToggle = this.handleButtonToggle.bind(this);
}
handleButtonToggle() {
this.props.handleToggle();
}
componentWillReceiveProps(nextProps) {
if (nextProps.pressed !== this.props.pressed) {
this.setState({ pressed: nextProps.pressed });
}
}
render() {
return (
<div>
<button onClick={this.handleButtonToggle}>
{this.state.pressed ? "pressed" : "depressed"}
</button>
</div>
);
}
}
/*
* Render the above component into the div#app
*/
ReactDOM.render(<Application />, document.getElementById('app'));
You need to do it because you want to share a state between two of your components. This is documented here.
I let you look at the codepen and ask your questions about it. ;)