I'm beginner with React testing, learning by coding, here i have a component 'cam.tsx'
i want to test it, when i want to test Add function it goes straight like this, but when i want to test Update function it still shows Add function in my test, how to test both of them ?
Add and Update functions are forms where user can fill.
describe("Testing component ", () => {
const Camera = (): RenderResult =>
render(
<Provider store={store}>
<Cam
}}
/>{" "}
</Provider>
);
test("Cam", () => {
Camera();
const name = screen.queryByTestId(/^AddName/i);
});
});
cam.tsx:
const ADD = "ADD";
let [state, setState] = useState<State>({mode: ADD });
if (props.mode) {
state.mode = props.mode;
}
const option = state.mode;
return (
<React.Fragment>
<div data-testid="header">
{option == ADD ? Add() : <></>}
{option == UPDATE ? Update() : <></>}
</div>
</React.Fragment>
Basically cam.tsx is a component which has two forms one for updating camera and another for adding new camera.When user clicks add/update icon then cam component gets 'mode' via props ' state.mode = props.mode '
English is not my mother language, so could be mistakes
Here is how to test a component that conditionally renders components from state and can be updated via props.
import {render, screen} from '#testing-library/react';
import {Cam} from './Cam';
test('renders add by default', () => {
render(<Cam/>);
expect(screen.getByTestId('addForm'))
.toBeInTheDocument();
expect(screen.queryByTestId('updateForm'))
.not.toBeInTheDocument();
});
test('renders edit by passing props', () => {
const {rerender} = render(<Cam mode={undefined}/>);
rerender(<Cam mode={'UPDATE'} />)
expect(screen.getByTestId('updateForm'))
.toBeInTheDocument();
expect(screen.queryByTestId('addForm'))
.not.toBeInTheDocument();
});
However, it is known in the React community that updating state via props is usually an anti-pattern. This is because you now have two sources of truth for state and can be easy to have these two states conflicting. You should instead just use props to manage rendering.
If state comes from a parent component, use props.
export function Cam(props) {
const option = props.mode;
return (
<div data-testid="header">
{option === ADD ? Add() : <></>}
{option === UPDATE ? Update() : <></>}
</div>
);
}
If you really want to keep state in the child component even if props are passed in, you should update props in an useEffect hook. Additionally, you should use the setState function rather than setting state manually state.mode = props.mode
Use the useEffect hook to update state via props.
...
const [state, setState] = useState({mode: ADD});
useEffect(() => {
if (props.mode) {
setState({mode: props.mode});
}
}, [props.mode]) <-- checks this value to prevent infinite loop.
const option = state.mode;
return (
...
I have a prop being passed from a parent component to a child component which changes based on the user's input.
I want to trigger a data fetch in the child component when that prop changes before the child component is rendered. How can I do it?
I tried in the following manner by using useEffects(()=>{},[props.a, props.b]) but that is always called after the render. Please help!
import React, { useEffect, useState } from "react";
import "./styles.css";
export default function parentComponent() {
const [inputs, setInputs] = useState({ a: "", b: "" });
return (
<>
<input
value={inputs.a}
onChange={(event) => {
const value = event.target.value;
setInputs((prevState) => {
return { ...prevState, a: value };
});
}}
/>
<input
value={inputs.b}
onChange={(event) => {
const value = event.target.value;
setInputs((prevState) => {
return { ...prevState, b: value };
});
}}
/>
<ChildComponent a={inputs.a} b={inputs.b} />
</>
);
}
function ChildComponent(props) {
const [isLoading, setIsLoading] = useState(true);
const [data, setData] = useState({});
useEffect(() => {
console.log("updating new data based on props.a: " + props.a);
setData({ name: "john " + props.a });
return () => {};
}, [props.a, props.b]);
useEffect(() => {
console.log("data successfully changed");
console.log(data);
if (Object.keys(data).length !== 0) {
setIsLoading(false);
}
return () => {};
}, [data]);
function renderPartOfComponent() {
console.log("rendering POC with props.a: " + props.a);
return <div>data is: {data.name}</div>;
}
return (
<div className="App">{isLoading ? null : renderPartOfComponent()}</div>
);
}
In the console what I get is:
rendering POC with props.a: fe
rendering POC with props.a: fe
updating new data based on props.a: fe
rendering POC with props.a: fe
rendering POC with props.a: fe
data successfully changed
Object {name: "john fe"}
rendering POC with props.a: fe
rendering POC with props.a: fe
If you know how I can make the code more efficient, that would be a great help as well!
Here's the codesandbox link for the code: https://codesandbox.io/s/determined-northcutt-6z9f8?file=/src/App.js:0-1466
Solution
You can use useMemo, which doesn't wait for a re-render. It will execute as long as the dependencies are changed.
useMemo(()=>{
doSomething() //Doesn't want until render is completed
}, [dep1, dep2])
You can use function below:
// utils.js
const useBeforeRender = (callback, deps) => {
const [isRun, setIsRun] = useState(false);
if (!isRun) {
callback();
setIsRun(true);
}
useEffect(() => () => setIsRun(false), deps);
};
// yourComponent.js
useBeforeRender(() => someFunc(), []);
useEffect is always called after the render phase of the component. This is to avoid any side-effects from happening during the render commit phase (as it'd cause the component to become highly inconsistent and keep trying to render itself).
Your ParentComponent consists of Input, Input & ChildComponent.
As you type in textbox, ParentComponent: inputs state is modified.
This state change causes ChildComponent to re-render, hence renderPartOfComponent is called (as isLoading remains false from previous render).
After re-render, useEffect will be invoked (Parent's state propagates to Child).
Since isLoading state is modified from the effects, another rendering happens.
I found the solution by creating and maintaining state within the ChildComponent
So, the order of processes was this:
props modified -> render takes place -> useEffect block is executed.
I found the workaround by simply instantiating a state within the childComponent and making sure that the props state is the same as the one in the child component before rendering, else it would just show loading... This works perfectly.
Nowadays you can use useLayoutEffect which is a version of useEffect that fires before the browser repaints the screen.
Docs: https://beta.reactjs.org/reference/react/useLayoutEffect
I`m having some problems trying to listen to state changes in this application. Basically I was expecting a useEffect hook to be fired after some state changed, but nothing at all is happening.
This is what I got
index.jsx
// this is a simplification.
// I actually have a react-router-dom's Router wrapping everything
// and App is a Switch with multiple Route components
ReactDOM.render(
<Provider>
<App>
</Provider>
, document.getElementById('root'));
useSession.jsx
export const useSession = () => {
const [session, setSession] = useState(null)
const login = useCallback(() => {
// do something
setSession(newSession)
})
return {
session,
setSession,
login
}
}
Provider.jsx
const { session } = useSession();
useEffect(() => {
console.log('Print this')
}, [session])
// more code ...
App.jsx
export function Login() {
const { login } = useSession();
return <button type="button" onClick={() => { login() }}>Login</button>
}
Well I have this Parent component Provider watching the session state, but when it is updated the useEffect of the provider is never called.
The useEffect is fired only if the setSession is called in the same hook/method. For example, if I import the setSession in the Provider and use it there, the useEffect will be fired; Or if I add a useEffect in the useSession method, it is gonna be fired when login updates the state.
The callback of useEffect is called but only once, when the component is mounted, but not when the state is changed.
How can I achieve this behavior? Having the Provider's useEffect fired whenever session is updated?
Thanks in advance!
I think this is just a bit of misunderstanding of how custom hooks work.Every instance of the component has its own state. Let me just show a simple example illustrating this.
function App () {
return (
<div>
<ComponentA/>
<ComponentB/>
<ComponentC/>
<ComponentD/>
</div>
)
}
function useCounter() {
const [counter, setCounter] = React.useState(0);
function increment() {
setCounter(counter+1)
}
return {
increment, counter, setCounter
}
}
function ComponentA() {
const { counter, increment }= useCounter()
return (
<div>
<button onClick={()=>increment()}>Button A</button>
ComponentA Counter: {counter}
</div>
)
}
function ComponentB() {
const { counter, increment }= useCounter()
return (
<div>
<button onClick={()=>increment()}>Button B</button>
ComponentB Counter: {counter}
</div>
)
}
function ComponentC() {
const { counter }= useCounter();
return (
<div>
ComponentC Counter: {counter}
</div>
)
}
function ComponentD() {
const [toggle, setToggle] = React.UseState(false);
const { counter }= useCounter();
React.useEffect(() => {
setInterval(()=>{
setToggle(prev => !prev);
}, 1000)
})
return (
<div>
ComponentD Counter: {counter}
</div>
)
}
From the above code if you can see that incrementing count by clicking Button Awill not affect the count instance of ComponentB.This is because every instance of the component has its own state. You can also see that clicking either buttons won't trigger ComponentC to rerender since they don't share the same instance. Even if i trigger rerender every one second like in Component D thus invoking useCounter the counter in ComponentD remains 0.
Solution
However there are multiple ways of making components share/listen to same state changes
You can shift all your state i.e [session state] to the Provider component and make it visible to other components by passing it via props.
You can move state to a global container Redux or simply use Context Api + UseReducer Hook here is an example
But since you are dealing with auth and session management, I suggest you persist the session state in local storage or session storage, and retrieve it whenever you need it. Hope that helped
I create a context and a provider as below. As you can see, I use useState() within my provider (for state) along with functions (all passed within an object as the value prop, allows for easy destructuring whatever I need in child components).
import React, { useState, createContext } from "react";
const CountContext = createContext(null);
export const CountProvider = ({ children }) => {
const [count, setCount] = useState(0);
const incrementCount = () => {
setCount(count + 1);
};
const decrementCount = () => {
setCount(count - 1);
};
return (
<CountContext.Provider value={{ count, incrementCount, decrementCount }}>
{children}
</CountContext.Provider>
);
};
export default CountContext;
I wrap my app within such a provider(s) at a higher location such as at index.js.
And consume the state using useContext() as below.
import React, { useContext } from "react";
import CountContext from "../contexts/CountContext";
import Incrementer from "./Incrementer";
import Decrementer from "./Decrementer";
const Counter = () => {
const { count } = useContext(CountContext);
return (
<div className="counter">
<div className="count">{count}</div>
<div className="controls">
<Decrementer />
<Incrementer />
</div>
</div>
);
};
export default Counter;
Everything is working just fine, and I find it easier to maintain things this way as compared to some of the other methods of (shared) state management.
CodeSandbox: https://codesandbox.io/s/react-usecontext-simplified-consumption-hhfz6
I am wondering if there is a fault or flaw here that I haven't noticed yet?
One of the key differences with other state management tools like Redux is performance.
Any child that uses a Context needs to be nested inside the ContextProvider component. Every time the ContextProvider state changes it will render, and all its (non-memoized) children will render too.
In contrast, when using Redux we connect each Component to the store, so each component will render only if the part of the state it is connect to changes.
Considering below hooks example
import { useState } from 'react';
function Example() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
Basically we use this.forceUpdate() method to force the component to re-render immediately in React class components like below example
class Test extends Component{
constructor(props){
super(props);
this.state = {
count:0,
count2: 100
}
this.setCount = this.setCount.bind(this);//how can I do this with hooks in functional component
}
setCount(){
let count = this.state.count;
count = count+1;
let count2 = this.state.count2;
count2 = count2+1;
this.setState({count});
this.forceUpdate();
//before below setState the component will re-render immediately when this.forceUpdate() is called
this.setState({count2: count
}
render(){
return (<div>
<span>Count: {this.state.count}></span>.
<button onClick={this.setCount}></button>
</div>
}
}
But my query is How can I force above functional component to re-render immediately with hooks?
This is possible with useState or useReducer, since useState uses useReducer internally:
const [, updateState] = React.useState();
const forceUpdate = React.useCallback(() => updateState({}), []);
forceUpdate isn't intended to be used under normal circumstances, only in testing or other outstanding cases. This situation may be addressed in a more conventional way.
setCount is an example of improperly used forceUpdate, setState is asynchronous for performance reasons and shouldn't be forced to be synchronous just because state updates weren't performed correctly. If a state relies on previously set state, this should be done with updater function,
If you need to set the state based on the previous state, read about the updater argument below.
<...>
Both state and props received by the updater function are guaranteed
to be up-to-date. The output of the updater is shallowly merged with
state.
setCount may not be an illustrative example because its purpose is unclear but this is the case for updater function:
setCount(){
this.setState(({count}) => ({ count: count + 1 }));
this.setState(({count2}) => ({ count2: count + 1 }));
this.setState(({count}) => ({ count2: count + 1 }));
}
This is translated 1:1 to hooks, with the exception that functions that are used as callbacks should better be memoized:
const [state, setState] = useState({ count: 0, count2: 100 });
const setCount = useCallback(() => {
setState(({count}) => ({ count: count + 1 }));
setState(({count2}) => ({ count2: count + 1 }));
setState(({count}) => ({ count2: count + 1 }));
}, []);
React Hooks FAQ official solution for forceUpdate:
const [_, forceUpdate] = useReducer((x) => x + 1, 0);
// usage
<button onClick={forceUpdate}>Force update</button>
Working example
const App = () => {
const [_, forceUpdate] = useReducer((x) => x + 1, 0);
return (
<div>
<button onClick={forceUpdate}>Force update</button>
<p>Forced update {_} times</p>
</div>
);
};
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.1/umd/react.production.min.js" integrity="sha256-vMEjoeSlzpWvres5mDlxmSKxx6jAmDNY4zCt712YCI0=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.1/umd/react-dom.production.min.js" integrity="sha256-QQt6MpTdAD0DiPLhqhzVyPs1flIdstR4/R7x4GqCvZ4=" crossorigin="anonymous"></script>
<script>var useReducer = React.useReducer</script>
<div id="root"></div>
Generally, you can use any state handling approach you want to trigger an update.
With TypeScript
codesandbox example
useState
const forceUpdate: () => void = React.useState({})[1].bind(null, {}) // see NOTE below
useReducer (recommended)
const forceUpdate = React.useReducer(() => ({}), {})[1] as () => void
as custom hook
Just wrap whatever approach you prefer like this
function useForceUpdate(): () => void {
return React.useReducer(() => ({}), {})[1] as () => void // <- paste here
}
How this works?
"To trigger an update" means to tell React engine that some value has changed and that it should rerender your component.
[, setState] from useState() requires a parameter. We get rid of it by binding a fresh object {}.
() => ({}) in useReducer is a dummy reducer that returns a fresh object each time an action is dispatched.
{} (fresh object) is required so that it triggers an update by changing a reference in the state.
PS: useState just wraps useReducer internally, so use reducer to reduce complexity. source
NOTE: Referential instability
Using .bind with useState causes a change in function reference between renders.
It is possible to wrap it inside useCallback as already explained in this answer here, but then it wouldn't be a sexy one-linerâ„¢. The Reducer version already keeps reference equality (stability) between renders. This is important if you want to pass the forceUpdate function in props to another component.
plain JS
const forceUpdate = React.useState({})[1].bind(null, {}) // see NOTE above
const forceUpdate = React.useReducer(() => ({}))[1]
As the others have mentioned, useState works - here is how mobx-react-lite implements updates - you could do something similar.
Define a new hook, useForceUpdate -
import { useState, useCallback } from 'react'
export function useForceUpdate() {
const [, setTick] = useState(0);
const update = useCallback(() => {
setTick(tick => tick + 1);
}, [])
return update;
}
and use it in a component -
const forceUpdate = useForceUpdate();
if (...) {
forceUpdate(); // force re-render
}
See https://github.com/mobxjs/mobx-react-lite/blob/master/src/utils.ts and https://github.com/mobxjs/mobx-react-lite/blob/master/src/useObserver.ts
Alternative to #MinhKha's answer:
It can be much cleaner with useReducer:
const [, forceUpdate] = useReducer(x => x + 1, 0);
Usage:
forceUpdate() - cleaner without params
You can simply define the useState like that:
const [, forceUpdate] = React.useState(0);
And usage: forceUpdate(n => !n)
Hope this help !
You should preferably only have your component depend on state and props and it will work as expected, but if you really need a function to force the component to re-render, you could use the useState hook and call the function when needed.
Example
const { useState, useEffect } = React;
function Foo() {
const [, forceUpdate] = useState();
useEffect(() => {
setTimeout(forceUpdate, 2000);
}, []);
return <div>{Date.now()}</div>;
}
ReactDOM.render(<Foo />, document.getElementById("root"));
<script src="https://unpkg.com/react#16.7.0-alpha.0/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom#16.7.0-alpha.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Simple code
const forceUpdate = React.useReducer(bool => !bool)[1];
Use:
forceUpdate();
Potential option is to force update only on specific component using key. Updating the key trigger a rendering of the component (which failed to update before)
For example:
const [tableKey, setTableKey] = useState(1);
...
useEffect(() => {
...
setTableKey(tableKey + 1);
}, [tableData]);
...
<DataTable
key={tableKey}
data={tableData}/>
You can (ab)use normal hooks to force a rerender by taking advantage of the fact that React doesn't print booleans in JSX code
// create a hook
const [forceRerender, setForceRerender] = React.useState(true);
// ...put this line where you want to force a rerender
setForceRerender(!forceRerender);
// ...make sure that {forceRerender} is "visible" in your js code
// ({forceRerender} will not actually be visible since booleans are
// not printed, but updating its value will nonetheless force a
// rerender)
return (
<div>{forceRerender}</div>
)
One line solution:
const useForceUpdate = () => useState()[1];
useState returns a pair of values: the current state and a function that updates it - state and setter, here we are using only the setter in order to force re-render.
react-tidy has a custom hook just for doing that called useRefresh:
import React from 'react'
import {useRefresh} from 'react-tidy'
function App() {
const refresh = useRefresh()
return (
<p>
The time is {new Date()} <button onClick={refresh}>Refresh</button>
</p>
)
}
Learn more about this hook
Disclaimer I am the writer of this library.
My variation of forceUpdate is not via a counter but rather via an object:
// Emulates `forceUpdate()`
const [unusedState, setUnusedState] = useState()
const forceUpdate = useCallback(() => setUnusedState({}), [])
Because {} !== {} every time.
Solution in one single line:
const [,forceRender] = useReducer((s) => s+1, 0)
You can learn about useReducer here.
https://reactjs.org/docs/hooks-reference.html#usereducer
This will render depending components 3 times (arrays with equal elements aren't equal):
const [msg, setMsg] = useState([""])
setMsg(["test"])
setMsg(["test"])
setMsg(["test"])
const useForceRender = () => {
const [, forceRender] = useReducer(x => !x, true)
return forceRender
}
Usage
function Component () {
const forceRender = useForceRender()
useEffect(() => {
// ...
forceRender()
}, [])
For regular React Class based components, refer to React Docs for the forceUpdate api at this URL. The docs mention that:
Normally you should try to avoid all uses of forceUpdate() and only
read from this.props and this.state in render()
However, it is also mentioned in the docs that:
If your render() method depends on some other data, you can tell React
that the component needs re-rendering by calling forceUpdate().
So, although use cases for using forceUpdate might be rare, and I have not used it ever, however I have seen it used by other developers in some legacy corporate projects that I have worked on.
So, for the equivalent functionality for Functional Components, refer to the React Docs for HOOKS at this URL. Per the above URL, one can use the "useReducer" hook to provide a forceUpdate functionality for Functional Components.
A working code sample that does not use state or props is provided below, which is also available on CodeSandbox at this URL
import React, { useReducer, useRef } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
function App() {
// Use the useRef hook to store a mutable value inside a functional component for the counter
let countref = useRef(0);
const [, forceUpdate] = useReducer(x => x + 1, 0);
function handleClick() {
countref.current++;
console.log("Count = ", countref.current);
forceUpdate(); // If you comment this out, the date and count in the screen will not be updated
}
return (
<div className="App">
<h1> {new Date().toLocaleString()} </h1>
<h2>You clicked {countref.current} times</h2>
<button
onClick={() => {
handleClick();
}}
>
ClickToUpdateDateAndCount
</button>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
NOTE: An alternate approach using the useState hook (instead of useReducer) is also available at this URL.
There are many ways to force re-render in Hook.
For me simple way with useState() and tip of reference object values.
const [, forceRender] = useState({});
// Anywhre
forceRender({});
Codesandbox Example
A bit late to the party but I notice that most (all) of the answers have missed the part where you can pass a callback to forceUpdate lifecycle method.
As per the react source code, this callback has the same behavior as the one in the setState method - it is executed after the update.
Hence, the most correct implementation would be like this:
/**
* Increments the state which causes a rerender and executes a callback
* #param {function} callback - callback to execute after state update
* #returns {function}
*/
export const useForceUpdate = (callback) => {
const [state, updater] = useReducer((x) => x + 1, 0);
useEffect(() => {
callback && callback();
}, [state]);
return useCallback(() => {
updater();
}, []);
};
I was working with an array and spotted this issue. However, instead of explicit forceUpdate I found another approach - to deconstruct an array and set a new value for it using this code:
setRoutes(arr => [...arr, newRoute]); // add new elements to the array
setRouteErrors(routeErrs => [...routeErrs]); // the array elements were changed
I found it very interesting that setting even a copy of an array will not trigger the hook. I assume React does the shallow comparison