How to dispatch action in event handler in React & Redux application - reactjs

My code is here: https://stackblitz.com/edit/react-8g3p3l
There are 2 dumb components:
insertion_citylist.jsx with the following code:
insertion_shoplist.jsx with the following code:
I expect the change on the selected city from the inseriton_citylist.jsx will trigger change on the shop list in insertion_shoplist.jsx. So my container component, app.js contains a function findShops, where the action shoplist(city) is called.
My container component code, app.jsx, is the following:
import React, { Component } from "react";
import { connect } from "react-redux";
import { citylist, shoplist } from "../actions";
import { bindActionCreators } from "redux";
import CityList from "../components/insertion_citylist";
import ShopList from "../components/insertion_shoplist";
class App extends Component {
componentDidMount() {
console.log("mount");
this.props.citylist();
this.props.shoplist();
}
findShops(city) {
console.log("findShops:", city);
this.props.shoplist(city);
}
/* renderShops = shops =>
shops
? shops.map(shop => <option key={shop.id} value={shop.name} />)
: null; */
render() {
console.log("render:" + this.props);
return (
<div>
<CityList data={this.props.data} findShops={this.findShops.bind(this)} />
<ShopList {...this.props} />
{/* <input list="shop" placeholder="shop" />
<datalist id="shop">
{this.renderShops(this.props.shop_data.shops)}
</datalist> */}
</div>
);
}
}
const mapStateToProps = state => {
console.log("map state to props:" + state.shops);
return { data: state.insertion_data }; //Use of spread operator: https://www.youtube.com/watch?v=NCwa_xi0Uuc&t=1721s
//return { city_data: state.cities, shop_data: state.shops };
};
const mapDispathToProps = dispatch => {
console.log("map dispatch to props");
return bindActionCreators({ citylist, shoplist }, dispatch);
};
export default connect(
mapStateToProps,
mapDispathToProps
)(App);
The `findShops` can be called successfully when the city from the downdown list is changed, but seems that the `this.props.shoplist()` just called the action without calling the *reducer*. And the actions code from `actions/index.js` looks like this:
export function citylist() {
return { type: "CITY_LIST", payload: [{city:"Berlin"}, {city: "Frankfurt"}] };
}
export function shoplist(city) {
let data = [{name:"original"},{name:"original 2"}];
if (city === 'Frankfurt') data=[{name:"new"},{name:"new 2"}];
return {
type: "SHOP_LIST",
payload: data
};
}
Problem: The current code is able to trigger the event handler findShops(), but does not succeed in changing the city list state and thus the city list on the insertion_citylist.jsx just keeps unchanged all the while. Could anyone help on this?
Thanks in advance!

it turns out very simple issue
onChange={(e) => props.findShops(e)}
here you are passing e as paramenter
findShops(city) {
this.props.shoplist(city);
}
and using it directly, this is invalid as you will have event in your city parameter.
you need e.target.value
change
<select name="city"
onChange={(e) => props.findShops(e)}>{renderCities(props.data.cities)}</select>
to
<select name="city"
onChange={(e) => props.findShops(e.target.value)}>{renderCities(props.data.cities)}</select>
Demo,
remember react pass an event when you change the value, you need to extract the value when you are reading from it. like you are doing in javascript or jquery.
for checkboxes its e.target.checked and for inputs its e.target.value.

Related

Using react-hook-form in React with Typescript in child component

