Related
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;
`
}} />
I am using mobx-react-from and i have a a problem to figure how i can use an action that i have in my store inside obSubmit hook ....
the mobx form is working ok .. i can see the inputs and the validation
and when i submit the form all i want is to use an action from store ...
my AutStore file :
import {observable,action} from 'mobx';
class AuthStore {
constructor(store) {
this.store = store
}
#action authLogin=(form)=>{
this.store.firebaseAuth.signInWithEmailAndPassword().then(()=>{
}).catch(()=>{
})
}
}
export default AuthStore
my AuthForm File :
import {observable, action} from 'mobx';
import MobxReactForm from 'mobx-react-form';
import {formFields,formValidation} from './formSettings';
const fields = [
formFields.email,
formFields.password
];
const hooks = {
onSuccess(form) {
// Here i want to use an action - authLogin from my AuthStore
console.log('Form Values!', form.values());
},
onError(form) {
console.log('All form errors', form.errors());
}
};
const AuthForm = new MobxReactForm({fields}, {plugins:formValidation,
hooks});
export default AuthForm
i would like to know how can i connect all together thanks !!!
I haven't used mobx-react-form before but have used mobx and react extensively. There's a couple ways to do this. The way I have done it is as follows, assuming Webpack & ES6 & React 14 here. Instantiate the store, and use a Provider around the component that hosts the form.
import { Provider } from 'mobx-react'
import React, { Component, PropTypes } from 'react'
import AuthStore from '{your auth store rel path}'
import FormComponent from '{your form component rel path}'
// instantiate store
const myAuthStore = new AuthStore()
// i don't think the constructor for AuthStore needs a store arg.
export default class SingleFormApplication extends Component {
render() {
return (
<Provider store={myAuthStore} >
<FormComponent />
</Provider>
)
}
}
Your FormComponent class will need to take advantage of both the observer and inject methods of the mobx-react package that will wrap it in a higher order component that both injects the store object as a prop and registers a listener on the store for changes that will rerender the component. I typically use the annotation syntax and it looks like this.
#inject('{name of provider store prop to inject}') #observer
export default class Example extends Component {
constructor(props) {
super(props)
this.store = this.props.store
}
}
Finally, with the store injected, you can now pass an action from the store into an AuthForm method, which I would advise you modify accordingly. Have the AuthForm file export a method that takes an onSuccess method as an arg and returns the mobx-react-form object. I would also modify your store action to simply take the email and password as an arg instead of the whole form. In the FormComponent try this:
import { formWithSuccessAction } from '{rel path to auth form}'
then in constructor after this.store = this.props.store assignment...
this.form = formWithSuccessAction(this.store.authLogin)
then in your render method of the FormComponent use the this.form class variable to render a form as you would in the mobx-react-form docs.
To be as clear as possible, the AuthForm.formWithSuccessAction method should look something like this:
const formWithSuccessAction = (storeSuccessAction) => {
const fields = [
formFields.email,
formFields.password
];
const hooks = {
onSuccess(form) {
// Here i want to use an action - authLogin from my AuthStore
console.log('Form Values!', form.values());
// get email and password in separate vars or not, up to you
storeSuccessAction(email, password)
},
onError(form) {
console.log('All form errors', form.errors());
}
};
const AuthForm = new MobxReactForm({fields}, {plugins:formValidation,
hooks});
return AuthForm
}
Hopefully this helps you on your way.
This is the way I've been doing it for quite some time now:
export default class AttachmentCreator extends Component {
render() {
return <div>
<RaisedButton primary label="Add Attachment" />
</div>
}
}
AttachmentCreator.propTypes = {
id: PropTypes.string,
};
But I've seen people doing it this way:
export default class AttachmentCreator extends Component {
static propTypes = {
id: PropTypes.string,
};
render() {
return <div>
<RaisedButton primary label="Add Attachment" />
</div>
}
}
And in fact I've seen people setting initial state outside the constructor as well. Is this good practice? It's been bugging me, but I remember a discussion somewhere where someone said that setting default props as a static is not a good idea - I just don't remember why.
In fact, it's exactly the same in terms of performance. React.JS is a relatively new technology, so it's not clear yet what are considered good practices or don't. If you want to trust someone, check this AirBNB's styleguide:
https://github.com/airbnb/javascript/tree/master/react#ordering
import React, { PropTypes } from 'react';
const propTypes = {
id: PropTypes.number.isRequired,
url: PropTypes.string.isRequired,
text: PropTypes.string,
};
const defaultProps = {
text: 'Hello World',
};
class Link extends React.Component {
static methodsAreOk() {
return true;
}
render() {
return <a href={this.props.url} data-id={this.props.id}>{this.props.text}</a>
}
}
Link.propTypes = propTypes;
Link.defaultProps = defaultProps;
export default Link;
They are declaring a const with the propTypes object literals, keep the class pretty clean and assign them later to the static properties. I personally like this approach :)
Oh yes, it's totaly legit with Babel (or other transpilers)
class DataLoader extends React.Component {
static propTypes = {
onFinishedLoading: PropTypes.func
}
static defaultProps = {
onFinishedLoading: () => {}
}
}
...as it gets transpiled to this anyway.
class DataLoader extends React.Component {}
DataLoader.propTypes = {
onFinishedLoading: PropTypes.func
};
DataLoader.defaultProps = {
onFinishedLoading: () => {}
};
Static fields get transpiled as properties on the class object under the hood.
Look here at Babel REPL.
Moreover, assigning state or other class fields directly in the class body gets transpiled into the constructor.
non-function properties are not currently supported for es2015 classes. its a proposal for es2016. the second method is considerably more convenient, but a plugin would be required to support the syntax (theres a very common babel plugin for it).
On the other end, a hand full of open source projects are beginning to treat proptypes like TypeScript interfaces, or ActionConstants and actually create separate folders/files that house various component prop types and are then imported into the component.
So in summary, both syntaxes are ok to use. but if you want to only use strictly ES2015, the latter syntax is not yet supported in the specification.
If the component is state-less, meaning it does not change it's own state at all, you should declare it as a stateless component (export default function MyComponent(props)) and declare the propTypes outside.
Whether it's good practice to put initial state declaration in constructor is up to you. In our project we declare initial state in componentWillMount() just because we do not like the super(props) boilerplate you have to use with the constructor.
I have a string which contains a name of the Class (this is coming from a json file). This string tells my Template Class which layout / template to use for the data (also in json). The issue is my layout is not displaying.
Home.jsx:
//a template or layout.
var Home = React.createClass({
render () {
return (
<div>Home layout</div>
)
}
});
Template.jsx:
var Template = React.createClass({
render: function() {
var Tag = this.props.template; //this is the name of the class eg. 'Home'
return (
<Tag />
);
}
});
I don't get any errors but I also don't see the layout / Home Class. I've checked the props.template and this logs the correct info. Also, I can see the home element in the DOM. However it looks like this:
<div id='template-holder>
<home></home>
</div>
If I change following line to:
var Tag = Home;
//this works but it's not dynamic!
Any ideas, how I can fix this? I'm sure it's either simple fix or I'm doing something stupid. Help would be appreciated. Apologies if this has already been asked (I couldn't find it).
Thanks,
Ewan
This will not work:
var Home = React.createClass({ ... });
var Component = "Home";
React.render(<Component />, ...);
However, this will:
var Home = React.createClass({ ... });
var Component = Home;
React.render(<Component />, ...);
So you simply need to find a way to map between the string "Home" and the component class Home. A simple object will work as a basic registry, and you can build from there if you need more features.
var components = {
"Home": Home,
"Other": OtherComponent
};
var Component = components[this.props.template];
No need to manually map your classes to a dictionary, or "registry", as in Michelle's answer. A wildcard import statement is already a dictionary!
import * as widgets from 'widgets';
const Type = widgets[this.props.template];
...
<Type />
You can make it work with multiple modules by merging all the dictionaries into one:
import * as widgets from 'widgets';
import * as widgets2 from 'widgets2';
const registry = Object.assign({}, widgets, widgets2);
const widget = registry[this.props.template];
I would totally do this to get dynamic dispatch of react components. In fact I think I am in a bunch of projects.
I had the same problem, and found out the solution by myself. I don't know if is the "best pratice" but it works and I'm using it currently in my solution.
You can simply make use of the "evil" eval function to dynamically create an instance of a react component. Something like:
function createComponent(componentName, props, children){
var component = React.createElement(eval(componentName), props, children);
return component;
}
Then, just call it where you want:
var homeComponent = createComponent('Home', [props], [...children]);
If it fits your needs, maybe you can consider something like this.
Hope it helps.
I wanted to know how to create React classes dynamically from a JSON spec loaded from a database and so I did some experimenting and figured it out. My basic idea was that I wanted to define a React app through a GUI instead of typing in code in a text editor.
This is compatible with React 16.3.2. Note React.createClass has been moved into its own module.
Here's condensed version of the essential parts:
import React from 'react'
import ReactDOMServer from 'react-dom/server'
import createReactClass from 'create-react-class'
const spec = {
// getDefaultProps
// getInitialState
// propTypes: { ... }
render () {
return React.createElement('div', null, 'Some text to render')
}
}
const component = createReactClass(spec)
const factory = React.createFactory(component)
const instance = factory({ /* props */ })
const str = ReactDOMServer.renderToStaticMarkup(instance)
console.log(str)
You can see a more complete example here:
https://github.com/brennancheung/02-dynamic-react/blob/master/src/commands/tests/createClass.test.js
Here is the way it will work from a string content without embedding your components as statically linked code into your package, as others have suggested.
import React from 'react';
import { Button } from 'semantic-ui-react';
import createReactClass from 'create-react-class';
export default class Demo extends React.Component {
render() {
const s = "return { render() { return rce('div', null, rce(components['Button'], {content: this.props.propA}), rce(components['Button'], {content: 'hardcoded content'})); } }"
const createComponentSpec = new Function("rce", "components", s);
const componentSpec = createComponentSpec(React.createElement, { "Button": Button });
const component = React.createElement(createReactClass(componentSpec), { propA: "content from property" }, null);
return (
<div>
{component}
</div>
)
}
}
The React class specification is in string s. Note the following:
rce stands for React.createElement and given as a first param when callingcreateComponentSpec.
components is a dictionary of extra component types and given as a second param when callingcreateComponentSpec. This is done so that you can provide components with clashing names.
For example string Button can be resolved to standard HTML button, or button from Semantic UI.
You can easily generate content for s by using https://babeljs.io as described in https://reactjs.org/docs/react-without-jsx.html. Essentially, the string can't contain JSX stuff, and has to be plain JavaScript. That's what BabelJS is doing by translating JSX into JavaScript.
All you need to do is replace React.createElement with rce, and resolve external components via components dictionary (if you don't use external components, that you can skip the dictionary stuff).
Here is equivalent what in the code above. The same <div> with two Semantic UI Buttons in it.
JSX render() code:
function render() {
return (
<div>
<Button content={this.props.propA}/>
<Button content='hardcoded content'/>
</div>
);
}
BabelJS translates it into:
function render() {
return React.createElement("div", null, React.createElement(Button, {
content: this.props.propA
}), React.createElement(Button, {
content: "hardcoded content"
}));
}
And you do replacement as outlined above:
render() { return rce('div', null, rce(components['Button'], {content: this.props.propA}), rce(components['Button'], {content: 'hardcoded content'})); }
Calling createComponentSpec function will create a spec for React class.
Which then converted into actual React class with createReactClass.
And then brought to life with React.createElement.
All you need to do is return it from main component render func.
When you use JSX you can either render HTML tags (strings) or React components (classes).
When you do var Tag = Home, it works because the JSX compiler transforms it to:
var Template = React.createElement(Tag, {});
with the variable Tag in the same scope and being a React class.
var Tag = Home = React.createClass({
render () {
return (
<div>Home layout</div>
)
}
});
When you do
var Tag = this.props.template; // example: Tag = "aClassName"
you are doing
var Template = React.createElement("aClassName", null);
But "aClassName" is not a valid HTML tag.
Look here