useLoadScript React Hook needs data from a backend API request, but react hook can't be run conditionally - node.js

I have attempted to hide my Google API key in my Node.js .env file. So I use axios to fetch it when I need it in my Map.js. I also use a useLoadScript React Hook which needs the API key in order to render the map.
However, when I do this I get this error:
"You have included the Google Maps JavaScript API multiple times on this page. This may cause unexpected errors."
This is because the apiKey is null for the first three renders and useLoadScript wants to run one time with the correct apiKey. So what I don't understand is how I can only run it one time when the apiKey has arrived from Node.js?
This is my first attempt:
import React from 'react';
import { GoogleMap, useLoadScript} from '#react-google-maps/api';
import useWindowDimensions from '../getWindowDimensions';
import './Map.css';
import axios from 'axios';
//coordinates to the house
const center = {lat: 38.15230, lng: -80.34552};
const zoom = 16;
const MapStyle = {
height: '60%',
width: '40%'
}
const Map_resized = {
height: '60%',
width: '80%'
}
const Map_resized_small = {
height: '415px',
width: '80%'
}
const GOOGLE_MAPS_LIBRARIES = ['places'];
function Map() {
const width = useWindowDimensions().width;
const [apiKey, setApiKey] = React.useState(null);
console.log(apiKey);
React.useEffect(() => {
axios.get("/googleMapsApiKey")
.then((res) => {setApiKey(res.data.key)})
.catch((error) => console.error(error));
}, []);
const { isLoaded, loadError } = useLoadScript({
googleMapsApiKey: apiKey,
libraries: GOOGLE_MAPS_LIBRARIES
});
if (isLoaded) {
return (
<GoogleMap
mapContainerStyle={(width < 1152) ? (width < 629) ? Map_resized_small : Map_resized : MapStyle}
center={center}
zoom={zoom}
/>
);
}
else if (loadError) {
return (
<span>Error loading map!</span>
);
}
else {
return (
<span>Map Loading</span>
);
}
}
export default Map;
Error: You have included the Google Maps JavaScript API multiple times on this page. This may cause unexpected errors.
Then I tried to run useLoadScript only when the apiKey has arrived from the backend.
import React from 'react';
import { GoogleMap, useLoadScript} from '#react-google-maps/api';
import useWindowDimensions from '../getWindowDimensions';
import './Map.css';
import axios from 'axios';
//coordinates to the house
const center = {lat: 38.15230, lng: -80.34552};
const zoom = 16;
const MapStyle = {
height: '60%',
width: '40%'
}
const Map_resized = {
height: '60%',
width: '80%'
}
const Map_resized_small = {
height: '415px',
width: '80%'
}
const GOOGLE_MAPS_LIBRARIES = ['places'];
function Map() {
const width = useWindowDimensions().width;
const [apiKey, setApiKey] = React.useState(null);
console.log(apiKey);
React.useEffect(() => {
axios.get("/googleMapsApiKey")
.then((res) => {setApiKey(res.data.key)})
.catch((error) => console.error(error));
}, []);
if (apiKey) {
const { isLoaded, loadError } = useLoadScript({
googleMapsApiKey: apiKey,
libraries: GOOGLE_MAPS_LIBRARIES
});
}
if (isLoaded) {
return (
<GoogleMap
mapContainerStyle={(width < 1152) ? (width < 629) ? Map_resized_small : Map_resized : MapStyle}
center={center}
zoom={zoom}
/>
);
}
else if (loadError) {
return (
<span>Error loading map!</span>
);
}
else {
return (
<span>Map Loading</span>
);
}
}
export default Map;
Error: React Hook "useLoadScript" is called conditionally. React Hooks must be called in the exact same order in every component render
I'm not really sure what else to try here other than put the google api key on the client side. But everything I research online tells me that it's bad practice. I know for sure that my key is arriving because I used console.log() to check.
[SOLUTION]
The trick was to use conditional functional components instead. I don't know if this is the best way to do it but this got rid of my warnings and errors.
import React from 'react';
import { GoogleMap, useLoadScript} from '#react-google-maps/api';
import useWindowDimensions from '../getWindowDimensions';
import './Map.css';
import axios from 'axios';
//coordinates to the house
const center = {lat: 40.15230, lng: -80.34552};
const zoom = 16;
const MapStyle = {
height: '60%',
width: '40%'
}
const Map_resized = {
height: '60%',
width: '80%'
}
const Map_resized_small = {
height: '415px',
width: '80%'
}
const GOOGLE_MAPS_LIBRARIES = ['places'];
function MapWithLoadScript({ apiKey }) {
const width = useWindowDimensions().width;
const { isLoaded, loadError } = useLoadScript({
googleMapsApiKey: apiKey,
libraries: GOOGLE_MAPS_LIBRARIES,
});
if (isLoaded) {
return (
<GoogleMap
mapContainerStyle={(width < 1152) ? (width < 629) ? Map_resized_small : Map_resized : MapStyle}
center={center}
zoom={zoom}
/>
);
}
if (loadError) {
return <span>Error loading map!</span>;
}
return <span>Map Loading</span>;
}
function Map() {
const [apiKey, setApiKey] = React.useState(null);
console.log(apiKey);
React.useEffect(() => {
axios.get("/googleMapsApiKey")
.then((res) => {setApiKey(res.data.key)})
.catch((error) => console.error(error));
}, []);
if (apiKey === null) {
return <span>Loading API key...</span>;
}
else {
return <MapWithLoadScript apiKey={apiKey} />;
}
}
export default Map;

