I creating chat application by React.
In the chat application, there is a field for entering user_name and text.
I thought about managing those data with state, I made onNameChange and onTextChange events.
However, in the code I created, onTextChange was loaded but onNameChange was not loaded.
I know that onTextChange in the same file will be loaded.
Even though the files are different, I thought that data can be exchanged via props if the relationship is between parent and child.
I described the code with such a recognition, but I could not get the results I expected.
How can I pass data from LogoutStateForm.js to user_name in ChatForm.js via onNameChange?
ChatForm.js
import React,{Component} from 'react'
import firebase from 'firebase/app'
import { firebaseApp,firebaseDB } from '../firebase/firebase'
import LogoutStateForm from './LogoutStateForm'
const messagesRef = firebaseDB.ref('messages')
class ChatForm extends Component {
constructor(props){
super(props)
this.onNameChange = this.onNameChange.bind(this)
this.onTextChange = this.onTextChange.bind(this)
this.state = {
user: null,
user_name: "",
text: ""
}
}
componentDidMount(){
firebase.auth().onAuthStateChanged(user => {
this.setState({ user })
})
}
onNameChange(e) {
if (e.target.name == 'user_name') {
this.setState({
user_name: e.target.value
}),
console.log(this.state.user_name);
}
}
onTextChange(e) {
if (e.target.name == 'text') {
this.setState({
text: e.target.value
}),
console.log(this.state.text);
}
}
render(){
return(
<div id='Form'>
{this.state.user ?
<LogoutStateForm onClick={this.onNameChange} />:
null
}
//In order to switch display depending on login availability
<textarea name='text' onChange={this.onTextChange} placeholder='メッセージ'/>
</div>
)
}
}
export default ChatForm
LogoutStateForm.js
import React,{Component} from 'react'
import firebase from 'firebase/app'
class LogoutStateForm extends Component {
constructor(props){
super(props)
}
login() {
const provider = new firebase.auth.GoogleAuthProvider()
firebase.auth().signInWithPopup(provider)
}
componentDidMount(){
firebase.auth().onAuthStateChanged(user => {
this.setState({ user })
})
}
render(){
return(
<div className='logout'>
<input name='user_name' onChange={this.props.onNameChange} placeholder='名前'/>
<button onClick={this.login}>Goggle Login</button>
</div>
)
}
}
export default LogoutStateForm
Please lend me your wisdom.
Thank you.
First, in ChatForm.js, what you render LoginStateForm not LogoutStateForm.
Second, assuming it's supposed to be LogoutStateForm, at ChatForm component you pass onNameChange as onClick to LogoutStateForm.
However, you access the props as onNameChange in LogoutStateForm which is wrong. You should access it as the props name that you give, which is this.props.onClick.
Hope it helps.
In ChatForm.js, you are rendering wrong component, It should be LogoutStateForm.
Second you should access prop which you have passed.
ChatForm.js
<LogoutStateForm onNameChange={this.onNameChange} />
In LogoutStateForm.js
render(){
return(
<div className='logout'>
<input name='user_name' onChange={this.props.onNameChange} placeholder='名前'/>
<button onClick={this.login}>Goggle Login</button>
</div>
)
}
Also, define PropTypes in LogoutStateForm.js for verifying type check.
https://reactjs.org/docs/typechecking-with-proptypes.html
Related
I was looking up how to dynamically populate a dropdown() component I have set up and I am having a hard time getting it to work. The data for the categories is getting pulled (fetched) from a Rails Api I have setup on the backend. I am also getting a 'TypeError: this.props.state is undefined' message as well if that helps with factors to my issue.
So far in my RecipeInput form component this is what I have so far with the dropdown rendered:
//I took out some of the event handlers to try to keep this explanation straight to the point
import React, { Component } from 'react'
import Catagories from './Catagories.js'
class RecipeInput extends Component{
constructor(props){
super(props)
this.state = {
catagories: [],
name:'',
ingredients: '',
chef_name: '',
origin: '',
}
}
componentDidMount(){
let initialCats = [];
const BASE_URL = `http://localhost:10524`
const CATAGOREIS_URL =`${BASE_URL}/catagories`
fetch(CATAGOREIS_URL)
.then(resp => resp.json())
.then(data => {
initialCats = data.results.map((catagory) => {
return catagory
})
this.setState({
catagories: initialCats,
})
});
}
render(){
return(
<div>
<form onSubmit={this.handleSubmit}>
<Catagories catagories={this.state.catagories}/>
<input value='submit' type='submit'/>
</form>
</div>
)
}
}
export default RecipeInput
And here is my actual Catagories component:
import React, { Component } from 'react'
class Catagories extends Component{
constructor(){
super()
}
render(){
let catagories = this.props.state.catagories
let optionItems = catagories.map((catagory,index) =>
<option key={index}>{catagory.name}</option>
)
return (
<div>
<select>
{optionItems}
</select>
</div>
)
}
}
export default Catagories
Who is able to point out my dropdown is not populating based on the code provided?
Use this.props.catagories instead this.props.state.catagories
I have one component in which I have one button and I am calling one node js service on that. I am getting a response back from that service and I want to pass that response on next component to display a data there. Below is my component which is doing a node js call.
import { FormGroup } from "react-bootstrap";
import "bootstrap/dist/css/bootstrap.css";
import axios from "axios";
export default class Abc extends React.Component {
constructor(props) {
super(props);
this.state = {
data: {}
}
this.handleClick = this.handleClick.bind(this);
}
handleClick = (e) => {
e.preventDefault();
axios.get(url)
.then(res => {
this.setState({
data: res.data// I need this variable to pass to next component Pqr where I can use it for display purpose.
})
this.props.history.push("/Pqr",{ response:res.data});
})
};
render() {
return (
<form >
<button className="btn btn-info btn-sm" onClick={this.handleClick} style={{ whitespace: 'nowrap' }} >
Launch
</button>
</form>
)
}
}
My Pqr component code is as below.
import React from "react";
export default class ModelLaunch extends React.Component {
constructor(props) {
super(props);
this.state = {
}
}
render() {
const state = this.props.location.state
return (
<h1>This page will display model Inputs : {state} </h1>
)
}
}
I have solved above problem with other way. Instead of calling a node js service on Abc component I am just redirecting it to new coponent and in new component's componentDidMount() method I am calling a node js service and storind a data in props. In this way I have my data on new copmonent. Below is my updated code in Abc component now.
import { FormGroup } from "react-bootstrap";
import "bootstrap/dist/css/bootstrap.css";
import axios from "axios";
export default class Abc extends React.Component {
constructor(props) {
super(props);
this.state = {
data: {}
}
this.handleClick = this.handleClick.bind(this);
}
handleClick = (e) => {
e.preventDefault();
this.props.history.push("/Pqr");
})
};
render() {
return (
<form >
<button className="btn btn-info btn-sm" onClick={this.handleClick} style={{ whitespace: 'nowrap' }} >
Launch
</button>
</form>
)
}
And in pqr coponent's code as below
import React from "react";
import axios from "axios";
export default class Pqr extends React.Component{
constructor(props)
{
super(props);
this.state = {
data :[]
}
}
componentDidMount(){
axios.get(url).then((res) => {
console.log("res****",res.data)
this.setState({
data:res.data
})
}).catch((err) =>{
console.log("err", err)
})
}
render()
{
return(
<h1>This page will display data</h1>
)
}
}
I see you're changing a route (using react-router?).
Remember that this.setState is async and specific for your component, when you call this.props.history.push('/Pqr'), maybe the this.state.data is not updated yet.
To share this data through different routes in the same react project, I actually know that you can:
Store it on window.localStorage and then get on the next route here have a tutorial
Use react contexts to share data between components (if you're not reloading the page)
Send data through routes with react-router, as explained here
If its not the case, and you just want to pass the property down or above the hierarchy tree, in addition to the comments above, maybe it can help:
As you probably know, react projects are composed of components that are put all together to work in a specific way. In the example below, there are two components (father and child)
import React from 'react';
// We can say that this component is "below" Father
function Child(props) {
return (
<button>
Hey, I'm a button!
</button>
);
}
// We can say that this component is "above" Child
function Father(props) {
return (
<div>
<Child />
</div>
);
}
I couldn't find in the provided code/question, one child component, maybe you forgot to write it?
If the response is "yes", I'll create a fictional component called... FictionalComponent (I'm a Genius!), and pass the data on state as a property named... data.
In order to pass this property, if its the case, you just need to update your render method to look like this:
render() {
return (
<form >
<button
className="btn btn-info btn-sm"
onClick={this.handleClick}
style={{ whitespace: 'nowrap' }}
>
Launch
<FictionalComponent data={this.state.data} />
</button>
</form>
)
}
This way, when this.state.data changes, the FictionalComponent will be re-rendered with the new data value.
But, maybe you want the reverse operation and you need to pass the this.state.data to the component above your Abc, listed there when the button is pressed.
To achieve it you need to have a "Father" component to your Abc, the "Father" component must provide an onDataChanged callback in order to capture the event. This callback will receive the data and handle it.
In this case, I'll create another component to be the component above your Abc. I'll name it... AboveAbcComponent, perfect!
...
class AboveAbcComponent extends React.Component {
constructor(props) {
this.state = {
dataFromChild: null
};
this.onDataChanged = this.onDataChanged.bind(this);
}
onDataChanged(dataReceived) {
console.log("Yey! It works!");
this.setState({ dataFromChild: dataReceived });
}
render() {// Just the passed props changes here
...
<Abc
onDataChanged={this.onDataChanged}
/>
...
}
}
export default class Abc extends React.Component {
constructor(props) { ... } // No changes here
handleClick = (e) => {
e.preventDefault();
axios.get(url)
.then(res => {
this.setState({
data: res.data
});
this.props.onDataChanged(res.data);
this.props.history.push("/Pqr"); // I really didn't understand Why this push is here... but ok
})
};
render() { ... } // No changes here
}
Hope it helps, have fun!
I have a form in a React/Redux application that is used to update information - hence require the fields to be pre-populated with current data.
Before the component is mounted, the data for the form is already sitting in Redux state.
Currently, within the componentDidMount()lifecycle, an axios GET request is sent to retrieve the information from the database again and loads it into the redux state.
This method works fine, however I would like to avoid the additional/redundant GET request as the information is already in the redux state.
How do I port the redux state to the component's state when it loads, so that the input fields are populated (without the need for the GET request)?
component code is below.
import React, { Component } from 'react';
import { Link } from 'react-router-dom';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import Navbar from '../components/layout/Navbar';
import Sidebar from '../components/layout/Sidebar';
import Breadcrumbs from '../components/layout/Breadcrumbs';
import TextFieldGroup from '../components/form-components/TextFieldGroup';
import { getPatientById } from '../redux/actions/patient.actions';
class PatientEdit extends Component {
constructor() {
super();
this.state = {
firstName: '',
lastName: '',
errors: {}
};
}
componentDidMount = () => {
if (this.props.match.params.patient_id) {
this.props.getPatientById(this.props.match.params.patient_id);
}
};
componentWillReceiveProps = nextProps => {
if (nextProps.errors) {
this.setState({ errors: nextProps.errors });
}
if (nextProps.patients.patient) {
const patient = nextProps.patients.patient;
this.setState({
firstName: patient.firstName.patients,
lastName: patient.lastName.patients
});
}
};
onChange = e => {
this.setState({ [e.target.name]: e.target.value });
};
onSubmit = (e, patient_id) => {
e.preventDefault();
// boring script to handle form submission...
};
render() {
const { errors } = this.state;
const { patient } = this.props.patients;
return (
<>
<Navbar />
<div className="app-body">
<Sidebar />
<main className="main">
<div className="container">
<form onSubmit={e => this.onSubmit(e, patient._id)}>
<div>
<input
type="text"
name="firstName"
value={this.state.firstName}
onChange={this.onChange}
/>
<input
type="text"
name="lastName"
value={this.state.lastName}
onChange={this.onChange}
/>
</div>
<div>
<Link to="/patients" className="btn btn-light mr-2">
Cancel
</Link>
<button type="submit" className="btn btn-primary">
Submit
</button>
</div>
</form>
</div>
</main>
</div>
</>
);
}
}
PatientEdit.propTypes = {
getPatientById: PropTypes.func.isRequired,
patients: PropTypes.object.isRequired,
errors: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
patients: state.patients,
errors: state.errors
});
export default connect(
mapStateToProps,
{ getPatientById }
)(PatientEdit);
getPatientById action
export const getPatientById = id => dispatch => {
dispatch(setPatientLoading());
axios
.get(`/api/patients/${id}`)
.then(res => {
dispatch({
type: GET_PATIENTS_SINGLE,
payload: res.data
});
})
.catch(err => {
dispatch({
type: GET_ERRORS,
payload: err.response.data
});
});
};
It seems you are copying the data from redux to local state. That might be needed, or not. As for your goal, why not directly render the data received from Redux (without copying them to state)? In that case, you can skip the axios call in componentDidMount.
If you want to have data from Redux in state anyway, you could copy them to state in constructor or in componentDidMount. This makes the copy only once though. If you then need to keep data from redux and state in sync, you need to ensure this in componentWillReceiveProps.
I believe the problem you encountered with your current set up is that componentWillReceiveProps isn't called for first render, hence nothing was copied to your state.
On a remount, couldn't you pass down Redux state and use that for control logic? I am assuming it comes from patients Redux state as you're using it in the componentWillReceiveProps lifecycle method, so if that is the case, couldn't you do:
// rest of code emitted for brevity
componentDidMount = () => {
// assuming init state for patients.patient is null
if (!this.patients.patient && this.props.match.params.patient_id) {
this.props.getPatientById(this.props.match.params.patient_id);
}
};
This assumes this.props.patients.patient initial state is null from the reducer. If this.props.patients is null instead, then change the control logic code to be !this.props.patients && this.props.match.params.patient_id.
The control logic here is saying "If patients is null and we have the param ID then make the API GET call". If you already have the patient, then it won't bother.
I creating chat system by React and Firebase.
The data of chat stystem is managemented by Firebase RealTimeDatabase.
Now site here
URL: https://react-chat-b0e8a.firebaseapp.com/
Github: https://github.com/kaibara/React-chat
I'm trying to implement the delete button, but I do not know how to make the child component event read the parent componentthis.props.
As a solution to this, I was thinking to have this.props read in front of render.
But I do not know how to do it.
Can you share the solution to this problem in the following code?
App.js - parenet component
import React, { Component } from 'react'
import firebase from 'firebase/app'
import { firebaseApp,firebaseDB } from './firebase/firebase'
import ChatMessage from './components/ChatMessage'
const messagesRef = firebaseDB.ref('messages')
class App extends Component {
constructor(props) {
super(props)
this.state = {
text : "",
user_name: "",
messages: []
}
}
componentWillMount() {
messagesRef.on('child_added', (snapshot) => {
const m = snapshot.val()
let msgs = this.state.messages
msgs.push({
'text' : m.text,
'user_name' : m.user_name,
'key': snapshot.key
})
console.log({msgs})
this.setState({
messages : msgs
})
console.log(this.state.messages)
})
}
render() {
return (
<div className="App">
<div className="MessageList">
<h2>メッセージログ</h2>
{this.state.messages.map((m, i) => {
return <ChatMessage key={i} messages={m} />
})}
</div>
</div>
)
}
}
export default App
ChatMessage.js - child component
import React,{Component} from 'react'
import { firebaseDB } from '../firebase/firebase'
const messagesRef = firebaseDB.ref('messages')
class ChatMessage extends Component {
onRemoveClick(){
messagesRef.child(this.props.messages.key).remove()
// I want to load `this.props.messages.key` here
}
render(){
return(
<div className="Message">
<p>{this.props.messages.key}</p>
<p className="MessageText">{this.props.messages.text}</p>
<p className="MessageName" style={user}>by {this.props.messages.user_name}</p>
<button className="MessageRemove" onClick={this.onRemoveClick}>削除</button>
</div>
)
}
}
export default ChatMessage
Please lend me your knowledge.
Thank you.
Implement the handler in your parent component and pass the reference down to child component has props
implement onRemoveClick() in App component and pass the handler refrence in `props' to ChatMessage component.
App component:
deleteMessageHandler = (key) =>{
const messages = [...this.state.messages];
messages = messages.splice(key,1);
this.setState({messages:messages});
}
ChatMessage:
render() {
return (
<div className="App">
<div className="MessageList">
{this.state.messages.map((m, i) => {
return <ChatMessage key={i} messages={m} deleteMessageHandler={this.deleteMessageHandler}/>
})}
</div>
</div>
)
}
Note: Don't use the index of the map has a key to the components in the map, its an antipattern, it should be proper unique id's.
I'm attempting at creating a simple todo app to try to cement some concepts.
This app gets some previous todos from a .js file with json object. And every time they get clicked they are deleted from the current app.
Now I wanted to add the ability to add todos, first to the current instance of app itself and afterwards to the file to ensure continuity.
My problem arises adding to the app instance.
When using the component with a form, it all goes south.
I tried putting all the component parts in the App.js main file but still the same result, it refreshes after the alert(value) line.
I've also tried changing the order inside the addTodo function and the alert only works if it's the first line of the function, anywhere else the refresh happens before it. So I assumed it's something about using the state of the app component?
App.js
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
import todosData from "./todosData"
import TodoItem from "./TodoItem"
import TodoForm from './TodoForm'
class App extends Component {
constructor() {
super()
this.state = {
todos: todosData
}
this.handleChange = this.handleChange.bind(this)
}
handleChange(id) {
const allTodos = this.state.todos
const filtered = allTodos.filter(x => x.id !== id)
const filtered2 = filtered
filtered2.push({id:4,text:"HAKUNA MATATA",completed:false })
this.setState({todos:filtered2})
//added testing so that whenever I delete a todo it adds one with "HAKUNA MATATA"
}
addTodo(value) {
alert(value)
const allTodos = this.state.todos
const lastTodo = this.state.todos.slice(-1)[0]
const newId = lastTodo.id + 1
const newTodo = {id: newId, text: value, completed:false}
allTodos.push(newTodo)
this.setState({todos:allTodos})
}
render() {
const todoItems = this.state.todos.map( item =>
<TodoItem
key={item.id}
item={item}
handleChange={this.handleChange}
/>
)
const text = ""
return (
<div className="todo-list">
{todoItems}
<TodoForm addTodo={this.addTodo}/>
</div>
);
}
}
export default App;
TodoForm.js
import React from 'react'
class TodoForm extends React.Component {
constructor(props) {
super(props)
this.handleSubmit = this.handleSubmit.bind(this)
}
handleSubmit(event) {
this.props.addTodo(this.input.value)
event.preventDefault()
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
</label>
<input type="text" ref={(input) => this.input = input}/>
</form>
)
}
}
export default TodoForm
Can you guys help me with this? From what I understood preventDefault was supposed to prevent this?
Call the preventDefault method on the event the first thing you do in handleSubmit and it should work.
handleSubmit(event) {
event.preventDefault()
this.props.addTodo(this.input.value)
}
You also need to bind the addTodo method to this in the App constructor.
class App extends Component {
constructor() {
super()
this.state = {
todos: todosData
}
this.handleChange = this.handleChange.bind(this)
this.addTodo = this.addTodo.bind(this)
}
// ...
}
In case anyone comes to this answer from Google:
In my case, when I submitted the form, I lazy loaded a component underneath, but it had not been wrapped in <Suspense>. Adding that fixed my issue.