I am using a basic example of the react-hook-form library and even after looking up the documentary, I do not know how to pass the data from the form to another component. Here is my form component:
import { useForm, SubmitHandler } from "react-hook-form";
type FormInputs = {
minimalFrequency: number;
maximialFrequency: number;
};
// const onSubmit: SubmitHandler<FormInputs> = data => console.log(data);
export default function BasicUsage() {
const { register, formState: { errors }, handleSubmit, getValues } = useForm<FormInputs>({
defaultValues: {
min: 250,
max: 8000,
}
});
const onSubmit = (data: any) => {
console.log(data);
}
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register("minimalFrequency", { required: true })} />
{errors.minimalFrequency && "Only numbers are allowed"}
<input {...register("maximialFrequency", { required: true })} />
{errors.maximialFrequency && "Only numbers are allowed"}
<input type="submit" />
</form>
);
}
I would want to get the min and max values, in form of the given data object, of the user after they pushed the "submit" button and I just can't get my head around how it works.
My main component is a quite large class component, and I read that it might not work because react-hook-form needs a functional component. If true, is there a way to still use my class component somehow?
UPDATE: Added the parent component
import { useState } from "react";
import React from "react";
import BasicUsage from "./BasicUsage"
type Props = {
}
type State = {
dataFreq: object;
}
export default class Parent extends React.Component<Props, State>{
private timer: any;
constructor(props: Props) {
super(props);
this.state = {
dataFreq: {
minimalFrequency: 250,
maximialFrequency: 8000
}
};
}
getDataFromForm = (dataFreq: any) => {
this.setState({dataFreq: dataFreq })
console.log(dataFreq)
};
render() {
const minFreq = this.state.dataFreq;
console.log("This is a this dataFreq", this.state.dataFreq);
console.log("This is a this minimalFrequency", minFreq);
return (
<div>
<BasicUsage getDataFromForm={this.getDataFromForm}/>
</div>
);
}
}
You are still able to use your class component as a parent.
If I am I correct in assuming that you want to use data from the form in your main component, and the main component is the parent, you can define a function in your main component, something like
getDataFromForm(data){
this.setState({data: data })
}
Then you pass this function into your BasicUsage component
//In your main components render function, or wherever you are using the BasicUsage component
<BasicUsage
//other props you want to send into BasicUsage from the main component
getDataFromForm={getDataFromForm}
/>
Now in your BasicUsage component's onSubmit function you can call the function you passed as a prop as such
const onSubmit = (data: any) => {
//Do something with your data if you want to change format or process it somehow;
//in this case you should probably make a new variable and pass the new variable into getDataFromForm
props.getDataFromForm(data) //Call the function in the parent component
}
If you're using the form data in a sibling component and not a parent component, you would make the getDataFromForm function in a common parent and pass the function to the BasicUsage component and the state.data value into the sibling component where you want to access the data

Cannot pass data between components in React/Redux