Related

Unhandled Runtime Error ReferenceError: require is not defined (Electron + Next.js)

I'm writing (Electron + Next.js) nextron desktop app.
I want to use contextIsolation=true , when i enabled contextIsolation=true and use import in renderer file like (import axios from 'axios') and something like that (third party package) it shows required is not define i think so its webpack error.
Need help to solve this issue
Error message:
Preload.js
import { contextBridge, ipcRenderer } from 'electron'
//i try both import or require
contextBridge.exposeInMainWorld('electronAPI', {
setTitle: (title) => ipcRenderer.send('set-title', title)
})
**Renderer.js **
import React, { useEffect } from 'react';
import Head from 'next/head';
import Link from 'next/link';
//When i import axios here, it doesn't works
import axios from 'axios'
function Home() {
useEffect(() => {
const setButton = document.getElementById('btn')
const titleInput = document.getElementById('title')
setButton.addEventListener('click', () => {
const title = titleInput.value
window.electronAPI?.setTitle(title)
});
data()
}, [])
return (
<React.Fragment>
<Head>
<title>Home - Nextron (with-typescript-tailwindcss)</title>
</Head>
Title: <input id="title" />
<button id="btn" type="button">Set</button>
</React.Fragment>
);
}
export default Home;
Background.ts
import { app, ipcMain, BrowserWindow } from "electron";
import serve from "electron-serve";
import path from "path";
import { createWindow } from "./helpers";
const isProd: boolean = process.env.NODE_ENV === "production";
if (isProd) {
serve({ directory: "app" });
} else {
app.setPath("userData", `${app.getPath("userData")} (development)`);
}
(async () => {
await app.whenReady();
const mainWindow = createWindow("main", {
width: 1000,
height: 600,
webPreferences: {
nodeIntegration:true,
contextIsolation: true,
preload: path.join(__dirname, "preload.js"),
},
});
if (isProd) {
await mainWindow.loadURL("app://./home.html");
} else {
const port = process.argv[2];
await mainWindow.loadURL(`http://localhost:${port}/home`);
mainWindow.webContents.openDevTools();
}
})();
ipcMain.on("set-title", (event, title) => {
const webContents = event.sender;
const win = BrowserWindow.fromWebContents(webContents);
win.setTitle(title);
console.log(title, "title");
});
app.on("window-all-closed", () => {
app.quit();
});
NextConfig.js
module.exports = {
webpack: (config, { isServer }) => {
if (!isServer) {
config.target = 'electron-renderer';
config.node = {
__dirname: true,
};
}
config.output.globalObject = 'this';
return config;
},
};

I wonder why there are different results from errors when I use express, axios, and cors

