I'm writing a test for a component that takes a few props such as "isLoading", "clients" and "refreshClients". refreshClients is defined like this:
function refreshClients() {
setIsLoading(true)
getClients().then(response => {
setClients(response)
setIsLoading(false)
})
}
clients and isLoading are states from the parent component that are used as props of the child component. The client component also gets the refreshClients prop, which allows it to update its own props (isLoading and clients), through the function passed in by the parent component.
This is one of the use cases: after taking an action over a client, like deletion, the component will call refreshClients, which should take care of reloading the clients table displayed in the component. While the new listing is being loaded the table isn't displayed (isLoading is true). The component works well, however I'm unsure on how to properly test it using testing-library. I'm basically calling rerender in the tests but I feel there should be a way to replicate this behavior in the tests...
Is there a way to create states to pass as props to the tested component? Or is there another recommended approach to handle cases like this?
In case it makes it easier to visualize the idea, here is a complete simple example of how it would work:
import {createRoot} from 'react-dom'
import React, {useState} from 'react';
export function App() {
const [isLoading, setIsLoading] = useState(false);
const [clients, setClients] = useState(['david', 'sara']);
const refreshClients = () => {
setIsLoading(true)
setTimeout(() => {
setClients(['john', 'mary'])
setIsLoading(false)
}, 1000)
}
return <ClientsTable isLoading={isLoading} clients={clients} refreshClients={refreshClients} />
}
function ClientsTable({ isLoading, clients, refreshClients}) {
const deleteClient = () => {
console.log('TODO: delete client')
refreshClients()
}
return (
<div>
{isLoading && <p>Loading... please wait</p>}
{!isLoading && clients.map(client => (
<div>{client} <button onClick={deleteClient}>delete</button></div>
))}
</div>
);
}
createRoot(
document.getElementById('root')
).render(<App />)
In my Next.js app I can't seem to access window:
Unhandled Rejection (ReferenceError): window is not defined
componentWillMount() {
console.log('window.innerHeight', window.innerHeight);
}
̶A̶n̶o̶t̶h̶e̶r̶ ̶s̶o̶l̶u̶t̶i̶o̶n̶ ̶i̶s̶ ̶b̶y̶ ̶u̶s̶i̶n̶g̶ ̶p̶r̶o̶c̶e̶s̶s̶.̶b̶r̶o̶w̶s̶e̶r ̶ ̶t̶o̶ ̶j̶u̶s̶t̶ ̶e̶x̶e̶c̶u̶t̶e̶ ̶ ̶y̶o̶u̶r̶ ̶c̶o̶m̶m̶a̶n̶d̶ ̶d̶u̶r̶i̶n̶g̶ ̶r̶e̶n̶d̶e̶r̶i̶n̶g̶ ̶o̶n̶ ̶t̶h̶e̶ ̶c̶l̶i̶e̶n̶t̶ ̶s̶i̶d̶e̶ ̶o̶n̶l̶y̶.
But process object has been deprecated in Webpack5 and also NextJS, because it is a NodeJS variable for backend side only.
So we have to use back window object from the browser.
if (typeof window !== "undefined") {
// Client-side-only code
}
Other solution is by using react hook to replace componentDidMount:
useEffect(() => {
// Client-side-only code
})
Move the code from componentWillMount() to componentDidMount():
componentDidMount() {
console.log('window.innerHeight', window.innerHeight);
}
In Next.js, componentDidMount() is executed only on the client where window and other browser specific APIs will be available. From the Next.js wiki:
Next.js is universal, which means it executes code first server-side,
then client-side. The window object is only present client-side, so if
you absolutely need to have access to it in some React component, you
should put that code in componentDidMount. This lifecycle method will
only be executed on the client. You may also want to check if there
isn't some alternative universal library which may suit your needs.
Along the same lines, componentWillMount() will be deprecated in v17 of React, so it effectively will be potentially unsafe to use in the very near future.
If you use React Hooks you can move the code into the Effect Hook:
import * as React from "react";
export const MyComp = () => {
React.useEffect(() => {
// window is accessible here.
console.log("window.innerHeight", window.innerHeight);
}, []);
return (<div></div>)
}
The code inside useEffect is only executed on the client (in the browser), thus it has access to window.
With No SSR
https://nextjs.org/docs/advanced-features/dynamic-import#with-no-ssr
import dynamic from 'next/dynamic'
const DynamicComponentWithNoSSR = dynamic(
() => import('../components/hello3'),
{ ssr: false }
)
function Home() {
return (
<div>
<Header />
<DynamicComponentWithNoSSR />
<p>HOME PAGE is here!</p>
</div>
)
}
export default Home
The error occurs because window is not yet available, while component is still mounting. You can access window object after component is mounted.
You can create a very useful hook for getting dynamic window.innerHeight or window.innerWidth
const useDeviceSize = () => {
const [width, setWidth] = useState(0)
const [height, setHeight] = useState(0)
const handleWindowResize = () => {
setWidth(window.innerWidth);
setHeight(window.innerHeight);
}
useEffect(() => {
// component is mounted and window is available
handleWindowResize();
window.addEventListener('resize', handleWindowResize);
// unsubscribe from the event on component unmount
return () => window.removeEventListener('resize', handleWindowResize);
}, []);
return [width, height]
}
export default useDeviceSize
Use case:
const [width, height] = useDeviceSize();
componentWillMount() lifecycle hook works both on server as well as client side. In your case server would not know about window or document during page serving, the suggestion is to move the code to either
Solution 1:
componentDidMount()
Or, Solution 2
In case it is something that you only want to perform in then you could write something like:
componentWillMount() {
if (typeof window !== 'undefined') {
console.log('window.innerHeight', window.innerHeight);
}
}
In the constructor of your class Component you can add
if (typeof window === 'undefined') {
global.window = {}
}
Example:
import React, { Component } from 'react'
class MyClassName extends Component {
constructor(props){
super(props)
...
if (typeof window === 'undefined') {
global.window = {}
}
}
This will avoid the error (in my case, the error would occur after I would click reload of the page).
global?.window && window.innerHeight
It's important to use the operator ?., otherwise the build command might crash.
Best solution ever
import dynamic from 'next/dynamic';
const Chart = dynamic(()=> import('react-apexcharts'), {
ssr:false,
})
A bit late but you could also consider using Dynamic Imports from next turn off SSR for that component.
You can warp the import for your component inside a dynamic function and then, use the returned value as the actual component.
import dynamic from 'next/dynamic'
const BoardDynamic = dynamic(() => import('../components/Board.tsx'), {
ssr: false,
})
<>
<BoardDynamic />
</>
I have to access the hash from the URL so I come up with this
const hash = global.window && window.location.hash;
Here's an easy-to-use workaround that I did.
const runOnClient = (func: () => any) => {
if (typeof window !== "undefined") {
if (window.document.readyState == "loading") {
window.addEventListener("load", func);
} else {
func();
}
}
};
Usage:
runOnClient(() => {
// access window as you like
})
// or async
runOnClient(async () => {
// remember to catch errors that might be raised in promises, and use the `await` keyword wherever needed
})
This is better than just typeof window !== "undefined", because if you just check that the window is not undefined, it won't work if your page was redirected to, it just works once while loading. But this workaround works even if the page was redirected to, not just once while loading.
I was facing the same problem when i was developing a web application in next.js This fixed my problem, you have to refer to refer the window object in a life cycle method or a react Hook. For example lets say i want to create a store variable with redux and in this store i want to use a windows object i can do it as follows:
let store
useEffect(()=>{
store = createStore(rootReducers, window.__REDUX_DEVTOOLS_EXTENSION__ &&
window.__REDUX_DEVTOOLS_EXTENSION__())
}, [])
....
So basically, when you are working with window's object always use a hook to play around or componentDidMount() life cycle method
I wrapped the general solution (if (typeof window === 'undefined') return;) in a custom hook, that I am very pleased with. It has a similiar interface to reacts useMemo hook which I really like.
import { useEffect, useMemo, useState } from "react";
const InitialState = Symbol("initial");
/**
*
* #param clientFactory Factory function similiar to `useMemo`. However, this function is only ever called on the client and will transform any returned promises into their resolved values.
* #param deps Factory function dependencies, just like in `useMemo`.
* #param serverFactory Factory function that may be called server side. Unlike the `clientFactory` function a resulting `Promise` will not be resolved, and will continue to be returned while the `clientFactory` is pending.
*/
export function useClientSideMemo<T = any, K = T>(
clientFactory: () => T | Promise<T>,
deps: Parameters<typeof useMemo>["1"],
serverFactory?: () => K
) {
const [memoized, setMemoized] = useState<T | typeof InitialState>(
InitialState
);
useEffect(() => {
(async () => {
setMemoized(await clientFactory());
})();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, deps);
return typeof window === "undefined" || memoized === InitialState
? serverFactory?.()
: memoized;
}
Usage Example:
I am using it to dynamically import libaries that are not compatible with SSR in next.js, since its own dynamic import is only compatible with components.
const renderer = useClientSideMemo(
async () =>
(await import("#/components/table/renderers/HighlightTextRenderer"))
.HighlightTextRendererAlias,
[],
() => "text"
);
As you can see I even implemented a fallback factory callback, so you may provide a result when initially rendering on the server aswell. In all other aspects this hook should behave similiar to reacts useMemo hook. Open to feedback.
For such cases, Next.js has Dynamic Import.
A module that includes a library that only works in the browser, it's suggested to use Dynamic Import. Refer
Date: 06/08/2021
Check if the window object exists or not and then follow the code along with it.
function getSelectedAddress() {
if (typeof window === 'undefined') return;
// Some other logic
}
For Next.js version 12.1.0, I find that we can use process.title to determine whether we are in browser or in node side. Hope it helps!
export default function Projects(props) {
console.log({ 'process?.title': process?.title });
return (
<div></div>
);
}
1. From the terminal, I receive { 'process?.title': 'node' }
2. From Chrome devtool, I revice { 'process?.title': 'browser' }
I had this same issue when refreshing the page (caused by an import that didn't work well with SSR).
What fixed it for me was going to pages where this was occurring and forcing the import to be dynamic:
import dynamic from 'next/dynamic';
const SomeComponent = dynamic(()=>{return import('../Components/SomeComponent')}, {ssr: false});
//import SomeComponent from '../Components/SomeComponent'
Commenting out the original import and importing the component dynamically forces the client-side rendering of the component.
The dynamic import is covered in Nextjs's documentation here:
https://nextjs.org/docs/advanced-features/dynamic-import
I got to this solution by watching the youtube video here:
https://www.youtube.com/watch?v=DA0ie1RPP6g
You can define a state var and use the window event handle to handle changes like so.
const [height, setHeight] = useState();
useEffect(() => {
if (!height) setHeight(window.innerHeight - 140);
window.addEventListener("resize", () => {
setHeight(window.innerHeight - 140);
});
}, []);
You can try the below code snippet for use-cases such as - to get current pathname (CurrentUrl Path)
import { useRouter } from "next/router";
const navigator = useRouter()
console.log(navigator.pathname);
For anyone who somehow cannot use hook (for example, function component):
Use setTimeout(() => yourFunctionWithWindow()); will allow it get the window instance. Guess it just need a little more time to load.
I want to leave this approach that I found interesting for future researchers. It's using a custom hook useEventListener that can be used in so many others needs.
Note that you will need to apply a little change in the originally posted one, like I suggest here.
So it will finish like this:
import { useRef, useEffect } from 'react'
export const useEventListener = (eventName, handler, element) => {
const savedHandler = useRef()
useEffect(() => {
savedHandler.current = handler
}, [handler])
useEffect(() => {
element = !element ? window : element
const isSupported = element && element.addEventListener
if (!isSupported) return
const eventListener = (event) => savedHandler.current(event)
element.addEventListener(eventName, eventListener)
return () => {
element.removeEventListener(eventName, eventListener)
}
}, [eventName, element])
}
If it is NextJS app and inside _document.js, use below:
<script dangerouslySetInnerHTML={{
__html: `
var innerHeight = window.innerHeight;
`
}} />
How to validate Textinput correclty? I want to validate my form correctly with custom form validation and after validation display errors in Text component, but how? Please, guys show me example!
install react-native-snackbar to show error messages.
import React, { Component } from 'react';
import { View, Text, TextInput } from 'react-native';
import Snackbar from 'react-native-snackbar';
export default class LoginPasswordScreen extends Component {
constructor(props) {
super(props);
this.state = {
password: ''
}
}
validate = () => {
//include your validation inside if condition
if (this.state.password == "") {
() => {
setTimeout(() => {
Snackbar.show({
title: 'Invalid Credintials',
backgroundColor: red,
})
}, 1000);
}
}
else {
Keyboard.dismiss();
// navigate to next screen
}
}
render() {
return (
<View>
<TextInput
returnKeyType="go"
secureTextEntry
autoCapitalize="none"
autoCorrect={false}
autoFocus={true}
onChangeText={(password) => this.setState({ password })}
/>
<TouchableOpacity>
<Text onPress={this.validate}>Next</Text>
</TouchableOpacity>
</View>
);
}
}
Every field, you have to do a comparison and show the error message and as I see there is no direct form validation even though there is form component available in react native.
In One of my react native project, I added a form and later on click of Submit, I had written one validate function to check all my inputs.
For this, I used one nice javascript library-
npm library- validator
And for showing error message, you can use, Toast, ALert or Snackbar
Would be nice if you provide some thoughts or code on how you would think it can be approached. But the way i did it was pretty simple, on my component state i got the following object:
this.state = {
loading: false,
username: {
text: '',
valid: false
},
password: {
text: '',
valid: false
},
isLoginValid: false
};
Then on the TextInput for username, i would first, bind its value to this.state.username.text, also, during onChangeText I just do a simple validation of the field, if the form is quite big, you may have a switch(fieldtype) where you have for each field, what treatment you want to apply a.k.a validation.
onChangeText={ (text) => { this.validateInput(text, 'username')}} (username would be the form input on the state object)
For instance, for username you want only to be length != 0 and length <= 8 characters, for email you may run a RegExp() with the email validation and also its length, for password a different logic, etc... after that i just simply save the state for that field input and if it's valid or not. Like this:
validateInput(text, fieldname) {
let stateObject = {
text: text,
valid: text.length !== 0
}
this.setState({ [fieldname]: stateObject }, () => {
this.checkValidation();
});
}
In checkValidation I check for all the input fields and if every one is valid, i set formValid to true.
This formValid would for example, allow the user to tap on the "Login" button otherwise it applies an opacity of 0.5 to it and disables it.
The rest you may guess, is just playing around with the valid variables of each field to what you want to display and what not.
In a Register form I also added an X or a "Tick" icon if the input text field is ok or not. Let your imagination guide you.
Hope it helps.
We have a Figure decorator which allows us to insert a link which you can hover over to get a preview of an image. We insert this image along with some metadata (caption, etc.) using a modal form. This all works great. However we also want the ability to click the link and pop up the modal to edit it.
Entity.replaceData() works great for updating the metadata, the only problem that remains is the decorated text which comes from the modal too. It appears the Entity knows little to nothing about the content it's decorating.
How can we find and replace the text? Is there a way around this?
(I've tried setting the content in Draft to an arbitrary single character and making the decorator show the content/title (which would be fine), however when trying to delete the figure, Draft seems to jump over the content and delete something before it. I guess it's due to different text lengths. I thought setting it as 'IMMUTABLE' would solve this but that didn't help.)
EDIT:
Here's my decorator:
function LinkedImageDecorator(props: Props) {
Entity.mergeData(props.entityKey, { caption: "hello world" });
const entity = Entity.get(props.entityKey);
const data = entity.getData();
return <LinkedImage data={data} text={props.children} offsetKey={props.offsetKey} />
}
function findLinkedImageEntities(contentBlock: ContentBlock, callback: EntityRangeCallback) {
contentBlock.findEntityRanges((character) => {
const entityKey = character.getEntity();
return (
entityKey != null &&
Entity.get(entityKey).getType() === ENTITY_TYPE.IMAGE
);
}, callback);
}
export default {
strategy: findLinkedImageEntities,
component: LinkedImageDecorator,
editable: false,
};
As you can see, I'm testing out Entity.mergeData which will eventually be the callback of my LinkedImage component (which would open a modal onClick.) So the metadata is easy to update, I just need to be able to update the decorated text which is passed in as props.children.
So I finally solved this with the help of Jiang YD and tobiasandersen. Here goes...
First I inject my decorator with a reference to my editor (which keeps track of EditorState):
const decorators = new CompositeDecorator([{
strategy: findLinkedImageEntities,
component: LinkedImageDecorator,
props: { editor: this }
}];
this.editorState = EditorState.set(this.editorState, { decorator });
From there I can do this in my LinkedImageDecorator:
const { decoratedText, children, offsetKey, entityKey, editor } = this.props;
// This looks messy but seems to work fine
const { startOffset, blockKey } = children['0'].props;
const selectionState = SelectionState.createEmpty(blockKey).merge({
anchorOffset: startOffset,
focusOffset: startOffset + decoratedText.length,
});
const editorState = editor.getEditorState();
let newState = Modifier.replaceText(
editorState.getCurrentContent(),
selectionState,
"my new text",
null,
entityKey,
);
editor.editorState = EditorState.push(editorState, newState, 'insert-fragment');
Not sure if this is the cleanest way of doing this but it seems to work well!
just put the global component instance reference in the decorator props.
const compositeDecorator = new CompositeDecorator([
{
strategy: handleStrategy,
component: HandleSpan,
props: {parent:refToYourComponentWhichContainsTheEditorState}
}
props.parent.state.editorState.getCurrentContent()
In angular2 I want to trigger Validators for some controls when a another control is changed. Is there some way that I can just tell the form to re-validate? Better still, can I request validation of specific fields?
Example:
Given Checkbox X and input P.
Input P has a validator that behaves differently based on the model value of X.
When X is checked/unchecked I need to invoke the validator on P. The Validator on P will look at the model to determine the state of X and will validate P accordingly.
Here's some code:
constructor(builder: FormBuilder) {
this.formData = { num: '', checkbox: false };
this.formGp = builder.group({
numberFld: [this.formData.num, myValidators.numericRange],
checkboxFld: [this.formData.checkbox],
});
}
this.formGp.controls['checkboxFld'].valueChanges.observer({
next: (value) => {
// I want to be able to do something like the following line:
this.formGp.controls['numberFld'].validator(this.formGp.controls['numberFld']);
}
});
Anybody have a solution? Thanks!
I don't know if you are still looking for an answer, so here is my suggestions:
Have a look at this: Angular 2 - AbstractControl
I think what you could do is following:
this.formGp.controls['checkboxFld'].valueChanges.observer({
next: (value) => {
this.formGp.controls['numberFld'].updateValueAndValidity();
}
});
This should trigger and run the validators. Furthermore the state gets updated as well. Now you should be able to consult the checkbox value within your validator logic.
Validaton-Guide
FormControl Documentation
with my ControlGroup I do this because I have errors divs checking if touched
for (var i in this.form.controls) {
this.form.controls[i].markAsTouched();
}
(this.form is my ControlGroup)
With the help of this blog
blog link
I have came across a solution with the combine of Nightking answer
Object.keys(this.orderForm.controls).forEach(field => {
const control = this.orderForm.get(field);
control.updateValueAndValidity();
});
this.orderForm is the form group
This did the trick for me
this.myForm.markAllAsTouched();
There are more elegant ways of modeling this behavior - for example, putting your state into a ReplaySubject and observing that, and then using async validators observing the state - but the pseudo-coded approach below should work. You simply observe the value changes in the checkbox, update the model as appropriate, then force a re-validation of the numberFld with the updateValueAndValidity cal.
constructor(builder: FormBuilder) {
this.formData = { num: '', checkbox: false };
const numberFld = builder.control(this.formData.num, myValidators.numericRange);
const checkbox = builder.control(this.formData.checkbox);
checkbox.valueChanges.map(mapToBoolean).subscribe((bool) => {
this.formData.checked = bool;
numberFld.updateValueAndValidity(); //triggers numberFld validation
});
this.formGp = builder.group({
numberFld: numberFld,
checkboxFld: checkbox
});
}
You can trigger validation in this way:
this.myform.get('myfield').updateValueAndValidity();
static minMaxRange(min: number, max: number): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
if (Validators.min(min)(control)) { // if min not valid
return Validators.min(min)(control);
} else {
return Validators.max(max)(control);
}
};
}
Here is another similar way that also uses markAsDirty and updateValueAndValidity, particularly good if you use angular material where markAsTouched is not enough.
export function forceValidation(form: AbstractControl) {
if (form instanceof FormGroup || form instanceof FormArray) {
for (const inner in form.controls) {
const control = form.get(inner);
control && forceValidation(control);
}
} else {
form.markAsDirty();
form.markAsTouched();
form.updateValueAndValidity();
}
}