Simple - React and OData - reactjs

I'm completely new to React and having a hard time understanding it.
I've been tasked with creating a really simple API fetch to an OData endpoint.
Now, I've come across this library https://www.npmjs.com/package/react-odata
Which looks fantastic! However I just do not understand how to even get something like this working.
I understand the very basic principles of how react works and have gone through many basic tutorials. But for whatever reason I can not get my head around this one.
So how could I use this library to simply query an OData endpoint and display the raw data?

So the issue with this, is that I didn't understand that I still have to explicitly make the call and return the data from that.
import React, { Component } from 'react';
import Fetch from 'react-fetch-component';
import OData from 'react-odata';
const baseUrl = 'http://services.odata.org/V4/TripPinService/People';
const query = { filter: { FirstName: 'Russell' } };
export default class App extends Component {
render() {
return (
<div>
<h1>Basic</h1>
<OData baseUrl={baseUrl} query={query}>
{({ loading, data, error }) => (
<div>
{loading && <span>Loading... (()=>{console.log(loading)}) </span>}
{data && data.value.map((d, i) => <div key={i} id={i}>{d.FirstName}</div>)}
</div>
)}
</OData>
</div>
);
}
/* Setup consistent fetch responses */
componentWillMount() {
fetch('http://services.odata.org/V4/TripPinService/People')
.then((response) => response.json())
.then((responseJson) => {
return responseJson.value[0].FirstName
})
.catch((error) => {console.error(error)});
}
}
from the given link in the question I found that this component used the react-fetch-component as a base to make the call.

It seems that the package you linked would expose a React component that you would use to wrap your own components so you would have access to the fetched data and could pass it down as properties. At least that is what I understand from its README.
I imagine it would be something like this:
<OData baseUrl={baseUrl} query={query}>
{ ({ loading, error, data }) => (
<div>
<YourComponent data={data} />
</div>
)}
</OData>
This would be using react-odata, but you don't need that package to do what you want. You could just do a regular AJAX call on the URL and feed your components with the returned data.
This post may help: http://andrewhfarmer.com/react-ajax-best-practices/

Related

Next.js Fetching Json to Display in Components

