React Redux: отправляю один экшн, улетают все и страница обновляется
может кто-нибудь подсказать, куда рыть? Я отправляю диспатч с одним экшеном (обратный вызов в классе Converter при вводе в input) а у меня отправляется другой диспатч в классе Api ComponentDidMount. Причем в инпуте событие не срабатывает. Перечитал кучу статей, ничего не нашел, перебрал пару готовых решений - тоже не увидел ничего нового. Ниже код: /container/Root.js:
import { Provider } from 'react-redux'
import configureStore from '../configureStore'
import Api from './Api'
import Converter from './Converter'
const store = configureStore()
export default class Root extends Component {
render() {
return (
<Provider store={store}>
<Converter />
<Api />
</Provider>
)
}
}
/container/Api.js:
import React from 'react'
import { connect } from 'react-redux'
import getRequestApi from '../actions'
import CreateTable from '../components/CreateTable'
class Api extends React.Component {
componentDidMount() {
const { dispatch } = this.props
dispatch(getRequestApi())
}
render() {
const { getload } = this.props
if (typeof getload !== 'object') {
return (<div>
{getload}
</div>)
} else {
const date = new Date(Date.parse(getload.Date)).toLocaleString(),
previousDate = new Date(Date.parse(getload.PreviousDate)).toLocaleString(),
timestamp = new Date(Date.parse(getload.Timestamp)).toLocaleString(),
valute = getload.Valute,
arrayList = []
for (const key in valute) {
if (valute.hasOwnProperty(key)) {
arrayList.push(valute[key])
}
}
const list = arrayList.map((prop) => {
return <CreateTable key={prop.ID} name={prop.Name} code={prop.CharCode} value={prop.Value} target = {prop.Nominal} />
})
return (<div>
<h4>Курс на: { date }</h4>
<h4>Предыдущий срез на: { previousDate }</h4>
<h5>Отметка времени: { timestamp }</h5>
<table>
<thead>
<tr>
<td>Код</td><td>Номинал</td><td>Валюта</td><td>Курс (по отношению к рублю)</td>
</tr>
</thead>
<tbody>
{ list }
</tbody>
</table>
</div>)
}
}
}
const mapStateToProps = store => {
return {
getload: store.requestApi.getload
}
}
export default connect(mapStateToProps)(Api)
/container/Converter.js:
import React from 'react'
import { connect } from 'react-redux'
import setInput from '../actions'
import CreateInputs from '../components/CreateInputs'
class Converter extends React.Component {
constructor(props) {
super(props)
this.changeInput = this.changeInput.bind(this)
this.changeFirstOption = this.changeFirstOption.bind(this)
this.changeSecondOption = this.changeSecondOption.bind(this)
this.buttonsResult = this.buttonsResult.bind(this)
}
changeInput(event) {
event.preventDefault()
const {dispatch} = this.props
if (!event.target.value.trim()) {
return
}
dispatch(setInput(event.target.value))
}
changeFirstOption() {
}
changeSecondOption() {
}
buttonsResult() {
}
render() {
return <div>
<CreateInputs arrayList = {this.props.arrayList } changeInput={this.changeInput} changeFirstOption={this.changeFirstOption} changeSecondOption={this.changeSecondOption} buttonsResult={this.buttonsResult} />
</div>
}
}
const mapStateToProps = store => {
return {
defaultInput: store.getInput
}
}
export default connect(mapStateToProps)(Converter)
/components/CreateTable.js:
import React from 'react'
function CreateTable(props) {
const name = props.name,
code = props.code,
value = props.value,
target = props.target
return (<tr>
<td>{code}</td><td>{target}</td><td>{name}</td><td>{value.toFixed(2)}</td>
</tr>)
}
export default CreateTable
/components/CreateInputs.js:
import React from 'react'
function CreateOption(props) {
const name = props.name,
code = props.code
return (
<option value={name}>{code} - {name}</option>
)
}
function CreateInputs(props) {
const changeInput = props.changeInput,
changeFirstOption = props.changeFirstOption,
changeSecondOption = props.changeSecondOption,
buttonsResult = props.buttonsResult,
arrayList = props.arrayList,
newArrayList = []
let input
return <div>
<form>
<input ref={node => (input = node)} type='text' onChange={changeInput}></input>
</form>
</div>
}
export default CreateInputs
/actions/index.js:
export const GET_VALUE_API = 'GET_VALUE_API'
export const ERROR_GET_API = 'ERROR_GET_API'
export const SET_INPUT = 'SET_INPUT'
export function beforeRequestApi(load) {
return {
type: GET_VALUE_API,
load
}
}
export function errorRequestApi(load) {
return {
type: ERROR_GET_API,
load
}
}
export default function getRequestApi() {
return function (dispatch) {
dispatch(beforeRequestApi('Загрузка валют...'))
return fetch('https://www.cbr-xml-daily.ru/daily_json.js')
.then(function (response) {
if (response.status >= 200 && response.status < 300) {
return response
} else {
let error = new Error(response.statusText)
error.response = response
throw error
}
})
.then(function (response) {
if (typeof response !== 'object') {
let error = new Error('Получен недопустимый формат данных')
error.response = response
throw error
}
return response;
})
.then(function (response) {
const API = response.json()
return API
})
.then(function (API) {
dispatch(beforeRequestApi(API))
})
.catch(function (text) {
return dispatch(errorRequestApi('Ошибка в получении данных: ' + text.message))
});
}
}
export function setInput(value) {
return {
type: SET_INPUT,
value
}
}
/reducers/reducers.js:
import { combineReducers } from 'redux'
import { GET_VALUE_API, ERROR_GET_API, SET_INPUT} from './../actions'
function requestApi(state = [], action) {
switch (action.type) {
case GET_VALUE_API:
return {...state, getload: action.load }
case ERROR_GET_API:
return action.load
default:
return state
}
}
function getInput(state = '1', action) {
switch (action.type) {
case SET_INPUT:
return { ...state, input: action.value }
default:
return state
}
}
const rootReducer = combineReducers({ requestApi, getInput })
export default rootReducer```
Спасибо.
Ответы (2 шт):
ComponentDidMount() срабатывает после re-rendera, поэтому срабатывает dispatch.
Изменение store --> re-render Api --> componentDidMount() --> dispatch
а store изменяется после каждого нажатия кнопки в input. А так как Api подключен к store через connect он каждый раз перерисовывается.
Action можно привязывать к dispatch через connect:
export default connect(mapStateToProps, {getRequestApi})(Api)
Но input не получает state из store. Должно быть что-то типа
<input value={state.value} ....
Поэтом событие возможно доходит до store но мы его не видим в input.
в reducer getInput: state='1' и далее { ... state} - не совсем корректно
тоже в requestApi state=[] и далее {...state}
____ дополнение:
чтобы не было повторного диспатча в class Api дописать:
componentDidMount() {
if (!this.props.getload) {
getRequestApi();
}
}
далее:
export default connect(mapStateToProps, {getRequestApi})(Api)
в requestApi state={}
в редьюсере:
case SET_INPUT: return action.value
в классе Converter:
<CreateInputs value={this.props.defaultInput} ...
в CreateInputs:
<input value={props.value} ...
Я нашел причину: при монтировании всегда вызывается fetch со строковым значением, соответственно, при перерисовке компонента(обновлении store), fetch всегда вызывается, т.к. react видит произошедшие изменения