I'm using react-redux to access the state of a form from another component. This is how I export the form:
import _ from 'lodash';
import { reduxForm, Field } from 'redux-form';
import formFields from './formFields';
import OrderField from './OrderField';
import React, { Component } from 'react';
import { Link } from 'react-router-dom';
class OrderForm extends Component {
constructor(props) {
super(props);
this.renderFields = this.renderFields.bind(this);
}
renderFields() {
return _.map(formFields, ({ label, name }) => {
return (
<Field
key={name}
component={OrderField}
type="text"
label={label}
name={name}
/>
);
});
}
render() {
return (
<div>
<form onSubmit={this.props.handleSubmit(this.props.onOrderSubmit)}>
{this.renderFields()}
<button type="submit" className="waves-effect waves-light btn-large red darken-2 white-text">
Submit Order
<i className="material-icons right">done</i>
</button>
<p>Pay cash on delivery</p>
</form>
</div>
);
}
}
export default reduxForm({
form: 'orderForm'
})(OrderForm);
Also a container form:
import OrderForm from './OrderForm';
import React, { Component } from 'react';
import { reduxForm } from 'redux-form';
class OrderNew extends Component {
constructor(props) {
super(props);
this.submitOrder = this.submitOrder.bind(this);
}
submitOrder(values) {
console.log("handle submit order");
}
render() {
return (
<OrderForm
onOrderSubmit={ this.submitOrder }/>
);
}
}
export default reduxForm({
form: 'orderForm'
})(OrderNew);
and I'm trying to access the state.form.orderForm.values property like so in another component:
function mapStateToProps(state) {
console.log(state);
return {
cart_items: state.order.cart_items,
products: state.order.products,
total: state.order.total,
formValues: state.form.orderForm.values
};
}
But I get the error that state.form.orderForm.values is undefined. What am I doing wrong?
In fact, console.log(state.form) prints {} in mapStateToProps.
Related
I want to get the value of TextField input and render the message conditionally. I tried this one, its working but this one is functioning dynamically because I used onChange. I want to achieve the same but using onSubmit on <Button> Is there anyway to do that?
import React from 'react';
import { Component } from 'react';
import Button from '#mui/material/Button';
import { TextField } from '#mui/material';
class App extends Component
{
state = {
myValue: null,
}
handleChange = (e) => this.setState({
myValue: e.target.value
})
render() {
return (
<div>
<TextField
value={this.state.myValue}
onSubmit={this.handleChange}
/>
<button >Get Weather</button>
{this.state.myValue ? <p>value inputed </p>: <p>no input</p>}
</div>
)
}
}
export default App;
Using Refs is what you need. You can get the current value of your input by clicking a button and only then change the state.
Demo
import React, { createRef } from "react";
import { Component } from "react";
import { TextField } from "#mui/material";
class App extends Component {
constructor(props) {
super(props);
this.textInput = createRef();
this.state = {
myValue: ""
};
}
showRefContent = () => {
this.setState({
myValue: this.textInput.current.value
});
};
handleChange = (e) =>
this.setState({
myValue: e.target.value
});
render() {
return (
<div>
<TextField inputRef={this.textInput} />
<button onClick={this.showRefContent}>Get Weather</button>
<p>
{this.state.myValue.length > 0
? `text:${this.state.myValue}`
: "no text"}
</p>
</div>
);
}
}
export default App;
you just need to you onkeydown instead onsubmit.
<TextField
value={this.state.myValue}
onKeyDown={this.handleChange}
/>
or use
<form onSubmit={handleChange }>
<TextField
value={this.state.myValue}
onKeyDown={this.handleChange}
/>
<button type="submit">submit</button>
</form>
I have a form that submit some data and i have item component that has a delete button that delete an item but when i submit something it submit and delete the item in the same time
import React, {Component} from 'react';
import ApiClient from './apiClient';
import './MessageForm.css';
class MessageForm extends Component {
constructor(props){
super(props);
this.state = {
submitted: false
}
}
handleSubmit = async (event) => {
event.preventDefault();
const messageData = new FormData(event.target);
await ApiClient.addMessage({
license_plate: messageData.get('license'),
body: messageData.get('body')
});
// console.log("submitted");
// this.props.refreshList();
};
render() {
return(
<form onSubmit={this.handleSubmit} className="MessageForm">
<div>
<label htmlFor="license">License Plate</label>
<input id="license" name="license" type="text" />
</div>
<div>
<label htmlFor="body">Message</label>
<textarea id="body" name="body" type="text"/>
</div>
<div>
<input type="submit" value="Submit"/>
</div>
</form>
)
}
};
export default MessageForm;
this is the item component
import React from 'react';
import moment from 'moment';
import SocailShare from './SocialShare.css'
import { FacebookShareButton, LinkedinShareButton,
TwitterShareButton,
TelegramShareButton,
WhatsappShareButton,
EmailShareButton,} from 'react-share';
import { FacebookIcon, EmailIcon,
TwitterIcon,
TelegramIcon,
WhatsappIcon,
LinkedinIcon,} from 'react-share';
import {
FacebookShareCount,
PinterestShareCount,
VKShareCount,
OKShareCount,
RedditShareCount,
TumblrShareCount,
} from 'react-share';
import './MessageItem.css';
export default ({ id, submission_date, license_plate, body, handleDelete }) => {
var timePosted = moment(submission_date).format('DD/MM/YYYY - HH:mm');
const onDelete = (id) => {
handleDelete(id);
}
return (
<li className="MessageItem">
<span>Time: {timePosted} - </span>
<span>To license: {license_plate} : </span>
<span> {body} </span>
<button onClick={onDelete(id)}>X</button>
<div className="SocialShare">
<FacebookShareButton url="https://github.com/nygardk/react-share#readme" >
<FacebookIcon size={30}/>
<FacebookShareCount url="https://github.com/nygardk/react-share#readme">
{shareCount => (
<span className="myShareCountWrapper">{shareCount}</span>
)}
</FacebookShareCount>
</FacebookShareButton>
<TwitterShareButton url="https://github.com/nygardk/react-share#readme">
<TwitterIcon size={30}/>
</TwitterShareButton >
<EmailShareButton url="https://github.com/nygardk/react-share#readme">
<EmailIcon size={30}/>
</EmailShareButton>
</div>
</li>
);
};
and this is the message list component that renders the message item
import React, { Component } from 'react';
import './MessageList.css';
import MessageItem from './MessageItem';
import ApiClient from './apiClient'
class MessageList extends Component {
constructor(props) {
super(props);
}
handleOnDelete = async (id) => {
console.log(id + "deleted")
await ApiClient.deleteMessage(id);
this.props.refreshList();
}
render() {
const {
messages
} = this.props;
messages.sort(function(a,b){
//the list will be ordered in descending date order (most recent first)
return new Date(b.submission_date) - new Date(a.submission_date);
});
const $messages = messages.map((message) => <MessageItem handleDelete={this.handleOnDelete} key={message._id} {...message} />);
return (
<section className="MessageList">
<h1>Message Board</h1>
<ul>
{$messages}
</ul>
</section>
)
}
}
export default MessageList;
and this is the app component where everything is rendered
import React, { Component} from 'react';
import {BrowserRouter, Route, Switch} from 'react-router-dom';
import ApiClient from './apiClient';
import './App.css';
import MessageForm from './MessageForm';
import MessageList from './MessageList';
class App extends Component {
constructor(props){
super(props);
this.state = {
messages: []
}
}
componentDidMount = async () => {
this.refreshList();
}
refreshList = async () => {
const messages = await ApiClient.getMessages();
this.setState({
messages
})
}
render () {
return (
<BrowserRouter>
<div className="App">
<header className="App-header">
<h1>Hello License</h1>
<p>Send your messages to a plate number easily!</p>
</header>
<MessageForm refreshList = {this.refreshList}/>
</div>
<Switch>
<Route exact path="/api" render ={props => <MessageList refreshList = {this.refreshList} messages={this.state.messages} {...props}/> }/>
</Switch>
</BrowserRouter>
)
}
};
export default App;
in your item component, this line <button onClick={onDelete(id)}>X</button> is your problem.
What you are inadvertently saying is that when the DOM renders this component, it should call onDelete right away, and the onClick handler will refer to void. To avoid this, what you want is to pass in a function like so: <button onClick={(id) => onDelete(id)}>X</button>
I am trying to concat the data entered in text field passing data from another stateless component, using props. Not sure why it is not working.
I have created two components
app.js 2. title.js
Data entered in input field needs to concat the string every time and display dynamically using props.
App.js
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
import Home from './Home';
import Title from './Title';
class App extends Component {
constructor(props)
{
super(props);
this.state =
{
text : ' ',
collection: []
}
this.eventHandler = this.eventHandler.bind(this);
this.eventSubmit = this.eventSubmit.bind(this);
}
eventHandler(event)
{
this.setState(
{text:event.target.value}
)
}
eventSubmit(event)
{
this.setState(
{collection:this.state.collection.concat(this.state.text)}
)
}
render() {
return (
<div className="App">
<input type="text" onChange ={this.eventHandler} />
<p> {this.state.text} </p>
<input type="submit" onClick={this.eventSubmit} />
<title collection={this.state.collection} />
</div>
);
}
}
export default App;
Title.js
import React from 'react';
const title = (props) =>
{
return (
<div>
<h1> {this.props.collection.toString()} </h1>
<h1> hello </h1>
</div>
);
}
export default title;
setState is async and when you use this.state inside it, it might not re-render. Use function inside setState instead:
eventSubmit(event) {
this.setState((prevState, props) => ({
collection: prevState.collection.concat(prevState.text)
}));
}
See 3. setState() is async: https://codeburst.io/how-to-not-react-common-anti-patterns-and-gotchas-in-react-40141fe0dcd
Mutations are bad in general and can lead to side effects use spread operator(...) to copy prevState array instead.
eventSubmit(event) {
this.setState((prevState) => ({
collection: [...prevState.collection, prevState.text]
}));
}
That's how you append data in array and update the state
Instead of stateless component I have created class component and it worked. Can someone explain me why it didn't worked with stateless why it worked now.
App.js
<code>
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
import Home from './Home';
import Title from './Title';
import Collection from './Collection';
class App extends React.Component {
constructor(props)
{
super(props);
this.state =
{
text : ' ',
collection: []
}
this.eventHandler = this.eventHandler.bind(this);
this.eventSubmit = this.eventSubmit.bind(this);
}
eventHandler(event)
{
this.setState(
{text:event.target.value}
)
}
eventSubmit(event)
{
this.setState(
{collection:this.state.collection.concat(this.state.text)}
)
}
render() {
return (
<div className="App">
<h1> ramesh </h1>
<input type="text" onChange ={this.eventHandler} />
<p> {this.state.text} </p>
<input type="submit" onClick={this.eventSubmit} />
<title name ={this.state.collection} />
<Collection name={this.state.collection} />
</div>
);
}
}
export default App;
</code>
Collection.js
<code>
import React, {Component} from 'react';
class Collection extends React.Component
{
render()
{
return(
<div>
<h1> {this.props.name.toString()} </h1>
</div>
);
}
}
export default Collection;
</code>
I'm doing a simple redux / react todo app. I can't get the todo items to show up. I'm able to console.log the data, but can't get it to appear. What am I doing wrong?
I separated the files, here is my app.js:
import React, { Component } from 'react';
import Todos from './todos';
import TodoList from "./todo_list";
export default class App extends Component {
render() {
return (
<div>
<Todos />
<TodoList/>
</div>
);
}
}
Here is the container Todos:
import React, {Component} from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { addTodo } from '../actions/index';
class Todos extends Component {
constructor(props) {
super(props);
this.state = {text: ''};
}
addTodo(e) {
e.preventDefault();
this.props.addTodo(this.state.text);
this.setState({
text: ''
});
}
updateValue(e) {
this.setState({text: e.target.value})
}
render() {
return (
<div>
<form onSubmit={(e) => this.addTodo(e)}>
<input
placeholder="Add Todo"
value={this.state.text}
onChange={(e) => {
this.updateValue(e)
}}
/>
<button type="submit">Add Todo</button>
</form>
</div>
);
}
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({addTodo}, dispatch);
}
export default connect(null, mapDispatchToProps)(Todos);
Here is the TodoList:
import React, {Component} from 'react';
import {connect} from 'react-redux';
class TodoList extends Component {
render() {
return (
<ul>
{ this.props.todo.map((tod) => {
return <li key={tod.message}>{ tod.message }</li>
})}
</ul>
);
}
}
function mapStateToProps({ todo }) {
console.log({ todo });
return { todo };
}
export default connect(mapStateToProps)(TodoList);
Reducer:
import { ADD_TODO } from '../actions/types';
export default function(state=[], action) {
switch(action.type) {
case ADD_TODO:
return [ action.payload.message, ...state ]
}
return state;
}
And action
import { ADD_TODO } from './types';
const uid = () => Math.random().toString(34).slice(2);
export function addTodo(message) {
const action = {
id: uid(),
message: message
};
return {
type: ADD_TODO,
payload: action
};
}
This is what I get from the console.log({todo});
Here is my reducers/index:
import { combineReducers } from 'redux';
import TodosReducer from './reducer_addTodo';
const rootReducer = combineReducers({
todo: TodosReducer
});
export default rootReducer;
It's because there's a disconnect between your TodoList and reducer. TodoList, when mapping, expects each todo to have a message prop, but your reducer, when returning next state, only includes the message in the state array, not an object with the message property:
case ADD_TODO:
return [ action.payload.message, ...state ]
Instead, do not just put the message string in the next state's array, put in the whole object:
case ADD_TODO:
return [ action.payload, ...state ]
Now every single element in the todo array will be an object and have a message and id property. Also, try using an always unique expression for key -- it really shouldn't be the todo message, nor the id you supplied because it's using Math.random which both have a possibility of keys being the same.
I'm working on react-redux & there are something that I can't understand. I created action & root reducer & active_step reducer & list_step reducer. I can change active step component from the navigation that mapped dynamically.
//step-list.js
import React, {Component} from 'react'
import {connect} from 'react-redux'
import {bindActionCreators} from 'redux'
import {selectStep} from '../actions/index'
class StepList extends Component {
render() {
const renderSteps = this.props.steps.map((item, index) => {
return (
<li
key={item.stepNumber}
onClick={() => {
this.props.selectStep(item)
}
}
className="step-group-item">
{item.title}
</li>
)
}
)
return (
<ul className="step-group">
{renderSteps}
</ul>
)
}
}
function mapStateToProps(state) {
return {
steps: state.steps
}
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({selectStep: selectStep}, dispatch)
}
export default connect(mapStateToProps, mapDispatchToProps)(StepList)
//active-step.js
import React, {Component} from 'react'
import {connect} from 'react-redux'
class ActiveStep extends Component {
render() {
if(!this.props.step) {
return <div className="active-step">There are no steps selected</div>
}
return (
<div className="active-step">
<h3>Step Title: {this.props.step.title}</h3>
<h5>Step Subtitle: {this.props.step.subTitle}</h5>
<p>Step Number: {this.props.step.stepNumber}</p>
<div>
{this.props.step.content}
</div>
</div>
)
}
}
function mapStateToProps(state) {
return {
step: state.activeStep
}
}
export default connect(mapStateToProps)(ActiveStep)
//
Now, There is an another component in the main component(app.js). Its name is Footer & it has 3 buttons. I can hide & show "Back", "Next", "Success" buttons key to stepNumber. But I want active_step to change when every click next & back buttons.
// app.js
import React, {Component} from 'react'
import StepList from '../containers/step-list'
import ActiveStep from '../containers/active-step'
import Footer from './footer'
import {connect} from 'react-redux'
class App extends Component {
constructor(props) {
super(props)
this.state = {
whichStep: 0
}
this.handleBackStep = this.handleBackStep.bind(this)
this.handleNextStep = this.handleNextStep.bind(this)
}
componentWillReceiveProps(nextProps) {
this.setState({
whichStep: nextProps.step.stepNumber
})
}
render() {
return (
<div className="container">
<StepList />
<ActiveStep whichStep={this.state.whichStep}/>
<Footer
whichStep={this.state.whichStep}
onBackClick={this.handleBackStep}
onNextClick={this.handleNextStep} />
</div>
)
}
handleBackStep() {
console.log('clicked back')
const whichStep = this.state.whichStep
this.setState({
whichStep: whichStep - 1
})
}
handleNextStep() {
console.log('clicked next')
const whichStep = this.state.whichStep
this.setState({
whichStep: whichStep + 1
})
}
}
function mapStateToProps(state) {
return {
step: state.activeStep
}
}
export default connect(mapStateToProps)(App)
//
I hope I explained correctly. Sorry for my bad language.
So, correct me if I'm wrong but you want to update the state using the reducer active_step.
In order to achieve that you may want to move the selectStep action call from step-list.js to app.js.
So this will be step-list.js
import React, {Component} from 'react'
import {connect} from 'react-redux'
import {bindActionCreators} from 'redux'
import {selectStep} from '../actions/index'
class StepList extends Component {
render() {
const renderSteps = this.props.steps.map((item, index) => {
return (
<li
key={item.stepNumber}
onClick={() => {
this.props.selectStep(item)
}
}
className="step-group-item">
{item.title}
</li>
)
}
)
return (
<ul className="step-group">
{renderSteps}
</ul>
)
}
}
export default StepList
and this will be app.js
import React, {Component} from 'react'
import StepList from '../containers/step-list'
import ActiveStep from '../containers/active-step'
import Footer from './footer'
import {connect} from 'react-redux'
class App extends Component {
constructor(props) {
super(props)
this.state = {
whichStep: 0
}
this.handleBackStep = this.handleBackStep.bind(this)
this.handleNextStep = this.handleNextStep.bind(this)
}
componentWillReceiveProps(nextProps) {
this.setState({
whichStep: nextProps.step.stepNumber
})
}
render() {
return (
<div className="container">
<StepList
steps={this.props.steps}
selectStep={this.props.selectStep}
/>
<ActiveStep whichStep={this.props.activeStep}/>
<Footer
whichStep={this.props.activeStep}
onBackClick={this.handleBackStep}
onNextClick={this.handleNextStep} />
</div>
)
}
handleBackStep() {
console.log('clicked back')
const activeStep = this.props.activeStep
this.props.selectStep(activeStep - 1);
}
handleNextStep() {
console.log('clicked next')
const activeStep = this.props.activeStep;
this.props.selectStep(activeStep + 1);
}
}
function mapStateToProps(state) {
return {
activeStep: state.activeStep,
steps: state.steps
}
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({selectStep: selectStep}, dispatch)
}
export default connect(mapStateToProps, mapDispatchToProps)(App)
In this way you're doing the selectStep action always in the same place