When i'm crawling to my college site of one part, there is error.
Failed to load resource: net::ERR_CONNECTION_REFUSED (localhost:3000/:1)
createError.js:16 Uncaught (in promise) Error: Network Error
at createError (createError.js:16)
at XMLHttpRequest.handleError (xhr.js:99)
I've tried other things but I'm not sure why it is getting shows 'Load...' and in the console there is error.
The site is working but crawling is not working properly and showing 'Load...'
this is kangnam.js :
const axios = require('axios');
const cheerio = require('cheerio');
const log = console.log;
const express = require("express");
const cors = require("cors");
const PORT = 4000;
const app = express();
app.use(cors());
const getHTML = async () => {
try {
return await axios.get('https://web.kangnam.ac.kr', {
headers: {
Accept: 'text/html'
}
});
} catch (error) {
console.log(error);
}
};
app.get("/", (req, res) => {
getHTML()
.then(html => {
const $ = cheerio.load(html.data);
const $allNotices = $("ul.tab_listl div.list_txt");
let resultArr = [];
$allNotices.each(function(idx, element) {
let itemObj = {
title : $(this).children('a').attr('title'),
url : $(this).children('a').attr('href'),
};
resultArr.push(itemObj);
});
resultArr.forEach((element) => {
console.log(`현재 ${element._title}의 현황 : ${element._url}`);
});
return resultArr;
// const data = ulList.filter(n => n.title);
// return data;
}). then((data) => res.send(data));
});
app.listen(PORT, () =>
console.log(`Example app listening at http://localhost:${PORT}`)
);
this is NoticesList.js :
import React, {useEffect, useState} from 'react';
import styled from 'styled-components';
import NoticesItem from './NoticesItem';
import axios from "axios";
const NoticesListBlock = styled.div`
box-sizing: border-box;
padding-bottom: 3rem;
width: 768px;
margin: 0 auto;
margin-top: 2rem;
`;
const sampleArticle = {
title: 'title',
url: 'https://google.com',
};
const NoticesList = () => {
const [data, setData] = useState(null);
useEffect(() => {
const getData = async() => {
const datas = await axios.get("http://localhost:3000/");
setData(datas.data);
};
getData();
}, []);
useEffect(() => {
console.log(data);
}, [data]);
if(data === null){
return <div>Load....</div>;
}else{
console.log(data);
return (
<div>
{data.map((ele) => (
<>
<div>
{ele.title};
</div>
<br/>
</>
))}
</div>
);
};
};
export default NoticesList;
this is NoticesItem.js :
import React from 'react';
import styled from 'styled-components';
const NoticesItemBlock = styled.div`
display: flex;
.contents {
h6 {
margin: 15px;
a {
color: black;
}
}
}
& + & {
margin-top: 1.5rem;
}
`;
const NoticesItem = ({ article }) => {
const { title, url } = article;
return (
<NoticesItemBlock>
<div className="contents">
<h6>
<a href={url} target="_blank" rel="noopener noreferrer">
{title}
</a>
</h6>
</div>
</NoticesItemBlock>
);
};
export default NoticesItem;
try deploying an API on goormide, just change the last part of kangnam.js for:
app.listen(3000, function() {
console.log('Server listening on port 3000');
});
this will give a link like: https://server-clgoz.run-eu-central1.goorm.io/
wich you will use instead of: http://localhost:4000/
on NoticesList.js
Also in this file you can set a variable like:
const site="https://web.kangnam.ac.kr/";
and then in the map function:
<a href={site+decodeURIComponent(ele.url.slice(17))}>{ele.title}</a>
this will give the links to the site.
Good luck :) !!!

async function not workin

