Use dynamically created react components and fill with state values - javascript

Below is a proof of concept pen. I'm trying to show a lot of input fields and try to collect their inputs when they change in one big object. As you can see, the input's won't change their value, which is what I expect, since they're created once with the useEffect() and filled that in that instance.
I think that the only way to solve this is to use React.cloneElement when values change and inject the new value into a cloned element. This is why I created 2000 elements in this pen, it would be a major performance hog because every element is rerendered when the state changes. I tried to use React.memo to only make the inputs with the changed value rerender, but I think cloneElement simply rerenders it anyways, which sounds like it should since it's cloned.
How can I achieve a performant update for a single field in this setup?
https://codepen.io/10uur/pen/LYPrZdg
Edit: a working pen with the cloneElement solution that I mentioned before, the noticeable performance problems and that all inputs rerender.
https://codepen.io/10uur/pen/OJLEJqM

Here is one way to achieve the desired behavior :
https://codesandbox.io/s/elastic-glade-73ivx
Some tips :
I would not recommend putting React elements in the state, prefer putting plain data (array, objects, ...) in the state that will be mapped to React elements in the return/render method.
Don't forget to use a key prop when rendering an array of elements
Use React.memo to avoid re-rendering components when the props are the same
Use React.useCallback to memoize callback (this will help when using React.memo on children)
Use the functional form of the state setter to access the old state and update it (this also helps when using React.useCallback and avoid recreating the callback when the state change)
Here is the complete code :
import React, { useEffect } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
const INPUTS_COUNT = 2000;
const getInitialState = () => {
const state = [];
for (var i = 0; i < INPUTS_COUNT; i++) {
// Only put plain data in the state
state.push({
value: Math.random(),
id: "valueContainer" + i
});
}
return state;
};
const Root = () => {
const [state, setState] = React.useState([]);
useEffect(() => {
setState(getInitialState());
}, []);
// Use React.useCallback to memoize the onChangeValue callback, notice the empty array as second parameter
const onChangeValue = React.useCallback((id, value) => {
// Use the functional form of the state setter, to update the old state
// if we don't use the functional form, we will be forced to put [state] in the second parameter of React.useCallback
// in that case React.useCallback will not be very useful, because it will recreate the callback whenever the state changes
setState(state => {
return state.map(item => {
if (item.id === id) {
return { ...item, value };
}
return item;
});
});
}, []);
return (
<>
{state.map(({ id, value }) => {
// Use a key for performance boost
return (
<ValueContainer
id={id}
key={id}
onChangeValue={onChangeValue}
value={value}
/>
);
})}
</>
);
};
// Use React.memo to avoid re-rendering the component when the props are the same
const ValueContainer = React.memo(({ id, onChangeValue, value }) => {
const onChange = e => {
onChangeValue(id, e.target.value);
};
return (
<>
<br />
Rerendered: {Math.random()}
<br />
<input type="text" value={value} onChange={onChange} />
<br />
</>
);
});
ReactDOM.render(<Root />, document.getElementById("root"));

Related

Re-rendering on key-value pair object components

I want to avoid re-render of my child component <ChildComponent/> whenever I update my state using a onClick in <ChildComponent/>.
I have my callback function in <ParentComponent/> which updates one of the values for the key-value pair object.
In the parent component
const _keyValueObject = useMemo(() => utilityFunction(array, object), [array, object])
const [keyValueObject, setKeyValueObject] = useState<SomeTransport>(_keyValueObject)
const handleStateChange = useCallback((id: number) => {
setKeyValueObject(keyValueObject => {
const temp = { ... keyValueObject }
keyValueObject[id].isChecked = ! keyValueObject[id].isChecked
return temp
})
}, [])
return(
<Container>
{!! keyValueObject &&
Object.values(keyValueObject).map(value => (
<ValueItem
key={value.id}
category={value}
handleStateChange ={handleStateChange}
/>
))}
</Container>
)
In child component ValueItem
const clickHandler = useCallback(
event => {
event.preventDefault()
event.stopPropagation()
handleStateChange(value.id)
},
[handleStateChange, value.id],
)
return (
<Container>
<CheckBox checked={value.isChecked} onClick={clickHandler}>
{value.isChecked && <Icon as={CheckboxCheckedIcon as AnyStyledComponent} />}
</CheckBox>
<CategoryItem key={value.id}>{value.title}</CategoryItem>
</Container>
)
export default ValueItem
In child component if I use export default memo(ValueItem), then the checkbox does not get updated on the click.
What I need now is to not re-render every child component, but keeping in mind that the checkbox works. Any suggestions?
Spreading (const temp = { ... keyValueObject }) doesn't deep clone the object as you might think. So while keyValueObject will have a new reference, it's object values will not be cloned, so will have the same reference, so memo will think nothing changes when comparing the category prop.
Solution: make sure you create a new value for the keyValueObject's id which you want to update. Example: setKeyValueObject(keyValueObject => ({...keyValueObject, [id]: {...keyValueObject[id], isChecked: !keyValueObject[id].isChecked})). Now keyValueObject[id] is a new object/reference, so memo will see that and render your component. It will not render the other children since their references stay the same.
Working Codesandbox
Explanation
What you need to do is wrap the child with React.memo. This way you ensure that Child is memoized and doesn't re-render unnecessarily. However, that is not enough.
In parent, handleStateChange is getting a new reference on every render, therefore it makes the parent render. If the parent renders, all the children will re-render. Wrapping the handleStateChange with useCallback makes sure react component remembers the reference to the function. And memo remembers the result for Child.
Useful resource

