how to get data from graphql server using react and react-apollo? - javascript

After about 6-8 hours trying, I'm resorting to help.
All I want is to query my graphql server and the response data to be entered into my react component as props (See ParameterBox.js). Please let me know what I'm doing wrong.
For Reference: INDEX.JS FILE (Likely correct)
import React from 'react';
import ReactDOM from 'react-dom';
import {
ApolloClient,
createNetworkInterface,
ApolloProvider,
} from 'react-apollo';
import App from './App';
const networkInterface = createNetworkInterface({
uri: 'http://localhost:3001/graphql'
});
const client = new ApolloClient({
networkInterface: networkInterface
});
ReactDOM.render(
<ApolloProvider client={client}>
<App />
</ApolloProvider>,
document.getElementById('root')
);
For Reference: APP.JS FILE (I think it's correct)
//App.js
import React, { Component } from 'react';
import ParameterBox from './ParameterBox';
class App extends Component {
render(){
return(
<div>
<ParameterBox />
</div>
)
}
}
export default App;
PARAMETERBOX.JS FILE (Here is where the issue is, somewhere...)
//ParameterBox.js
import React, { Component } from 'react';
import axios from 'axios';
import { gql, graphql } from 'react-apollo';
import ParameterList from './ParameterList';
class ParameterBox extends Component {
constructor (props) {
super(props);
this.state = { data: [] };
this.loadParamsFromServer = this.loadParamsFromServer.bind(this);
}
//*** Old Method ***/
// I still set my data using the old methods of API urls. I want to switch to GraphQL
loadParamsFromServer(){
axios.get('http://localhost:3001/api/params')
.then (res => {
this.setState({ data: res.data });
})
}
//**** end old method ****/
componentDidMount() {
this.loadParamsFromServer();
}
render(){
return(
<div >
<h2> Parameters: </h2>
<p> HOW DO I GET IT HERE? {this.props.AllParamsQuery } </p>
<ParameterList
data={ this.state.data }/>
</div>
)
}
}
const AllParams = gql`
query AllParamsQuery {
params {
id,
param,
input
}
}`;
export default graphql(AllParams, {name: 'AllParamsQuery'})(ParameterBox);

You may find it helpful to review the Apollo documentation for basic queries here.
When you wrap your component with the graphql HOC, it will send your query to the server and then make the result available to your component as this.props.data. So, the result for your particular query would be found at this.props.data.params (the operation name, AllParamsQuery is not referenced inside the returned data).
The other thing to bear in mind is that the GraphQL is asynchronous, so while props.data will always be available, initially it will be empty. Your render logic will need to account for that fact by verifying that this.props.data.params is truthy before tyring to render it. You can also check whether the query is still in flight or has completed.
Edit: because you define a name property (AllParamsQuery) inside the config object you pass to graphql(), your query results will be available as that prop instead of data -- i.e. 'this.props.AllParamsQuery.params`.

Related

Push to new route without any further actions on the component

We use an external componet which we don't control that takes in children which can be other components or
used for routing to another page. That component is called Modulation.
This is how we are currently calling that external Modulation component within our MyComponent.
import React, {Fragment} from 'react';
import { withRouter } from "react-router";
import { Modulation, Type } from "external-package";
const MyComponent = ({
router,
Modulation,
Type,
}) => {
// Need to call it this way, it's how we do modulation logics.
// So if there is match on typeA, nothing is done here.
// if there is match on typeB perform the re routing via router push
// match happens externally when we use this Modulation component.
const getModulation = () => {
return (
<Modulation>
<Type type="typeA"/> {/* do nothing */}
<Type type="typeB"> {/* redirect */}
{router.push('some.url.com')}
</Type>
</Modulation>
);
}
React.useEffect(() => {
getModulation();
}, [])
return <Fragment />;
};
export default withRouter(MyComponent);
This MyComponent is then called within MainComponent.
import React, { Fragment } from 'react';
import MyComponent from '../MyComponent';
import OtherComponent1 from '../OtherComponent1';
import OtherComponent2 from '../OtherComponent2';
const MainComponent = ({
// some props
}) => {
return (
<div>
<MyComponent /> {/* this is the above component */}
{/* We should only show/reach these components if router.push() didn't happen above */}
<OtherComponent1 />
<OtherComponent2 />
</div>
);
};
export default MainComponent;
So when we match typeB, we do perform the rerouting correctly.
But is not clean. OtherComponent1 and OtherComponent2 temporarily shows up (about 2 seconds) before it reroutes to new page.
Why? Is there a way to block it, ensure that if we are performing router.push('') we do not show these other components
and just redirect cleanly?
P.S: react-router version is 3.0.0

React: store uploaded array into global variable?

I'm currently working on using React to upload a CSV file and convert the data to an array so I can access phone numbers. I've actually got it almost completely functional, with just one problem: I can't figure out how to store the array properly in a variable (dataDump) on the global level. It stores it inside another array.
Here's a picture of my console so you can see what I mean.
I'm able to access the contents of dataDump if I use dataDump[0] (as seen in the function for handleClick), but that won't work for a global variable. I need to be able to send the array's values to other components/files, so I don't think having to call it like that will work. Chances are I'm over-complicating this in my head and the answer is incredibly simple, but I've spent the past 2-3 weeks learning React, Twilio, Mongodb etc. from scratch so my brain's not cooperating.
I'll appreciate any help! Thanks! Code below. (Note this is a component that's imported to the App page.)
import React from "react";
import CSVReader from "react-csv-reader";
var dataDump = [];
console.log(dataDump);
const papaparseOptions = {
header: true,
dynamicTyping: true,
skipEmptyLines: true,
transformHeader: header => header.toLowerCase().replace(/\W/g, "_"),
complete: function(results) {
dataDump.push(results.data);
console.log(dataDump);
var rows = results.data;
let numbers = rows.map(a => a.phone_number); //make the results ONLY the phone numbers
// console.log(numbers);
document.getElementById("data2").innerHTML=numbers; //display the phone numbers
}
};
class Import extends React.Component {
constructor(props) {
super(props);
this.state = {data:[]};
this.handleClick = this.handleClick.bind(this);
}
handleForce = data => {
// console.log(data.length);
console.log(data);
this.setState({data: data});
};
handleClick = () => {
console.log("success");
console.log(this.state.data);
console.log("Next is Numbies:");
let numbies = dataDump[0].map(a => a.phone_number);
console.log(numbies);
document.getElementById("data").innerHTML=numbies;
}
render() {
return (
<div className="container">
<CSVReader
className="csv-input"
label="Select CSV file to import"
onFileLoaded={this.handleForce}
parserOptions={papaparseOptions}
/>
<div>
</div>
<button onClick={this.handleClick.bind(this)}>
Test
</button>
<div id="data" />
<div id="data2" />
<div id="data3">
</div>
</div>
);
}
}
export default Import;
// export default DataController;
Under the hood React-Redux is using context and hooks these days, so don't bother implementing a Redux stack until you've outgrown the simpler, React API, or at least you've fixed your issue. Folks joke that Redux is like shooting a fly with a bazooka. More info on React-Redux internals here and here's the documentation for React's Context.
Some psuedo-code to get you on the right path:
// context.js
import { createContext } from 'react';
export const Store = createContext();
// app.js
import React from 'react';
import { Store } from './context';
import Import from './import'; // I wouldn't change the casing on or reuse a reserved keyword personally, maybe calling this something like 'CsvImporter' would be an improvement
function App() {
const [dataDump, setDataDump] = React.useState([]);
return (
<Store.Provider value={{ dataDump, setDataDump }}>
<Import dataDump={dataDump} setDataDump={setDataDump} />
</Store.Provider>
);
}
Now your import component has two new props, dataDump and setDataDump. You can call setDataDump just like any other call to setting state. Nice!
So you need the dataDump in a new component? That's easy peasy, lemon squeezy, and all without global variables or tossing module scoping to the side:
// foobar.js
import React from 'react';
import { Store } from './context';
export function Foobar() {
// you probably want to do more than force render an array as a string, but this is just a proof of concept
return (
<Store.Consumer>
{({ dataDump, setDataDump }) => (
<p>
`${dataDump}`
</p>
)}
</Store.Consumer>
);
}
Just make sure that Foobar or other components are rendered as children of the Provider in app.js and now you have a 'global' context for passing around dataDumps.

