I was playing around with react-dev-tools chrome extension and found out that all my components are re-rendering.
App.js
import React from 'react';
import './App.css';
import Header from './components/molecules/Header/Header';
// import { colorListGenerator } from './core-utils/helpers';
import ColorPalette from './components/organisms/ColorPalette/ColorPalette';
export const colorListGenerator = (n) => {
let colorArray = []
for(let i=0; i<n; i++) {
let randomColor = '#'+Math.floor(Math.random()*16777215).toString(16);
let id="id" + Math.random().toString(16).slice(2)
console.log(typeof(id), id)
let color = {
id: id,
hex: randomColor
}
colorArray.push(color);
}
return colorArray
}
const App = () => {
const colors=colorListGenerator(10);
return (
<div className="App">
<Header/>
<ColorPalette colorPalette={colors} />
</div>
);
}
export default App;
ColorPalette.js
/* eslint-disable eqeqeq */
import React from 'react';
import Color from '../../atoms/Color';
import './ColorPalette.css';
const ColorPalette = ({ colorPalette }) => {
const [colors, setColors] = React.useState(colorPalette);
// const handleColorClick = (event) => {
// const id = event.currentTarget.getAttribute('id')
// const index = colors.findIndex(item => item.id == id);
// setColors(colors.filter(item => item.id != id))
// }
const deleteItem = (id) => {
setColors(colors.filter(item => item.id != id))
}
return (
<div className={'colorPalette'}>
{colors && colors.map((color, index) => {
// const key = index
const key = color.id
return <Color
key={key}
color={color.hex}
colorKey={key}
handleColorClick = {() => {deleteItem(color.id)}}
/> })}
</div>
)
}
// export default React.memo(ColorPalette);
export default ColorPalette;
Color.js
import React from 'react';
import './Color.css';
import deleteIcon from '../../../delete-icon.png'
const Color = ({ color, colorKey, handleColorClick }) => {
return (
<div className="color"
style={{ backgroundColor: color }}
// no need to mention key here
// key={colorKey}
id={colorKey}
onClick={handleColorClick} >
<p> {colorKey} </p>
<img src={deleteIcon}
alt={'delete'}
className="delete"
/>
</div>
)
}
// export default React.memo(Color);
export default Color;
When I use the profiler to check why all my 'Color' components have re-rendered after deleting a single item, it complains that handleColorClick prop has changed. I changed the deleteItem to handleColorClick which isn't an arrow function, but the result is the same. I'm also passing unique ids. Interestingly, when I pass const key = Math.random() instead of const key = color.id my Color components are not rerendering. So it has something to do with the keys. I want to understand why my components are rerendering when I pass unique ids as keys.
The only way a React functional component will be prevented from rerendering is by using React.memo to memoize the component. Memoization here means that if the component's props do not change - they are strictly equivalent to each other using the === operator - then the component's last render output will be re-used instead of rerendering the entire component.
However, React.memo itself gets tricky when you're talking about props that are object or functions - values for which the strict === comparison checks referential equality. That means that for functions like deleteItem need to use something like React.useCallback to memoize the references themselves so that they themselves do not change between renders, which will trip up React.memo and lead to rerenders in situations where intuitively it seems like it shouldn't.
As you can see, it quickly starts to get quite complicated, as you try to keep track of memoizing your functions, your objects, your components, etc.
And really, what's the point?
The performance gains you get from memoization - if they even materialize - are miniscule. This is a classic case of premature optimization, sometimes called the "root of all evil" because of what an unnecessary time sink it is, for little to no gain, and the cost of added complexity.
React itself in its optimized production build is insanely fast, good at resolving diffs, and in most cases could rerender your entire app dozens of times per second without any perceivable slowdown. You should ONLY start optimizing your app with things like memoization when you have ACTUAL, MEASURABLE impacts to performance that you need to address.
In short, you do not need to worry about "unnecessary" rerenders.
I'll say it again for emphasis:
DO NOT WORRY ABOUT "UNNECESSARY" RERENDERS.
Seriously.
PS: The reason using a random value for key makes it seem like unnecessary rerenders are eliminated is because every time a component renders it is literally a brand new instance of that component, not the same component being rerendered. React uses the key prop under the hood to track which component is which between renders. If that value is unreliable, it means that React is literally rendering NEW components every time. You're basically destroying all the old components and recreating them from scratch, albeit with the same props or whatever, but make no mistake, they are NOT the same components between renders. (Even their internal state including hooks will be erased)
As per what you said handleColorClick prop has changed, which is why the components are getting re-rendered. Since you are using functional component and hooks in the component, when the component is getting re-rendered the function handleColorClick is redefined again and the reference is getting changed. That's the reason why the components are getting re-rendered even though you pass unique ids as keys.
In order to avoid that you can use useCallback hook which will help you not to get a new function reference unless there's a change in the dependencies provided to the useCallback hook
/* eslint-disable eqeqeq */
import React, {useCallback} from 'react';
import Color from '../../atoms/Color';
import './ColorPalette.css';
const ColorPalette = ({ colorPalette }) => {
const [colors, setColors] = React.useState(colorPalette);
// const handleColorClick = (event) => {
// const id = event.currentTarget.getAttribute('id')
// const index = colors.findIndex(item => item.id == id);
// setColors(colors.filter(item => item.id != id))
// }
const deleteItem = useCallback((id) => {
setColors(colors.filter(item => item.id != id))
}, [])
return (
<div className={'colorPalette'}>
{colors && colors.map((color, index) => {
// const key = index
const key = color.id
return <Color
key={key}
color={color.hex}
colorKey={key}
handleColorClick = {() => {deleteItem(color.id)}}
/> })}
</div>
)
}
// export default React.memo(ColorPalette);
export default ColorPalette;
Related
Imagine two components like this in React:
import MyComponent2 from "./components/MyComponent2";
import React from "react";
export default function App() {
const [myState, setMyState] = React.useState([]);
React.useEffect(() => {
console.log("useEffect triggered");
}, [myState]);
return <MyComponent2 myState={myState} setMyState={setMyState} />;
}
import React from "react";
export default function MyComponent2(props) {
const [inputValue, setInputValue] = React.useState("");
function handleChange(e) {
setInputValue(e.target.value);
let list = props.myState;
list.push(`${e.target.value}`);
props.setMyState(list);
console.log(props.myState);
}
return (
<div>
<input
type="text"
value={inputValue}
name="text"
onChange={handleChange}
/>
</div>
);
}
As you can see I am making changes with props.setMyState line in second component. State is changing but Somehow I could not trigger React.useEffect in first component even tough It is connected with [myState]. Why ?
In short form of my question : I can not get "useEffect triggered" on my console when i make changes in input
Instead of providing myState and setMyState to MyComponent2, you should only provide setMyState and use the functional update argument in order to access the current state.
In your handleChange function you are currently mutating the React state (modifying it directly):
let list = props.myState; // This is an array that is state managed by React
list.push(`${e.target.value}`); // Here, you mutate it by appending a new element
props.setMyState(list);
// ^ You update the state with the same array here,
// and since they have the same object identity (they are the same array),
// no update occurs in the parent component
Instead, you should set the state to a new array (whose object identity differs from the current array):
props.setMyState(list => {
const newList = [...list];
newList.push(e.target.value);
return newList;
});
// A concise way to write the above is like this:
// props.setMyState(list => [...list, e.target.value]);
i use react.js to build my spa app.
I use functional style to make my components.
As the business logic gonna bigger, there are inevitably many functions.
So i tried to divide in to multiple components. Because it's hard to put many codes in one file even if it is just a 1 component.
However, this also has obvious limitations. In the case of complex components, there are a large number of event callback functions in addition to the functions directly called by the user.
Depending on the size of the component, it is sometimes difficult to write all the logic in one jsx file, so I want to divide the code into different files as needed. (Like c# partial class)
However, this is not easy. As an example, let's assume that the callback functions are made external functions of other files and imported into this component jsx file and used. But it seems that the component states, props information and the dispatch function also should be passed as parameters to the function. This seems hassle but except this, i have no idea a way to access this component's states, props, dispatch function from a function in another file.)
//For example of callback function
const onHoldButtonClicked = (state, props, dispatch, event) =>
{
~~~
}
//For example of normal function
const updateValidUser = (state, props, dispatch, userInfo, data) =>
{
let id = userInfo.id;
if(id == data.passID)
{
if(props.valid == 10)
dispatch({action: 'ChangeUser', user: id});
}
}
In React, how to divide logic(functions) when the logic gonna bigger in one component? (In general case)
Even if it is divided into several components, a big component inevitably has many functions.
I would recommend to extract logic into hooks and place these hooks into their own files.
hook.js
const useAwesomeHook = () => {
const [someState, setSomeState] = useState("default");
const myCoolFunction = useCallback(() => {
console.log('do smth cool', someState);
}, [someState]);
return myCoolFunction;
};
export default useAwesomeHook;
main.js
import useAwesomeHook from './hook';
const Main = ({ someProperty }) => {
const myCoolFunction = useAwesomeHook(someProperty);
return <button onClick={myCoolFunction}>Click me</button>;
};
Here is an example for logic and business and component separation.
The separation makes your code testable, atomic, maintainable, readable and SRP(single responsibility rule )
// Presentational component
const QuantitySelector = () => {
const { onClickPlus, onClickMinus, state } = useQuantitySelector();
const { message, value } = state;
return (
<div className="quantity-selector">
<button onClick={onClickMinus} className="button">
-
</button>
<div className="number">{value}</div>
<button onClick={onClickPlus} className="button">
+
</button>
<div className="message">{message}</div>
</div>
);
};
export default QuantitySelector;
and below code is the above component logic
import { useState } from "react";
// Business logic. Pure, testable, atomic functions
const increase = (prevValue, max) => {
return {
value: prevValue < max ? prevValue + 1 : prevValue,
message: prevValue < max ? "" : "Max!"
};
};
const decrease = (prevValue, min) => {
return {
value: prevValue > min ? prevValue - 1 : prevValue,
message: prevValue > min ? "" : "Min!"
};
};
// Implementation/framework logic. Encapsulating state and effects here
const useQuantitySelector = () => {
const [state, setState] = useState({
value: 0,
message: ""
});
const onClickPlus = () => {
setState(increase(state.value, 10));
};
const onClickMinus = () => {
setState(decrease(state.value, 0));
};
return { onClickPlus, onClickMinus, state };
};
export default useQuantitySelector;
I separate components into the logic and UI by function composition.The idea came from recompse which I used before hooks came into react.
you can add two helper functions.
first one is compose you can learn more about it here:
const compose = (...fns) => x => fns.reduceRight((y, f) => f(y), x);
export default compose;
the second one is withHooks:
import React from 'react';
export default (hooks) =>
(WrappedComponent) =>
(props) => {
const hookProps = hooks(props);
return (
<WrappedComponent
{...props}
{...hookProps}
/>
);
}
with these two functions, you can put your logic in the hooks file and pass the as props to your UI with compose file you can see sandbox example here
what I usually do is create a folder for the big component, in the folder I create a functions file and put functions with state passed and other params necessary . as simple as that .
export const increment=(count,setCount)=>{...}
.
.
and in your component
import{increment,....} from './functions'
const Component=(props)=>{
const [count,setCount]=useState(1)
return <div>
<button onClick={e=>increment(count,setCount)}> count ={count}</button>
</div>
}
As per the docs:
When the nearest <MyContext.Provider> above the component updates, this Hook will trigger a rerender with the latest context value passed to that MyContext provider. Even if an ancestor uses React.memo or shouldComponentUpdate, a rerender will still happen starting at the component itself using useContext.
...
A component calling useContext will always re-render when the context value changes.
In my Gatsby JS project I define my Context as such:
Context.js
import React from "react"
const defaultContextValue = {
data: {
filterBy: 'year',
isOptionClicked: false,
filterValue: ''
},
set: () => {},
}
const Context = React.createContext(defaultContextValue)
class ContextProviderComponent extends React.Component {
constructor() {
super()
this.setData = this.setData.bind(this)
this.state = {
...defaultContextValue,
set: this.setData,
}
}
setData(newData) {
this.setState(state => ({
data: {
...state.data,
...newData,
},
}))
}
render() {
return <Context.Provider value={this.state}>{this.props.children}</Context.Provider>
}
}
export { Context as default, ContextProviderComponent }
In a layout.js file that wraps around several components I place the context provider:
Layout.js:
import React from 'react'
import { ContextProviderComponent } from '../../context'
const Layout = ({children}) => {
return(
<React.Fragment>
<ContextProviderComponent>
{children}
</ContextProviderComponent>
</React.Fragment>
)
}
And in the component that I wish to consume the context in:
import React, { useContext } from 'react'
import Context from '../../../context'
const Visuals = () => {
const filterByYear = 'year'
const filterByTheme = 'theme'
const value = useContext(Context)
const { filterBy, isOptionClicked, filterValue } = value.data
const data = <<returns some data from backend>>
const works = filterBy === filterByYear ?
data.nodes.filter(node => node.year === filterValue)
:
data.nodes.filter(node => node.category === filterValue)
return (
<Layout noFooter="true">
<Context.Consumer>
{({ data, set }) => (
<div onClick={() => set( { filterBy: 'theme' })}>
{ data.filterBy === filterByYear ? <h1>Year</h1> : <h1>Theme</h1> }
</div>
)
</Context.Consumer>
</Layout>
)
Context.Consumer works properly in that it successfully updates and reflects changes to the context. However as seen in the code, I would like to have access to updated context values in other parts of the component i.e outside the return function where Context.Consumer is used exclusively. I assumed using the useContext hook would help with this as my component would be re-rendered with new values from context every time the div is clicked - however this is not the case. Any help figuring out why this is would be appreciated.
TL;DR: <Context.Consumer> updates and reflects changes to the context from child component, useContext does not although the component needs it to.
UPDATE:
I have now figured out that useContext will read from the default context value passed to createContext and will essentially operate independently of Context.Provider. That is what is happening here, Context.Provider includes a method that modifies state whereas the default context value does not. My challenge now is figuring out a way to include a function in the default context value that can modify other properties of that value. As it stands:
const defaultContextValue = {
data: {
filterBy: 'year',
isOptionClicked: false,
filterValue: ''
},
set: () => {}
}
set is an empty function which is defined in the ContextProviderComponent (see above). How can I (if possible) define it directly in the context value so that:
const defaultContextValue = {
data: {
filterBy: 'year',
isOptionClicked: false,
filterValue: ''
},
test: 'hi',
set: (newData) => {
//directly modify defaultContextValue.data with newData
}
}
There is no need for you to use both <Context.Consumer> and the useContext hook.
By using the useContext hook you are getting access to the value stored in Context.
Regarding your specific example, a better way to consume the Context within your Visuals component would be as follows:
import React, { useContext } from "react";
import Context from "./context";
const Visuals = () => {
const filterByYear = "year";
const filterByTheme = "theme";
const { data, set } = useContext(Context);
const { filterBy, isOptionClicked, filterValue } = data;
const works =
filterBy === filterByYear
? "filter nodes by year"
: "filter nodes by theme";
return (
<div noFooter="true">
<div>
{data.filterBy === filterByYear ? <h1>Year</h1> : <h1>Theme</h1>}
the value for the 'works' variable is: {works}
<button onClick={() => set({ filterBy: "theme" })}>
Filter by theme
</button>
<button onClick={() => set({ filterBy: "year" })}>
Filter by year
</button>
</div>
</div>
);
};
export default Visuals;
Also, it seems that you are not using the works variable in your component which could be another reason for you not getting the desired results.
You can view a working example with the above implementation of useContext that is somewhat similar to your example in this sandbox
hope this helps.
Problem was embarrassingly simple - <Visuals> was higher up in the component tree than <Layout was for some reason I'm still trying to work out. Marking Itai's answer as correct because it came closest to figuring things out giving the circumstances
In addition to the solution cited by Itai, I believe my problem can help other people here
In my case I found something that had already happened to me, but that now presented itself with this other symptom, of not re-rendering the views that depend on a state stored in a context.
This is because there is a difference in dates between the host and the device. Explained here: https://github.com/facebook/react-native/issues/27008#issuecomment-592048282
And that has to do with the other symptom that I found earlier: https://stackoverflow.com/a/63800388/10947848
To solve this problem, just follow the steps in the first link, or if you find it necessary to just disable the debug mode
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"));
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).