I'm trying to take the function MainMenu and getStaticProps from being in the same page (index.js) and break it up into components. Here is the index.js page below that is working good.
#index.js
import Link from 'next/link';
function MainMenu({ menuLists }) {
return (
<div>
{menuLists.map(menuItem => (
<div>
<Link href={menuItem.absolute}><a>{menuItem.title}</a></Link>
{menuItem.below && menuItem.below.map(childItem => (
<div>
<Link href={childItem.absolute}><a>{childItem.title}</a></Link>
</div>
))}
</div>
))}
</div>
)
}
export async function getStaticProps() {
const response = await fetch('http://localhost:8888/api/menu_items/main');
const menuLists = await response.json();
return {
props: {
menuLists: menuLists,
},
}
}
export default MainMenu
I have created fetch-mainmenu.js in a lib directory with the following code.
#fetch-mainmenu.js
export async function loadMainMenu() {
const response = await fetch('http://localhost:8888/api/menu_items/main')
const menuLists = await response.json()
return {
props: {
menuLists: menuLists,
},
}
}
I then created sidebar.js to show the menu system from the json file. The sidebar.js file is working because the hard coded menus are showing.
# sidebar.js
import Link from 'next/link'
import styles from './sidebar.module.css'
import { loadMainMenu } from '../lib/fetch-mainmenu'
export default function Sidebar( { menuLists } ) {
const menus = loadMainMenu()
return (
<nav className={styles.nav}>
<input className={styles.input} placeholder="Search..." />
<Link href="/">
<a>Home</a>
</Link>
<Link href="/about">
<a>About</a>
</Link>
<Link href="/contact">
<a>Contact</a>
</Link>
</nav>
)
}
Getting the following error "TypeError: Failed to fetch".
What is the best way of getting this done using components.
Solution
1. Prop Drilling
Easy. Just send down all the data from getStaticProps(). This is the safest bet at current stage but it may create some redundant props.
// I've omitted fetch().json() to ease the reading. just assume it's a proper code.
const MainMenuComponent = ({menuLists}) => {
return <div>{menuLists}</div>
}
const MainPage = ({menuLists}) => {
return <MainMenuComponent menuLists={menuLists} />
}
export async function getStaticProps() {
const req = await fetch('...');
return {
props: {
menuLists: req,
},
}
}
export default MainPage
2. React.useEffect
A React component can't have asynchronous code inside render code. It is pretty obvious in a class component but it's hard to tell in a functional component
// I've omitted fetch().json() to ease the reading. just assume it's a proper code.
// class component
class SampleComponent extends React.Component {
constructor(props) {
super(props);
this.state = { data: {} };
}
async getData() {
// ✅ this works
const data = await fetch('...');
// data has to be put in state because it's asynchronous.
this.setState({ ...this.state, data });
}
componentDidMount() {
this.getData();
}
render() {
// ❌ this can't happen here because render is synchronous
await fetch('...');
// use state to display asynchronous data.
return <h1>Hello, {JSON.stringify(this.state.data)}</h1>;
}
}
// functional component
function SampleComponent = () => {
// everything outside `useEffect, useLayoutEffect` is mostly assumed as render function.
// ❌ thus, this does not work here
await fetch('...');
const [data, setData] = useState({});
useEffect(async () => {
// everything inside here treated as componentDidMount()
// not the exact same thing though.
// ✅ this works!
setData(await fetch('...'))
}, []);
return <h1>Hello, {JSON.stringify(data)}</h1>
}
WARNING if there's getStaticProps inside your page, it means the component also has to be synchronous. If the rendered component changes its content in a very short time, in a fraction of second, then it may get rehydration error. It needs to be wrapped with dynamic() so that the Next.js can ignore the component when rendering server-side & rehydrating the component. Please refer to Next.js official document on Dynamic Import.
It does work but the code seems long.
3. TanStack Query(or React-Query) or useSWR
There are nice 3rd party libraries that help writing asynchronous data fetching code inside a react component; TanStack Query and SWR are the most well known. These libraries also implement caching and revalidation. It can help handling complex issues invoked due to asynchronous requests.
// example code from useSWR
import useSWR from 'swr'
function Profile() {
const { data, error } = useSWR('/api/user', fetcher)
if (error) return <div>failed to load</div>
if (!data) return <div>loading...</div>
return <div>hello {data.name}!</div>
}
4. State Management with Context
Most cases are easily dealt with the Query-SWR solution but if the app gets big enough, there could be a need to synchronize the data.
In that case, fetch the data in server code and share the data with a central state management library(a.k.a store libs). A good example is this github repo of Zustand + Next.js. A bare React.Context can be used as well.
However, this method can get very complicated later, maybe not suitable for an inexperienced team; it's basically similar to building another complex layer as big as backend. That's why the trend has moved to Query-SWR solution these days. Still, this comes handy in certain cases.
import { useStore } from "../lib/store";
const SampleComponent = () => {
const { data } = useStore();
return <div>{JSON.stringify(data)}</div>
}
const MainPage() {
return <SampleComponent />
}
export async function getStaticProps() {
// refer to the github repo for this store structure
const zustandStore = initializeStore();
// this is a POC. the actual code could be different.
// store data is updated, and can be used globally in other components in a synchronized state.
const data = await useStore.setData(await fetch('...'));
return {
props: {
initialZustandState: JSON.parse(JSON.stringify({ ...zustandStore.getState(), data })),
},
};
}
5. Server-side Component
With the emergence of React 18 server side component, Next.js is also working on Next.js Server Components.
This implementation is probably the closest implementation to code from the question. Nevertheless, the work is still in progress and highly unstable.
I've kept my eyes on this method for about a year but the implementation has been constantly changing. Until we get the stable release, this can wait.

How to use SWR mutate function with multiple keys but same type object array

