Замена connect () на useSelector () и useDispatch (), больше не может читать состояние - PullRequest
0 голосов
/ 07 августа 2020

Приложение 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.

Заранее спасибо!

...