How do I rerender a different React functional component? - javascript

I am trying to solve how to rerender a different functional component.
onComplete: function (data) {
console.log("everything is complete");
console.log("this is the applicant id", id);
let obj;
axios
.post("http://localhost:5000/post_id", {
applicant_id: id,
})
.then((response) => {
obj = response.data.data.data.json_data.result;
});
setTimeout(() => {
if (obj === "clear") {
onfidoOut.tearDown(); // <- the Onfido view did not unmount
renderResult(); // <- BAD APPROACH? `renderResult` renders a HTML node instead of triggers a view rerender.
}
}, 3000);
},
I embarked upon an approach of rendering an HTML node instead of triggering a view rerender because I have never implemented a view rerender to a different component outside of an authentication flow in a class-based component. Also this is a microfrontend application. This is the whole file from where the snippet above is obtained:
const useOnfidoFetch = (URL) => {
const [token, setToken] = useState();
const [id, setId] = useState();
useEffect(() => {
axios
.get("http://localhost:5000/post_stuff")
.then((response) => response.data.data.data.json_data)
.then((json_data) => {
console.log("this is the json data", json_data);
const id = json_data.applicant_id;
const token = json_data.onfido_sdk_token;
setId(id);
setToken(token);
});
}, [URL]);
useEffect(() => {
if (!token) return;
console.log("this is working!");
OnfidoSDK.init({
token,
containerId: "root",
steps: [
{
type: "welcome",
options: {
title: "Open your new bank account",
},
},
"document",
],
onComplete: function (data) {
console.log("everything is complete");
console.log("this is the applicant id", id);
let obj;
axios
.post("http://localhost:5000/post_id", {
applicant_id: id,
})
.then((response) => {
obj = response.data.data.data.json_data.result;
});
setTimeout(() => {
if (obj === "clear") {
onfidoOut.tearDown();
renderResult();
}
}, 3000);
},
});
}, [id, token]);
function renderResult() {
return <Redirect to="/result" />;
}
};
export default function() {
const URL = `${transmitAPI}/anonymous_invoke?aid=onfido_webapp`;
const result = useOnfidoFetch(URL, {});
return (
<div id={onfidoContainerId} />
)
}
And this is the bootstrap.js file:
import React from 'react';
import ReactDOM from 'react-dom';
import {createMemoryHistory, createBrowserHistory} from 'history';
import App from './App';
// Mount function to start up the app
const mount = (el, { onNavigate, defaultHistory, initialPath}) => {
const history = defaultHistory || createMemoryHistory({
initialEntries: [initialPath],
});
if (onNavigate) {
history.listen(onNavigate);
}
ReactDOM.render(<App history={history} />, el);
return {
onParentNavigate({ pathname: nextPathname }) {
const {pathname} = history.location;
if (pathname !== nextPathname) {
history.push(nextPathname);
}
},
};
};
// If I am in development and isolation
// call mount immediately
if (process.env.NODE_ENV === 'development') {
const devRoot = document.querySelector('#_marketing-dev-root');
if (devRoot) {
mount(devRoot, {defaultHistory: createBrowserHIstory()});
}
}
// Assuming we are running through container
// and we should export the mount function
export {mount};

It seems obj (from the response) could be used to manage state, for example:
const [receivedResults, setReceivedResults] = useState(false);
Then when the response is received:
setReceivedResults(obj === "clear");
and use the flag to allow the component to render itself (not sure of the exact syntax):
if (receivedResults) {
return <Redirect to="/result" />;
} else {
return null;
}

Related

Failed to mock requests from suspended component

