How to divide business logic in one react component? - javascript

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>
}

Related

Using redux to take a time on a stopwatch and put it into a database

I was recommended using redux to place a time into a database but I'm having issues. I have a stopwatch in my index.js that the user can start and stop. After, I have a button that allows them the ability to to put their time into a database. From my node file, I'm getting UnhandledPromiseRejectionWarning: error: null value in column "time" violates not-null constraint. I'm wondering if I'm getting that because I have difference = 0 at the stop of the index file and it doesn't retrieve difference = this.state.endTime - this.state.startTime; or if there is another issue I'm not seeing.
index.js
export let difference = 0;
export default class Home extends React.Component {
constructor(props);
super(props);
this.state = {
startTime: 0,
endTime: 0,
}
}
handleStart = () => {
this.setState({
startTime: Date.now()
})
}
handleStop = () => {
this.setState({
endTime: Date.now()
})
}
render() {
difference = this.state.endTime - this.state.startTime;
return (
<App />
<reducer />
);
}
}
reducer.js
import * as actionTypes from "./actions.js";
import difference from "../pages/index.js";
const initialState = {
count: 0
}
const reducer = (state = initialState, action) => {
switch (action.type) {
case actionTypes.NUMBER:
return { state, count: difference };
default: return state;
}
};
export default reducer;
store.js
import { createStore } from 'redux';
import reducer from './reducer';
const store = createStore(reducer);
export default store;
actions.js
export const NUMBER = 'NUMBER';
App.js
import React from 'react';
import { addName } from "./util";
function App() {
const [name, setName] = React.useState("")
function handleUpdate(evt) {
setName(evt.target.value);
}
async function handleAddName(evt) {
await addName(name);
}
return <div>
<p><input type='text' value={name} onChange={handleUpdate} /></p>
<button className='button-style' onClick={handleAddName}>Add Name</button>
</div>
}
export default App;
util.js
import "isomorphic-fetch"
import difference from "../pages/index.js";
export function addName(name) {
return fetch('http://localhost:3001/addtime', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name, time: difference })
})
}
node server.js
app.post("/addtime", cors(), async (req,res) => {
const name = req.body.name;
const time = req.body.time;
const timeStamp = dateFormat(time, dateFormat.masks.isoDateTime);
const template = 'INSERT INTO times (name,time) VALUES ($1,$2)';
const response = await pool.query(template, [name,time]);
res.json({name: name, time: time});
});
State Management
In React we manage changing values through state. There is a way to access the value and a way to update the value. When the value changes, React knows to re-render the component.
This state can be stored within a component of your app. When you write const [name, setName] = React.useState(""), name is your "getter" and setName is your "setter".
The state can also be stored inside a Context Provider component that is a parent of the current component. Any children of this Provider can use the useContext hook to access the stored value.
The React Redux package uses these React contexts to make the redux state globally available. You wrap your entire App in a Redux Provider component to enable access.
With Redux, your "getters" are selector functions, which you call though useSelector, and your "setters" are actions that you pass though the dispatch function. React actually supports this syntax of state management on a local component level (without Redux) though the useReducer hook.
You don't need to use contexts in order to pass around state from component to component. You can also pass values and "setters" as props from a parent component to a child. React recommends this approach and has a section on Lifting State Up.
This is just a brief overview of some of the concepts at play. The issues that you are having here are due to state management. There are many possible ways to handle your state here. In my opinion Redux and Contexts are both unnecessary overkill. Of course you can use them if you want to, but you need to set them up properly which you haven't done.
Errors
export let difference = 0;
Variables which exist at the top-level of a file, outside of a component, should be immutable constants only. When you have a value that changes, it needs to be part of your app state.
When you have a "stateful" value like difference you can't just use a variable that exists outside of your components.
render() {
difference = this.state.endTime - this.state.startTime;
...
This is not how you update a value. We don't want to be just setting a constant. We also don't want to trigger changes of a stateful value on every render as this creates infinite re-render situations.
handleStart & handleStop
The functions themselves are fine but they are never called anywhere. Therefore difference will always be 0.
<reducer />
A reducer is not a component so you cannot call it like this. It is a pure function. In order to introduce redux into your JSX code, you use the reducer to create a store variable and you pass that store to a react-redux Provider component.
const reducer = (state = initialState, action) => {
switch (action.type) {
case actionTypes.NUMBER:
return { state, count: difference };
default: return state;
}
};
You want to include the difference as a property of your action rather than accessing an external variable. The standard convention is to add a payload property to the action. The payload could be the difference number itself or an object with a property difference. That sort of design choice is up to you. An action might look like { type: actionTypes.NUMBER, payload: 0.065 } or { type: actionTypes.NUMBER, payload: { difference: 0.065 } }.
Your state is an object with a property count. Your reducer should return the next state, which should also be an object with a property count. This will not return the right state shape: return { state, count: difference };.
It is typical to use the spread operator ... to copy all other properties of the state and update just one (or a few), like this: return { ...state, count: difference }. However your state does not have any other properties, so that is the same as return { count: difference };. Since you are just storing a single number, there is no value from the previous state that is copied or preserved. (Which is a large part of why I think that Redux is not helpful or necessary here.)
There may be some issues on the backend as well, but there are such serious issues with the front end that I think that's your main problem.
Structuring Components
Think about what your app needs to do. What actions does it respond to? What information does it need to know?
Based on your description, it needs to:
Start a timer when a button is clicked
Stop a timer when a button is clicked
Store the most recent timer value
Allow a user to enter and update a name
Store that name
POST to the backend when a button is clicked, if there is a stored difference and name
Next you break that up into components. Store each stateful value at the highest component in the tree which needs to access it, and pass down values and callbacks as props. The startTime and endTime are only used by the timer itself, but the timer need to set the difference after stopping. The difference would be stored higher up because it is also needed for the POST request.
Think about what buttons and actions are available at any given time, and what conditions are used to determine this. For example, you shouldn't be able to stop a timer before you have started it. So we would see if startTime is greater than 0.
Minimal Front-End Example
import * as React from "react";
const Timer = ({ setDifference }) => {
const [startTime, setStartTime] = React.useState(0);
const handleStart = () => {
setStartTime(Date.now());
};
const handleStop = () => {
const difference = Date.now() - startTime;
setDifference(difference);
};
return (
<div>
<button onClick={handleStart}>
Start
</button>
<button disabled={startTime === 0} onClick={handleStop}>
Stop
</button>
</div>
);
};
const Submission = ({ time }) => {
const [name, setName] = React.useState("");
function handleUpdate(evt) {
setName(evt.target.value);
}
async function handleAddName(evt) {
return await fetch("http://localhost:3001/addtime", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name, time })
});
}
return (
<div>
<div>Time: {time}</div>
<input type="text" value={name} onChange={handleUpdate} />
<button onClick={handleAddName} disabled={name.length === 0}>
Add Name
</button>
</div>
);
};
const App = () => {
const [time, setTime] = React.useState(0);
return (
<div>
<Timer setDifference={setTime} />
{time > 0 && <Submission time={time} />}
</div>
);
};
export default App;
CodeSandbox Link
You might not be saving any value to DOB column, you need to allow NULL value. You should change to
DOB = models.DateField(auto_now = False, null = True)
You should also make sure DOB column in your table allows null.

