ComponentWillUnmount is no longer recommended in React. I want to send a request to the server to see if the user is logged and then update the state before actually displaying the page. I don't know how to do this with React hooks or a way that is recommended in React.
If you’re familiar with React class lifecycle methods, you can think
of useEffect Hook as componentDidMount, componentDidUpdate, and
componentWillUnmount combined.
The following demonstrates componentDidMount , componentDidUpdate, and componentWillUnmount
useEffect(() => {
// function to run to check if user is log in
},[]) //<-- similar to componentDidMount , empty array, run once.
useEffect(() => {
}, [state]) //<-- similar to componentDidUpdate. Run when state changes.
useEffect(() => {
return () => cleanUp //<-- similar to componentWillUnMount, clean up functions.
}, [])
Recommended reading for useEffect Hook
Recommended reading for setting state with hook
Example of checking user login
const Component = () => {
const [ loggedIn, setLoggedIn ] = useState(false) //set login initial state to false
useEffect(() => {
const checkLogin = async() => {
const checking = await fetch(api);
if (checking) {
setLoggedIn(true) //set login state to true
}
}
checkLogin()
},[]) //<-- run once when component mounted
return (<div>{ loggedIn ? 'Logged In' : 'Not Logged In' }</div>)
}
Related
EDIT: I should mention, that I only have problems during testing. When I run ng serve and use msw to serve the data everything works correctly.
I stumbled upon mswjs recently and wanted to use the mock service workers to test my frontend services without waiting on the backend team and avoid having to write mock-service classes. I setup everything according to the examples provided in the documentation.
At first I got the message that stating spec 'UserService should get list of users' has no expectations.
I researched this and added a done() function call at the end of my subscribe callback. After doing that, I get the following error:
Error: Timeout - Async function did not complete within 3000ms (set by jasmine.DEFAULT_TIMEOUT_INTERVAL) in node_modules/jasmine-core/lib/jasmine-core/jasmine.js (line 7609)
I already tried increasing the default_timout in Karma but even setting it to 30.000 did not change the result.
I also tried working around by using waitForAsync without any success. This way I get no error and the test succeeds but only because it still finds no expectations within the spec.
Most example I found online do not deal with mock service workers and instead resort to using mock-services and fakeasync which does not help in my case.
This is how my code looks like:
My Angular Service:
#Injectable({
providedIn: 'root'
})
export class UserService {
private url = 'http://localhost:3000/api/users';
constructor(private http: HttpClient) { }
getUser(id: string): Observable<User> {
return this.http.get<User>(`${this.url}/${id}`);
}
listUsers(): Observable<User[]> {
return this.http.get<User[]>(this.url);
}
}
My Test Code:
describe('UserService', () => {
let service: UserService;
beforeAll(() => {
worker.start();
});
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientModule],
});
service = TestBed.inject(UserService);
});
afterAll(() => {
worker.stop();
});
it('should be created', () => {
expect(service).toBeTruthy();
});
it('should get list of users', (done) => {
service.listUsers().subscribe((data) => {
expect(data.length).toBe(5);
done();
});
})
});
The Worker setup:
const handlers = [
rest.get('http://localhost:3000/api/users', (req, res, ctx) => {
return res(
ctx.status(200),
ctx.json(users))
})
]
export const worker = setupWorker(...handlers)
I managed to solve my own problem by using firstValueFrom and waitForAsync.
I changed the code in my tests to the following:
it('should get list of users', waitForAsync(async () => {
const source$ = service.listUsers();
const result = await firstValueFrom(source$);
expect(result.length).toEqual(5);
}));
I am making API calls from my CRA (Create React App). Sometimes while opening a random page all of the API calls are showing the status of "pending" forever. When I refresh my page then, all the APIs work fine.
These are the details of the API in "pending" status.
const checkWelcomeRewardShown = async () => {
try {
if (localStorage.getItem("userType") === "student") {
let res = await axios.get(`${process.env.REACT_APP_BASE_URL}/api/gethasnotseenintro`);
if (res?.data?.status === "success" && res?.data?.notSeen) {
await axios.get(`${process.env.REACT_APP_BASE_URL}/api/getunlocktokenuser`);
await axios.get(`${process.env.REACT_APP_BASE_URL}/api/falsewhenfirstlogin`);
}
let response = await axios.get(`${process.env.REACT_APP_BASE_URL}/api/seenPopupForLinkedinAndFeedback`);
}
} catch (error) {}
};
useEffect(() => {
checkWelcomeRewardShown();
dispatch(showAllPopups());
}, [localStorage.getItem("user_id")]);
It is solved. There was an API in Backend that was failing and there was no logic for what should happen if it fails. This was like a global level API that was being used everywhere so when it failed, it kind of blocked the execution of the rest of the APIs. That's why all of them were showing "pending".
I am using axios on my React app to get data from my server (Node). My GET request stays pending in chrome developer tools and does not reach the server if I refresh the app on the provided route (e.g., http://localhost:3000/category/5d68936732d1180004e516cb). However, if I redirect from the home page, it will work.
I have tried different variations of making sure I end my responses on the server end of things.
Several posts have had related problems (e.g., request not reaching the server, POST request does not reach the server) but unfortunately not been helpful in my situation.
Here is my main call in my react app:
componentDidMount() {
console.log('I am here!'); // this gets executed even on page refresh
axios.get(`/api/categories/${this.props.id}`)
.then( (res) => {
this.setState({
title: res.data.category.title,
module: res.data.category.module ? true : false,
...res.data
})
}, (err) => console.log(err))
.catch(err => console.log(err));
}
On my back end I call this function after going through user verification:
module.exports.publishedCategories = async function(req, res) {
try {
// some code that I removed for clarity
res.json({
category,
children,
flagged: flaggedCategories
});
} catch(err) {
console.log(err);
res.sendStatus(500).end();
}
}
Some more code regarding my routing:
index.js
<Route
path='/category/:id'
render={ (props) => {
return <Category id={props.match.params.id} />
}}
/>
I do not get any error messages...
I was overzealous with my initial solution of switching to componentDidUpdate(). This only worked for page refreshes but not for redirects (i.e., I had the reverse problem). My final solution is as follows:
componentDidMount = async () => {
setTimeout( async () => {
this.loadCategory();
}, 10)
}
componentDidUpdate = async (props, state) => {
if(props.match.params.id !== this.props.match.params.id) {
this.loadCategory();
return;
}
return false;
}
loadCategory = async () => {
let result = await axios.get(`/api/categories/${this.props.match.params.id}`);
this.setState({
title: result.data.category.title,
module: result.data.category.module ? true : false,
...result.data
});
}
I am not sure why adding a setTimeout() works for componentDidMount().
As per the documentation componentDidUpdate(), it is good for netork updates however you need to compare prior props with current props.
Unforunately I am not sure how to get around the setTimeout() hack but it seems to work.
I am attempting to build a React component library which can be independently deployed from the apps which use it. I'm doing this by loading the components over the network, and then rendering them in a Next.js app. I know I can achieve this using react-umd-loader, but that only works client side because it relies on scriptjs. I was able to get this working using the vm npm package. This is what my page in Next.js looks like.
const Index = (props) => {
let sandbox = {'React': React, 'ReactDOM': ReactDOM, 'MyComponent': null, 'self': {}};
vm.runInNewContext(props.MyComponent, sandbox);
const MyComponent = sandbox.MyComponent.default;
return (<div><p>Hello from Next.js</p><MyComponent /></div>)
}
Index.getInitialProps = async function() {
const res = await fetch('http://localhost:3001/MyComponent.bundle.js')
const script = await res.text()
return { MyComponent: script }
}
MyComponent is a react component built with the following webpack.config.js
entry: {
MyComponent: "./src/components/MyComponent",
},
output: {
path: path.resolve(__dirname, 'build/static/js'),
filename: "[name].bundle.js",
libraryTarget: "umd",
library: "[name]",
umdNamedDefine: true,
globalObject: 'this'
},
This actually works fine, but, I want the component to fetch data and then display that data, which is not working.
class MyComponent extends Component {
constructor() {
super();
this.state = {data: {msg: 'data not loaded'}};
}
componentDidMount() {
fetch('https://jsonplaceholder.typicode.com/todos/1')
.then(response => response.json())
.then(json => this.setState({data: json}))
.catch(err => console.log(err));
}
render() {
return (
<div style={{border: '1px solid black'}}>
<h1>My Component</h1>
This data was fetched from an API:
<pre>
{JSON.stringify(this.state.data)}
</pre>
</div>
);
}
}
I think the reason this is not working is because the fetch is asynchronous, and the page is rendered and returned to the client before the fetch completes. I tried using fetch-suspense but this did not work. I see Promise { <pending> } on the server console, so maybe the fetch promise just isn't completing?
Is it possible to make the server wait to respond until the fetch from the API has completed?
My full repo is at https://github.com/bernardwolff/ReactRemoteComponentSsr
Thanks!
I'm new to redux and programming in general and am having trouble wrapping my head around certain unit testing concepts.
I have some async actions in redux, which involve calls to a third party API (from the 'amazon-cognito-identity-js' node module).
I have wrapped the external API call in a promise function, and I call this function from the 'actual' action creator. So for testing I just want to stub the result of externalAWS() function so that I can check that the correct actions are being dispatched.
I'm using redux-thunk for my middleware.
import { AuthenticationDetails,
CognitoUser
} from 'amazon-cognito-identity-js';
export function externalAWS(credentials) {
//This is required for the package
let authenticationDetails = new AuthenticationDetails(credentials);
let cognitoUser = new CognitoUser({
//Construct the object accordingly
})
return new Promise ((resolve, reject) => {
cognitoUser.authenticateUser(authenticationDetails, {
onSuccess: result => {
resolve(result);
},
onFailure: error => {
reject(error)
}
})
}
}
export function loginUser(credentials) {
//return function since it's async
return dispatch => {
//Kick off async process
dispatch(requestLogin());
externalAWS(credentials)
.then((result) => {
dispatch(receiveLogin(result.getAccessToken().getJwtToken(), credentials.username))
})
.catch((error) => {
dispatch(failedLogin(error.message, etc))
})
}
}
I don't have any test code yet because I am really not sure how to approach this. All the examples deal with mocking a HTTP request, which I know is
what this boils down to, so am I supposed to inspect the HTTP requests in my browser and mock them out directly?
It's further complicated by the fact that the second argument of authenticateUser is not even a plain callback, but an object with callbacks as it's values.
Can anyone offer some advice on whether my intention in unit testing the async function is correct, and how I should approach it? Thank you.
Edit: I'm testing in Jest.
Edit2: Request Headers
First POST request,
Second POST request
Edit3: Split the function, trying my best to isolate the external API and create something that is 'easily mock/stub-able'. But still running into issues of how to properly stub this function.
Redux thunk gives you the ability to dispatch future actions within the context of a main action that kicks off the process. This main action is your thunk action creator.
Therefore tests should focus on what actions are dispatched within your thunk action creator according to the outcome of the api request.
Tests should also look at what arguments are passed to your action creators so that your reducers can be informed about the outcome of the request and update the store accordingly.
To get started with testing your thunk action creator you want to test that the three actions are dispatched appropriately depending on whether login is successful or not.
requestLogin
receiveLogin
failedLogin
Here are some tests I wrote for you to get started using Nock to intercept http requests.
Tests
import nock from 'nock';
const API_URL = 'https://cognito-idp.us-west-2.amazonaws.com/'
const fakeCredentials = {
username: 'fakeUser'
token: '1234'
}
it('dispatches REQUEST_LOGIN and RECEIVE_LOGIN with credentials if the fetch response was successful', () => {
nock(API_URL)
.post( ) // insert post request here e.g - /loginuser
.reply(200, Promise.resolve({"token":"1234", "userName":"fakeUser"}) })
return store.dispatch(loginUser(fakeCredentials))
.then(() => {
const expectedActions = store.getActions();
expect(expectedActions.length).toBe(2);
expect(expectedActions[0]).toEqual({type: 'REQUEST_LOGIN'});
expect(expectedActions[1]).toEqual({type: 'RECEIVE_LOGIN', token: '1234', userName: 'fakeUser'});
})
});
it('dispatches REQUEST_LOGIN and FAILED_LOGIN with err and username if the fetch response was unsuccessful', () => {
nock(API_URL)
.post( ) // insert post request here e.g - /loginuser
.reply(404, Promise.resolve({"error":"404", "userName":"fakeUser"}))
return store.dispatch(loginUser(fakeCredentials))
.then(() => {
const expectedActions = store.getActions();
expect(expectedActions.length).toBe(2);
expect(expectedActions[0]).toEqual({type: 'REQUEST_LOGIN'});
expect(expectedActions[1]).toEqual({type: 'FAILED_LOGIN', err: '404', userName: 'fakeUser'});
})
});
So I figured it out in the end.
First, I had to require() the module into my test file (as opposed to ES6 import). Then I removed the promise for now since it was adding a layer of complexity and combined everything into one function, let's call it loginUser(). It is a redux async action, that dispatches one action upon being called, and then a success or failure action depending on the result of the API call. See above for what the API call looks like.
Then I wrote the test as follows:
const CognitoSDK = require('/amazon-cognito-identity-js')
const CognitoUser = CognitoSDK.CognitoUser
//Set up the rest of the test
describe('async actions', (() => {
it('should dispatch ACTION_1 and ACTION_2 on success', (() => {
let CognitoUser.authenticateUser = jest.fn((arg, callback) => {
callback.onSuccess(mockResult)
})
store.dispatch(loginUser(mockData))
expect(store.getActions()).toEqual([{ACTION_1}, {ACTION_2}])
}))
}))
So basically once requiring the module in, I mocked it in Jest and did a mock implementation too, so that I could access the onSuccess function of the callback object.