I understand/believe I should favour Functional Components instead of Class Components in order to apply Responsibility Isolation. With advent of React Hooks it brings a new approach to avoid Class and still have the feature keep state.
I create a very simple Web Flux rest service. I created a very simple React page using Hooks and Axon and I am getting "data.map is not a function at RestApiHooksComponent". If I try this exact same code consuming a typical rest service it will work properly (by typical rest service I mean a syncronous code without any reactive feature).
I guess I am missing some basic idea when mathing React with Web Flux. There are few examples around showing how to consume Webflux by React but no one showing how using Hooks (at least I didn't find it). Isn't Hooks compliance with Reactive paradigma? Based on my React limited knowlodge I guess it is going to be a good match: common user case is consuming a list from service that come in stream and showing it as it is available.
Front End
package.json
...
"dependencies": {
"axios": "^0.18.0",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"react-scripts": "3.0.1"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
}
...
App.js
import React, { useEffect, useState } from "react";
import axios from "axios";
export default function RestApiHooksComponent() {
const [data, setData] = useState([]);
useEffect(() => {
axios
.get("http://127.0.0.1:8080")
.then(result => setData(result.data));
}, []);
return (
<div>
<ul>
{data.map(item => (
<li key={item.result}>
{item.result}: {item.result}
</li>
))}
</ul>
</div>
);
}
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
ReactDOM.render(<App />, document.getElementById('root'));
LoanTable.js
import React from 'react'
const LoanTable = props => (
<table>
<thead>
<tr>
<th>Result</th>
</tr>
</thead>
<tbody>
{props.loans.length > 0 ? (
props.loans.map(loan => (
<tr>
<td>{loan.result}</td>
</tr>
))
) : (
<tr>
<td>No loans</td>
</tr>
)}
</tbody>
</table>
)
export default LoanTable
Webflux Rest Service
Controller:
@RestController
public class LoansController {
@Autowired
private LoansService loansService;
@CrossOrigin
@RequestMapping(method = RequestMethod.GET, produces = MediaType.TEXT_EVENT_STREAM_VALUE)
@ResponseBody
public Flux<Loans> findAll() {
Flux<Loans> loans = loansService.findAll();
return loans;
}
}
Service:
@Service
public class LoansService implements ILoansService {
@Autowired
private LoansRepository loansRepository;
@Override
public Flux<Loans> findAll() {
return loansRepository.findAll();
}
}
Webflux config
@Configuration
@EnableWebFlux
public class WebFluxConfig implements WebFluxConfigurer
{
}
NOT MUCH RELEVANT DETAILS BUT I ADDED TO SHOW IT IS COMPLETELY NOBLOCKING CODE:
MongoDb config
@Configuration
@EnableReactiveMongoRepositories(basePackages = "com.mybank.web.repository")
public class MongoConfig extends AbstractReactiveMongoConfiguration
{
@Value("${port}")
private String port;
@Value("${dbname}")
private String dbName;
@Override
public MongoClient reactiveMongoClient() {
return MongoClients.create();
}
@Override
protected String getDatabaseName() {
return dbName;
}
@Bean
public ReactiveMongoTemplate reactiveMongoTemplate() {
return new ReactiveMongoTemplate(reactiveMongoClient(), getDatabaseName());
}
}
Pom:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.mybank</groupId>
<artifactId>web</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>web</name>
<description>webflux</description>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb-reactive</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
*** edited
web flux service output
data:{"timestamp":1558126555269,"result":"8"} data:{"timestamp":1558132444247,"result":"10"} data:{"timestamp":1558132477916,"result":"10"} data:{"timestamp":1558132596327,"result":"14"}
Your React component works fine.
Try to log the backend result before setting it into the data state at useEffect. The problem is your result, it's may be not an array and the map
function is only available on array object (in this case).
useEffect(() => {
axios
.get("http://127.0.0.1:8080")
.then(result => {
const items = result.data // It should be an array to map
console.log(items)
if (items && items.length) {
setData(items)
}
});
}, []);