React global state no context or redux?

I recently game across the following article State Management with React Hooks — No Redux or Context API. Since reacts inception the most talked about issue is always state management and global state. Redux has been the popular choice and more recently the context API. But this approach seems to be much easier, less code and more scalable.
My question is can anyone see a down side to using the this type of state management approach that I may have overlooked. I have tweeked the code a little to support SSR and it works in Nextjs and also made it a little more friendly to use actions and the setting of the state variable.
useGlobalState.js
import React, { useState, useEffect, useLayoutEffect } from 'react';
const effect = typeof window === 'undefined' ? useEffect : useLayoutEffect;
function setState(newState) {
if (newState === this.state) return;
this.state = newState;
this.listeners.forEach((listener) => {
listener(this.state);
});
}
function useCustom() {
const newListener = useState()[1];
effect(() => {
this.listeners.push(newListener);
return () => {
this.listeners = this.listeners.filter((listener) => listener !== newListener);
};
}, []);
return [this.state, this.setState, this.actions];
}
function associateActions(store, actions) {
const associatedActions = {};
if (actions) {
Object.keys(actions).forEach((key) => {
if (typeof actions[key] === 'function') {
associatedActions[key] = actions[key].bind(null, store);
}
if (typeof actions[key] === 'object') {
associatedActions[key] = associateActions(store, actions[key]);
}
});
}
return associatedActions;
}
const useGlobalHook = (initialState, actions) => {
const store = { state: initialState, listeners: [] };
store.setState = setState.bind(store);
store.actions = associateActions(store, actions);
return useCustom.bind(store, React);
};
export default useGlobalHook;
Then set up a custom hook for a state variable can be a simple string or a object here is a simple one:
import useGlobalState from './useGlobalState';
const initialState = 'Hi';
// Example action for complex processes setState will be passed to component for use as well
const someAction = (store, val) => store.setState(val);
const useValue = useGlobalState(initialState, { someAction });
export default useValue;
And use in component:
import React from 'react'
import useVal from './useVal'
export default () => {
const [val, setVal, actions] = useVal();
const handleClick = () => {
setVal('New Val');
// or use some actions
actions.someAction('New Val');
}
return(
<div>{val}</div>
<button onClick={handleClick}>Click Me</button>
)
}
This all seems like a much cleaner and easier approach and I am wondering why this isn't the go to approach for state management in react. First you don't have to wrap everything in a provider. Next it is extremely easy to implement and much less code is involved in the actual app. Can anyone see a downside to using this approach. The only thing I can think of is the re rendering issue that the context api has but in small chunks this shouldn't be an issue.
I have been using a similar approach and I really like it. I actually can't believe more people don't talk about this approach. I wrote a custom hook here React Global Store Hook. It gives you the freedom to dispatch from anywhere in the app and shallow compares to avoid unwanted re-renders. I don't see any performance issues as long as you can avoid the unwanted re-renders.
In all it is a simple concept. You basically create a function to store your state and return 2 functions. One will be a function to set the stored state and one will be a hook to be used in the react component. In the hook you grab the setState function of react on initial render with a createEffect and store it in an array. You can then use this setState function to re render your component. So when you call the dispatch function you can just loop through these setState functions and call them.
Simple example:
import { useState, useEffect } from 'react'
const createStore = (initialStore) => {
let store = initialStore
const listeners = new Set()
const dispatch = (newStore) => {
// Make it like reacts setState so if you pass in a function you can get the store value first
store = typeof newStore === 'function' ? newStore(store) : newStore
listeners.forEach(listener => listener(() => store))
}
const useStore = () => {
const [, listener] = useState()
useEffect(() => {
listeners.add(listener)
return () => listeners.delete(listener)
}, [])
return store
}
return [useStore, dispatch]
}
Then just create a store and use in your component
const [useStore, dispatch] = createStore(0)
const Display = () => {
const count = useStore()
return <div>{count}</div>
}
const addToCount = () =>
<button onClick={ () => dispatch(count => count + 1}>+</button>
Then if you want to avoid re renders you can do a shallow compare in the dispatch function to compare the store to the new store similar to what redux does. Something like the following:
const shouldUpdate = (a, b) => {
for( let key in a ) {
if(a[key] !== b[key]) return true
}
return false
}
and then in dispatch you can check this before firing the listener in your forEach loop.
const dispatch = (newStore) => {
if(!shouldUpdate(
store,
store = typeof newStore === 'function' ? newStore(store) : newstore
) return
listeners.forEach(listener => listener(() => store))
}
Its way less boilerplate than redux and seems to be much cleaner. The best thing is it allows you to decouple your actions from functions without attaching the actions to anything. You can simply create a store anywhere in your app and export the useStore and dispatch functions. Then you can dispatch from anywhere in your app.
well good approach but i still see redux better for larger application especially when come to performance. A example using your approach,is adding The button as separated component while wrapping it with React.memo and firing actions.toggle() from the button component, but the button re render 2 times which it doesn't relay on the changed state.
so when building big apps you are always looking for performance improvement by removing unnecessary re renders but this is not the case here.
this is my analyses, thanks for your work.
here the code showcase

Why are all my child components re-rendering despite having keys?

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;

React watch imported class property

I'm importing a plain class to my react (functional) component and want to be notified when an imported class property is set/updated. I've tried setting my imported class with just new, as a state variable with useState, as a ref with useRef - and have tried passing each one as a parameter to useEffect, but none of them are triggering the useEffect function when the property is updated a second time.
I've excluded all other code to drill down to the problem. I'm using Typescript, so my plain vanilla MyClass looks like this:
class MyClass {
userId: string
user: User?
constructor(userId: string){
this.userId = userId
// Do a network call to get the user
getUser().then(networkUser => {
// This works because I tried a callback here and can console.log the user
this.user = networkUser
}).catch(() => {})
}
}
And then in my component:
// React component
import { useEffect } from 'react'
import MyClass from './MyClass'
export default () => {
const myClass = new MyClass(userId)
console.log(myClass.user) // undefined here
useEffect(() => {
console.log(myClass.user) // undefined here and never called again after myClass.user is updated
}, [myClass.user])
return null
}
Again, this is greatly simplified. But the problem is that React is not re-rendering my component when the instance user object is updated from undefined to a User. This is all client side. How do I watch myClass.user in a way to trigger a re-render when it finally updates?
Let me guess you want to handle the business logic side of the app with OOP then relay the state back to functional React component to display.
You need a mechanism to notify React about the change. And the only way for React to be aware of a (view) state change is via a call to setState() somewhere.
The myth goes that React can react to props change, context change, state change. Fact is, props and context changes are just state change at a higher level.
Without further ado, I propose this solution, define a useWatch custom hook:
function useWatch(target, keys) {
const [__, updateChangeId] = useState(0)
// useMemo to prevent unnecessary calls
return useMemo(
() => {
const descriptor = keys.reduce((acc, key) => {
const internalKey = `##__${key}__`
acc[key] = {
enumerable: true,
configurable: true,
get() {
return target[internalKey]
},
set(value) {
if (target[internalKey] !== value) {
target[internalKey] = value
updateChangeId(id => id + 1) // <-- notify React about the change,
// the value's not important
}
}
}
return acc
}, {})
return Object.defineProperties(target, descriptor)
},
[target, ...keys]
)
}
Usage:
// React component
import { useEffect } from 'react'
import { useWatch } from './customHooks'
import MyClass from './MyClass'
export default () => {
const myClass = useMemo(() => new MyClass(userId), [userId])
useWatch(myClass, ['user'])
useEffect(() => {
console.log(myClass.user)
}, [myClass, myClass.user])
return null
}
Side Note
Not related to the question per se, but there're a few words I want to add about that myth I mentioned. I said:
props and context changes are just state change at a higher level
Examples:
props change:
function Mom() {
const [value, setValue] = useState(0)
setTimeout(() => setValue(v => v+1), 1000)
return <Kid value={value} />
}
function Dad() {
let value = 0
setTimeout(() => value++, 1000)
return <Kid value={value} />
}
function Kid(props) {
return `value: ${props.value}`
}
context change:
const Context = React.createContext(0)
function Mom() {
const [value, setValue] = useState(0)
setTimeout(() => setValue(v => v+1), 1000)
return (<Context.Provider value={value}>
<Kid />
</Context.Provider>)
}
function Dad() {
let value = 0
setTimeout(() => value++, 1000)
return (<Context.Provider value={value}>
<Kid />
</Context.Provider>)
}
function Kid() {
const value = React.useContext(Context)
return `value: ${value}`
}
In both examples, only <Mom /> can get <Kid /> to react to changes.
You can pass this.user as props and use props,.user in useEffeect. You could do that from the place getUser called.
A wholesome solution would be using a centralized state solution like redux or context API. Then you need to update store in getUser function and listen globalstate.user.
Conclusion
You need to pass this.user to the component one way or another. You need to choose according to the project.

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