I'm stuck with Vercel's SWR mutate system. I'm going to try to explain my situation, I hope I can make it.
I'm working on a social media app, it's something like a combination of Instagram and Twitter. We have a weird RestAPI design in here. for example, we have three endpoints, and let's say A, B, C. all of them return the same type object array(post[]) and have three routes for these endpoints.
// Post.jsx
const Post =(props)=> {
const deleteHandler =()=> {
//delete post
// update cached SWR data with mutate func
}
return(
<div>
// some other UI components
</div>
)
}
// pageX.jsx
const PageX =()=> {
const {data:posts} =useSWR("/api/A", fetcher);
return(
<List dataSource={posts} render={p=>(<Post data={p}/>)}/>
)
}
// pageY.jsx
const PageY =()=> {
const {data:posts} =useSWR(["/api/B",crateDate],fetcher);
return(
<List dataSource={posts} render={p=>(<Post data={p}/>)}/>
)
}
// pageZ.jsx
const PageZ =()=> {
const {data:posts} =useSWR(["/api/C",categoryId], fetcher);
return(
<List dataSource={posts} render={p=>(<Post data={p}/>)}/>
)
}
as you can see, I need to update cached data but I don't know how can I pass the correct SWR key to mutate function in that delete handler. In the end, I have many keys because of API design. Also, I use useSWRInfinite too, and it generates new keys when scrolling down.
And we are using NextJS React Framework.

Reactjs fetch and show data directly

Is there anyway or example to make fetch and show data directly.
Say we have JSON data and we want to show once some data available so if there many objects in the JSON and once one data object is ready we show it and wait until next once and load once ready as well. We do this all in one endpoint.
Regards
What your describing is pretty standard in React.
Say you have your List component, and when the component mounts you watch to make a fetch to your API and render the data. You could do something like the below.
import React, {Component} from 'react'
export default class List extends Component {
state = {
listData: []
}
componentDidMount = () =>{
fetch('YOUR_URL_HERE')
.then(response => response.json())
.then(data => this.setState({ listData: data}));
}
render(){
return(
<div>
{this.state.listData.map((listItem, i)=>{
return <p>{listItem.propertyToRender}</p>
})}
</div>
)
}
}

ReactJS error - “Cannot read property 'data' of undefined”

I’m building a React form that will have several drop-down boxes populated with data from MongoDB. I’m relatively new to React and a beginner with MongoDB.
I’m starting with just trying to create a basic page with one drop-down on it. Once I get that right, I can move on to adding the other drop-downs.
I’ve been able to successfully get the data into the drop-down by just cramming all of the code into one file. Now, I’m trying to start refactoring the code by properly splitting pieces into separate files. And that’s where I’m running into problems.
I’ve split out my MongoDB data pull (using Mongo Stitch) into a separate “service” file. And I’m still getting data through that new service file. But when I then try to pull that service-file data into my main (App.js) page, I’m getting a “Cannot read property 'data' of undefined” error. So, clearly, the way I’m trying to pull the data into my App.js file is wrong. I’d appreciate any expert insights anyone could offer!
Here’s my App.js file –
import React, { Component } from "react";
import "./App.css";
import { getProjects } from "./services/svcProjects";
class App extends Component {
state = {
data: {
selProject: ""
},
Projects: []
};
async componentDidMount() {
await this.fetchProjects();
}
async fetchProjects() {
const { data: projects } = await getProjects();
console.log(projects);
this.setState({ projects });
}
render() {
return (
<div className="App">
<h1>Project Log</h1>
<label htmlFor={"selProject"}>{"Project"}</label>
<select name={"selProject"} id={"selProject"} className="form-control">
<option value="" />
{this.state.projects.map(a => (
<option key={a._id} value={a.project}>
{a.project}
</option>
))}
</select>
</div>
);
}
}
export default App;
And here’s the “projects service” file. Again, please note that the console.log statements here show that I’m still getting data back from MongoDB. I’m just not pulling the data into my App.js file the right way.
Also, by the way, I realize that having my Mongo connection info in this file is a huge security hole. I’ll be fixing that later.
import {
Stitch,
RemoteMongoClient,
AnonymousCredential
} from "mongodb-stitch-browser-sdk";
export function getProjects() {
const client = Stitch.initializeDefaultAppClient("------");
const db = client
.getServiceClient(RemoteMongoClient.factory, "-----")
.db("----------");
client.auth
.loginWithCredential(new AnonymousCredential())
.then(() =>
db
.collection("--------")
.find({}, { sort: { Project: 1 } })
.asArray()
)
.then(res => {
console.log("Found docs", res);
console.log("[MongoDB Stitch] Connected to Stitch");
return res;
})
.catch(err => {
console.error(err);
});
}
It looks like you're using destructuring to get the data member from an object returned by getProjects(), but getProjects() doesn't return an object with such a member.
Perhaps you'd like to change it to something like the following -
async fetchProjects() {
const projects = await getProjects();
console.log(projects);
this.setState({ projects });
}
Also, like #Win mentioned, the P in projects is capitalized in your state initialization but not afterwards. You might wanna fix that.
I've discovered that my problem boils down to the fact that my data isn't arriving in the componentDidMount lifecycle hook soon enough. So, by the time the process moves on, the "projects" data is still undefined. In other words, my "async / await" isn't . . . awaiting!
I'll be posting a separate question about how I can make that async wait until I actually get the data back. But again, just wanted to mark this as "solved" because the problem isn't happening downstream from the data fetch. The data simply isn't arriving soon enough.