How to prevent re-rendering of components that have not changed?

I have a component that consists of several other components such as text fields, and when an input is made to the text field, all other components are re-rendered.
I would like to prevent the re-rendering and only re-render the component that actually changes.
I have seen that useCallback is the right way to do this and I have already seen how to use it. However, I'm having some trouble getting useCallBack to work correctly for my case.
Even if I set it up in a simple manner as below, each new character entered into the text field causes the button to be rendered again.
I don't see my mistake.
See working example in sandbox.
const Button = () => {
console.log("Button Rendered!");
window.alert("Button Rendered");
return <button onClick="">Press me</button>;
};
export default function App() {
const [textInput, setTextInput] = useState("Hallo");
const onChangeInput = useCallback(
(e) => {
setTextInput(e.target.value);
},
[textInput]
);
return (
<div>
<input
type="text"
onChange={onChangeInput}
value={textInput}
/>
<Button />
</div>
);
}
I am happy for any calrification.
Personally I would avoid React.memo / React.useRef / React.useCallback.
The simplest solution to your example is just create another component, and store the state with this.
eg.
const Button = () => {
console.log("Button Rendered!");
window.alert("Button Rendered");
return <button onClick="">Press me</button>;
};
const TextInput = () => {
const [textInput, setTextInput] = useState("Hallo");
const onChangeInput = useCallback(
(e) => {
setTextInput(e.target.value);
},
[textInput]
);
return (
<input
type="text"
onChange={onChangeInput}
value={textInput}
/>
);
}
export default function App() {
return (
<div>
<TextInput/>
<Button />
</div>
);
}
In the above if you change Text, there is not State change in App, so Button doesn't get re-rendered, no need for useMemo etc..
You will find React works really well, the more you divide your components up, not only does it solve issues of re-render, but potentially makes it a lot easier to re-use components later.
IOW: keep state as close to the component as possible, and performance will follow.
Of course your example is simple, and in a real app you will have HOC's etc to cope with, but that's another question.. :)
useCallback does not prevent rerenders. React.memo is what prevents renders. It does a shallow comparison of the previous props with the new props, and if they're the same, it skips rendering:
const Button = React.memo(() => {
console.log("Button Rendered!");
window.alert("Button Rendered");
return <button onClick="">Press me</button>;
});
The only role that useCallback plays in this is that sometimes you want to pass a function as a prop to a memoized component. For the memoization to work, props need to not change, and useCallback can help the props to not change.
changing the state causes re-render component along with all his heirs, to prevent re-render some sections, you can use useMemo to prevent unwanted re-rendering...
NOTE: useMemo has some costs... so don't overuse it (In this small example, it is not recommended at all).
in this case, if you do not need to re-rendering, you can use the useRef to save the input reference to get that value whenever you need it.
e.g:
const BlahBlah = () => {
const inputRef = React.useRef(undefined);
return (
<div>
<input ref={inputRef} />
<button onClick={() => console.log(inputRef.current.value)}
</div>
);
};
I had similar problem. I wanted to avoid rendering a component which dependent on part2 of state, when only part1 has changed.
I used shouldComponentUpdate(nextProps, nextState) as described here https://reactjs.org/docs/optimizing-performance.html to avoid rendering it.
I'm using it this way:
const [state_data, set_state_data] = useState([
true, // loading.
{}, // vehicle.
{} // user.
]);
This allows rendering only 1x instead of 3x and React keeps track of the changes. For some reason it won't track the change of objects, but arrays yes.

