React, render & console.log() displays different values when onInput event - javascript

When i write in the input element, it triggers onInputChange() function, that updates inputValue state, then calls the getValue() that gets the inputValue state and log in console. The value that is rendered is not the same that is in console, what's happening here?
Reproducible example: https://stackblitz.com/edit/react-i4wprk?file=src%2FApp.js
import React from 'react';
import './style.css';
export default class App extends React.Component {
constructor() {
super();
this.state = {
inputValue: '',
};
}
getValue = () => {
const { inputValue } = this.state;
console.log(inputValue);
};
onInputChange = (event) => {
const inputValue = event.currentTarget.value;
this.setState({ inputValue });
this.getValue();
};
render() {
const { inputValue } = this.state;
return (
<div>
<input placeholder="texto" onInput={this.onInputChange} />
<p>{inputValue}</p>
</div>
);
}
}

setState is not a synchronous call so there is no guarantee that your console log will fire after the value has been updated in state. You can add a callback to setState
this.setState({inputValue}, () => {this.getValue()}

Related

How to set an event listener for search input change using react-select?

I have a select drop-down that has roughly 17k options, so rendering them outright isn't an option. Instead I will wait for a user to begin searching in the drop-down search bar, and once the data is reduced to a renderable size, the drop-down will show.
I can't seem to find a way to set an event handler for the search bar using react-select.
Here is the MRE:
import Select from 'react-select';
class CategoryDropdown extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [],
selected: '',
}
}
async componentDidMount() {
const response = await fetch('some endpoint');
let data = await response.json();
this.setState({
data,
});
}
handleChange(event) {
console.log(event);
}
render() {
return (
<Select
name="category_id",
options={this.state.data}
value={this.state.selected}
onChange={this.handleChange}
onInputChange={this.handleChange}
/>
);
}
}
The handleChange listener triggers when an option is selected, but not when text is entered into the search bar.
What am I missing?
You need to use AsyncSelect instead of Select then use the input value and callback to fetch the data that match typed input :
import React, { Component } from 'react';
import AsyncSelect from 'react-select/async';
const loadOptions =async (inputValue, callback) => {
const response = await fetch('some endpoint');//use that input value in the endpoint
let data = await response.json();
callback(data)
};
export default class WithCallbacks extends Component {
state = { inputValue: '' };
handleInputChange = (newValue) => {
const inputValue = newValue.replace(/\W/g, '');
this.setState({ inputValue });
return inputValue;
};
render() {
return (
<div>
<AsyncSelect
cacheOptions
loadOptions={loadOptions}
defaultOptions
onInputChange={this.handleInputChange}
/>
</div>
);
}
}
Please check this example

Props are showing empty value above `render` function while it work inside `render` function?

So I am passing props from app.js to state.jsx but while console logging the props it give me a undefined but it perfectly works inside the render function. Why that's happening?
App.js
class App extends React.Component{
state = {
data : {},
states: '',
}
async componentDidMount () {
const dataFromApi = await StateData();
this.setState({ data: dataFromApi})
}
handleStateChange = async(states) => {
const fetchedData = await StateData(states);
this.setState({data: fetchedData, states: states})
}
render(){
const {data, states} = this.state;
console.log(data);
return (
<div className="App">
<StateCard data={data} states={states}/>
</div>
);
}
}
State.jsx
export default class StateCard extends React.Component{
constructor(props) {
//this doesn't work
super(props)
const dta = this.props.data.confirmed
console.log(dta)
}
// spacing deafult value is 8px , so the 3*8=24px width column
render(){
//This works
console.log(this.props.data.confirmed)
console.log(this.props.states)
const {confirmed,active, deaths, recovered} = this.props.data
return (
<div>
<span>Confirmed : {confirmed}</span>
</div>);}
}
you can use useEffect and Function component instead of React.Component.
it's more easier then using componentdidMount and other lifecycle methods.
import React, { useEffect } from "react";
const StateCard = props => {
const { data, states } = props;
const { confirmed, active, deaths, recovered } = data;
useEffect(() => {
console.log(confirmed);
console.log(states);
}, [props]);
return (
<div>
<span>Confirmed : {confirmed}</span>
</div>
);
};
export default StateCard;