How can pass a props to a variable?

I'm trying "hydrate" props from elements to child components that will render. The problem is that I can't figure out how I can do it with my configuration.
I have seen this answer, I tried to adapt it, but so far I'm getting errors (see bottom).
For a bit of background, I'm developing a Rails based application that uses React for the front end. So I don't use React router or such, it just "displays" the datas.
Here is how I set everything up:
front.js (where everything gets rendered)
import React from 'react';
import ReactDOM from 'react-dom';
import extractActionName from './lib/extractActionName';
import {elementForActionName} from './lib/elementForActionName';
import 'jquery';
import 'popper.js';
import 'bootstrap';
let actionName = extractActionName();
let value = "value";
let renderElement = function (Element, id) {
ReactDOM.render(
<Element value={value} />,
document.getElementById(id)
);
};
renderElement(elementForActionName[actionName], actionName);
lib/elementForActionName.js
import React from 'react';
import Homeindex from '../home/home';
import Contact from '../home/contact';
// This files create an associative array with id React will be
// looking for as a key and the component as value
export const elementForActionName = {
'index': <Homeindex />,
'contact': <Contact/>,
};
lib/extractActionName.js
export default function extractActionName() {
// The body contains classes such as "home index", so
// I return the "action name" of my controller (home) to
// front.js so I will render the good component
return document.body.className.split(' ').pop();
}
home/home.js
import React from 'react';
import Header from '../layout/header';
import Footer from '../layout/footer';
export default class homeIndex extends React.Component {
render() {
return(
<div>
<Header/>
<h1>Hello this will be the content of the landing page hello</h1>
<Footer/>
</div>
)
}
}
My problem is that I'd like to make an Ajax call in my "front.js" file, then transmit the received data (here, "value"). The error I'm getting is the following:
Uncaught Error: Element type is invalid: expected a string (for
built-in components) or a class/function (for composite components)
but got: object.
I'm lacking experience with React, how can I resolve this problem?
Thank you in advance.
You are currently returning the instance of a component:
export const elementForActionName = {
'index': <Homeindex />, <--- here
'contact': <Contact/>,
};
And then attempting to instantiate it again:
let renderElement = function (Element, id) {
ReactDOM.render(
<Element value={value} />, // <--- here
document.getElementById(id)
);
};
Instead, just use the component class:
export const elementForActionName = {
'index': Homeindex,
'contact': Contact,
};