I have a component that waits until some data from a resource. I'm using React suspense to show the loading screen as until it gets the response. Everything work as expected.
However when testing, even though onGet is registered, in axiosMock, it never gets the request from <Cmp /> component. Test fails due to connection error.
import { render, waitFor } from '#testing-library/react';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import React, { Suspense } from 'react';
const axiosMock = new MockAdapter(axios, { onNoMatch: 'throwException' });
const wrapPromise = (promise) => {
let status = 'pending';
let result: any;
promise.then(
(r) => {
status = 'success';
result = r;
},
(e) => {
status = 'error';
result = e;
}
);
return {
read() {
return {
status,
result,
};
},
};
};
const fetchData = () => {
return wrapPromise(axios.get('/test').then((r) => r.data));
};
const resource = fetchData();
export const Cmp = () => {
const str = resource.read();
return <h1>{str}</h1>;
};
describe('Main Tests', () => {
beforeAll(() => {
axiosMock.reset();
});
/*
THIS WORKS
*/
it('Sample Request Test', async () => {
axiosMock.onGet('/test').reply(200, 'hello');
expect(await axios.get('/test').then((r) => r.data)).toBe('hello');
});
/*
THIS DOES NOT WORK
*/
it('Component Request Test', async () => {
axiosMock.onGet('/test').reply(200, 'hello');
const { getByText } = render(
<Suspense fallback={<p>Loading...</p>}>
<Cmp />
</Suspense>
);
await waitFor(() => {
return getByText('hello');
});
});
});

converting class to hooks getting messages

i'm new to react hooks, here i have been converting my project to hooks from classes, i'm getting this kind of message 'Error: Server error
at build_error (actions.js:57)
at eval (actions.js:83)' and 'GET http://127.0.0.1:8000/api/kamera/undefined 404 (Not Found)'
those errors come when i'm changing class to hooks (everything is set correcly using useState and useEffect), any idea ?
class:
initializeCollapses() {
const data = this.props[this.props.action];
let collapseStates = this.state.collapseStates;
if (!data || data.length < 1) {
return;
}
data.map((el) => {
collapseStates["" + el.name + el.identifier] = false;
return;
});
this.setState({
...this.state,
collapseStates: collapseStates,
});
}
componentDidMount() {
this.props.getItems[this.props.action](this.state.actionArgs).then(() => {
this.initializeCollapses();
});
}
Hooks:
const initializeCollapses = () => {
const data = [action];
if (!data || data.length < 1) {
return;
}
data.map((el) => {
collapseStates["" + el.name + el.identifier] = false;
return;
});
setCollapseStates(collapseStates);
};
useEffect(() => {
getItems[action](actionArgs).then(() => {
initializeCollapses();
});
}, []);
initializeCollapses() {
const data = this.props[this.props.action];
let collapseStates = this.state.collapseStates;
if (!data || data.length < 1) {
return;
}
data.map((el) => {
collapseStates["" + el.name + el.identifier] = false;
return;
});
this.setState({
...this.state,
collapseStates: collapseStates,
});
}
componentDidMount() {
this.props.getItems[this.props.action](this.state.actionArgs).then(() => {
this.initializeCollapses();
});
}
const mapDispatchToProps = (dispatch) => {
return {
getItems: {
analysers: (site) => dispatch(getAnalysers(site)),
platforms: (site) => dispatch(getPlatforms(site)),
brokers: (site) => dispatch(getBrokers(site)),
cameras: (site) => dispatch(getCameras(site)),
sites: (site) => dispatch(getSites())
},
};
};
The above class implementation in hooks would roughly be as below
import React from "react";
import { useDispatch, useSelector } from "react-redux";
import getItems from "./store/actions";
or
import { cameras, sites, platform, brokers } from "./store/actions";
const actionArgs = useSelector(state => state.actionArgs); // In place of mapStateToProps
const dispatch = useDispatch();
useEffect(() => {
dispatch(getItems.cameras(actionArgs)) or dispatch(cameras(actionArgs)) //If destructured
}, []);
I have provided an understandable example with whatever data you provided. Refer this for a completely different approach or this one for the same mapDispatchToProps approach.
Good to refer
Example:
import React, {useReducer} from 'react';
const init = 0;
const myReducer = (state, action) => {
switch(action.type){
case 'increment':
return state + 1 // complex actions are kept in seperate files for better organised, clean code
case 'decrement':
return state - 1
case 'reset': // action types as well are kept as selectors
return init
default:
return state
}
};
function ReducerExample(){
const [count, dispatch] = useReducer(myReducer, init)
const add = () => {
dispatch({type: 'increment'})
}
const sub = () => {
dispatch({type: 'decrement'})
}
const reset = () => {
dispatch({type: 'reset'})
}
return (
<div>
<h4>Count: {count}</h4>
<button onClick={add} style={{margin: '10px'}}>Increment</button>
<button onClick={sub}>Decrement</button>
<button onClick={reset} style={{margin: '10px'}}>Reset</button>
</div>
)
}
export default ReducerExample;

