I'm new to hooks and trying to use them more
How can I get data (with Apollo) when a component mount ?
I'm trying to use useQuery inside a useEffect, my code so far looks like this
const MyComponent = () => {
const getME = () => {
const { loading, error, data } = useQuery(ME);
setMe(data.me) // useState hook
console.log('query me: ', me);
};
useEffect(getME);
return (<>
...
</>)
}
but this gives me an error
Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
edit: this is the query
import { gql } from '#apollo/client';
export const ME = gql`
query me {
profile {
firstName
lastName
}
}
`;
Here is an example on how you should use the useQuery hook and then stock the data in the state
const { loading, data, error } = useQuery(SOME_QUERY)
const [state, setState] = React.useState([])
useEffect(() => {
// do some checking here to ensure data exist
if (data) {
// mutate data if you need to
setState(data)
}
}, [data])`enter code here`
from https://github.com/trojanowski/react-apollo-hooks/issues/158
I am trying to create a search functionality.
So the values from the search input is actually getting passed in my actions and I can see the values from redux logger. However redux saga seems not able to intercept the payload value from the action creator. When I console log it it prints undefined.
Actions
//ACTIONS
import SearchActionTypes from "./search.types";
export const SearchActionStart = (value) => ({
type: SearchActionTypes.SEARCH_START,
value
});
export const SearchActionSuccess = (items) => ({
type: SearchActionTypes.SEARCH_SUCCESS,
payload: items,
});
export const SearchActionFailure = (e) => ({
type: SearchActionTypes.SEARCH_FAILURE,
payload: e,
});
Search Component
import React, { useEffect, useState } from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectFieldData } from "../../redux/search/search.selector";
import { SearchActionStart } from "../../redux/search/search.actions";
const SearchComponent = (props) => {
const { searchResults, value } = props;
useEffect(() => {}, []);
const onSearchChange = (event) => {
const { value } = event.target;
searchResults(value);
};
return (
<div>
<input
type="text"
value={value}
onChange={onSearchChange}
/>
</div>
);
};
const mapDispatchToProps = (dispatch) => ({
searchResults: (value) =>
dispatch(SearchActionStart(value)),
});
const mapStateToProps = createStructuredSelector({
searchItem: selectFieldData,
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(SearchComponent);
searchSaga
import {
put,
call,
takeLatest,
all,
} from "redux-saga/effects";
import { SearchImage } from "../../api/search-image";
import Axios from "axios";
import {
SearchActionStart,
SearchActionSuccess,
SearchActionFailure,
} from "./search.actions";
import SearchActionTypes from "./search.types";
function* fetchFieldAsync(value) {
try {
// const images = yield call(SearchImage, value);
console.log(value);
// yield put(SearchActionSuccess(value));
} catch (e) {
yield put(SearchActionFailure(e));
console.log(e);
}
}
export function* fetchFieldStart() {
yield takeLatest(
SearchActionTypes.SEARCH_START,
fetchFieldAsync
);
}
export function* searchFieldSaga() {
yield all([call(fetchFieldAsync)]);
}
rootSaga
import { call, all } from "redux-saga/effects";
import { searchFieldSaga } from "./search/search.saga";
export default function* rootSaga() {
yield all([call(searchFieldSaga)]);
}
Please have a look into this code sandbox(https://codesandbox.io/s/basic-redux-saga-49xyd?file=/index.js) ... Your code is working fine. In saga function you will get the object that has been sent from the action as the param. You can destructure it into {value} to get the search term alone as param instead of action object.
A very silly mistake.
In my searchSaga instead of exporting the watcher function fetchFieldStart function. I mistakenly exported the intermediary functions instead, which is the fetchFieldAsync function whose job is to fetch an API.
So in
searchSaga.js
instead of:
export function* searchFieldSaga() {
yield all([call(fetchFieldAsync)]);
}
It should be:
export function* searchFieldSaga() {
yield all([call(fetchFieldStart)]);
}
For anyone who might encounter undefined error in your sagas, it might be worth reviewing if your exporting correct functions.
I hope this could also help anyone who have encountered similar problem
Thanks evryone.
This code, containing two effects, returns no data. However, when commenting out either one, data is returned normally.
When both effects are run, dev-tools network view shows vehicleList request headers 'Provisional' and vendorList with a status code of `500 Internal Server Error'.
Is it possible to request two effects in the same functional component? If yes, how?
PowMaintenance.js
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import { PowForm } from './PowForm';
const server = `http://${process.env.REACT_APP_API_BACK_END_ADDRESS}:${
process.env.REACT_APP_API_BACK_END_PORT
}`;
const PowMaintenance = () => {
const [vehicleList, setVehicleList] = useState([]);
useEffect(() => {
const fetchData = async () => {
const result = await axios(`${server}/api/vehicle/VehicleList`);
setVehicleList(result.data);
};
fetchData();
}, []);
const [vendorList, setVendorList] = useState([]);
useEffect(() => {
const fetchData = async () => {
const result = await axios(`${server}/api/vendor/VendorList`);
setVendorList(result.data);
};
fetchData();
}, []);
console.log('vehicle', vehicleList);
console.log('vendor', vendorList);
return (
<div>
<PowForm vendorList={vendorList} onChange={onChange} />
</div>
);
};
export { PowMaintenance };
```
Yozi, thank you for your help. You were correct, it was in the API. I researched it only because of your insistence.
A programming error in TDS/MSSQL involving connections.
Right now I am using the HOC withApollo like:
export default connect(mapStateToProps, mapDispatchToProps)(withApollo(withData(Browse)));
then in that component:
render() {
const { client } = this.props;
<Button onPress={() => searchInterestsTab(client)} />
then outside that component:
export const searchInterestsTab = (client) => {
^ but am finding this gets very messy having to pass it into every outside function from my component.
Couldn't I just use:
const apolloClient = new ApolloClient({...})
export default apolloClient;
then:
import apolloClient from './apolloClient';
everywhere?
It should be possible to use it with kind of:
import apolloClient from './apolloClient'
If you look at the usage documantation you see that you can use it. So somewhere most possible in your index.js you should already have
const apolloClient = new ApolloClient({...})
My apollo client is instantiated like this:
import ApolloClient, { addTypename } from 'apollo-client';
const createApolloClient = options => {
return new ApolloClient(Object.assign({}, {
queryTransformer: addTypename,
dataIdFromObject: (result) => {
if (result.id && result.__typename) {
return result.__typename + result.id;
}
return null;
},
}, options))
};
export default createApolloClient;
and in the index.js it is used like this:
...
const client = createApolloClient({
networkInterface: networkInterface,
initialState: window.__APOLLO_STATE__,
ssrForceFetchDelay: 100,
});
....
export {
client,
...
};
For all I know, I have to write request in action create. How to use a promise in action for submitting a request? I am getting data in action. Then new state is created in reducer. Bind action and reducer in connect. But I don't know how to use promise for request.
Action
import $ from 'jquery';
export const GET_BOOK = 'GET_BOOK';
export default function getBook() {
return {
type: GET_BOOK,
data: $.ajax({
method: "GET",
url: "/api/data",
dataType: "json"
}).success(function(data){
return data;
})
};
}
Reducer
import {GET_BOOK} from '../actions/books';
const booksReducer = (state = initialState, action) => {
switch (action.type) {
case GET_BOOK:
return state;
default:
return state;
}
};
export default booksReducer;
Container
How display data in container?
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import getBook from '../actions/books';
import Radium from 'radium';
import {Link} from 'react-router';
function mapStateToProps(state) {
return {
books: state.data.books,
};
}
function mapDispatchToProps(dispatch) {
return {
getBooks: () => dispatch(getBook()),
};
}
#Radium
#connect(mapStateToProps, mapDispatchToProps)
class booksPage extends Component {
static propTypes = {
getBooks: PropTypes.func.isRequired,
books: PropTypes.array.isRequired,
};
render() {
const {books} = this.props;
return (
<div>
<Link to={`/authors`}><MUIButton style="flat">All Authors</MUIButton></Link>
<ul>
{books.map((book, index) =>
<li key={index}>
<Link to={`/book/${book.name}`}><MUIButton style="flat"><div class="mui--text-black mui--text-display4">
"{book.name}"</div></MUIButton></Link>
<Link to={`/author/${book.author}`}><MUIButton style="flat"><div class="mui--text-black mui--text-display4">
{book.author}</div></MUIButton></Link>
</li>
)}
</ul>
</div>
);
}
}
export default booksPage;
Since you are already using redux you can apply redux-thunk middleware which allows you to define async actions.
Installation & usage: Redux-thunk
export function fetchBook(id) {
return dispatch => {
dispatch(setLoadingBookState()); // Show a loading spinner
fetch(`/book/${id}`, (response) => {
dispatch(doneFetchingBook()); // Hide loading spinner
if(response.status == 200){
dispatch(setBook(response.json)); // Use a normal function to set the received state
}else {
dispatch(someError)
}
})
}
}
function setBook(data) {
return { type: 'SET_BOOK', data: data };
}
You should use Async Actions described in Redux Documentation
Here an example of reducer for async action.
const booksReducer = (state = {}, action) => {
switch (action.type) {
case 'RESOLVED_GET_BOOK':
return action.data;
default:
return state;
}
};
export default booksReducer;
and then you create your Async Action.
export const getBook() {
return fetch('/api/data')
.then(response => response.json())
.then(json => dispatch(resolvedGetBook(json)))
}
export const resolvedGetBook(data) {
return {
type: 'RESOLVED_GET_BOOK',
data: data
}
}
Several Notes:
We could return Promise (instead of Object) in action by using redux-thunk middleware.
Don't use jQuery ajax library. Use other library specifically for doing that (e.g. fetch()). I use axios http client.
Remember, in redux you only use pure function in reducer. Don't make ajax call inside reducer.
Read the complete guide from redux docs.
You should be able to use dispatch inside the callback (if you pass it as an argument):
export default function getBook(dispatch) {
$.ajax({
method: "GET",
url: "/api/data",
dataType: "json"
}).success(function(data){
return dispatch({type:'GET_BOOK', data: data});
});
}
Then, pass dispatch to the action:
function mapDispatchToProps(dispatch) {
return {
getBooks: () => getBook(dispatch),
};
}
Now, you should have access to the action.data property in the reducer:
const booksReducer = (state = initialState, action) => {
switch (action.type) {
case GET_BOOK:
//action.data <--- here
return state;
default:
return state;
}
};
You might want to separate concerns, to keep action creators "pure".
Solution; write some middleware. Take this for example (using superagent).
import Request from 'superagent';
const successHandler = (store,action,data) => {
const options = action.agent;
const dispatchObject = {};
dispatchObject.type = action.type + '_SUCCESS';
dispatchObject[options.resourceName || 'data'] = data;
store.dispatch(dispatchObject);
};
const errorHandler = (store,action,err) => {
store.dispatch({
type: action.type + '_ERROR',
error: err
});
};
const request = (store,action) => {
const options = action.agent;
const { user } = store.getState().auth;
let method = Request[options.method];
method = method.call(undefined, options.url)
if (user && user.get('token')) {
// This example uses jwt token
method = method.set('Authorization', 'Bearer ' + user.get('token'));
}
method.send(options.params)
.end( (err,response) => {
if (err) {
return errorHandler(store,action,err);
}
successHandler(store,action,response.body);
});
};
export const reduxAgentMiddleware = store => next => action => {
const { agent } = action;
if (agent) {
request(store, action);
}
return next(action);
};
Put all this in a module.
Now, you might have an action creator called 'auth':
export const auth = (username,password) => {
return {
type: 'AUTHENTICATE',
agent: {
url: '/auth',
method: 'post',
resourceName: 'user',
params: {
username,
password
}
}
};
};
The property 'agent' will be picked up by the middleware, which sends the constructed request over the network, then dispatches the incoming result to your store.
Your reducer handles all this, after you define the hooks:
import { Record } from 'immutable';
const initialState = Record({
user: null,
error: null
})();
export default function auth(state = initialState, action) {
switch (action.type) {
case 'AUTHENTICATE':
return state;
case 'AUTHENTICATE_SUCCESS':
return state.merge({ user: action.user, error: null });
case 'AUTHENTICATE_ERROR':
return state.merge({ user: null, error: action.error });
default:
return state;
}
};
Now inject all this into your view logic. I'm using react as an example.
import React from 'react';
import ReactDOM from 'react-dom';
/* Redux + React utils */
import { createStore, applyMiddleware, bindActionCreators } from 'redux';
import { Provider, connect } from 'react-redux';
// thunk is needed for returning functions instead
// of plain objects in your actions.
import thunkMiddleware from 'redux-thunk';
// the logger middleware is useful for inspecting data flow
import createLogger from 'redux-logger';
// Here, your new vital middleware is imported
import { myNetMiddleware } from '<your written middleware>';
/* vanilla index component */
import _Index from './components';
/* Redux reducers */
import reducers from './reducers';
/* Redux actions*/
import actionCreators from './actions/auth';
/* create store */
const store = createStore(
reducers,
applyMiddleware(
thunkMiddleware,
myNetMiddleware
)
);
/* Taint that component with store and actions */
/* If all goes well props should have 'auth', after we are done */
const Index = connect( (state) => {
const { auth } = state;
return {
auth
};
}, (dispatch) => {
return bindActionCreators(actionCreators, dispatch);
})(_Index);
const provider = (
<Provider store={store}>
<Index />
</Provider>
);
const entryElement = document.getElementById('app');
ReactDOM.render(provider, entryElement);
All of this implies you already set up a pipeline using webpack,rollup or something, to transpile from es2015 and react, to vanilla js.
Consider using the new thunk API
export const load = createAsyncThunk(
'example/api',
async (arg, thunkApi) => {
const response = await fetch('http://example.api.com/api')
if (response.status === 200) {
const json = await response.json()
return json
},
)
Also, in the new redux template application, actions are part of the reducer/slice, and you can use extraReducers to response to events related to the async action status. It is much simpler using redux this way.
See documentation of async thunk here: https://redux.js.org/usage/writing-logic-thunks