How to determine mutation loading state with react-apollo graphql

2018 Update: Apollo Client 2.1 added a new Mutation component that adds the loading property back. See #robin-wieruch's answer below and the announcement here https://dev-blog.apollodata.com/introducing-react-apollo-2-1-c837cc23d926 Read on for the original question which now only applies to earlier versions of Apollo.
Using the current version of the graphql higher-order-component in react-apollo (v0.5.2), I don't see a documented way to inform my UI that a mutation is awaiting server response. I can see that earlier versions of the package would send a property indicating loading.
Queries still receive a loading property as documented here: http://dev.apollodata.com/react/queries.html#default-result-props
My application is also using redux, so I think one way to do it is to connect my component to redux and pass down a function property that will put my UI into a loading state. Then when rewriting my graphql mutation to a property, I can make calls to update the redux store.
Something roughly like this:
function Form({ handleSubmit, loading, handleChange, value }) {
return (
<form onSubmit={handleSubmit}>
<input
name="something"
value={value}
onChange={handleChange}
disabled={loading}
/>
<button type="submit" disabled={loading}>
{loading ? 'Loading...' : 'Submit'}
</button>
</form>
);
}
const withSubmit = graphql(
gql`
mutation submit($something : String) {
submit(something : $something) {
id
something
}
}
`,
{
props: ({ ownProps, mutate }) => ({
async handleSubmit() {
ownProps.setLoading(true);
try {
const result = await mutate();
} catch (err) {
// #todo handle error here
}
ownProps.setLoading(false);
},
}),
}
);
const withLoading = connect(
(state) => ({ loading: state.loading }),
(dispatch) => ({
setLoading(loading) {
dispatch(loadingAction(loading));
},
})
);
export default withLoading(withSubmit(Form));
I'm curious if there is a more idiomatic approach to informing the UI that the mutation is "in-flight." Thanks.
Anyone who stumbles across this question, since Apollo Client 2.1 you have access to those properties in the Query and Mutation component's render props function.
import React from "react";
import { Mutation } from "react-apollo";
import gql from "graphql-tag";
const TOGGLE_TODO = gql`
mutation ToggleTodo($id: Int!) {
toggleTodo(id: $id) {
id
completed
}
}
`;
const Todo = ({ id, text }) => (
<Mutation mutation={TOGGLE_TODO} variables={{ id }}>
{(toggleTodo, { loading, error, data }) => (
<div>
<p onClick={toggleTodo}>
{text}
</p>
{loading && <p>Loading...</p>}
{error && <p>Error :( Please try again</p>}
</div>
)}
</Mutation>
);
Note: Example code taken from the Apollo Client 2.1 release blog post.
I have re-posted this question on github and the suggested solution was to use something like a react higher order component just as you proposed in your original question. I did a similar thing – without using redux though – as outlined in this gist.
To cite Tom Coleman's response from the github issue:
It doesn't really make sense to include loading state on the mutation
container; if you think about it you could call the mutation twice
simultaneously -- which loading state should get passed down to the child? My
feeling is in general it's not nice to mix imperative (this.mutate(x, y, z))
with declarative (props) things; it leads to irresolvable inconsistencies.

Resources