im trying to use async function to consume my api, but it simply doesent work, it didnt even console log anything, here is my code:
import React, { Component } from 'react';
import { View, Text, StyleSheet, Dimensions } from 'react-native';
import { ScrollView } from 'react-native-gesture-handler';
import {NavigationContainer} from '#react-navigation/native'
import { SafeAreaView } from 'react-navigation';
import Profile from './Profile'
import api from '../services/api';
//import Home from './Mainpage'
const {heigth, width} = Dimensions.get('window')
here is just styles
styles = StyleSheet.create({
Container:{
flex:1,
justifyContent:"flex-end",
alignItems: "flex-end",
},
PlaceContainer:{
width: '100%',
maxHeight:200,
},
button:{
width:"10px",
height:"10px"
},
title:{
fontWeight:'bold',
fontSize: 20
},
place:{
width:width -40 ,
padding:20,
maxHeight:200,
marginHorizontal: 20,
marginVertical: 8,
backgroundColor: '#0ff'
},
separator:{
flex: 1,
height: StyleSheet.hairlineWidth,
backgroundColor: '#8E8E8E',
}
})
here I created a class to make everything
export default class Dermatologistas extends Component{
state ={
MessageError: null,
users: []
}
that is not working, it isnt even console logging
getUserList = async () => {
try {
const response = await api.get('/auth/list');
const { users } = response.data;
this.setState({ users });
} catch (response) {
this.setState({ errorMessage: response.data.error });
}
};
just rendered to test
render(){
const users = this.state.users
in this console.log it return Array []
console.log(users)
return(
<View>
{this.state.users.map(user => (
<View key={user._id} style={{marginTop: 15}}>
<Text>{user.title}</Text>
<Text>{user.speciality}</Text>
</View>
))}
</View>
)
}
}
Where's the call to getUserList?
Also, to your get method you should include the promise method then() which followed by a catch() method.
To summarize, your call should look like that:
fetch(url)
.then(function() {
})
.catch(function() {
});

Jest - Invariant failed: You should not use <Link> outside a <Router>

