I am learning Redux and trying to Add/View users using redux in my react app.By using 'ref' in react, I am reading the payload(name,account_number) of new user and passing to 'addProfile' action creator onClick of 'Add' button in this way -
AddView.js
import React from 'react';
import { connect } from 'react-redux';
import { addProfile } from '../actions';
class AddView extends React.Component{
addValues(){
return(
<div>
Name : <input type="text" value={this.props.profiles.name} ref={el => this.nameValue=el}/>
Account Number : <input type="text" value={this.props.profiles.account_number} ref={el => this.accountValue=el}/>
<button onClick={() => this.props.addProfile(this.nameValue,this.accountValue)}>Add</button>
</div>
);
}
render(){
return (
<div>
Add Profile
<br /><br />
{this.addValues()}
</div>
);
}
}
const mapStateToProps = (state) => {
return { profiles : state.profiles }
}
export default connect(mapStateToProps, {addProfile}) (AddView);
Now am trying to console log the name,account_number in my action creator but I get html instead of values.
export const addProfile = (name, account_number) => {
console.log(name,account_number)
return{
type :'ADD_PROFILE',
payload : {
name : name,
account_number : account_number
}
};
}
Can anyone please help where I went wrong. Full code here - https://codesandbox.io/s/239j97y36p
React refs give you a ref to the dom element, if you just want the value of the input you can get it with .value. I would also rename your ref variables then to be accurate like nameInputRef and accountInputRef.
Name :{" "}
<input
type="text"
value={this.props.profiles.name}
ref={el => (this.nameInputRef = el)}
/>
Account Number :{" "}
<input
type="text"
value={this.props.profiles.account_number}
ref={el => (this.accountInputRef = el)}
/>
<button
onClick={() =>
this.props.addProfile(this.nameInputRef.value, this.accountNumberRef.value)
}
> Add
</button>
You can see full sample adapted from yours here: https://codesandbox.io/s/k3mp28lr3o
class UserProfile extends Component {
constructor(props) {}
render() {
// ref={el => this.nameValue=el} to access input variable
// or
// use onChange event which fire another dispatcher which mutate profile state since we assign input values to profile state you can use state to get latest values
//this.props.profiles
//this.props.onAddProfile()
}
}
const mapStateToProps = state => {
return {
profiles : state.profiles
}
}
const mapDispatchToProps = dispatch => {
return {
onAddProfile:dispatch(addProfile())
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(UserProfile);
You are assigning the element into the variable during the onclick event.
ref={el => this.nameValue=el}
You can use a local state to store the value of the while onChange instead of ref
npm install redux-thunk --save
profile.jsx
class Profile extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<input
type="text"
onChange={({ target }) =>
this.props.onUsernameUpdated(target.value)
}
value={this.props.profile.username}
placeholder="Username"
/>
<input
type="text"
onChange={({ target }) =>
this.props.onAccNumberUpdated(target.value)
}
value={this.props.profile.accNumber}
placeholder="Account Number"
/>
<button onclick={this.props.onUpdateProfile}>Submit</button>
</div>
)
}
}
const mapStateToProps = state => {
return {
profile: state.profile
};
};
const mapDispatchToProps = dispatch => {
return {
onUsernameUpdated: username => dispatch(usernameUpdated(username)),
onAccNumberUpdated: password => dispatch(accNumberUpdated(accNumber)),
onUpdateProfile: () => dispatch(updateProfile())
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(Profile);
actions.js
export function usernameUpdated(username) { // need reducer to update state profile name
return {
type: 'USER_NAME_UPDATE',
data: username
}
}
export function accNumberUpdated(accNumber) { // need reducer to update state profile acc number
return {
type: 'ACC_NUM_UPDATE',
data: accNumber
}
}
export function updateProfile() {
return (dispatch, getState) => { //thunk magic
// latest profile after user input
const profile = getState().profile
}
}
Related
I have two components in two files: Firebase and Recipe. I call in Recipe a function createRecipe from Firebase file.
When I call this.setState({ recipies }) an error occurs. I searched a solution and tried to bind context according to results here.
Firebase file:
class Firebase {
constructor () {
app.initializeApp(config)
// TRIED
this.createRecipe = this.createRecipe.bind(this)
this.auth = app.auth()
this.db = app.database()
}
state = {
recipies: {}
}
createRecipe = recipe => {
const recipies = {...this.state.recipies}
recipies[`recipe-${Date.now()}`] = recipe
this.setState({ recipies })
}
}
export default Firebase
Recipe file:
import { withAuthorization } from '../Session'
import { withFirebase } from '../Firebase'
class Recipe extends Component {
state = {
name: '',
image: '',
ingredients: '',
instructions: ''
}
handleChange = event => {
const { name, value } = event.target
this.setState({ [name]: value })
}
handleSubmit = event => {
event.preventDefault()
const recipe = { ...this.state }
// TRIED
this.props.firebase.createRecipe(recipe)
this.props.firebase.createRecipe(recipe).bind(this)
this.resetForm(recipe)
}
render () {
return (
<div>
<div className='card'>
<form
// TRIED
onSubmit={this.handleSubmit>
onSubmit={() => this.handleSubmit>
onSubmit={this.handleSubmit.bind(this)}>
<input value={this.state.name} type='text' name='nom' onChange={this.handleChange} />
<input value={this.state.image} type='text' name='image' onChange={this.handleChange} />
<textarea value={this.state.ingredients} rows='3' name='ingredients' onChange={this.handleChange} />
<textarea value={this.state.instructions} rows='15' name='instructions' onChange={this.handleChange} />
<button type='submit'>Add recipe</button>
</form>
</div>
</div>
)
}
}
const condition = authUser => !!authUser
export default withAuthorization(condition)(withFirebase(Recipe))
Do you have an idea about what's going wrong ? Many thanks.
class Firebase doesn't extend React.component so it doesn't know what state is. This is expected, extend React.component or use state hooks useState().
You are not using react in your Firebase component
How to fix:
import React, {Component }from 'react';
class Firebase extends Component {
constructor(props){
super(props)
// your code
}
// your code
render(){
return null; // this won't change anything on your UI
}
}
I am new in React and want to develop easy app - there is input field from which I want to take values and render list. After added option in text field I want to update this list whith new option.
setState function does not work and I don't know how to connect input submit and list rendering. My code is below.
WaterApp.js
import React from 'react';
import AddWater from './AddWater';
import WaterElements from './WaterElements';
export default class WaterApp extends React.Component {
state = {
quantities: ['aaaaa', 'bbbbb', 'ccccc']
};
handleAddQuantity = (quantity) => {
this.setState(() => ({
quantities: ['ddddd', 'eeeee']
}));
console.log('works');
}
render() {
return (
<div>
<WaterElements quantities={this.state.quantities} />
<AddWater handleAddQuantity={this.handleAddQuantity} />
</div>
)
}
}
AddWater.js
import React from 'react';
export default class AddWater extends React.Component {
handleAddQuantity = (e) => {
e.preventDefault();
const quantity = e.target.elements.quantity.value;
console.log(quantity);
};
render() {
return (
<form onSubmit={this.handleAddQuantity}>
<input type="text" name="quantity" />
<input type="submit" value="Submit" />
</form>
)
}
}
WaterElements.js
import React from 'react';
const WaterElements = (props) => (
<div>
<p>Water Quantity:</p>
{
props.quantities.map((quantity) =>
<p key={quantity}>{quantity}</p>
)
}
</div>
);
export default WaterElements;
I expect list to be ddddd, eeeee at this moment.
You're never calling props.handleAddQuantity
export default class AddWater extends React.Component {
handleAddQuantity = (e) => {
e.preventDefault();
const quantity = e.target.elements.quantity.value;
props.handleAddQuantity(quantity)
};
render() {
return (
<form onSubmit={this.handleAddQuantity}>
<input type="text" name="quantity" />
<input type="submit" value="Submit" />
</form>
)
}
this.setState(() => ({
quantities: ['ddddd', 'eeeee']
}));
should be
this.setState({
quantities: ['ddddd', 'eeeee']
});
and after for add
this.setState({
quantities: [...state.quantities, quantity]
});
to update use this format
this.state({key:value});
not this.state(()=>{key:value});
handleAddQuantity = (quantity) => {
this.setState({
quantities: ['ddddd', 'eeeee']
}));
console.log('works');
}
I am adding a feature to the survey form review for a user to be able to upload files and my concern is that I do not want to mutate state with this implementation, how do I refactor the below to ensure this? Is my only option refactoring it to a class-based component?
// SurveyFormReview shows users their form inputs for review
import _ from "lodash";
import React from "react";
import { connect } from "react-redux";
import { withRouter } from "react-router-dom";
import formFields from "./formFields";
import * as actions from "../../actions";
export const onFileChange = event => {
this.setState({ file: event.target.files });
};
const SurveyFormReview = ({ onCancel, formValues, submitSurvey, history }) => {
this.state = { file: null };
const reviewFields = _.map(formFields, ({ name, label }) => {
return (
<div key={name}>
<label>{label}</label>
<label>{formValues[name]}</label>
</div>
);
});
return (
<div>
<h5>Please confirm your entries</h5>
{reviewFields}
<h5>Add an Image</h5>
<input
onChange={this.onFileChange.bind(this)}
type="file"
accept="image/*"
/>
Or do I have no choice except to refactor this to a class-based component as a best course?
You should refactor this into a class. Something on the lines of this should work
class SurveyFormReview extends React.Component {
state = { file: null };
onFileChange = event => {
this.setState({ file: event.target.files });
};
render() {
const { onCancel, formValues, submitSurvey, history } = this.props
const reviewFields = _.map(formFields, ({ name, label }) => {
return (
<div key={name}>
<label>{label}</label>
<label>{formValues[name]}</label>
</div>
);
});
return (
<div>
<h5>Please confirm your entries</h5>
{reviewFields}
<h5>Add an Image</h5>
<input
onChange={this.onFileChange}
type="file"
accept="image/*"
/>
</div>
)
}
}
just as a note about optimizations and stuff.
Because this is a form, I'd recommend you use better html elements.
const reviewFields = _.map(formFields, ({ name, label }) => {
return (
<fieldset key={name}>
<span>{label}</span>
<span>{formValues[name]}</span>
</fieldset>
);
});
label elements are usually used with input elements.
fieldset elements are usually for form groups of data
you could use a legend element for the title of your fieldset if you wanted :)
If you don't want to touch existing code, you can create HOC
const withFile = (Component) => class extends React.Component {
state = { file: null }
render() {
return <Component {...this.props} file={file} onAttach={files => this.setState({ file: files }) />
}
}
export default withFile(SurveyForm)
Now your form will receive file and onAttach as props.
I set the state when I onChange of a input field and then have a submit function on the onClick. The onClick doesn't register at all if I click it within a second or so of the last input.
I have cut everything out of the component that I don't need and am left this:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import Loader from '../utils/Loader';
import './CustomMenu.scss';
import {custom_getCategory} from '../../actions/customActions';
import propsDebug from '../utils/propsDebug';
class CustomMenu extends Component {
constructor(props) {
super(props);
this.state = {
input: "",
customWords: [],
}
this.formToState = this.formToState.bind(this);
this.submit = this.submit.bind(this);
}
formToState(e) {
const {value, name} = e.target;
this.setState({[name]: value});
}
submit() {
const {input} = this.state;
this.setState( state => {
state.input = "";
console.log("newState", state);
return state;
});
}
render() {
const {input, customWords} = this.state;
return (
<div className="CustomMenu">
<input name="input" value={input} onChange={(e) => this.formToState(e)}/>
<button onClick={(e) => {
console.log("onClick");
this.submit(e);
}} style={{margin: "10px"}}>Add</button>
</div>
)
}
}
const mapStateToProps = (state, ownProps) => ({
data: state[ownProps.reduxState],
custom: state.custom,
})
export default connect(mapStateToProps, {custom_getCategory})(CustomMenu);
Any ideas how I can fix this? I feel I have used this pattern many times before without issues - I don't know what I am missing.
<button onClick={(e) => {
console.log("onClick");
this.submit(e);
}} style={{margin: "10px"}}>Add</button>
I think the bug is from the callback you passed to your onClick(). You need to return the this.submit() before you can get it to fire.
<button onClick={(e) => (
console.log("onClick");
this.submit(e);
)} style={{margin: "10px"}}>Add</button>
use an implicit return instead.
I'm still fairly new to Javascript and React, and have been trying to introduce Redux into my admin-site project.
Right now the only thing I want to use it for is to change the ip-address of all axios calls to my backend. So on my admin site, when the user goes to log in, they will be presented with a drop-down of different servers that the site can make a request to, e.g. development, staging, production...
So when the user does log in, any component that needs to make a request, which is actually every single component, can use the IP-address that is stored in the redux state.
A kind of mini-question is I see a lot of people recommend not to connect all components to the redux store, but never sure why, for this, should I just put the IP-address in local-storage and clear() it everytime the user tries to log in? Anyway, I want to at least successfully implement Redux so I can use it in the future if need be.
The problem is that I have some buttons, which DO successfully change the state in the redux-store, and then other components once logged in make the requests to that particular server. But in the dropdown I just put in <Select />, upon any change, it then calls a function inside my component, the function runs and everything gets logged to console as it should, EXCEPT that the state in the redux-store doesnt change anymore, but as I can see it, its still using the same call to react-redux connect as the buttons were doing
So I have my actions.js:
export const DEV_ADDRESS = 'dev.example.com';
export const STAGE_ADDRESS = 'stage.exmaple.com';
export const PROD_ADDRESS = 'prod.example.com';
export function devServer() {
return {
type: DEV_ADDRESS,
};
}
export function stageServer() {
return {
type: STAGE_ADDRESS,
};
}
export function prodServer() {
return {
type: PROD_ADDRESS,
};
}
And my reducer.js:
import {
DEV_ADDRESS,
STAGE_ADDRESS,
PROD_ADDRESS,
} from '../actions/serverActions';
const initialState = {
serverAddress: PROD_ADDRESS,
};
export default function (state = initialState, action) {
switch (action.type) {
case DEV_ADDRESS:
console.log("REDUCER DEV");
return {
...state,
serverAddress: DEV_ADDRESS
};
case STAGE_ADDRESS:
console.log("REDUCER STAGE");
return {
...state,
serverAddress: STAGE_ADDRESS
};
case PROD_ADDRESS:
console.log("REDUCER PROD");
return {
...state,
serverAddress: PROD_ADDRESS
};
default:
return state;
}
}
Now in my LogIn.jsx:
import React, { PureComponent } from 'react';
import { Field, reduxForm } from 'redux-form';
import { connect } from 'react-redux';
import { Button } from 'reactstrap';
import axios from 'axios';
import Select from 'react-select';
import { Redirect } from 'react-router-dom';
import PropTypes from 'prop-types';
import EyeIcon from 'mdi-react/EyeIcon';
import Logo from '../../shared/img/logo/logo_light_2.svg';
import * as serverActionTypes from '../../redux/actions/serverActions';
class LogIn extends PureComponent {
static propTypes = {
handleSubmit: PropTypes.func.isRequired,
};
constructor(props) {
super(props);
this.state = {
showPassword: false,
in: false,
};
}
componentDidMount() {
localStorage.clear();
}
.
.
.
.
.
handleEmailChange(event) {
this.setState({ email: event.target.value })
}
handlePasswordChange(event) {
this.setState({password: event.target.value})
handleChange = (selectedOption) => {
console.log("$$$$$$$$$$$$");
console.log(selectedOption.value);
console.log(this.props)
if (selectedOption.value === serverActionTypes.devServer().type) {
console.log("handle dev change");
{this.props.onSelectDevServerAddress}
}
else if (selectedOption.value === serverActionTypes.stageServer().type) {
console.log("handle stage change");
{this.props.onSelectStageServerAddress}
}
else if (selectedOption.value === serverActionTypes.prodServer().type) {
console.log("handle prod change");
{this.props.onSelectProdServerAddress}
}
};
render() {
const { handleSubmit } = this.props;
if (this.state.in === true) {
return <Redirect to={{pathname: "/dashboard"}} />;
}
return (
<div className="account">
<div className="account__wrapper">
<div className="account__card" align="center">
<img
src={Logo}
alt="example-logo"
height="35"
style={{marginBottom: '50px'}}
/>
</div>
<div className="account__card">
<form className="form form--horizontal" onSubmit={handleSubmit}>
<div className="form__form-group">
<span className="form__form-group-label">E-mail</span>
<div className="form__form-group-field">
<Field
name="email"
component="input"
type="email"
placeholder="example#mail.com"
value={this.state.email}
onChange={this.handleEmailChange.bind(this)}
/>
</div>
</div>
<div className="form__form-group">
<span className="form__form-group-label">Password</span>
<div className="form__form-group-field">
<Field
name="password"
component="input"
type={this.state.showPassword ? 'text' : 'password'}
placeholder="Password"
value={this.state.password}
onChange={this.handlePasswordChange.bind(this)}
/>
<button
className={`form__form-group-button${this.state.showPassword ? ' active' : ''}`}
onClick={e => this.showPassword(e)}
><EyeIcon />
</button>
</div>
</div>
<div className="form__form-group">
<span className="form__form-group-label">Server</span>
<div className="form__form-group-field">
<div className="form__form-group-input-wrap">
<Select
name='server_address_selector'
value='prod.example.com'
onChange={this.handleChange}
options={[
{ value: 'dev.example.com', label: 'Dev' },
{ value: 'stage.example.com', label: 'Stage' },
{ value: 'prod.example.com', label: 'Prod' },
]}
clearable={false}
className="form__form-group-select"
/>
</div>
</div>
</div>
<Button
color="success"
onClick={this.onLogin.bind(this)}
outline>
Sign In
</Button>
<Button
color="success"
onClick={this.props.onSelectDevServerAddress}
outline>
DEV
</Button>
<Button
color="success"
onClick={this.props.onSelectStageServerAddress}
outline>
STAGE
</Button>
<Button
color="success"
onClick={this.props.onSelectProdServerAddress}
outline>
PROD
</Button>
</form>
</div>
</div>
</div>
);
}
}
const mapStateToProps = state => {
return {
ipAddress: state.server
}
}
const mapDispatchToProps = dispatch => ({
onSelectDevServerAddress: () => dispatch(serverActionTypes.devServer()),
onSelectStageServerAddress: () => dispatch(serverActionTypes.stageServer()),
onSelectProdServerAddress: () => dispatch(serverActionTypes.prodServer()),
});
LogIn = connect(
mapStateToProps,
mapDispatchToProps
)(LogIn);
export default reduxForm({
form: 'log_in_form'
})(LogIn);
The problem is that you didn't dispatch any action to the reducer. You have to actually invoke a functions from mapStateToDispatch in order to perform some update on the redux store state.
In the code below:
handleChange = (selectedOption) => {
console.log("$$$$$$$$$$$$");
console.log(selectedOption.value);
console.log(this.props)
if (selectedOption.value === serverActionTypes.devServer().type) {
console.log("handle dev change");
{this.props.onSelectDevServerAddress}
}
else if (selectedOption.value === serverActionTypes.stageServer().type) {
console.log("handle stage change");
{this.props.onSelectStageServerAddress}
}
else if (selectedOption.value === serverActionTypes.prodServer().type) {
console.log("handle prod change");
{this.props.onSelectProdServerAddress}
}
};
Instead of doing something like this:
{this.props.onSelectProdServerAddress}
You have to call that function:
this.props.onSelectProdServerAddress();
Now, your action will be dispatched to the reducer.