Приложение React, управляемое с помощью Redux Saga, не отправляет обновленное состояние компонентам после замены техники mapDispatch
/ connect
. Данные, полученные от вызова API, не передаются в приложение. js.
App.js
import api from './store/fetcher/index';
const { client } = api;
const store = createStore();
function App() {
const dispatch = useDispatch();
const fetchLiveData = () => {
return dispatch({
type: actions.START_POLLING,
});
};
const historicData = () => {
return dispatch({
type: actions.FETCH_RECENT,
metricName: "oilTemp"
});
};
return(
<MuiThemeProvider theme={theme}>
<CssBaseline />
<ApolloProvider client={client}>
<Provider store={store}>
<Wrapper>
<ToastContainer />
<Graph
historicData={historicData}
fetchLiveData={fetchLiveData}
/>
</Wrapper>
</Provider>
</ApolloProvider>
</MuiThemeProvider>
);
}
export default App;
graph.js
function GraphMetrics(props) {
const {historicData, fetchLiveData} = props;
const dispatch = useDispatch();
const classes = useStyles(props);
const [axes, setVisible] = React.useState({
pressure: false,
temp: false,
percentage: false,
});
// const metricValues = useSelector(state => state.metrics);
const [metricValues, setSelectedMetrics] = React.useState([]);
React.useEffect(() => {
fetchLiveData();
}, [fetchLiveData]);
const YAxis = metric => {
if (metric.toLowerCase().endsWith('pressure')) {
return 1;
} else if (metric.toLowerCase().endsWith('temp')) {
return 2;
} else {
return 0;
}
};
const selection = selected => {
const metricSelected = selected();
if (metricValues.length < metricSelected.length) {
historicData(metricSelected[metricSelected.length - 1]);
}
setVisible({
pressure: metricSelected.some(m => YAxis(m) === 1),
temp: metricSelected.some(m => YAxis(m) === 2),
percentage: metricSelected.some(m => YAxis(m) === 0),
});
setSelectedMetrics(selected);
};
return (
<HTML CONTENT>
);
}
// const mapDispatch = dispatch => ({
// fetchLiveData: () => {
// dispatch({
// type: actions.START_POLLING,
// });
// },
// historicData: metricName => {
// dispatch({
// type: actions.FETCH_RECENT,
// metricName: "oilTemp",
// });
// },
// });
// export default connect(
// () => ({}),
// mapDispatch,
// )(GraphMetrics);
export default GraphMetrics;
fetcher/index.js
import { ApolloClient } from 'apollo-client';
import { WebSocketLink } from 'apollo-link-ws';
import { HttpLink } from 'apollo-link-http';
import { getMainDefinition } from 'apollo-utilities';
import { split } from 'apollo-link';
import gql from 'graphql-tag';
import { InMemoryCache } from 'apollo-cache-inmemory';
const httpLink = new HttpLink({
uri: 'https://react.eogresources.com/graphql',
});
const webSocketLink = new WebSocketLink({
uri: `ws://react.eogresources.com/graphql`,
options: {
reconnect: true,
},
});
const link = split(
({ query }) => {
const definition = getMainDefinition(query);
return definition.kind === 'OperationDefinition' && definition.operation === 'subscription';
},
webSocketLink,
httpLink,
);
const cache = new InMemoryCache();
const client = new ApolloClient({
cache,
link,
});
const subscribeLive = async () =>
await client.subscribe({
query: gql`
subscription {
newMeasurement {
at
metric
value
unit
}
}
`,
});
const getLatest = async metric => {
const last30minData = new Date(new Date().getTime() - 30 * 60000).getTime();
return await client.query({
query: gql`
{
getMeasurements(
input: {
metricName: "${metric}"
after: ${last30minData}
}
) {
at
metric
value
unit
}
}
`,
});
};
export default { client, subscribeLive, getLatest };
store/index.js
import { createStore, applyMiddleware } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
import createSagaMiddleware from 'redux-saga';
import { combineReducers } from 'redux-starter-kit';
import sagas from './state';
import metricsReducer from './reducers/metrics';
export default () => {
const rootReducer = combineReducers({
metrics: metricsReducer,
})
const composeEnhancers = composeWithDevTools({})
const sagaMiddleware = createSagaMiddleware()
const middlewares = applyMiddleware(sagaMiddleware)
const store = createStore(rootReducer, composeEnhancers(middlewares))
sagas.forEach(sagaMiddleware.run);
return store;
}
reducers/metrics.js
import * as actions from '../Provider'
const initialState = {
metrics: [],
latestValue: {},
};
const consolidated = (state, action) => {
const data = action.metrics;
return {
...state,
metrics: data,
};
};
const metricsData = (state, action) => {
const {metrics, latestValue} = action;
return {
...state,
metrics,
latestValue
};
};
const handlers = {
[actions.METRICS_DATA]: metricsData,
[actions.CONSOLIDATED_DATA]: consolidated,
};
export default (state = initialState, action) => {
const handler = handlers[action.type];
if (typeof handler === 'undefined') return state;
return handler(state, action);
}
sagaMetrics.js
import { takeEvery, take, call, put, fork, select } from 'redux-saga/effects'
import * as actions from '../Provider'
import api from '../fetcher';
import { eventChannel } from 'redux-saga';
const getData = state => state.metrics.metrics;
function* aggregateSaga(list) {
let data = yield select(getData);
list.map(item => {
const { metric, at, value } = item;
const hours = new Date(at).getHours() % 12 || 12;
const minutes = new Date(at).getMinutes();
const timeAt = `${("0" + hours).slice(-2)}:${("0" + minutes).slice(-2)}`;
data = {
...data,
[at]: {
...data[at],
[metric]: value,
at: timeAt,
},
}
return null;
})
yield put({ type: actions.CONSOLIDATED_DATA, metrics: data })
}
function* dataProcessor(newData) {
const { metric, at, value } = newData;
let data = yield select(getData);
const prevState = yield select(state => state.metrics.latestValue)
const hours = new Date(at).getHours() % 12 || 12;
const minutes = new Date(at).getMinutes()
const timeAt = `${("0" + hours).slice(-2)}:${("0" + minutes).slice(-2)}`
data = {
...data,
[at]: {
...data[at],
[metric]: value,
at: timeAt,
},
};
const latestValue = {
...prevState,
[metric]: value
}
yield put({ type: actions.METRICS_DATA, metrics: data, latestValue })
}
const stream = sub => eventChannel((emit) => {
const handler = (data) => {
emit(data);
};
sub.subscribe(handler);
return () => {
sub.unsubscribe()
};
});
function* liveFeed(action) {
const sub = yield call(api.subscribeLive);
const subscription = yield call(stream, sub);
while(true) {
const {data} = yield take(subscription);
console.log(data)
yield fork(dataProcessor, data.newMeasurement)
}
}
function* fetch30MinutesData(action) {
const { data } = yield call(api.getLatest, action.metricName);
const newData = data.getMeasurements;
yield fork(aggregateSaga, newData);
}
function* watchFetch() {
yield takeEvery(actions.FETCH_RECENT, fetch30MinutesData);
}
function* watchStartliveFeed() {
yield takeEvery(actions.START_POLLING, liveFeed)
}
export default [watchFetch, watchStartliveFeed];
Когда я пытаюсь прочитать latestValue
или metrics
из магазина, это всегда пустой объект (ожидая, что он будет содержать значение, возвращаемое API). Это отлично работает с connect () и mapDispatch.
Заранее спасибо!