What should be tested when calling onClick() with a redux action creator?

I'm trying to test a simple checkbox input component that fires an action in it's onChange method to save the value of the checkbox (True or False). The component is below:
import React, {Component} from 'react';
import uuid from 'uuid/v1';
import './styles.css';
import { connect } from 'react-redux';
import { saveCheckboxInput } from '../../actions/userInputActions';
class CheckboxSingle extends Component {
constructor () {
super();
this.onChange = this.onChange.bind(this);
this.state = {
id : uuid(), // generate a unique id
}
}
onChange(event) {
const target = event.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
this.props.saveCheckboxInput(this.props.linkId, value, this.props.desc, this.props.relatedLinkIds, this.props.stepNumber);
}
render(){
return(
<div className="col-sm-12 no-padding-left">
<label className="checkbox-container label-text">{this.props.desc}
<input id={this.state.id} type="checkbox" name="checkBoxValue" checked={this.props.isChecked}
onChange={(e) => this.onChange(e)}/>
<span className="checkmark"></span>
</label>
</div>
)
}
}
function mapStateToProps(state, ownProps) {
// Tie checkBoxValue to store answer
// Get answers in the context of checkbox (determines if checked or not)
var stepAnswers = state.userInputState.stepResponses[ownProps.stepNumber];
var isCheckedValue = null;
// Note: only functional w/ one checkbox input in flow
// TODO: make functional for multiple checkbox inputs in flow
for(var i=0; i < stepAnswers.length; i++) {
if(stepAnswers[i].type === "questionnaire-checkbox-input") {
isCheckedValue = stepAnswers[i].value;
}
}
return {
isChecked : isCheckedValue
};
}
export default connect(
mapStateToProps,
{ saveCheckboxInput },
)(CheckboxSingle);
With the test to simulate the onChange() function below:
describe('CheckboxSingle', () => {
const initialState = {
userInputState: {
stepResponses: [
{},
{
type: "questionnaire-checkbox-input",
name: "mockLinkId",
value: false,
prefixText: "mockDesc",
relatedLinkIds: ["mock1", "mock2"]
}
]
}
}
const mockStore = configureStore()
let store, shallowWrapper, dispatch
beforeEach(() => {
store = mockStore(initialState)
dispatch = jest.fn();
shallowWrapper = shallow(<CheckboxSingle store={store} dispatch={dispatch} desc="mockDesc"
linkId="mockLinkId" relatedLinkIds={["mock1", "mock2"]} stepNumber={1} />).dive()
});
// TODO: test action creator firing upon click
test('should call onChange after clicked', () => {
const onChangeFake = jest.spyOn(shallowWrapper.instance(), 'onChange');
shallowWrapper.find('input[type="checkbox"]').simulate('change', { target: { checked: true } });
expect(onChangeFake).toHaveBeenCalledTimes(1);
});
});
What would be the best way to test that this.props.saveCheckboxInput is fired upon a change to the component (similar to the simulated change test)? New to enzyme so any insight would be appreciated!
First of all onChange={(e) => this.onChange(e)} is a bad practise, because it will create a new function for each render of the component, you can simply write onChange={this.onChange}
Then to test the prop saveCheckboxInput has been called, you just need to check that the dispatch function of your store has been called with an argument corresponding to the action created by the original saveCheckboxInput function
import { saveCheckboxInput } from '../../actions/userInputActions';
let store, shallowWrapper;
beforeEach(() => {
store = mockStore(initialState)
store.dispatch = jest.fn();
shallowWrapper = shallow(
<CheckboxSingle
store={store}
desc="mockDesc"
linkId="mockLinkId"
relatedLinkIds={["mock1", "mock2"]}
stepNumber={1}
/>
).dive();
});
test('should call onChange after clicked', () => {
const action = saveCheckboxInput(
"mockLinkId",
true,
"mockDesc",
["mock1", "mock2"],
1
);
shallowWrapper.find('input[type="checkbox"]')
.simulate('change', { target: { checked: true } });
expect(store.dispatch).toHaveBeenCalledWith(action);
});