React loader won't stay visible during 'pending' axios requests

I'm using react context & axios interceptors to hide/show a loading spinner. While it does hide and show correctly when requests and responses are fired, my loading spinner is only showing at the start of a network request. The request fires and then goes into a 'pending' status. During the 'pending' status, the response interceptor is fired and hides the loading spinner.
How can I make sure the loading spinner stays visible during the pending requests?
I tried adding some console logs to fire when the requests, responses, and errors were returned including the count, and it showed it (for two requests) to successfully go from 0 - 1 - 2 - 1 - 0, but in the chrome devtools network tab showed as pending even though all requests were returned.
EDIT: thought I had it working after some refactor but it was a no-go. Added updated code
import React, { useReducer, useRef, useEffect, useCallback } from "react";
import { api } from "api/api";
import LoadingReducer from "reducer/LoadingReducer";
const LoadingContext = React.createContext();
export const LoadingProvider = ({ children }) => {
const [loader, dispatch] = useReducer(LoadingReducer, {
loading: false,
count: 0,
});
const loaderKeepAlive = useRef(null),
showLoader = useRef(null);
const showLoading = useCallback(() => {
dispatch({
type: "SHOW_LOADING",
});
}, [dispatch]);
const hideLoading = useCallback(() => {
loaderKeepAlive.current = setTimeout(() => {
dispatch({
type: "HIDE_LOADING",
});
}, 3000);
return clearTimeout(loaderKeepAlive.current);
}, [dispatch]);
const requestHandler = useCallback(
(request) => {
dispatch({ type: "SET_COUNT", count: 1 });
return Promise.resolve({ ...request });
},
[dispatch]
);
const errorHandler = useCallback(
(error) => {
dispatch({ type: "SET_COUNT", count: -1 });
return Promise.reject({ ...error });
},
[dispatch]
);
const successHandler = useCallback(
(response) => {
dispatch({ type: "SET_COUNT", count: -1 });
return Promise.resolve({ ...response });
},
[dispatch]
);
useEffect(() => {
if (loader.count === 0) {
hideLoading();
clearTimeout(showLoader.current);
} else {
showLoader.current = setTimeout(() => {
showLoading();
}, 1000);
}
}, [showLoader, showLoading, hideLoading, loader.count]);
useEffect(() => {
if (!api.interceptors.request.handlers[0]) {
api.interceptors.request.use(
(request) => requestHandler(request),
(error) => errorHandler(error)
);
}
if (!api.interceptors.response.handlers[0]) {
api.interceptors.response.use(
(response) => successHandler(response),
(error) => errorHandler(error)
);
}
return () => {
clearTimeout(showLoader.current);
};
}, [errorHandler, requestHandler, successHandler, showLoader]);
return (
<LoadingContext.Provider
value={{
loader,
}}
>
{children}
</LoadingContext.Provider>
);
};
export default LoadingContext;
I think a more standard approach would be to just utilize the loading state to conditionally render the Spinner and the result of the promise to remove it from the DOM (see demo below). Typically, the interceptors are used for returning the error from an API response, since axios defaults to the status error (for example, 404 - not found).
For example, a custom axios interceptor to display an API error:
import get from "lodash.get";
import axios from "axios";
const { baseURL } = process.env;
export const app = axios.create({
baseURL
});
app.interceptors.response.use(
response => response,
error => {
const err = get(error, ["response", "data", "err"]);
return Promise.reject(err || error.message);
}
);
export default app;
Demo
Code
App.js
import React, { useEffect, useCallback, useState } from "react";
import fakeApi from "./api";
import { useAppContext } from "./AppContext";
import Spinner from "./Spinner";
const App = () => {
const { isLoading, error, dispatch } = useAppContext();
const [data, setData] = useState({});
const fetchData = useCallback(async () => {
try {
// this example uses a fake api
// if you want to trigger an error, then pass a status code other than 200
const res = await fakeApi.get(200);
setData(res.data);
dispatch({ type: "loaded" });
} catch (error) {
dispatch({ type: "error", payload: error.toString() });
}
}, [dispatch]);
const reloadData = useCallback(() => {
dispatch({ type: "reset" });
fetchData();
}, [dispatch, fetchData]);
useEffect(() => {
fetchData();
// optionally reset context state on unmount
return () => {
dispatch({ type: "reset" });
};
}, [dispatch, fetchData]);
if (isLoading) return <Spinner />;
if (error) return <p style={{ color: "red" }}>{error}</p>;
return (
<div style={{ textAlign: "center" }}>
<pre
style={{
background: "#ebebeb",
margin: "0 auto 20px",
textAlign: "left",
width: 600
}}
>
<code>{JSON.stringify(data, null, 4)}</code>
</pre>
<button type="button" onClick={reloadData}>
Reload
</button>
</div>
);
};
export default App;
AppContext.js
import React, { createContext, useContext, useReducer } from "react";
const AppContext = createContext();
const initialReducerState = {
isLoading: true,
error: ""
};
const handleLoading = (state, { type, payload }) => {
switch (type) {
case "loaded":
return { isLoading: false, error: "" };
case "error":
return { isLoading: false, error: payload };
case "reset":
return initialReducerState;
default:
return state;
}
};
export const AppContextProvider = ({ children }) => {
const [state, dispatch] = useReducer(handleLoading, initialReducerState);
return (
<AppContext.Provider
value={{
...state,
dispatch
}}
>
{children}
</AppContext.Provider>
);
};
export const useAppContext = () => useContext(AppContext);
export default AppContextProvider;
Spinner.js
import React from "react";
const Spinner = () => <div className="loader">Loading...</div>;
export default Spinner;
fakeApi.js
const data = [{ id: "1", name: "Bob" }];
export const fakeApi = {
get: (status) =>
new Promise((resolve, reject) => {
setTimeout(() => {
status === 200
? resolve({ data })
: reject(new Error("Unable to locate data."));
}, 2000);
})
};
export default fakeApi;
index.js
import React from "react";
import ReactDOM from "react-dom";
import AppContextProvider from "./AppContext";
import App from "./App";
import "./styles.css";
const rootElement = document.getElementById("root");
ReactDOM.render(
<React.StrictMode>
<AppContextProvider>
<App />
</AppContextProvider>
</React.StrictMode>,
rootElement
);

