I have small asynchronous problems with render method.
I init my state variable textInRenameTodoPopOverInput with a props variable.
The problem is that when my props variable is changed, the state variable always remains at the initially given value (for this example it will be 'dummyText') so that when the value of my props this.props.todoToRename.text changes, the console always returns :
>> Props in Modal {this.props.todoToRename.text} // Here the props has the expected new value
>> State in modal dummyText // But the state is not refresh and keep 'dummyText'
here is my code that I simplified :
import React from "react";
export default class RenameTodoPopOver extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.state = {
textInRenameTodoPopOverInput: this.props.todoToRename.text,
}
}
handleChange = (event) => {
this.setState({ textInRenameTodoPopOverInput: event.target.value });
}
render() {
console.log('Props in Modal ' + this.props.todoToRename.text);
console.log('State in modal ' + this.state.textInRenameTodoPopOverInput);
return (
<input type="text" defaultValue={this.state.textInRenameTodoPopOverInput} onChange={this.handleChange} />
);
}
}
Do you have any idea what my mistake is or how i can resolve this problem ?
You should use getDerivedStateFromProps to update the component state based op props.
export default class RenameTodoPopOver extends React.Component {
state = { textInRenameTodoPopOverInput: '' }
static getDerivedStateFromProps(nextProps){
if(nextProps.todoToRename.text){
return {
textInRenameTodoPopOverInput: nextProps.todoToRename.text
}
}
}
handleChange = (event) => {
this.setState({ textInRenameTodoPopOverInput: event.target.value });
}
render() {
console.log('Props in Modal ' + this.props.todoToRename.text);
console.log('State in modal ' + this.state.textInRenameTodoPopOverInput);
return (
<input type="text" defaultValue={this.state.textInRenameTodoPopOverInput} onChange={this.handleChange} />
);
}
}
some problems with your code:
1. you are using the props and assigning it to the state which is bad.
2. you can shift the "state" to the parent component and let the parent component send the text and handle function as props
//in the parent component
import React from "react";
export default class ParentRenameTodoPopOver extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.state = {
textInRenameTodoPopOverInput: this.props.todoToRename.text,
}
}
handleChange = (event) => {
this.setState({ textInRenameTodoPopOverInput: event.target.value });
}
render() {
return (
<RenameTodoPopOver
textInRenameTodoPopOverInput=
{this.state.textInRenameTodoPopOverInput}
handleChange = {this.handleChange}
/>
);
}
}
export default ParentRenameTodoPopOver
now in the child component get the state and handle from the parent as props and work on it.
//in the child component
import React from "react";
const RenameTodoPopOver = (props)=>
return (
<input
type="text"
defaultValue={props.textInRenameTodoPopOverInput}
onChange={props.handleChange} />
)
}
export default RenameTodoPopOver;
while designing components in react, it is easier to write if we divide the components into purely funtional and purely presentational components. Here the parent component is purely functional i.e., it has all the logic on the other hand, the child component is purely functional, it has not logic and it gets all functions and state passed to it from the parent by props.
Related
I created a component/file in my React-Project, so that I can better organize it.
I have exported a Login-Component <SignInSide></SignInSide> and imported it in my main file:
Main-File:
export default class Login extends Component {
constructor(props) {
super(props);
this.onChangeUsername = this.onChangeUsername.bind(this);
this.state = {
username: ""
};
}
onChangeUsername(e) {
this.setState({
username: e.target.value
});
}
render() {
return (
<SignInSide></SignInSide>
);
}
}
This is my very basic Login-Component. As I already said, I just want to call this.onChangeUsernam when I imported this as <SignInSide></SignInSide> but i dont know what I have to write inside the onClick-Argument inside of the Login-Component to get/update the state from the Main-File.
Login-Component
export default function SignInSide(props) {
return (
<form className={classes.form}>
<TextField/>
<Button>
Login
</Button>
</form>
);
}
I am very thankful for helping. Can you give me a short and easy to understand example, so that I can add this by myself to my project? I only have to understand, what i have to do.
To update the Login component's state, you need to pass props from parent component to child component (passing props from Login to SignInSide component).
Therefore, you need to pass onChangeUsername method as a prop to the <SignInSide/> component. Inside SignInSide component, you need to manage a local state to keep the text input that you entered. It only needs to be used when you're submitting the data which trigger the onClick function onChangeUsername which has been passed from parent component.
Main-File:
import { Component } from "react";
import SignInSide from "./SignInSide";
export default class Login extends Component {
constructor(props) {
super(props);
this.onChangeUsername = this.onChangeUsername.bind(this);
this.state = {
username: "",
};
}
onChangeUsername(data) {
this.setState({
username: data,
});
}
render() {
console.log("username updated: ", this.state.username);
return <SignInSide onChangeUsername={this.onChangeUsername} />;
}
}
SignInSide Component:
import { useState } from "react";
export default function SignInSide(props) {
const [data, setData] = useState("");
const handleChange = (e) => setData(e.target.value);
return (
<form>
<label>
User Name:
<input type="text" value={data} onChange={handleChange} />
</label>
<button type="button" onClick={() => props.onChangeUsername(data)}>
Submit
</button>
</form>
);
}
Application View
Check the console logs as shown in the application view to identify whether username is updated in main component.
Once I click the text box some of my div will show a "typing." Before clicking, it should show "this is just a sample."
<input type="text" onClick={this.state}>
Although your question is really messy, here's the sample code for you. Maybe it'll point you the right direction.
import React from "react";
export default class Test extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
someState: ""
}
this.onChangeState = this.onChangeState.bind(this);
}
onChangeState(e) {
this.setState({
someState: //logic here
})
}
render() {
const {someState} = this.state;
return (
<>
<input type="text" onClick={e => this.onChangeState(e)}>
State value: {someState}
</>
)
}
}
Whenever you click chosen div, you'll call onChangeState method where you can manipulate your state. Don't forget that every state update causes rerender.
For more info visit official React docs.
if I understood correctly you want to display the word 'Typing' once you click on the div.
I think this will help you
import React from "react";
export default class Typing extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
yourStateName: ""
}
this.onChangeState = this.onChangeState.bind(this);
}
onChangeState(e) {
this.setState({
yourStateName: "Typing"
})
}
render() {
const {yourStateName} = this.state;
return (
<div>
<input type="text" onClick={e => this.onChangeState(e)}>
value: {yourStateName}
<div/>
)
}
}
I made a text input box which, when submitted, will update the state of the messages in the parent component. The messages in the parent component are passed down to the message display. I'd like the component responsible for displaying the messages to update and display the messages after each submission, but can't figure out how to do it. I made a code sandbox here:
https://codesandbox.io/s/unruffled-pasteur-nz32o
Here's my code:
Parent component:
import React, { Component } from "react";
import Messages from "./Messages";
import Input from "./Input";
export default class Container extends Component {
constructor(props) {
super(props);
this.state = {
messages: []
};
}
updateMessage(message) {
this.state.messages.push(message);
}
render() {
return (
<div>
<Messages messages={this.state.messages} />
<Input updateMessage={message => this.updateMessage(message)} />
</div>
);
}
}
Message input component:
import React, { Component } from "react";
export default class Input extends Component {
constructor(props) {
super(props);
this.state = {
message: ""
};
}
sendMessage() {
this.props.updateMessage(this.state.message);
this.setState({ message: "" });
}
render() {
return (
<div>
<input
type="text"
value={this.state.message}
onChange={({ target }) => {
this.setState({ message: target.value });
}}
/>
<button onClick={() => this.sendMessage()}>Send</button>
</div>
);
}
}
Message display component:
import React, { Component } from "react";
export default class Messages extends Component {
render() {
return this.props.messages.map(message => {
return <div>{message}</div>;
});
}
}
Thanks!
From your code:
updateMessage(message) {
this.state.messages.push(message);
}
You're modifying the state directly and you're not supposed to do that (except for in the constructor). It won't cause a re-render in this way. Instead, clone the state, modify it, then update the state via setState. Calling setState will invoke a re-render.
updateMessage(message) {
this.setState({
messages: [...this.state.messages, message],
});
}
In your updateMessage(message) method, can you try:
updateMessage(message) {
let { messages } = this.state;
messages.push(message)
this.setState({ messages })
}
Your error is in this part
updateMessage(message) {
this.state.messages.push(message);
}
You can not change state directly. You must use setState() method to change state
updateMessage(message) {
this.setState({
messages : [...this.state.messages, message]
});
}
I have simplified the code to isolate and reproduce the issue, so it may not make sense in real implementations:
import React, { Component } from 'react'
const obj = {
objProp: true
};
export default class MyButtonContainer extends Component {
render() {
return (
<MyButton
onClick={() => {obj.objProp = !obj.objProp;}}
text={obj.objProp.toString()}
/>
);
}
}
class MyButton extends Component {
render() {
return (
<button
onClick={this.props.onClick}
>
{this.props.text}
</button>
)
}
}
You can see that obj.objProp is assigned into MyButton.props.text, and it's value gets toggled when you click on an instance of MyButton. The value of obj.objProp does change as expected, but MyButton doesn't update and rerender.
My question is why is MyButton is not updating, and what is the proper way to implement such logic?
In addition, if the solution is to push obj into MyButtonContainer.state, why MyButton would of update if I have used Redux, which injects data only into props without changing the state?
Thanks :)
What you need is a state, You should not use variable this way, it needs to be on state and changing that state asynchronously.
Change your button container to this.
export default class MyButtonContainer extends Component {
constructor() {
this.state = {
objProp: true
}
this.onclick = this.onclick.bind(this);
}
onclick() {
this.setState({ objProp: !this.state.objProp })
}
render() {
return (
<MyButton
onClick={() => { this.onclick() }}
text={this.state.objProp.toString()}
/>
);
}
}
Demo
Use state to hold your objProp
React will rerender when there is setstate is called, it won't get rerendered automatically.
export default class MyButtonContainer extends Component {
state = {
objProp: true
}
onclick = () => {
this.setState({ objProp: !this.state.objProp })
}
render() {
return (
<MyButton
onClick={() => { this.onclick() }}
text={this.state.objProp.toString()}
/>
);
}
}
}
Whenever there is something where you want the UI to change it should be either through its State or by props passed to it.
Both the given answers are right, if you want to re-render your component you must use this.setState. so there is two way to get your updated data in React Component.
1) put your object in to state and setState.
2) if you really dont want to use your object in state, you can do a workaround like take a variable i in your state and when assigning the data in your object just do this.setState({i+1}), so due to change in state will re-render your component although this is not good way to resolve it, because to re-render you must setState.
import React, { Component } from 'react'
constructor(props){
super(props);
this.state = {i:0}
}
const obj = {
objProp: true
};
export default class MyButtonContainer extends Component {
render() {
return (
<MyButton
onClick={() => {
obj.objProp = !obj.objProp ;
let {i} = this.state ;
i = i + 1;
this.setState(i)}}
text={obj.objProp.toString()}
/>
);
}
}
class MyButton extends Component {
render() {
return (
<button
onClick={this.props.onClick}
>
{this.props.text}
</button>
)
}
}
I have 2 TextField in 2 components.
When a TextField change value, how I can send and change value of TextField of remaining?
This is example for my issue. This is my issue.
I have url http://localhost:8000/search?search=php&category=catqgkv4q01ck7453ualdn3sd&page=1
Search page Js:
class SearchPage extends Component {
constructor(props) {
super(props);
let search = typeof this.props.location.query.search !== '' ? this.props.location.query.search : '';
if(search){
this.props.dispatch(setTextSearch(search));
}
};
render() {
return (
<MuiThemeProvider>
<div id='search-page'>
<SearchTextBox textSearch={this.props.textSearch}/>
</div>
</MuiThemeProvider>
)
}
}
// Retrieve data from store as props
function mapStateToProps(state) {
return {
textSearch: getTextSearch(state)
}
}
SearchPage.contextTypes = {
router: React.PropTypes.object
};
export default connect(mapStateToProps)(SearchPage);
Search Action:
import callApi from '../../util/apiCaller';
// Export Constants
export const ACTIONS = {
SET_TEXT_SEARCH: 'SET_TEXT_SEARCH'
};
export function setTextSearch(search) {
return {
type: ACTIONS.SET_TEXT_SEARCH,
search
};
}
Search Reducer:
import { ACTIONS } from './SeachActions';
// Initial State
const initialState = {
textSearch: '',
};
const SearchReducer = (state = initialState, action) => {
switch (action.type) {
case ACTIONS.SET_TEXT_SEARCH:
state.textSearch = action.search;
return {...state};
default:
return state;
}
};
/* Selectors */
export const getTextSearch = state => state.categories.textSearch;
// Export Reducer
export default SearchReducer;
I have component SearchTextBox
import React from 'react';
import TextField from 'material-ui/TextField';
export default class SearchTextBox extends React.Component {
constructor(props) {
super(props);
this.state = {
value: this.props.textSearch,
};
};
render() {
return (
<TextField
hintText="Search"
className="search-txtbox"
ref='searchText'
style={{height : '40'}}
underlineShow={false}
value={this.state.value}
onChange={this.handleChange}
autoFocus
onKeyPress={this.handleEnter}
/>
);
}
}
How can I change value by data parameter "search" on URL
So your problem seems to be about sharing the same data with other components ( passing some kind of data to each other, not only the react component state, could be anything ).
You should be aware of the available ways to communicate data between components.
1 - Props
2 - Context
3 - Global variables ( anti - pattern until you really need, when you need you should use redux or similar that makes use of both props and context to create a big global data tree without creating global variables )
So there is no other way to communicate data between components.
Then since we know available options, second question becomes how are the component I want to communicate data between positioned relative to each other.
1 - One is the direct parent of another.
2 - One is the indirect parent of another.
3 - Both share the same parent.
Assuming your TextFields share the same parent, luckily, here is a working code for you to get the idea.
const TextField = ({
value = '', handleInputChange = ''
}) => <input type="text" value={value} onChange={ handleInputChange }/>
class ParentC extends React.Component {
state = {
sharedInputValue : ''
}
constructor(props){
super(props)
this.state = {
sharedInputValue : ''
}
this.handleInputChange = this.handleInputChange.bind(this)
}
handleInputChange( event ){
this.setState({ sharedInputValue : event.target.value})
}
render(){
return (
<div>
<TextField
value={ this.state.sharedInputValue }
handleInputChange={ this.handleInputChange }
/>
<TextField
value={ this.state.sharedInputValue }
handleInputChange={ this.handleInputChange }
/>
</div>
)
}
}