React losing focus on input field (component added dynamically)

Stack : React16, ES6, Redux
I'm currently unable to figure what's wrong here. The goal here is to add dynamically an infinite number of components (one by one) when clicking on an add button.
I need to make them appear, if possible by pair or more, e.g. if I click on the ADD button, there should be 2 fields appearing each time (one select field and one textfield at the same time, for ex)
I'm able to make the components appear, with the help of Redux, and I'm also able to manage the datas correctly (everything's wired on the back of the app)
THE PROBLEM HERE :
When trying to type text in an input field, it's ALWAYS losing the focus. I've seen that each time I update my props, the whole component named MultipleInputChoiceList is mounted again, and that each fields are re-created anew. That's what I need to fix here :
EDIT : The MultipleInputChoiceList component is mounted via a Conditional Rendering HOC (It takes some values and check if they are true, if they are, it's rendering the component without touching the whole form)
ConditionalRenderingHOC.js
import React from 'react'
import {connect} from 'react-redux'
import _ from 'lodash'
const mapStateToProps = state => {
return {
form: state.form.form
}
}
const mapDispatchToProps = dispatch => {
return {
}
}
/**
* HOC Component to check conditional rendering on form component, using requireField property
* To be enhanced at will
*/
export default (WrappedComponent, formItem = {}) => {
class ConditionalRenderingHOC extends React.Component {
componentWillMount() {
//Check if all informations are available
if (formItem.requireField !== undefined) {
const requireField = formItem.requireField
if (requireField.value !== undefined &&
requireField.name !== undefined &&
requireField.field !== undefined &&
requireField.property !== undefined) {
//If everything's here let's call canBeRendered
this.canBeRendered()
}
}
}
//Check if the count of fetched values is directly linked to the number of fetched config asked, if true, return the same number
canBeRendered() {
formItem.requireField.isRendered = false
let required = formItem.requireField
let isEqual = false
if (this.props.form[required.field] !== undefined) {
let field = this.props.form[required.field]
_.forEach(field.value, (properties, index) => {
if (properties[required.name] !== undefined) {
if (properties[required.name] === required.value) {
if (properties[required.property] === required.isEqualTo) {
formItem.requireField.isRendered = true
isEqual = true
}
}
}
})
}
return isEqual
}
render() {
let isConditionMet = this.canBeRendered()
let render = null
if (isConditionMet === true) {
render = <WrappedComponent items={formItem}/>
}
return (<React.Fragment>
{render}
</React.Fragment>)
}
}
return connect(mapStateToProps, mapDispatchToProps)(ConditionalRenderingHOC)
}
The code
//Essentials
import React, { Component } from 'react'
import _ from 'lodash'
//Material UI
import TextField from 'material-ui/TextField'
import IconButton from 'material-ui/IconButton'
import AddBox from 'material-ui/svg-icons/content/add-box'
//Components
import SelectItemChoiceList from '../form/SelectItemChoiceList'
import TextFieldGeneric from './TextFieldGeneric'
//Redux
import { connect } from 'react-redux'
import { createNewField } from '../../../actions/formActions'
const mapStateToProps = (state) => {
return {
form: state.form.form
}
}
const mapDispatchToProps = (dispatch) => {
return {
createNewField: (field, state) => dispatch(createNewField(field, state))
}
}
class MultipleInputChoiceList extends Component {
constructor(props) {
super(props)
this.state = {
inputList: [],
}
}
onAddBtnClick() {
const name = this.props.items.name
/**Create a new field in Redux store, giving it some datas to display */
this.props.createNewField(this.props.form[name], this.props.form)
}
render() {
const name = this.props.items.name
/**I think the error is around this place, as it always re-render the same thing again and again */
const inputs = this.props.form[name].inputList.map((input, index) => {
switch(input) {
case 'selectfield': {
return React.createElement(SelectItemChoiceList, {
items: this.props.form[name].multipleField[index],
key:this.props.form[name].multipleField[index].name
})
}
case 'textfield': {
return React.createElement(TextFieldGeneric, {
items: this.props.form[name].multipleField[index],
index:index,
key:this.props.form[name].multipleField[index].name
})
}
default: {
break
}
}
})
return (
<div>
<IconButton onClick={this.onAddBtnClick.bind(this)}>
<AddBox />
</IconButton>
{inputs}
</div>
)
}
}
const MultipleInputChoiceListRedux = connect(mapStateToProps, mapDispatchToProps)(MultipleInputChoiceList)
export default MultipleInputChoiceListRedux
And the TextField used here :
TextFieldGeneric.js
//Essentials
import React, { Component } from 'react';
//Components
import TextField from 'material-ui/TextField'
//Redux
import { connect } from 'react-redux'
import { validateField, isValid } from '../../../actions/formActions'
const mapStateToProps = (state) => {
return {
form: state.form.form
}
}
const mapDispatchToProps = (dispatch) => {
return {
validateField: (field) => dispatch(validateField(field)),
isValid: () => dispatch(isValid())
}
}
class TextFieldGeneric extends Component {
constructor(props) {
super(props)
this.state = {
form: {},
field: {},
index: 0
}
}
componentWillMount() {
console.log(this.props)
//first, let's load those dynamic datas before rendering
let form = this.props.form
let index = this.props.index
/** Check if there's a correctly defined parent in form (taken from the name) */
let matchName = /[a-zA-Z]+/g
let origin = this.props.items.name.match(matchName)
//form.company.value = this.getCompaniesFormChoice()
this.setState({form: form, field: form[origin], index: index})
}
//setState and check validationFields if errors
handleFieldChange(event){
const name = event.target.name
const value = event.target.value
//Change value of state form field
const item = this.props.items
item.value = value
//validate each fields
this.props.validateField(item)
//validate form
this.props.isValid()
event.preventDefault()
}
render() {
const index = this.state.index
console.log(index)
return (
<React.Fragment>
<TextField
key={index}
floatingLabelText={this.state.field.multipleField[index].namefield}
name={this.state.field.multipleField[index].namefield}
floatingLabelFixed={true}
value = {this.state.field.multipleField[index].value}
onChange = {this.handleFieldChange.bind(this)}
errorText={this.state.field.multipleField[index].error === 0 ? '' : this.state.field.multipleField[index].error}
/>
</React.Fragment>
)
}
}
const TextFieldGenericRedux = connect(mapStateToProps, mapDispatchToProps)(TextFieldGeneric)
export default TextFieldGenericRedux
I also do understand that a part of the problem lies in the render method of the parent class (MultipleInputChoiceList.js) ...
Any help or comments REALLY appreciated!
As of now, I've not come to a real answer to that question, as it's a structural problem in my data (When updating a field via a Redux action, it's re-rendering the whole component). Maybe stock those data elsewhere would be a better option.
I've only used a onBlur method on the field, that dismiss the validation I want to do on each user input, but for the moment I could not think to another viable solution.
So far, the TextFieldGeneric.js looks like that :
//setState and check validationFields if errors
handleFieldChange(event){
this.setState({value: event.target.value})
event.preventDefault()
}
handleValidation(event){
const value = this.state.value
//Change value of state form field
const item = this.props.items
item.value = value
//validate each fields
this.props.validateField(item)
//validate form
this.props.isValid()
}
render() {
return (
<React.Fragment>
<TextField
name='name'
floatingLabelFixed={true}
value = {this.state.value}
onChange = {this.handleFieldChange.bind(this)}
onBlur = {this.handleValidation.bind(this)}
/>
</React.Fragment>
)
}
If anyone have another solution, I'll gladly hear it !

How to use debounce with redux-form correctly?

I have a search input.
const { searchMails } = this.props;
searchMails(keyword);
I added the lodash's debounce based on this answer on Stack Overflow.
const { searchMails } = this.props;
const debounceSearchMails = debounce(searchMails, 1000);
debounceSearchMails(keyword);
The action
export const searchMails = keyword => ({ type: SEARCH_MAILS, payload: keyword });
However, after adding debounce, when I type "hello", it will still trigger 5 times searchMails after 1 second. The payload are
h
he
hel
hell
hello
How can I use debounce correctly? Thanks
UPDATE 1: add full codes
import React, { PureComponent } from 'react';
import { Field, reduxForm, reset } from 'redux-form';
import { Form } from 'reactstrap';
import debounce from 'lodash/debounce';
class Search extends PureComponent {
constructor(props) {
super(props);
this.onSubmit = this.onSubmit.bind(this);
}
onSubmit(values) {
const { searchMails } = this.props;
const debounceSearchMails = debounce(searchMails, 1000);
debounceSearchMails(values.keyword);
}
render() {
const { handleSubmit, keyword } = this.props;
return (
<Form onSubmit={handleSubmit(this.onSubmit)}>
<Field name="keyword" component="input" type="search" onChange={() => setTimeout(handleSubmit(this.onSubmit))} />
</Form>
);
}
}
function validate(values) {
const errors = {};
return errors;
}
export default reduxForm({
validate,
form: 'searchForm'
})(Search);
UPDATE 2:
I changed my action to
const searchMails0 = keyword => ({ type: SEARCH_MAILS, payload: keyword });
export const searchMails = debounce(searchMails0, 1000);
But still same.
UPDATE 3: this time I changed to this, but still same.
class Search extends PureComponent {
constructor(props) {
super(props);
this.onSubmit = this.onSubmit.bind(this);
this.debouncedSubmit = debounce(this.onSubmit, 1000);
}
onSubmit(values) {
const { searchMails } = this.props;
searchMails(values.keyword);
}
render() {
const { handleSubmit, keyword } = this.props;
return (
<Form onSubmit={handleSubmit(this.debouncedSubmit)}>
<Field name="keyword" component="input" type="search" onChange={() => setTimeout(handleSubmit(this.debouncedSubmit))} />
</Form>
);
}
}
UDPATE 4:
I found the issue is somehow related with setTimeout, if I have that like below, debounce won't work. If I remove setTimeout, debounce will work. But then onChange will always return last value. So I do need have it because of redux-form's this "issue"
<Field component="input" type="search" onChange={() => setTimeout(handleSubmit(debounce(this.onSubmit, 1000)))}/>
First big thank you for #zerkms. Without his guide to the right direction, I cannot make it.
You should only create the debounced function once and then use it. It is debounced function that holds internally the state necessary for debouncing. At the moment you recreate it on every keystroke. – zerkms
After checking onChange's type, this is final working code:
class Search extends PureComponent {
constructor(props) {
super(props);
this.onSubmit = this.onSubmit.bind(this);
this.onChange = this.onChange.bind(this);
this.debouncedOnChange = debounce(this.onChange, 1000);
}
onSubmit(values) {
const { searchMails } = this.props;
searchMails(values.keyword);
}
onChange(event, newValue, previousValue) {
const { searchMails } = this.props;
searchMails(newValue); // the second parameter is new value
}
render() {
const { keyword } = this.props;
return (
<Form onSubmit={handleSubmit(this.onSubmit)}>
<Field component="input" type="search" onChange={this.debouncedOnChange}/>
</Form>
);
}
}
Lessons learned:
I never thought I can do something like this.debouncedSubmit = debounce(this.onSubmit, 1000); in constructor.
And I always thought I have to use handleSubmit from redux-form, but turns out it is not for all cases.
Need go deep.

Categories

Resources