React hooks array passing in number when passed into props of component

I am currently working on a chat application and for some reason every time I pass in my array of messages as a prop to another component it passes in a number to the component instead of the message object. I have tried a lot of different methods of passing it in regarding using multiple components etc but it seems to still be passing in the number of elements for some reason. Any help is appreciated... code is below
Component receiving the props
import React, { useEffect } from 'react'
import Message from '../../Message/Message'
function Messages({ messages }) {
useEffect(() => {
console.log(messages)
}, [messages])
return (
<div>
test
</div>
)
}
export default Messages
// Import React dependencies.
import React, { useEffect, useState, } from "react";
// Import React dependencies.
import io from 'socket.io-client'
import axios from 'axios'
import Messages from './Messages/Messages'
import uuid from 'react-uuid'
import { Redirect } from 'react-router-dom'
// Import the Slate components and React plugin.
const ENDPOINT = 'http://localhost:5000/'
export const socket = io.connect(ENDPOINT)
const LiveChatFunction = ({ group_id }) => {
// Add the initial value when setting up our state.
const [message, setValue] = useState("")
const [user, setUser] = useState("")
const [groupId, setGroup] = useState('')
const [messages, setMessages] = useState([])
const [toLogin, userAuth] = useState(false)
useEffect(() => {
setGroup(group_id)
axios.post('http://localhost:5000/api/users/refresh_token', null, { withCredentials: true }).then(data => {
if (!data.data.accessToken) {
userAuth(true)
}
})
axios.get('http://localhost:5000/api/users/userInfo', { withCredentials: true }).then(data => {
setUser(data.data.user)
})
socket.on(`message-${group_id}`, data => {
setMessages(messages.push(data))
});
axios.get(`http://localhost:5000/live/${group_id}`).then(x => {
console.log(x.data)
})
}, [group_id, messages])
function setClick() {
const data = {
messageId: uuid(),
user,
groupId,
message
}
socket.emit('message', data)
}
if (toLogin) {
return (
<Redirect to="/login" />
)
}
return (
<div>
<input placeholder="message" type="text" onChange={value => {
setValue(value.target.value)
socket.emit('typing-message', { username: user, time: new Date() })
}} />
<button onClick={setClick}>Submit</button>
<Messages messages={messages} />
</div>
)
}
export default LiveChatFunction;
I have added some comments of what I think you can change:
useEffect(() => {
const recieveFunction = (data) => {
//using callback so no dependency on messages
setMessages((messages) => messages.push(data));
};
async function init() {
//next line is pointless, this runs when group_id
// has changed so something must have set it
// setGroup(group_id);
await axios //not sure if this should be done before listening to socket
.post(
'http://localhost:5000/api/users/refresh_token',
null,
{ withCredentials: true }
)
.then((data) => {
if (!data.data.accessToken) {
userAuth(true);
}
});
await axios
.get('http://localhost:5000/api/users/userInfo', {
withCredentials: true,
})
.then((data) => {
setUser(data.data.user);
});
//start listening to socket after user info is set
socket.on(`message-${group_id}`, recieveFunction);
axios
.get(`http://localhost:5000/live/${group_id}`)
.then((x) => {
console.log(x.data);
});
}
init();
//returning cleanup function, guessing socket.off exists
return () =>
socket.off(`message-${group_id}`, recieveFunction);
}, [group_id]); //no messages dependencies
console.log('messages are now:',messages);
If messages is still not set correctly then can you log it
So I think I found your problem:
In your useEffect hook, you're setting messages to the wrong thing.
socket.on(`message-${group_id}`, data => {
setMessages(messages.push(data))
});
An example:
const m = [].push();
console.log(m);
// m === 0
const n = [].push({});
console.log(n);
// n === 1
As you can see this is the index.
So what you need is:
socket.on(`message-${group_id}`, data => {
messages.push(data);
setMessages(messages);
});
This will set messages to the array of messages.