Bundle.js not recognizing one of my files?

Please bear with me because I am a javascript newbie, and just starting to learn react.
I am trying to make a small app but I keep getting an error that one of my files is not found... specifically this:
bundle.js:56 Uncaught Error: Cannot find module "./components/search_bar"
My file structure is that I have my index.js in a folder called src, then my search bar(search_bar.js) in a folder called components. I have triple checked the spelling on them but I continue to get this error.
This is my index.js
import SearchBar from './components/search_bar';
import React from 'react';
import ReactDOM from 'react-dom';
//Create a componant (some /HTML)
const API_KEY = 'AIzaSyC3Z3qTpvAacDLYEIxaueKflFJbWvdIHsw';
const App = () => {
return (
<div>
<SearchBar />
</div>
);
}
// Put that componant on the page (the DOM)
ReactDOM.render(<App />, document.querySelector('.container'));
And this is my search_bar.js
import React, { Component } from 'react';
class SearchBar extends Component {
contructor(props) {
super(props);
// when user updates the search bar this term will get updated.
this.state = { term: ''};
}
render() {
//update state
//use set state everywhere besides constructor!!
return (
<div>
<input onChange={event => this.setState({term: event.target.value})}
/>
Value of the input: {this.state.term}
</div>
);
}
}
export default SearchBar;
Any Ideas as to what I am doing wrong here?
Can you confirm the following directory structure?
my_project/src/index.js
my_project/src/components/search_bar.js
It seems like your current directory structure might instead look like this:
my_project/src/index.js, my_project/components/search_bar.js
AHHH I left an 's' out of constructor... so search_bar.js was unable to compile. I have been looking at this for about an hour now...

React event listeners based on changes in state made with Redux