I'm getting this error when running a test, however I have wrapped the component I am testing in a <BrowserRouter> component:
● Axios › gets a response
Invariant failed: You should not use <Link> outside a <Router>
91 |
92 | if (RootComponent) {
> 93 | const component = mountWithCustomWrappers(<Wrapper store={store}><RootComponent
{...props} />, rootWrappers);
nock.integration.test
import React from 'react';
import axios from 'axios';
const core = require('tidee-life-core');
import httpAdapter from 'axios/lib/adapters/http';
import doFetch from '../../../helpers/doFetch.js';
import ComponentBuilder from "../component-builder";
import LoginPage from "../../../scenes/login/login-page";
const host = 'http://example.com';
process.env.API_URL = host;
axios.defaults.host = host;
axios.defaults.adapter = httpAdapter;
const makeRequest = () => {
return doFetch({
url: core.urls.auth.login(),
queryParams: { foo: 'bar' },
})
.then(res => res.data)
.catch(error => console.log(error));
};
describe('Axios', () => {
let component;
let componentBuilder;
beforeEach(() => {
componentBuilder = new ComponentBuilder();
});
test('gets a response', async () => {
componentBuilder.includeInterceptor('login');
component = await componentBuilder.build({
RootComponent: LoginPage,
selector: 'LoginForm',
});
return makeRequest()
.then(response => {
expect(typeof response).toEqual('object');
expect(response.data.token).toEqual('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhNDY3MGE3YWI3ZWM0ZjQ2MzM4ODdkMzJkNzRkNTY5OSIsImlhdCI6MTU1MjA5MDI1NX0.vsKLXJEqSUZK-Y6IU9PumfZdW7t1SLM28jzJL89lcrA');
});
});
});
login-page.jsx:
import React, { Component } from 'react';
import PropTypes from "prop-types";
import { withRouter } from 'react-router-dom';
import { injectIntl, intlShape } from 'react-intl';
import queryString from 'query-string';
import { Link } from 'tidee-life-ui';
import LoginForm from './login-form.jsx';
import { doLogin } from '../../api/auth/auth-api';
import Auth from '../../modules/Auth';
import messages from '../../messages';
const { urls } = require('tidee-life-core');
class LoginPage extends Component {
constructor(props) {
super(props);
const { intl } = props;
if (Auth.isUserAuthenticated()) {
props.history.replace({ pathname: urls.pages.pathBoxes() });
}
this.messages = {
'account.activation.error.expired': intl.formatMessage(messages['account.activation.error.expired']),
'account.activation.required': intl.formatMessage(messages['account.activation.required']),
'common.click': intl.formatMessage(messages['common.click']),
'common.here': intl.formatMessage(messages['common.here']),
'error.500.msg': intl.formatMessage(messages['error.500.msg']),
'forgot.success': intl.formatMessage(messages['forgot.success']),
'login.account.needs.activating.partial': intl.formatMessage(messages['login.account.needs.activating.partial']),
'login.error.account.credentials': intl.formatMessage(messages['login.error.account.credentials']),
'login.validation.email': intl.formatMessage(messages['login.validation.email']),
'login.validation.password': intl.formatMessage(messages['login.validation.password']),
'signup.account.created': intl.formatMessage(messages['signup.account.created'])
};
let alertMessage;
let alertMessageType;
const query = queryString.parse(props.location.search);
if ('signup-success' in query) {
alertMessage = this.messages['signup.account.created'];
alertMessageType = 'success';
} else if ('forgot-success' in query) {
alertMessage = this.messages['forgot.success'];
alertMessageType = 'success';
}
this.state = {
alert: {
type: alertMessageType ? alertMessageType : '',
msg: alertMessage ? alertMessage : '',
},
user: {
email: '',
password: ''
}
};
this.changeUser = this.changeUser.bind(this);
this.clearAlert = this.clearAlert.bind(this);
this.processForm = this.processForm.bind(this);
}
clearAlert() {
this.setState({ alert: {
type: '',
msg: '',
}});
}
processForm(e) {
e.preventDefault();
return doLogin({
email: this.state.user.email,
password: this.state.user.password,
}).then((response) => {
Auth.authenticateUser(response.data.token);
this.props.history.replace({ pathname: urls.pages.pathBoxes() });
}).catch((error) => {
const msg = error.message && this.messages[error.message] ? [this.messages[error.message]] : [this.messages['error.500.msg']];
if (error.message === 'account.activation.error.expired' || error.message === 'account.activation.required') {
const to = urls.pages.pathResendLink(error.data.confirmHash);
msg.push(` ${this.messages['common.click']} `);
msg.push(<Link underline color="inherit" key="email" to={to}>{this.messages['common.here']}</Link>);
msg.push(` ${this.messages['login.account.needs.activating.partial']}`);
}
this.setState({
alert: {
type: 'error',
msg,
}
});
});
}
changeUser(event) {
const { name, value } = event.target;
this.setState((currentState) => ({
user: {
...currentState.user,
[name]: value,
}
}));
}
render() {
return (
<LoginForm
data-test="login-form"
alert={this.state.alert}
onSubmit={this.processForm}
onChange={this.changeUser}
user={this.state.user}
/>
);
}
}
LoginPage.propTypes = {
history: PropTypes.object,
intl: intlShape.isRequired,
location: PropTypes.object.isRequired,
};
export default injectIntl(withRouter(LoginPage));
component-builder.js
import React from "react";
import nock from 'nock';
import cloneDeep from 'lodash.clonedeep';
import { mountWithCustomWrappers } from 'enzyme-custom-wrappers';
import { waitForStoreState } from './wait/wait-for-store-state';
import Wrapper from './wrapper.jsx';
import waitForComponentPredicate from './wait-for-component-predicate/wait-for-component-predicate';
import waitForComponentSelector from './wait-for-component-selector/wait-for-component-selector';
import { startAllNockServiceIntercepts } from './nock/nock-manager';
import nockServices from './nock/services';
import store from "../../store/store";
import wrappers from './wrappers';
const rootWrappers = component => wrappers(component);
class ComponentBuilder {
constructor() {
this.nockInterceptors = [];
this.storePreparers = [];
}
includeInterceptor( interceptorName, nockConfigOverride = null ) {
// Maybe need to do a clone deep here if things start breaking!
const clonedNockService = cloneDeep(nockServices[interceptorName]().nockConfig);
const nockService = {
[interceptorName]: {
...clonedNockService,
...(nockConfigOverride || {}),
}
};
this.nockInterceptors.push(nockService);
}
prepareStore( storePreparer ) {
this.storePreparers.push(storePreparer);
}
async waitForStoreToUpdate() {
const promises = this.storePreparers
.map(service => waitForStoreState(service.redux.storeStateToWaitFor, store));
await Promise.all(promises);
}
async runStorePreparers() {
nock.cleanAll();
const interceptors = [];
this.storePreparers.forEach((service) => {
const interceptorName = service.http.interceptor;
const clonedNockService = service.http.interceptor && cloneDeep(nockServices[interceptorName]().nockConfig);
interceptors.push({
[interceptorName]: {
...clonedNockService,
}
});
});
startAllNockServiceIntercepts(interceptors);
this.storePreparers.forEach(service => service.redux.actionToDispatch && store.dispatch(service.redux.actionToDispatch()));
return await this.waitForStoreToUpdate();
}
/**
* Build a component to be tested.
* #param RootComponent
* #param selector {string} - A selector to wait for. CSS selector or name of component.
* #param props {object}
* #param store {object}
* #param predicate {function} - A function that returns true if a condition is met.
* #param predicateMaxTime {number}
* #param predicateInterval {number}
* #returns {Promise<*>}
*/
async build({
RootComponent = null,
selector = '',
props = {},
predicate = null,
predicateMaxTime = 2000,
predicateInterval = 10,
} = {}) {
try {
await this.runStorePreparers();
startAllNockServiceIntercepts(this.nockInterceptors);
if (RootComponent) {
const component = mountWithCustomWrappers(<Wrapper store={store}><RootComponent {...props} /></Wrapper>, rootWrappers);
if (selector) {
await waitForComponentSelector({ selector, rootComponent: component, store });
}
if (predicate) {
await waitForComponentPredicate({
predicate,
rootComponent: component,
store,
maxTime: predicateMaxTime,
interval: predicateInterval,
});
}
return component;
}
} catch(err) {
throw err;
}
}
}
export default ComponentBuilder;
wrapper.jsx
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { BrowserRouter } from "react-router-dom";
import { Provider } from 'react-redux';
import ThemeProvider from "../../theme/Theme.jsx";
import LocaleProviderWrapper from "./locale-provider-wrapper.jsx";
const propTypes = {
children: PropTypes.element.isRequired,
store: PropTypes.object.isRequired,
};
class Wrapper extends Component {
getStore() {
return this.props.store;
}
render() {
return (
<Provider store={this.props.store}>
<LocaleProviderWrapper>
<ThemeProvider>
<BrowserRouter>{this.props.children}</BrowserRouter>
</ThemeProvider>
</LocaleProviderWrapper>
</Provider>
);
}
}
Wrapper.propTypes = propTypes;
export default Wrapper;
Exactly as message says, you cannot use Link which doesn't have any parent of type Router. In you processForm function you are building a messaage with Link component which is worng.
if (error.message === 'account.activation.error.expired' || error.message === 'account.activation.required') {
const to = urls.pages.pathResendLink(error.data.confirmHash);
msg.push(` ${this.messages['common.click']} `);
msg.push(<Link underline color="inherit" key="email" to={to}>{this.messages['common.here']}</Link>);
msg.push(` ${this.messages['login.account.needs.activating.partial']}`);
}
You should use a tag to build dynamic link. May be something like:
msg.push(`${this.messages['common.here']}`);

Action creator is being dispatched but the new state is not showing in store

I have written a server and wired up React with Redux, also made use of container-componenent-seperation. The first action fetchSnus() is successfully dispatched, the selectors also seem to work, but for some reason all the actions that I call after the first rendering, such as fetchSnu() (that only fetches ONE single snu-object) e.g. is not accessible for me in the store, meaning: after mapping state and dispatch to props not accessible for me under this.props. I really don't understand why!
You can see the whole project here: https://github.com/julibi/shonagon
This is my container ReadSingleSnuContainer.js:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchSnus, fetchSnu, setToRead, createSnu, getSnusMatchingKeyword } from '../../actions/index';
import { unreadSnus, randomFirstSnu } from '../../selectors/index';
import ReadSingleSnu from './ReadSingleSnu';
class ReadSingleSnuContainer extends Component {
componentWillMount() {
this.props.fetchSnus();
}
render() {
return (
<ReadSingleSnu { ...this.props } />
);
}
}
const mapStateToProps = (state) => {
return {
snus: state.snus,
randomFirstSnu: randomFirstSnu(state),
candidate: state.candidate
};
}
const mapDispatchToProps = (dispatch) => {
return {
fetchSnus: () => dispatch(fetchSnus()),
getSnusMatchingKeyword,
fetchSnu,
setToRead,
createSnu
}
};
export default connect(mapStateToProps, mapDispatchToProps)(ReadSingleSnuContainer);
This is my component ReadSingleSnu.js:
import React, { Component } from 'react';
import style from './ReadSingleSnu.css'
import Typist from 'react-typist';
import ReactCSSTransitionGroup from 'react-addons-css-transition-group';
export default class ReadSingleSnu extends Component {
constructor(props) {
super(props);
this.state = { showTitles: false };
}
renderRandomFirstSnu() {
const { randomFirstSnu } = this.props;
const { showTitles } = this.state;
// preselect a keyword
// dispatch the action that searches for other keywords (action I)
if(randomFirstSnu) {
return (
<div>
<h3><Typist cursor={ { show: false } }>{ randomFirstSnu.title }</Typist></h3>
<ReactCSSTransitionGroup
transitionName="snu"
transitionAppear={ true }
transitionAppearTimeout={ 1000 }
transitionEnter={ false }
transitionLeave={ false }
>
<p>{ randomFirstSnu.text }</p>
</ReactCSSTransitionGroup>
{ !showTitles ? (
<div>
<button>Some other</button>
<button onClick={ () => this.handleDoneReading(randomFirstSnu) }>Done reading, next</button>
</div>
) : (
<ReactCSSTransitionGroup
transitionName="keywords"
transitionAppear={ true }
transitionAppearTimeout={ 1000 }
transitionEnter={ false }
transitionLeave={ false }
>
<ul>{ randomFirstSnu.keywords.map((keyword, idx) =>
<li key={ idx }>
<button onClick={ () => this.fetchNextSnu(randomFirstSnu) }>
{ keyword }
</button>
</li>) }
</ul>
</ReactCSSTransitionGroup>
)
}
</div>
);
}
return <div>Loading ...</div>
}
handleDoneReading(snu) {
const { setToRead, getSnusMatchingKeyword } = this.props;
const id = snu._id;
if (snu.keywords.length > 0 && setToRead) {
// setToRead(id, snu);
this.setState({ showTitles: true });
const randomIndex = Math.floor(Math.random() * snu.keywords.length);
const randomKeyword = snu.keywords[randomIndex];
console.log('This is the randomKeyword :', randomKeyword);
getSnusMatchingKeyword(randomKeyword);
} else {
console.log('Here will soon be the select random next snu action :)');
}
}
render() {
console.log('ReadSingleSnu, this.props: ', this.props);
return (
<div className={style.App}>
<div>{ this.renderRandomFirstSnu() }</div>
</div>
);
}
}
This is my actions file:
import axios from 'axios';
export const FETCH_SNUS = 'FETCH_SNUS';
export const FETCH_SNU = 'FETCH_SNU';
export const SET_TO_READ = 'SET_TO_READ';
export const CREATE_SNU = 'CREATE_SNU';
export function getSnusMatchingKeyword(keyword) {
const request = axios.get(`/snus/keyword/${keyword}`);
return {
type: GET_SNUS_MATCHING_KEYWORD,
payload: request
};
}
export function fetchSnus() {
const request = axios.get('/snus');
return {
type: FETCH_SNUS,
payload: request
};
}
export function fetchSnu(id) {
const request = axios.get(`/snus/${id}`);
return {
type: FETCH_SNU,
payload: request
};
}
export function setToRead(id, snu) {
const request = axios.patch(`/snus/${id}`, { title: snu.title, text: snu.text, keywords: snu.keywords, read: true });
return {
type: SET_TO_READ,
payload: request
}
}
export function createSnu(object) {
const request = axios.post('/snus', object);
return {
type: CREATE_SNU,
payload: request
};
}
A Reducer:
import { FETCH_SNUS, FETCH_SNU, SET_TO_READ, CREATE_SNU } from '../actions/index';
export default function(state = [], action) {
switch(action.type) {
case FETCH_SNUS:
return [ ...state, ...action.payload.data ];
case FETCH_SNU:
return [ ...state, ...action.payload.data ];
case SET_TO_READ:
return [ ...state, ...action.payload.data ];
case CREATE_SNU:
return [...state, ...action.payload.data ];
default:
return state;
}
}
I tested all endpoints via Postman and they work. So that should not be the problem… Please help! I cannot find a solution to this problem.

Resources