Fetch function in React Native with mobx-react-lite and React hooks

I'm newbie in mobx-react and i need to write fetch function that get data from API and after renders it to FlatList. I have already created fetch function, set initial state with useContext hook and wrapped my app by observer mobx class. But now i need to implement of getting the data from the server. Can you tell me please which will be the best way to do it?
import { createContext } from 'react'
import { action, decorate, observable, computed, runInAction } from 'mobx'
import fetchData from '../utils/fetchData'
import mapObjects from '../utils/mapObjects'
class DataStore {
data = null
error = false
loading = true
get getData(){
return this.data
}
get getError(){
return this.error
}
get getLoading(){
return this.loading
}
async fetchData(url) {
this.data = null
this.error = false
this.loading = true
try {
console.log('TRY')
const response = await fetch(url)
const jsonResponse = await response.json()
const obj = await mapObjects(jsonResponse)
runInAction(() => {
console.log('WRITE!!!')
this.loading = false
this.data = obj
})
} catch (err) {
runInAction(() => {
console.log(err)
this.loading = false
this.error = err
})
}
}
}
decorate(DataStore, {
data: observable,
error: observable,
loading: observable,
fetchData: action
})
export default createContext(new DataStore())
My component:
import React, { useContext, useEffect, useState } from 'react'
import { ActivityIndicator, FlatList, Platform, StyleSheet, View } from 'react-native'
import DataStore from '../mobx/DataStore'
import { autorun } from 'mobx'
import { ChartsHeader, CryptoItem, IconsHeader, ProjectStatusBar } from '../components'
import { useFetch } from '../hooks/useFetch'
import { WP, HP } from '../constants'
const styles = StyleSheet.create({
container: {
flex: 1
}
})
const ChartsScreen = ({ navigation }) => {
const { container } = styles
const store = useContext(DataStore)
const url = 'https://poloniex.com/public?command=returnTicker'
console.log('store', store)
useEffect(() => {
store.fetchData(url)
}, [])
//*Call custom hook and data distruction
//const { data, error, loading } = useFetch(url)
//*Change percent amount color depends on the amount
const percentColorHandler = number => {
return number >= 0 ? true : false
}
return (
<View style={container}>
{Platform.OS === 'ios' && <ProjectStatusBar />}
<IconsHeader
dataError={store.error}
header="Charts"
leftIconName="ios-arrow-back"
leftIconPress={() => navigation.navigate('Welcome')}
/>
<ChartsHeader />
<ActivityIndicator animating={store.loading} color="#068485" style={{ top: HP('30%') }} size="small" />
<FlatList
data={store.data}
keyExtractor={item => item.key}
renderItem={({ item }) => (
<CryptoItem
name={item.key}
highBid={item.highestBid}
lastBid={item.last}
percent={item.percentChange}
percentColor={percentColorHandler(item.percentChange)}
/>
)}
/>
</View>
)
}
export { ChartsScreen }
For everybody who is still looking for the approach for the fetch function in React with MobX. I checked a lot of information, but could not find a good decision. But in the end I created mine. Maybe it will help for somebody:
MobX store:
import { action, observable, runInAction } from 'mobx'
class DataStore {
#observable data = null
#observable error = false
#observable fetchInterval = null
#observable loading = false
//*Make request to API
#action.bound
fetchInitData() {
const response = fetch('https://poloniex.com/public?command=returnTicker')
return response
}
//*Parse data from API
#action.bound
jsonData(data) {
const res = data.json()
return res
}
//*Get objects key and push it to every object
#action.bound
mapObjects(obj) {
const res = Object.keys(obj).map(key => {
let newData = obj[key]
newData.key = key
return newData
})
return res
}
//*Main bound function that wrap all fetch flow function
#action.bound
async fetchData() {
try {
runInAction(() => {
this.error = false
this.loading = true
})
const response = await this.fetchInitData()
const json = await this.jsonData(response)
const map = await this.mapObjects(json)
const run = await runInAction(() => {
this.loading = false
this.data = map
})
} catch (err) {
console.log(err)
runInAction(() => {
this.loading = false
this.error = err
})
}
}
//*Call reset of MobX state
#action.bound
resetState() {
runInAction(() => {
this.data = null
this.fetchInterval = null
this.error = false
this.loading = true
})
}
//*Call main fetch function with repeat every 5 seconds
//*when the component is mounting
#action.bound
initInterval() {
if (!this.fetchInterval) {
this.fetchData()
this.fetchInterval = setInterval(() => this.fetchData(), 5000)
}
}
//*Call reset time interval & state
//*when the component is unmounting
#action.bound
resetInterval() {
if (this.fetchInterval) {
clearTimeout(this.fetchInterval)
this.resetState()
}
}
}
const store = new DataStore()
export default store

Categories

Resources