Good afternoon,
I am having some difficulty working with React and Redux when I am trying to redirect users of my app based on changes in state.
At a high level: I want my app's route to change when my user object in state is populated with information from home / to /student/:username.
Right now I have accomplished this in a hacky sort of fashion.
In my Login component I use the componentDidUpdate lifecycle hook to listen and dispatch an action when an access token from a 3rd party API is passed back to the client from my Express server.
import React from "react";
import LoginForm from "../minor/LoginForm";
const Login = React.createClass({
componentDidUpdate(){
const {success, access_token} = this.props.loginStatus;
if (success === true && access_token !== null){
console.log("It worked getting your data now...");
this.props.getUserData(access_token);
} else if (success === false || access_token === null){
console.log("Something went wrong...");
}
},
render(){
return(
<div className="loginComponentWrapper">
<h1>Slots</h1>
<LoginForm loginSubmit={this.props.loginSubmit}
router={this.props.router}
user={this.props.user} />
New User?
</div>
)
}
});
Notice that I am passing router and user to my LoginForm component. I do this in order to use ANOTHER componentDidUpdate where I use the .push method on router like so:
import React from "react";
const LoginForm = React.createClass({
componentDidUpdate(){
const {router, user} = this.props;
if (user.username !== null){
router.push(`/student/${user.username}`);
}
},
render(){
return(
<div className="loginWrapper">
<div className="loginBox">
<form className="loginForm" action="">
<input ref={(input) => this.username_field = input} type="text" placeholder="username" defaultValue="kitties#kit.com" />
<input ref={(input) => this.password_field = input} type="text" placeholder="password" defaultValue="meowMeow3" />
<button onClick={this.loginAttempt}>Login</button>
</form>
</div>
</div>
)
}
});
Sure it works but I'm certain this is a overly complicated solution and not what is considered a best practice. I've tried adding a custom listener method within my Login component but I've never had it successfully fire off, instead my app gets stuck in a loop.
Is there a way I can do this using Redux and keep my components tidier, or take advantage of componentDidUpdate in a more efficient way?
As always I'd appreciate some more experienced eyes on my issue and look forward to some feedback!
Thanks
UPDATE
App.js
import { bindActionCreators } from "redux";
import { connect } from "react-redux";
import * as actionCreators from "../actions/userActions.js";
import StoreShell from "./StoreShell.js";
function mapStateToProps(state){
return{
loginStatus: state.loginStatus,
user: state.user
}
}
function mapDispatchToProps(dispatch){
return bindActionCreators(actionCreators, dispatch)
}
const App = connect(mapStateToProps, mapDispatchToProps)(StoreShell);
export default App;
This component "sprinkles" all my redux stuff and state data into my container component named StoreShell that in turn passes all that data to props for the elements that make up the UI like Login and LoginForm am I taking too many steps?
StoreShell.js
import React from "react";
const StoreShell = React.createClass({
render(){
return(
<div className="theBigWrapper">
{React.cloneElement(this.props.children, this.props)}
</div>
)
}
})
export default StoreShell;
There are several things that could make the login flow easier to manage and reason about and tidy up your components a bit. First a few general points:
I'm not certain why you have divided login logic between the two components? The idiomatic React/Redux approach would be to have a container component that deals with logic, and a presentation component that deals with presentation. Currently both components do a little bit of each.
You don't need to pass props.router up and down through your components. React router provides a HOC that provides router as a props called withRouter( docs here). You just wrap a component in withRouter and props.router is available - everything else stays the same.
export default withRouter(LoginForm);
My personal feeling is that the URI is part of your app's state, and as such it should be maintained within the store and updated by dispatching actions. There is a cracking library available to do this - react-router-redux. Once you have it setup then you can pass the push method to your components (or elsewhere... see the next points) to navigate:
import { push } from 'react-router-redux';
import { connect } from 'react-redux';
const NavigatingComponent = props => (
<button onClick={() => props.push('/page')}>Navigate</button>
);
const mapStateToProps = null;
const mapDispatchToProps = {
push
};
export default connect(mapStateToProps, mapDispatchToProps)(NavigatingComponent);
One of the great things about having the URI in our store is that we don't need access to props.router to change location, which opens the avenue of moving the login logic outside of our components. There are several ways we can do this, the simplest is probably redux-thunk, which allows our action creators to return functions instead of objects. We could then write our component to simply call a login function with username and password entered, and our thunk takes care of the rest:
import { push } from 'react-router-redux';
// action creators....
const loginStarted = () => ({
type: 'LOGIN_STARTED'
)};
const loginFailed = (error) => ({
type: 'LOGIN_FAILED',
payload: {
error
}
};
const loginSucceeded = (user) => ({
type: 'LOGIN_SUCCEEDED',
payload: {
user
}
};
const getUserData = (access_token) => (dispatch) => {
Api.getUserData(access_token) // however you get user data
.then(response => {
dispatch(loginSucceeded(response.user));
dispatch(push(`/student/${response.user.username}`));
});
export const login = (username, password) => (dispatch) => {
dispatch(loginStarted());
Api.login({ username, password }) // however you call your backend
.then(response => {
if (response.success && response.access_token) {
getUserData(access_token)(dispatch);
} else {
dispatch(loginFailed(response.error));
}
});
}
The only thing your components do is call the initial login function, which could be implemented something like:
Container:
import React from 'react';
import { connect } from 'react-redux';
import { login } from '../path/to/actionCreators';
import LoginForm from './LoginForm';
const LoginContainer = React.createClass({
handleSubmit() {
this.props.login(
this.usernameInput.value,
this.passwordInput.value
);
},
setUsernameRef(input) {
this.usernameInput = input;
},
setPasswordRef(input) {
this.passwordInput = input;
},
render() {
return (
<LoginForm
handleSubmit={this.handleSubmit.bind(this)}
setUsernameRef={this.setUsernameRef.bind(this)}
setPasswordRef={this.setPasswordRef.bind(this)}
/>
);
}
});
const mapStateToProps = null;
const mapDispatchToProps = {
login
};
export default connect(mapStateToProps, mapDispatchToProps)(LoginContainer);
Component:
import React from 'react';
export const LoginForm = ({ handleSubmit, setUsernameRef, setPasswordRef }) => (
<div className="loginWrapper">
<div className="loginBox">
<form className="loginForm" action="">
<input ref={setUsernameRef} type="text" placeholder="username" defaultValue="kitties#kit.com" />
<input ref={setPasswordRef} type="text" placeholder="password" defaultValue="meowMeow3" />
<button onClick={handleSubmit}>Login</button>
</form>
</div>
</div>
);
This has achieved separation of a logic/data providing container, and a stateless presentational component. The LoginForm component can be written simply as a function above because it has no responsibility to deal with logic. The container is also a very simple component - the logic has all been isolated in our action creator/thunk and is much easier to read and reason about.
redux-thunk is just one way of managing asynchronous side effects with redux - there are many others with different approaches. My personal preference is toward redux-saga, which may be interesting for you to look at. In my own redux journey, however, I certainly benefited from using and understanding redux-thunk first before finding it's limitations/drawbacks and moving on, and would suggest this route to others.
If you're using react-router version 4.x.x: You can just render a Redirect component that handles the redirection for you (see example in react-router docs).
import React from "react";
import { Redirect } from "react-router";
import LoginForm from "../minor/LoginForm";
const Login = React.createClass({
componentDidUpdate(){
const {success, access_token} = this.props.loginStatus;
if (success === true && access_token !== null){
console.log("It worked getting your data now...");
this.props.getUserData(access_token);
} else if (success === false || access_token === null){
console.log("Something went wrong...");
}
}
render(){
// if user is defined, redirect to /student/:username
if (this.props.user.username !== null) {
return (<Redirect to={ `/student/${this.props.user.username}` } />)
}
// otherwise render the login form
return(
<div className="loginComponentWrapper">
<h1>Slots</h1>
<LoginForm loginSubmit={this.props.loginSubmit}
router={this.props.router}
user={this.props.user} />
New User?
</div>
)
}
});
If you're using react-router version 3.x.x: The the way you're doing it is mostly correct. I would move the redirect from the LoginForm component to Login component; that way LoginForm does not need to depend on the user prop.
I know, I don't like the pattern much either.
Hope this helps!

Categories

Resources