What is the reason behind react component showing the changes of state inside them?

When we have many components in react project and sometimes we use multiple pre-made components for making a page. While using onChange inside a component and showing the result of the state, in this case, what functionality of components allows the value render of state and how it works when we have multiple components inside other components.
Here is an Ex...
function Component() {
const [value, setValue] = React.useState()
const handleChange = val => {
setValue(val)
}
return (
<React.Fragment>
<Compo1 //perform adding +1
onChange={handleChange}
/>
Value: {value} // 1
{console.log("value", value)} // showing right value
<Compo2>
<Compo3>
<Compo1 //perform adding +1
onChange={handleChange}
/>
Value:{value} // undefined
{console.log("value", value)} // showing right value
</Compo3>
{console.log("value", value)} // showing right value
</Compo2>
</React.Fragment>
)
}
render(<Component />)
In this case why console is showing the right value but the state variable value is showing undefined.
The only way I can get that code to do what you say it does is when you incorrectly use React.memo on Compo3:
const Compo1 = ({ onChange }) => (
<button onClick={() => onChange(Date.now())}>+</button>
);
const Compo2 = ({ children }) => <div>{children}</div>;
const Compo3 = React.memo(
function Compo3({ children }) {
return <div>{children}</div>;
},
() => true//never re render unless you re mount
);
function Component() {
const [value, setValue] = React.useState(88);
const handleChange = React.useCallback(() => {
setValue((val) => val + 1);
}, []);
return (
<React.Fragment>
<Compo1 //perform adding +1
onChange={handleChange}
/>
works: {value}-----
<Compo2>
<Compo3>
<Compo1 //perform adding +1
onChange={handleChange}
/>
broken:{value}-----
</Compo3>
</Compo2>
</React.Fragment>
);
}
ReactDOM.render(
<Component />,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Maybe you can do the same if you do some wacky stuff with shouldComponentUpdate
A component will render when:
When the parent renders the child and the child is a functional component (not wrapped in React.memo)
When the parent renders the child with different prop values than the previous render.
When value in [value,setValue]=useState() or when this.state changes (when state changes).
When someContext in value = useContext(someContext) changes (even if value doesn't change).
In most cases when value in value = useCustomHoom() changes but this is not guaranteed for every hook.
When Parent renders and passes a different key prop to Child than the previous render (see 2). This causes the Child to unmount and re mount as well.
In the example the Compo3 wants to re render because Parent is re rendered due to a state change and passes different props (props.children).
Compo3 is not a functional component because it's wrapped in React.memo. This means that Compo3 will only re render if props changed (pure component).
The function passed as the second argument to React.memo can be used to custom compare previous props to current props, if that function returns true then that tells React the props changed and if it returns false then that tells React the props didn't change.
The function always returns true so React is never told that the props changed.

How to set initial state value for useState Hook in jest and enzyme?

I know this question is already asked here: How to set initial state for useState Hook in jest and enzyme?
const [state, setState] = useState([]);
And I totally agree with Jimmy's Answer to mock the useState function from test file but I have some extended version of this question, "What if I have multiple useState statements into the hooks, How can I test them and assign the respective custom values to them?"
I have some JSX rendering with the condition of hook's state values and depending on the values of that state the JSX is rendering.
How Can I test those JSX by getting them into the wrapper of my test case code?
Upon the answer you linked, you can return different values for each call of a mock function:
let myMock = jest.fn();
myMock
.mockReturnValueOnce(10)
.mockReturnValueOnce('x')
.mockReturnValue(true);
In my opinion this is still brittle. You may modify the component and add another state later, and you would get confusing results.
Another way to test a React component is to test it like a user would by clicking things and setting values on inputs. This would fire the event handlers of the component, and React would update the state just as in real configuration. You may not be able to do shallow rendering though, or you may need to mock the child components.
If you prefer shallow rendering, maybe read initial state values from props like this:
function FooComponent({initialStateValue}) {
const [state, setState] = useState(initialStateValue ?? []);
}
If you don't really need to test state but the effects state has on children, you should (as some comments mention) just then test the side effect and treat the state as an opaque implementation detail. This will work if your are not using shallow rendering or not doing some kind of async initialization like fetching data in an effect or something otherwise it could require more work.
If you cant do the above, you might consider removing state completely out of the component and make it completely functional. That way any state you need, you can just inject it into the component. You could wrap all of the hooks into a 'controller' type hook that encapsulates the behavior of a component or domain. Here is an example of how you might do that with a <Todos />. This code is 0% tested, its just written to show a concept.
const useTodos = (state = {}) => {
const [todos, setTodos] = useState(state.todos);
const id = useRef(Date.now());
const addTodo = useCallback((task) => {
setTodos((current) => [...current, { id: id.current++, completed: false, task }]);
}, []);
const removeTodo = useCallback((id) => {
setTodos((current) => current.filter((t) => t.id !== id));
}, []);
const completeTodo = useCallback((id) => {
setTodos((current) => current.map((t) => {
let next = t;
if (t.id === id) {
next = { ...t, completed: true };
}
return next;
}))
}, []);
return { todos, addTodo, removeTodo, completeTodo };
};
const Todos = (props) => {
const { todos, onAdd, onRemove, onComplete } = props;
const onSubmit = (e) => {
e.preventDefault();
onAdd({ task: e.currentTarget.elements['todo'].value });
}
return (
<div classname="todos">
<ul className="todos-list">
{todos.map((todo) => (
<Todo key={todo.id} onRemove={onRemove} onComplete={onComplete} />
)}
</ul>
<form className="todos-form" onSubmit={onSubmit}>
<input name="todo" />
<button>Add</button>
</form>
</div>
);
};
So now the parent component injects <Todos /> with todos and callbacks. This is useful for testing, SSR, ect. Your component can just take a list of todos and some handlers, and more importantly you can trivially test both. For <Todos /> you can pass mocks and known state and for useTodos you would just call the methods and make sure the state reflects what is expected.
You might be thinking This moves the problem up a level and it does, but you can now test the component/logic and increase test coverage. The glue layer would require minimal if any testing around this component, unless you really wanted to make sure props are passed into the component.

How to force a functional React component to render?

I have a function component, and I want to force it to re-render.
How can I do so?
Since there's no instance this, I cannot call this.forceUpdate().
🎉 You can now, using React hooks
Using react hooks, you can now call useState() in your function component.
useState() will return an array of 2 things:
A value, representing the current state.
Its setter. Use it to update the value.
Updating the value by its setter will force your function component to re-render,
just like forceUpdate does:
import React, { useState } from 'react';
//create your forceUpdate hook
function useForceUpdate(){
const [value, setValue] = useState(0); // integer state
return () => setValue(value => value + 1); // update state to force render
// A function that increment 👆🏻 the previous state like here
// is better than directly setting `setValue(value + 1)`
}
function MyComponent() {
// call your hook here
const forceUpdate = useForceUpdate();
return (
<div>
{/*Clicking on the button will force to re-render like force update does */}
<button onClick={forceUpdate}>
Click to re-render
</button>
</div>
);
}
You can find a demo here.
The component above uses a custom hook function (useForceUpdate) which uses the react state hook useState. It increments the component's state's value and thus tells React to re-render the component.
EDIT
In an old version of this answer, the snippet used a boolean value, and toggled it in forceUpdate(). Now that I've edited my answer, the snippet use a number rather than a boolean.
Why ? (you would ask me)
Because once it happened to me that my forceUpdate() was called twice subsequently from 2 different events, and thus it was reseting the boolean value at its original state, and the component never rendered.
This is because in the useState's setter (setValue here), React compare the previous state with the new one, and render only if the state is different.
Update react v16.8 (16 Feb 2019 realease)
Since react 16.8 released with hooks, function components have the ability to hold persistent state. With that ability you can now mimic a forceUpdate:
function App() {
const [, updateState] = React.useState();
const forceUpdate = React.useCallback(() => updateState({}), []);
console.log("render");
return (
<div>
<button onClick={forceUpdate}>Force Render</button>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.1/umd/react-dom.production.min.js"></script>
<div id="root"/>
Note that this approach should be re-considered and in most cases when you need to force an update you probably doing something wrong.
Before react 16.8.0
No you can't, State-Less function components are just normal functions that returns jsx, you don't have any access to the React life cycle methods as you are not extending from the React.Component.
Think of function-component as the render method part of the class components.
Official FAQ now recommends this way if you really need to do it:
const [ignored, forceUpdate] = useReducer(x => x + 1, 0);
function handleClick() {
forceUpdate();
}
Simplest way 👌
if you want to force a re-render, add a dummy state you can change to initiate a re-render.
const [rerender, setRerender] = useState(false);
...
setRerender(!rerender); //whenever you want to re-render
And this will ensure a re-render, And you can call setRerender(!rerender) anywhere, whenever you want :)
I used a third party library called
use-force-update
to force render my react functional components. Worked like charm.
Just use import the package in your project and use like this.
import useForceUpdate from 'use-force-update';
const MyButton = () => {
const forceUpdate = useForceUpdate();
const handleClick = () => {
alert('I will re-render now.');
forceUpdate();
};
return <button onClick={handleClick} />;
};
Best approach - no excess variables re-created on each render:
const forceUpdateReducer = (i) => i + 1
export const useForceUpdate = () => {
const [, forceUpdate] = useReducer(forceUpdateReducer, 0)
return forceUpdate
}
Usage:
const forceUpdate = useForceUpdate()
forceUpdate()
If you already have a state inside the function component and you don't want to alter it and requires a re-render you could fake a state update which will, in turn, re-render the component
const [items,setItems] = useState({
name:'Your Name',
status: 'Idle'
})
const reRender = () =>{
setItems((state) => [...state])
}
this will keep the state as it was and will make react into thinking the state has been updated
This can be done without explicitly using hooks provided you add a prop to your component and a state to the stateless component's parent component:
const ParentComponent = props => {
const [updateNow, setUpdateNow] = useState(true)
const updateFunc = () => {
setUpdateNow(!updateNow)
}
const MyComponent = props => {
return (<div> .... </div>)
}
const MyButtonComponent = props => {
return (<div> <input type="button" onClick={props.updateFunc} />.... </div>)
}
return (
<div>
<MyComponent updateMe={updateNow} />
<MyButtonComponent updateFunc={updateFunc}/>
</div>
)
}
The accepted answer is good.
Just to make it easier to understand.
Example component:
export default function MyComponent(props) {
const [updateView, setUpdateView] = useState(0);
return (
<>
<span style={{ display: "none" }}>{updateView}</span>
</>
);
}
To force re-rendering call the code below:
setUpdateView((updateView) => ++updateView);
None of these gave me a satisfactory answer so in the end I got what I wanted with the key prop, useRef and some random id generator like shortid.
Basically, I wanted some chat application to play itself out the first time someone opens the app. So, I needed full control over when and what the answers are updated with the ease of async await.
Example code:
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// ... your JSX functional component, import shortid somewhere
const [render, rerender] = useState(shortid.generate())
const messageList = useRef([
new Message({id: 1, message: "Hi, let's get started!"})
])
useEffect(()=>{
async function _ () {
await sleep(500)
messageList.current.push(new Message({id: 1, message: "What's your name?"}))
// ... more stuff
// now trigger the update
rerender(shortid.generate())
}
_()
}, [])
// only the component with the right render key will update itself, the others will stay as is and won't rerender.
return <div key={render}>{messageList.current}</div>
In fact this also allowed me to roll something like a chat message with a rolling .
const waitChat = async (ms) => {
let text = "."
for (let i = 0; i < ms; i += 200) {
if (messageList.current[messageList.current.length - 1].id === 100) {
messageList.current = messageList.current.filter(({id}) => id !== 100)
}
messageList.current.push(new Message({
id: 100,
message: text
}))
if (text.length === 3) {
text = "."
} else {
text += "."
}
rerender(shortid.generate())
await sleep(200)
}
if (messageList.current[messageList.current.length - 1].id === 100) {
messageList.current = messageList.current.filter(({id}) => id !== 100)
}
}
If you are using functional components with version < 16.8. One workaround would be to directly call the same function like
import React from 'react';
function MyComponent() {
const forceUpdate = MyComponent();
return (
<div>
<button onClick={forceUpdate}>
Click to re-render
</button>
</div>
);
}
But this will break if you were passing some prop to it. In my case i just passed the same props which I received to rerender function.
For me just updating the state didn't work. I am using a library with components and it looks like I can't force the component to update.
My approach is extending the ones above with conditional rendering. In my case, I want to resize my component when a value is changed.
//hook to force updating the component on specific change
const useUpdateOnChange = (change: unknown): boolean => {
const [update, setUpdate] = useState(false);
useEffect(() => {
setUpdate(!update);
}, [change]);
useEffect(() => {
if (!update) setUpdate(true);
}, [update]);
return update;
};
const MyComponent = () => {
const [myState, setMyState] = useState();
const update = useUpdateOnChange(myState);
...
return (
<div>
... ...
{update && <LibraryComponent />}
</div>
);
};
You need to pass the value you want to track for change. The hook returns boolean which should be used for conditional rendering.
When the change value triggers the useEffect update goes to false which hides the component. After that the second useEffect is triggered and update goes true which makes the component visible again and this results in updating (resizing in my case).

Categories

Resources