I have a component in which I'm trying to populate a <Select /> component with some options from my props. When the component mounts, I set the state of jobNumbers to an empty array.
I have two dropdowns in which one's values, depend on the other's selected value. When the value is selected, I run an onChange function to populate the second dropdown. The only problem is when I do this.setState({jobNumbers: [...array elements...]}), the state still shows the jobNumbers array to be empty. The function that actually does the state setting is getCustomerOptions().
Here is my component in it's entirety (it's not TOO terribly long)
import React from 'react';
import SelectInput from '../../components/SelectInput';
import LocationSelector from '../../components/LocationSelector';
import { Field } from 'redux-form/immutable';
import Select from 'react-select';
import 'react-select/dist/react-select.css';
class InputCurrentCustomerLocation extends React.Component {
constructor(props) {
super(props);
this.state = {
jobNumbers: [],
};
this.getCustomerOptions = this.getCustomerOptions.bind(this);
this.onChange = this.onChange.bind(this);
}
componentWillMount() {
if (this.props.active) {
this.props.input.onChange(this.props.active);
}
}
onChange(event) {
if (this.props.input.onChange) {
this.props.input.onChange(event.value); // <-- To be aligned with how redux-form publishes its CHANGE action payload. The event received is an object with 2 keys: "value" and "label"
// Fetch our Locations for this customer
this.props.handleCustomerLocationFetch(event.value);
this.getCustomerOptions(event);
}
}
getCustomerOptions(event) {
let options = [];
if(event) {
this.props.options.forEach((option, index) => {
if(option.value === event.value) {
console.log('props options', this.state);
this.setState({ jobNumbers: this.props.options[index] });
console.log('state options', this.state);
}
})
}
}
render() {
const { meta } = this.props;
return (
<div>
<Select
options={this.props.options} // <-- Receive options from the form
{...this.props}
value={this.props.input.value || ''}
// onBlur={() => this.props.input.onBlur(this.props.input.value)}
onChange={this.onChange.bind(this)}
clearable={false}
/>
{meta.error && <div className="form-error">{meta.error}</div>}
{this.props.activeLocations ? false : (
<div>
<div>
<p> Select a location </p>
<Field
name="locations"
component={props =>
<LocationSelector
{...props}
// active={this.props.activeLocations}
locations={this.props.locations}
/>
}
/>
</div>
<div>
<p> Select a job number</p>
<Field
name="jobNumber"
component={props =>
<Select
options={this.state.jobNumbers}
value={this.props.input.value || ''}
onChange={this.onChange.bind(this)}
clearable={false}
/>
}
/>
</div>
</div>
)}
</div>
);
}
}
export default InputCurrentCustomerLocation;
I'm relatively new to React and redux and I'm not entirely sure why this is happening. Shouldn't the state be populated?
this.setState({ jobNumbers: this.props.options[index] });
is async.
so when you do a console log on the state after setState, the state still won't change.
you should check the value on componentDidUpdate(prevProps, prevState) , and print it there.
Related
Codesandbox: https://codesandbox.io/s/github/adamschwarcz/react-firebase-app
I am really new to react and firebase and I followed this tutorial to come up with this app (full project – github link here) – it's an "Add your Wish app"
My problem is that I cannot store clap count on each post to my firebase – this component is called LikeButton.js.
I have been trying to add some similar firebase code (handleChange, handleSubmit, componentDidMount... etc.. etc..) as I learned in the tutorial to LikeButton.js to store the total amount of counts in firebase each time the button is clicked and the amount of claps incremented by +1.
Simply what I want – everytime the clap button is clicked and the initial ('0') state of count is incremented to +1 the current count is going to be updated into the database.
Just cannot come up with solution, can somebody please help?
My LikeButton.js code without any firebase:
import React, { Component } from 'react'
import firebase from '../../firebase.js';
import { makeStyles } from '#material-ui/core/styles';
import Button from '#material-ui/core/Button';
import './Like.css';
class LikeButton extends Component {
state = {
count: 0,
}
incrementLike = () => {
let newCount = this.state.count + 1
this.setState({
count: newCount
})
console.log(this.state.count);
}
render() {
return(
<div class="counter">
<Button type="submit" color="primary" onChange={this.handleCount} onClick={this.incrementLike}>{this.state.count} 👏</Button>
</div>
)
}
}
export default LikeButton
My Add.js code with firebase:
import React, { Component } from 'react';
import firebase from '../../firebase.js';
import Button from '#material-ui/core/Button';
import TextField from '#material-ui/core/TextField';
import FadeIn from "react-fade-in";
import Placeholder from '../Placeholder/Placeholder.js';
import LikeButton from '../Like/Like.js'
import './Add.css';
class Add extends Component {
constructor() {
super();
this.state = {
loading: true,
currentItem: '',
username: '',
items: []
}
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(e) {
this.setState({
[e.target.name]: e.target.value
});
}
handleSubmit(e) {
e.preventDefault();
const itemsRef = firebase.database().ref('items');
const item = {
title: this.state.currentItem,
user: this.state.username
}
itemsRef.push(item);
this.setState({
currentItem: '',
username: ''
});
}
componentDidMount() {
fetch("https://jsonplaceholder.typicode.com/posts")
.then(response => response.json())
.then(json => {
setTimeout(() => this.setState({ loading: false }), 1500);
});
const itemsRef = firebase.database().ref('items');
itemsRef.on('value', (snapshot) => {
let items = snapshot.val();
let newState = [];
for (let item in items) {
newState.push({
id: item,
title: items[item].title,
user: items[item].user
});
}
this.setState({
items: newState
});
});
}
removeItem(itemId) {
const itemRef = firebase.database().ref(`/items/${itemId}`);
itemRef.remove();
}
render() {
return (
<div className="container">
<div className="wrap">
<section className="add-item">
<h1>Napíš svoj wish</h1>
<h3>Možno prilepíš sebe, možno posunieš firmu.</h3>
<form onSubmit={this.handleSubmit}>
<TextField
id="filled-required"
label="Meno"
name="username"
variant="filled"
value={this.state.username}
onChange={this.handleChange}
/>
<TextField
required
id="standard-multiline-flexible"
label="Tvoje prianie"
name="currentItem"
variant="filled"
multiline
rows="6"
rowsMax="8"
value={this.state.currentItem}
onChange={this.handleChange}
/>
<Button
type="submit"
variant="contained"
color="primary">
Poslať wish
</Button>
</form>
</section>
<section className='items-list'>
<div className="item">
<div>
{this.state.items.map((item) => {
return (
<div>
{this.state.loading ? (
<>
<FadeIn>
<Placeholder />
</FadeIn>
</>
) : (
<div className="wish" key={item.id}>
<FadeIn>
<h2>{item.title}</h2>
<div className="name">
<p>poslal <span>{item.user}</span></p>
<LikeButton />
</div>
</FadeIn>
</div>
)}
</div>
)
})}
</div>
</div>
</section>
</div>
</div>
);
}
}
export default Add
First of all, you need to tell the LikeComponent which Wish it will be updating, and you will also need to be able to access the clapCount of the wish from the LikeComponent. This can be done easily using props. You should re-configure LikeComponent to accept a prop similar to wish, which would be the wish that you are displaying and modifying.
So, this line in Add.js
<LikeButton />
would instead look like <LikeButton wish={item} />. This way, your LikeComponent can access the item/wish.
Next, in the LikeComponent, you need to remove the local state and instead use the clap count stored in Firebase. Luckily, since you're passing the wish via a prop, you can simply refactor the LikeComponent to look like this:
class LikeButton extends Component {
incrementLike = () => {
// TODO: Implement clap incrementation via Firebase updates
}
render() {
return(
<div class="counter">
<Button type="submit" color="primary" onClick={this.incrementLike}>{this.props.wish.clapCount} 👏</Button>
</div>
)
}
}
Next, we need to actually implement incrementLike. Luckily, since we are getting the wish item passed to us via the wish prop, we can easily update it like so:
incrementLike = () => {
// get a reference to the item we will be overwriting
const wishRef = firebase.database().ref(`/items/${this.props.wish.id}`);
// get the current value of the item in the database
wishRef.once('value')
.then(snapshot => {
// get the value of the item. NOTE: this is unsafe if the item
// does not exist
let updatedWish = snapshot.val();
// update the item's desired property to the desired value
updatedWish.clapCount = updatedWish.clapCount + 1;
// replace the item with `wish.id` with the `updatedWish`
wishRef.set(updatedWish);
});
}
While this should work with only a few tweaks, I'm sure there's a better way to do it. You might even be able to avoid the call to once('value') since you're passing wish as a prop to LikeComponent. You should play around with it.
However, I strongly encourage you to explore migrating to Firebase Cloud Firestore. It's API is way more straightforward (in my opinion) than Realtime Database.
I am trying to print state of select element in the footer. But the problem is that it does not print anything because the default value is null. And I dont know how to print footer on change of select with state of the select inside.
class SingleColor extends React.Component {
state = {
selectedOption: null,
};
handleChange = selectedOption => {
this.setState({ selectedOption });
};
render() {
const { selectedOption } = this.state;
return (
<React.Fragment>
<h1 className="TITLE">Please choose your favourite colour</h1>
<Select
className="SINGLESELECT"
classNamePrefix="SINGLESELECT__options"
value={selectedOption}
onChange={this.handleChange}
options={options}
styles={customStyles}
/>
<Footer singlevalue={this.state.selectedOption} />
</React.Fragment>
);
}
}
export default class Footer extends Component {
render() {
return (
<div className="bla">
<h1> {this.props.singlevalue}</h1>
</div>
)
}
}
Assuming you are using react-select.
When you change value, react-select give you object as you provided array of object to options.
You should print value in Footer component as,
<h1> {this.props.singlevalue && this.props.singlevalue.label}</h1>
Demo
It’s because onChange event doesn’t send the value directly but an event.
handleChange = event => {
const { value } = event.target
this.setState({ selectedOption: value })
}
What I want to do is to be able to toggle an active class on my elements that are dynamically created, as to be able to change the css for the selected checkbox, giving the impression that a certain filter is selected. I have looked at so many solutions and guides to make this work for my app, but I can't seem to implement it correctly. Any help would be appreciated.
Checkboxes component
import React from 'react';
const Checkbox = (props) => {
const { label, subKey } = props;
const sub1 = `${subKey}1`;
return (
<label htmlFor={sub1} className="check_label">
{label}
<input
type="checkbox"
id={sub1}
checked={props.isChecked}
onChange={props.handleCheck}
onClick={() => console.log(label)}
value={`${label.toLowerCase()}/?search=`}
/>
</label>
);
};
export default Checkbox;
and the Search component that implements checkboxes
import React, { Component } from 'react';
import Checkbox from './Checkbox';
const APIQuery = 'https://swapi.co/api/';
const searchLabels = ['Planets', 'Starships', 'People', 'Species', 'Films', 'Vehicles'];
export default class Searchbutton extends Component {
constructor(props) {
super(props);
this.state = {
endpointValue: '',
searchValue: '',
};
}
/* Funcionality to handle form and state of form */
/* Changes state of value whenever the form is changed, in realtime. */
handleChange(event) {
this.setState({ searchValue: event.target.value });
}
/* Prevents default formsubmit */
handleSubmit(event) {
event.preventDefault();
}
/* Handles state of checkboxes and sets state as to prepend necessary filter for request */
handleCheck(event) {
this.setState({ endpointValue: event.target.value });
if (this.state.endpointValue === event.target.value) {
this.setState({ endpointValue: '' });
}
}
/* Creates the checkboxes dynamically from the list of labels. */
createBoxes() {
const checkboxArray = [];
searchLabels.map(item => checkboxArray.push(
<Checkbox
key={item}
className="madeBoxes"
subKey={item}
endpointValue={this.state.endpointValue}
handleChange={e => this.handleChange(e)}
handleCheck={e => this.handleCheck(e)}
label={item}
/>,
));
return checkboxArray;
}
render() {
return (
<div className="search_content">
<div className="search_wrapper">
<form onSubmit={this.handleSubmit} method="#">
<label htmlFor="searchBar">
<input type="text" id="searchbar" className="search_bar" value={this.state.searchValue} onChange={e => this.handleChange(e)} />
</label>
<div>
<input type="submit" className="search_button" value="May the Force be with you." onClick={() => this.props.searchWithApi(APIQuery + this.state.endpointValue + this.state.searchValue)} />
</div>
</form>
</div>
<div className="checkboxes">
{this.createBoxes(this.labels)}
</div>
<div className="sort_filters">
{' '}
{/* These are options that the user can make in order to sort and filter the results.
The idea is to make it so that changing the value auto-perform a new request */}
{/* For sorting the returned objects based on user choice */}
{/* eslint-disable-next-line jsx-a11y/anchor-is-valid, until href added */}
Choose sort method
<ul className="sorting">
<li className="sort_optn" href="#" value="lexicographical">Alphabetically</li>
<li className="sort_optn" href="#" value="by_added_date">By added date</li>
<li className="sort_optn" href="#" value="by_added_date_rev">By added date reversed</li>
</ul>
</div>
</div>
);
}
}
You don't really have to do it with react. You can reformat your code a little bit and solve it with CSS :checked pseudo-class.
In particular, don't wrap your checkbox within a label, but instead put the label after the input. Check this fiddle for example: https://jsfiddle.net/8c7a0fx5/
You can use the styled-component package. check the example below on how to use it:
import { Component } from 'react'
import { render } from 'react-dom'
import styled from 'styled-components'
const StyledCheckbox = styled.div`
label {
background: ${props => props.active ? 'red': 'white'}
}
`
class MyAwesomeComponent extends Component {
constructor(){
super()
this.state = {
isChecked: false
}
this.handleOnChange = this.handleOnChange.bind(this)
}
handleOnChange = ()=>{
this.setState({
isChecked: !this.state.isChecked,
})
}
render(){
const { isChecked } = this.state
return(
<StyledCheckbox active={isChecked}>
<label>Names</label>
<input type="checkbox" onChange={this.handleOnChange} />
</StyledCheckbox>
)
}
}
render(<MyAwesomeComponent/>, document.getElementById('root'))
Working code on codepen.io
I have a wizard that has many forms, at the end of the wizard I want to take them back to the first step. However every form is filled in with the previous values.
I just want to unmount and remount it to wipe everything clean. How do I do this in reactjs?
<StepWizard>
<Step>
<NewComponent/>
</Step>
<Step>
<NewComponent/>
</Step>
<Step>
<NewComponent/>
</Step>
<Step>
<NewComponent/>
</Step>
</StepWizard>
so how would I trigger something to just get "StepWizard" to render in a fresh state?
My components look something like this, I removed code that switches to the next step in the wizard.
export default class NewComponent extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
}
render() {
return (
<Formik
initialValues={{
name: "",
website: "",
}}
validationSchema={Yup.object().shape({
name: Yup.string().required('Company Name is Required'),
website: Yup.string().url('Company Url is Invalid'),
})}
onSubmit={(
values,
{ setSubmitting, setErrors}
) => {
}}
render={({
values,
handleChange,
handleBlur,
handleSubmit,
setFieldValue,
setFieldTouched
}) => (
<form onSubmit={handleSubmit}>
<div className="field">
<label className="label">Name</label>
<div className="control">
<input
className="input"
type="text"
name="name"
maxLength="50"
onChange={handleChange}
onBlur={handleBlur}
value={values.name}
/>
<ErrorMessage name="name"/>
</div>
</div>
<div className="field">
<label className="label">Website</label>
<div className="control">
<Field className="input" name="website" type="text" />
<ErrorMessage name="website"/>
</div>
</div>
</form>
)}
/>
);
}
}
I am using Mbox State Tree, so I could store something in my store that could be used to trigger whatever needs to be triggered to cause a reset.
Edit
I should mention that I am using this plugin: https://github.com/jcmcneal/react-step-wizard
So I am not sure if stopping a step from rendering is an option, also then that would mean I would have to handle the previous step state everytime.
I am more looking for something that just blows away everything if possible as I spent already too much time on this area and don't want to rework tons.
Highlighting the above methods you can also do something like this. Lift the default state into an object that can be pre-filled by whatever, hydrate it into the state and then when you call a reset you can control how much you reset the state back to. This is a very generic example but it's one way to overcome your issue.
Click here to view a working example
import React from "react";
import ReactDOM from "react-dom";
// generic stage renderer
const Stage = ({ step, currentStep, children }) => {
return step === currentStep ? <div>{children}</div> : null;
};
// generic input controller
const Input = ({ stateKey, value, handleOnChange }) => (
<input
value={value}
onChange={evt => handleOnChange(stateKey, evt.target.value)}
/>
);
// default state that is used to reference
const defaultState = {
firstName: '',
lastName: '',
// default state can also be prefilled with data..
telephoneNumber: '0123456789',
}
class App extends React.Component {
state = {
step: 1,
...defaultState
};
handleStateUpdate = (key, value) => {
this.setState({
[key]: value
});
};
incrementStep = () => {
if (this.state.step < 3) {
this.setState({
step: this.state.step + 1
});
}
};
goBack = () => {
const { step, lastName, telephoneNumber } = this.state;
this.setState({
step: 1,
// always reset this one
firstName: defaultState.firstName,
// only reset this one if it's step 3
lastName: step > 2
? defaultState.lastName
: lastName,
// last step blargh, let's reset anyway
telephoneNumber: step === 3
? defaultState.telephoneNumber
: telephoneNumber,
});
}
render() {
const { step, firstName, lastName, telephoneNumber } = this.state;
return (
<div>
{JSON.stringify(this.state)}
<h1>Step Wizard - {step}</h1>
<Stage step={1} currentStep={step}>
<Input
stateKey="firstName"
value={firstName}
handleOnChange={this.handleStateUpdate}
/>
</Stage>
<Stage step={2} currentStep={step}>
<Input
stateKey="lastName"
value={lastName}
handleOnChange={this.handleStateUpdate}
/>
</Stage>
<Stage step={3} currentStep={step}>
<Input
stateKey="telephoneNumber"
value={telephoneNumber}
handleOnChange={this.handleStateUpdate}
/>
</Stage>
<button onClick={this.goBack}>Go Back to Step 1</button>
<button onClick={this.incrementStep}>Next</button>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
You can achieve that by storing the current state of the wizard in, you guessed it, state object. That state and actions to mutate it can be passed to children as props. After that, when you need to reset the wizard, you just reset the state.
Here's an oversimplified example:
class StepWizard extends React.Component {
constructor(props) {
super(props);
this.state = {
step1: {},
step2: {}
};
}
setStep(step, data) {
this.setState({ `step${ step }`: data });
}
resetWizard() {
this.setState({
step1: {},
step2: {}
});
}
render() {
return (
<React.Fragment>
<Step
data={ this.state.step1 }
setData={ (data)=> this.setStep(1, data) }
/>
<Step
data={ this.state.step2 }
setData={ (data)=> this.setStep(2, data) }
/>
</React.Fragment>
);
}
}
Now, call resetWizard whenever you'll need to reset the wizard.
How about creating a Step object that would have the render logic for each step? I understand your use case correctly, since you would want to render only one step at a time why not only render which is relevant at that particular step?
Something like below.
class Wizard {
constructor(props) {
super(props);
this.stepMap = {
first: <FirstStep />,
second: <SecondtStep />,
third: <ThirdStep />,
fourth: <FourthStep />
}
this.state = {
activeStep: "first"
}
}
changeStep = (stepId) => {
this.setState({activeStep: stepId});
}
render() {
const activeStepCmp = this.stepMap[this.state.activeStep];
return (
<StepWizard>
{activeStepCmp}
</StepWizard>
)
}
}
I am building a basic react app combined with the Pokeapi. Whenever the user types something in the input field of my pokedex, I want to update the state to then (onSubmit) find this pokemon in the Pokeapi.
Whenever I log the state (in the state update function), it logs the state -1 character as typed in the input field.
Printscreen of result
Snippet of component:
import React, { Component } from 'react';
export default class Pokedex extends Component {
constructor(props) {
super(props);
this.state = {
pokemon: "",
result: {}
}
}
setPokemon(value) {
this.setState({
...this.state.pokemon,
pokemon: value.toLowerCase()
});
console.log(this.state.pokemon);
}
render() {
return (
<div className="container-fluid">
<div className="pokedex row">
<div className="col-half left-side">
<div className="screen"/>
<div className="blue-button"/>
<div className="green-button"/>
<div className="orange-button"/>
</div>
<div className="col-half right-side">
<input type="text" placeholder="Find a pokemon" onChange={(e) => this.setPokemon(e.target.value)}/>
</div>
</div>
</div>
)
}
}
Why does this happen?
setState is an async function. That means using console.log immediately after setState will print the last state value. If you want to see the latest updated value then pass a callback to setState function like this
setPokemon(value) {
this.setState({pokemon: value.toLowerCase()},
() => console.log(this.state.pokemon));
}
This first way you can directly set the state of pokemon inside of the input.
<input type="text" placeholder="Find a pokemon" onChange={(e) => this.setState({ pokemon:e.target.value }) }/>
remove the function set pokemon.
setPokemon(value) {
this.setState({
...this.state.pokemon,
pokemon: value.toLowerCase()
});
console.log(this.state.pokemon);
}
theres no reason to use the spread operator, all you would simply do if you did want to use a setter is,
setPokemon = (value) => {
this.setState({ pokemon:value })
}
but even then the first way is better.
Theres also
setPokemon = (e) => {
this.setState({ pokemon:e.target.value })
}
then in input <input onChange={this.setPokemon()} />