I'm new to React and am trying to build an app which shuffles football players into two teams and am having difficulty with passing data from one component to another. I have redux and react-redux installed.
My problem is that once 10 names have been inputted (which I am calling numbersReached), the button to submit the addPlayers form should be disabled so no more players can be submitted.
I have tried passing a state value, numbersReached, into my AddPlayers component, but it is not being imported correctly - it is showing in console.log as undefined.
My code so far:
'initialState.js'
export const initialState = {
playersList: [],
shuffledList: [],
teamA: [],
teamB: [],
numbersReached: false
};
export default initialState;
'src\components\NumbersReached\index.js':
import { connect } from "react-redux";
import NumbersReached from "./NumbersReached";
const mapStateToProps = (state) => {
return {
...state,
numbersReached: state.playersList.length >= 10 // Once state.playersList.length>=10 numbersReached should = true. (10 is for five-a-side)
};
};
export default connect(mapStateToProps)(NumbersReached);
src\components\NumbersReached\NumbersReached.js:
import React from "react";
const NumbersReached = ({ numbersReached }) => (
<div>
{numbersReached ? "Numbers Reached" : null}
</div>
);
export default NumbersReached;
'src\components\AddPlayer\index.js':
import { connect } from "react-redux";
import AddPlayer from "./AddPlayer";
import { addPlayer } from "../../data/actions";
const mapStateToProps = (state) => {
return {
playerName: state.playerName,
numbersReached: state.numbersReached
};
};
const mapDispatchToProps = (dispatch) => {
return {
handleSubmit: (data) => dispatch(addPlayer(data)),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(AddPlayer);
'src\components\AddPlayer\AddPlayer.js'
import React, { Component } from 'react';
class AddPlayer extends Component {
constructor(props) {
super(props);
this.state = {
playerName: props.playerName,
numbersReached: props.numbersReached
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
};
handleChange(e) {
this.setState({ playerName: e.currentTarget.value });
}
handleSubmit(e) {
e.preventDefault();
this.props.handleSubmit({ ...this.state });
}
render() {
return (
<React.Fragment>
<form className="entry-form" onSubmit={this.handleSubmit}>
<input
placeholder="Enter a player's name"
className="player-input"
type="text"
onChange={this.handleChange}
/>
<button
type="submit"
className="player-submit"
disabled={this.state.numbersReached} // Does not disable the button as this.state.numbersReached is undefined
>
Add a player
</button>
</form>
</React.Fragment>
)
}
};
export default AddPlayer;
Help is very much appreciated!
The problem you're likely having is that you're not actually using the value of numbersReached in your Redux store. Instead, you're using a numbersReached variable inner to your AddPlayer component' state.
<button
type="submit"
className="player-submit"
disabled={this.state.numbersReached}
>
To use the numbersReached you defined in mapStateToProps, you should access to this.props.numbersReached instead of this.state.numbersReached. The props you inherit from Redux will automatically be updated when the action addPlayer is dispatched and the Redux store changes.
The value for your this.state.numbersReached, however, won't change unless you call setState anywhere in your component. Right now, you're only initializing that value on the constructor:
class AddPlayer extends Component {
constructor(props) {
super(props);
this.state = {
playerName: props.playerName,
numbersReached: props.numbersReached
};
};
}
When you do this, you're creating a new numbersReached variable in your component' state with the value that the numbersReached property in your Redux store had at the time your component is built. As it doesn't have value when the constructor runs, this.state.numbersReached is undefined.
Doing numbersReached: props.numbersReached does not suscribe you to all of the changes to that property; it just assigns numbersReached the value props.numbersReached initially had. That's why, no matter how many players you add, it'll keep been undefined even if players have been succesfully added to your store.
you are duplicating redux state at AddPlayer state. duplicate state is bad practice and should be avoided. secondly, they are different states, redux'state and react component's state. as it is this.state.numbersReached receives the first value on mounting but is not not updated anywehere after.
Also it is weird at your state playerName: props.playerName. first, the fact that you dont have at your redux a piece of state called playerName. Second, the most revelant, it's a component that create players and adds to your playersList. you should better initialize that state as an empty string.
btw, I refactored a little your component to reduce code. if you declare your functions as arrow functions you dont need to bind them at constructor. and you dont need to declare your state at constructor anymore. But if you want to keep the original for consistency, or personal preference go ahead, that's ok also :) .
after all that, your component would look something like:
import React, { Component } from 'react';
class AddPlayer extends Component {
state = {
playerName: ''
};
handleChange = (e) => {
this.setState({ playerName: e.currentTarget.value });
}
handleSubmit = (e) => {
e.preventDefault();
this.props.handleSubmit({ ...this.state });
}
render() {
return (
<React.Fragment>
<form className="entry-form" onSubmit={this.handleSubmit}>
<input
placeholder="Enter a player's name"
className="player-input"
type="text"
onChange={this.handleChange}
/>
<button
type="submit"
className="player-submit"
disabled={this.props.numbersReached}
>
Add a player
</button>
</form>
</React.Fragment>
)
}
};
export default AddPlayer;
a last note, you have numbersReached state, but at your mapStateToProps you set based on a custom function, not on your numbersReached state.
if you want to keep numbersReached state on your redux state, you should handle that logic state.playersList.length >= 10 at your reducers to update properly there, and not at your mapStateToProps. though you could remove from your redux state numbersReached altogether given it's derived from other pieces from your state.

mobx UseObserver not re-rendering on store update

I am trying to create a simple react app Using MobX, TypeScript and React Hooks, where I will type a name of a city, and on clicking an add button, it will update the list of the cities in the UI with the newly added city.
CodeSandbox demo here
The problem is, the list is not getting updated when i click the add button with a new name of a city.
Please help
There are two problems with your code:
Don't use useLocalStore (context.tsx) for creating the store, create the store by directly calling the store function, and wrap the return value of createStore function (store.ts) in an observable
// context.tsx
// const store = useLocalStore(createStore);
const store = createStore()
// store.ts
import { observable } from "mobx";
export const createStore = () => {
const store = observable({
Cities: ["Gotham"],
get allCities(): Array<string> {
return store.Cities;
},
get cityCount(): Number {
return store.Cities.length;
},
addCity: (city: string) => {
console.log("store: adding city: " + city);
store.Cities.push(city);
console.log(store.Cities);
}
});
return store;
};
export type TStore = ReturnType<typeof createStore>;
The return value of your CityView component needs to be wrapped in the useObserver function from mobx-react-lite
// city.tsx
import { useObserver } from "mobx-react-lite";
export const CityView: React.FC<{ cities: string[] }> = ({ cities }) => {
return useObserver(() => {
return (
<ul>
{cities.map((city: string) => (
<li key={city}>{city}</li>
))}
</ul>
);
});
};
code sandbox

How the value in checkbox tag can be pushed to the array of current component's state

I am using the checkbox to determine the value and trying to push the value to the current component's state, but there is only one value can be pushed into the array in the state, I can't figure out exactly what caused this.
I am building a search bar and a result list. When the user input some data in the search bar, then the result list will display the result that related to the input data, then the user can select the multiple items to the state, then I can pass the data in the state array to the backend database.
I have done the search bar part, but when I trying to map the value in the checkbox to state array, the state array always displays a value when I selected multiple items.
this is CustomerSMS component:
import React, { Component } from "react";
import { connect } from "react-redux";
import { firestoreConnect } from 'react-redux-firebase'
import { compose } from 'redux'
import FilteredCustomerList from './FilteredCustomerList'
class CustomerSMS extends Component {
state ={
search:null
}
handleChange = (e) => {
this.setState({
...this.state,
search:e.target.value
})
}
render() {
const { customers } = this.props;
return(
<div className="customerSMS container">
<p>search bar here</p>
<input type="text" id="search-string" onChange=
{this.handleChange}></input>
<FilteredCustomerList customers={customers} search={this.state.search}/>
<p>send message box here</p>
</div>
)
}
}
const mapStateToProps = (state) => {
// console.log(state);
return{
customers:state.firestore.ordered.customers
}
}
export default compose(
connect(mapStateToProps),
firestoreConnect([
{collection:'customers'}
])
)(CustomerSMS)
this is the FilteredCustomerList component:
import React from 'react'
import CustomerSummary from './CustomerSummary';
const FilteredCustomerList = ({customers, search}) => {
// console.log(search);
return (
<div className="customer-list">
<ul>
{ customers && customers.map( customer => {
return (
<CustomerSummary customer={customer} key={customer.id} search={search}/>
)
}
)}
</ul>
</div>
)
}
export default FilteredCustomerList
This is the component included the checkbox:
import React, { Component } from 'react'
class CustomerSummary extends Component {
state = {
phonenumberList:[]
}
handleChange = (e) => {
let phonenumberList = this.state.phonenumberList;
console.log(e.target.checked);
console.log(e.target.value);
let index
if(e.target.checked){
phonenumberList.push(e.target.value)
}else{
index = phonenumberList.indexOf(e.target.value)
phonenumberList.splice(index, 1)
}
console.log(phonenumberList);
}
render(){
// console.log(this.props);
console.log(this.state);
if(this.props.customer.location === this.props.search){
return (
<div>
<li>
<input
type="checkbox"
name="phonenumber"
value={this.props.customer.phonenumber}
onChange={this.handleChange}
/>
{this.props.customer.fullname} {this.props.customer.location}
</li>
</div>
)
}else{
return null
}
}
}
export default CustomerSummary
I expect phonenumberlist in the state should be [number1, number2, number3...] when I selected number1 & number2 & number3 ...., but the actual output is [number1] when I selected number1, and the output is [number2] when I selected number1 & number2, or the output is [number3] when I selected number1 & number2 $ number3, it always pushes the last one I selected value, I am stuck here for a long time, if anyone can help me, I will be very grateful.
You don't have to do this. Remove the ...this.state part
handleChange = (e) => {
this.setState({
...this.state,
search:e.target.value
})
}
When pushing to a array you use the spread operator as such:
this.setState(prevState => ({
myArr: [ ...prevState.myArr, newElement]
}))
If you want to update phonenumberList in your state, you have to call this.setState({phonenumberList: yourValue}) minimally.
What you are doing now when a checkbox is clicked is:
let phonenumberList = this.state.phonenumberList;
that is assigning the current value of this.state.phonenumberList (that is []) to a newly created phonenumberList.
Then you add the checked value, and console.log phonenumberList.
Your state get never updated, so at each click you start again with the empty array.

Can't get attributes of material-ui SelectField in react

I'm using SelectField of material-ui for my react project.
I have tried many ways from this answer Can't get the target attributes of material-ui select react component
.
But they don't work.My target.id always equals ""
How can I get the attributes (like id).
Here is my code:
constructor(props) {
super(props);
this.state = {
form: {
resident_city: ''
},
ret_code: '',
ret_msg: ''
};
this.handleList = this.handleList.bind(this);
}
handleList(event, index, value) {
event.persist()
const field = event.target.id;
const form = this.state.form;
form[field] = value;
console.log(event, value)
this.setState({
form
});
console.log(form);
}
<form>
<SelectField
style={style}
id="city"
value={this.state.form.resident_city}
onChange={this.handleList}
maxHeight={200}
>
{cities}
</SelectField>
</form>
Update
I tried to use SelectField without form,and I still can't get the id attributes.It is really confusing me.
On the main component you define a prop name for select the form component let say your city component is called : cityForm
in your cityForm component
render() {
return (
<SelectField
style={style}
value={this.props.city}
onChange={(e, index, value) => this.props.selectChange(e, index, value, this.props.cityName)}
maxHeight={200}
>
{cities}
</SelectField>
);
}
}
In your main comp you will have let say (code is cutted some part omitted)
handleSelectChange(e, index, value, name){
this.setState({
[name] : value,
});
}
render(){
return (
<cityForm cityName = "city1" city={this.state.city1} selectChange={this.handleSelectChange}/>
);
}
}
Im building a dynamic form generator and it did the trick for me =).
If a React class component is used, selected value can be accessed through its state object.
Alternatively, with help of Redux the Select's onChange method can dispatch an action and update the value in the main application state.
In cases when updating the main state isn't feasible and function component is chosen instead of a class component, getting the value from the component outside the function becomes cumbersome.
An easy way to fix it would be to add a hidden input referencing the same value as select uses. Consider the following piece of code. It uses Selector field1 to update value through internal component state and binds it to the hidden field theField. The value can be further read an outside function during dispatch just like any other input field value in the form.
import React, { useState } from 'react';
import { Button, FormControl, MenuItem, Select } from '#material-ui/core';
import { connect } from 'react-redux';
const mapStateToProps = () => ({
});
const mapDispatchToProps = (dispatch, ownProps) => ({
submitForm: (event) => {
event.preventDefault();
console.log(event.target.theField.value);
dispatch({
type: 'UPDATE_THE_FIELD',
theField: event.target.theField.value,
});
}
});
function Main(props) {
const [field1, setField1] = useState("v1");
return (
<>
<form onSubmit={(event) => { props.submitForm(event, props) }}>
<FormControl>
<Select
value={field1}
onChange={(event) => setField1(event.target.value)}
>
<MenuItem value="v1">V1</MenuItem>
<MenuItem value="v2">V2</MenuItem>
</Select>
</FormControl>
<Button type="submit">Update</Button>
<input type="hidden" name="theField" value={field1} />
</form>
</>
);
}
export default connect(mapStateToProps, mapDispatchToProps)(Main);

Resources