Object Destructuring not working in a ReactJs script - javascript

I am using Next js for an application.
The code components have a global state stored in parent and passed down to child components using ReactContext.
In one of the components, when I am trying to create a state component by destructuring a parent state it is behaving in an unpredictable manner.
At times the destructuring initializes a local state, at times it does not.
const ContextEntry = () => {
const { widget, pageStates } = useContext(WidgetContext);
const { currentLanguage } = pageStates;
// this statement produces an output..
console.log("ContextEntry Component widget context ", widget.context);
//?? why not getting destructured
// const [contextEntryData, setContextEntryData] = useState(widget.context);
const [contextEntryData, setContextEntryData] = useState({
...widget.context,
});
console.log("State object ", contextEntryData);
return (
<div className={style.container}>
{widget.fields.map((field) => {
return (
<CustomFieldWidget
field={field}
key={field.fieldId}
updateContextEntry={updateContextEntry}
/>
);
})}
</div>
);
};
export default ContextEntry;

Related

React | Adding and deleting object in React Hooks (useState)

How to push element inside useState array AND deleting said object in a dynamic matter using React hooks (useState)?
I'm most likely not googling this issue correctly, but after a lot of research I haven't figured out the issue here, so bare with me on this one.
The situation:
I have a wrapper JSX component which holds my React hook (useState). In this WrapperComponent I have the array state which holds the objects I loop over and generate the child components in the JSX code. I pass down my onChangeUpHandler which gets called every time I want to delete a child component from the array.
Wrapper component:
export const WrapperComponent = ({ component }) => {
// ID for component
const { odmParameter } = component;
const [wrappedComponentsArray, setWrappedComponentsArray] = useState([]);
const deleteChildComponent = (uuid) => {
// Logs to array "before" itsself
console.log(wrappedComponentsArray);
/*
Output: [{"uuid":"acc0d4c-165c-7d70-f8e-d745dd361b5"},
{"uuid":"0ed3cc3-7cd-c647-25db-36ed78b5cbd8"]
*/
setWrappedComponentsArray(prevState => prevState.filter(item => item !== uuid));
// After
console.log(wrappedComponentsArray);
/*
Output: [{"uuid":"acc0d4c-165c-7d70-f8e-d745dd361b5",{"uuid":"0ed3cc3-
7cd-c647-25db-36ed78b5cbd8"]
*/
};
const onChangeUpHandler = (event) => {
const { value } = event;
const { uuid } = event;
switch (value) {
case 'delete':
// This method gets hit
deleteChildComponent(uuid);
break;
default:
break;
}
};
const addOnClick = () => {
const objToAdd = {
// Generate uuid for each component
uuid: uuid(),
onChangeOut: onChangeUpHandler,
};
setWrappedComponentsArray(wrappedComponentsArray => [...wrappedComponentsArray, objToAdd]);
// Have also tried this solution with no success
// setWrappedComponentsArray(wrappedComponentsArray.concat(objToAdd));
};
return (
<>
<div className='page-content'>
{/*Loop over useState array*/}
{
wrappedComponentsArray.length > 0 &&
<div>
{wrappedComponentsArray.map((props) => {
return <div className={'page-item'}>
<ChildComponent {...props} />
</div>;
})
}
</div>
}
{/*Add component btn*/}
{wrappedComponentsArray.length > 0 &&
<div className='page-button-container'>
<ButtonContainer
variant={'secondary'}
label={'Add new component'}
onClick={() => addOnClick()}
/>
</div>
}
</div>
</>
);
};
Child component:
export const ChildComponent = ({ uuid, onChangeOut }) => {
return (
<>
<div className={'row-box-item-wrapper'}>
<div className='row-box-item-input-container row-box-item-header'>
<Button
props={
type: 'delete',
info: 'Deletes the child component',
value: 'Delete',
uuid: uuid,
callback: onChangeOut
}
/>
</div>
<div>
{/* Displays generated uuid in the UI */}
{uuid}
</div>
</div>
</>
)
}
As you can see in my UI my adding logic works as expected (code not showing that the first element in the UI are not showing the delete button):
Here is my problem though:
Say I hit the add button on my WrapperComponent three times and adds three objects in my wrappedComponentsArray gets rendered in the UI via my mapping in the JSX in the WrapperComponent.
Then I hit the delete button on the third component and hit the deleteChildComponent() funtion in my parent component, where I console.log my wrappedComponentsArray from my useState.
The problem then occurs because I get this log:
(2) [{…}, {…}]
even though I know the array has three elements in it, and does not contain the third (and therefore get an undefined, when I try to filter it out, via the UUID key.
How do I solve this issue? Hope my code and explanation makes sense, and sorry if this question has already been posted, which I suspect it has.
You provided bad filter inside deleteChildComponent, rewrite to this:
setWrappedComponentsArray(prevState => prevState.filter(item => item.uuid !== uuid));
You did item !== uuid, instead of item.uuid !== uuid
Please try this, i hope this works
const deleteChildComponent = (uuid) => {
console.log(wrappedComponentsArray);
setWrappedComponentsArray(wrappedComponentsArray.filter(item => item !== uuid));
};
After update
const deleteChildComponent = (uuid) => {
console.log(wrappedComponentsArray);
setWrappedComponentsArray(wrappedComponentsArray.filter(item => item.uuid !== uuid)); // item replaced to item.uuid
};
Huge shoutout to #Jay Vaghasiya for the help.
Thanks to his expertise we managed to find the solution.
First of, I wasn't passing the uuid reference properly. The correct was, when making the objects, and pushing them to the array, we passed the uuid like this:
const addOnClick = () => {
const objToAdd = {
// Generate uuid for each component
uuid: uuid(),
parentOdmParameter: odmParameter,
onChangeOut: function(el) { onChangeUpHandler(el, this.uuid)}
};
setWrappedComponentsArray([...wrappedComponentsArray, objToAdd]);
};
When calling to delete function the function that worked for us, was the following:
const deleteChildComponent = (uuid) => {
setWrappedComponentsArray(item => item.filter(__item => __item.uuid !== uuid)); // item replaced to item.uuid
};

Nested object stored as state variable deleting newer values when older values are modified

I have a react component which passes user input value to a props function. The parent function just appends those inputs to an object. However, when an older value is modified, all the newer values are removed. Please refer to the screenshots.
This is the parent. ExamArea.js
import McqQuestion from './McqQuestion'
import React, { useState, useEffect } from 'react';
import './ExamArea.css'
function ExamArea(props) {
const[currentQuesID, setCurrentQuesID] = useState(2);
const[mcqQuestionList, setmcqQuestionList] = useState([<McqQuestion returnfunc={returnFromMcqQuestion} id={1}/>]);
const[ques, setQues] = useState({});
function returnFromMcqQuestion(quesID, thisQuestion) {
var temp = {...ques};
temp["ques"+quesID] = thisQuestion;
console.log(temp);
setQues(temp);
}
function generateMCQ(questionid) {
return (<McqQuestion returnfunc={returnFromMcqQuestion} id={questionid}/>)
}
function addAnotherQuestion() {
setmcqQuestionList(mcqQuestionList.concat(generateMCQ(currentQuesID)));
setCurrentQuesID(currentQuesID+1);
}
return (
<div className="ExamArea">
{mcqQuestionList}
<button onClick={()=>addAnotherQuestion()} class="add_another_question_button">+ Add Another Question</button>
</div>
);
}
export default ExamArea;
This is the child.
import './McqQuestion.css'
import React, { useState, useEffect } from 'react';
import { Paper, TextField } from '#material-ui/core';
import InputBase from '#material-ui/core/InputBase';
/*
This is the component that lets the maker create the question, and then stores the question to packedQuestion.
packedQuestipn is in a format which can be directly sent to the API to be uploaded to the database.
A basic question has question_text, question.title
Props passed:
props.id = The Question ID.
props.returnfunc = The function that gets called with packedQuestion and props.id when everything is done.
props.returnfunc(props.id, packedQuestion) is the thing that is called.
*/
function McqQuestion(props) {
const [packedQuestion, setPackedQuestion] = useState({});
useEffect(()=> props.returnfunc(props.id, packedQuestion));
/*These two variables store a local copy of packedQuestion. These variables are first updated with the information from
onChange (or a variation of it), and then packedQuestion is set to an instance of this. */
let local_question_mcq = {};
let local_answerChoices_mcq = {};
function fillUpQuestionWithDefault(){
function addOption(character, value) {
local_answerChoices_mcq[character] = value;
local_question_mcq["answer_choices"] = local_answerChoices_mcq;
}
function addQuestion(title, value){
if(title){
local_question_mcq['title'] = value;
}
else {
local_question_mcq['question_text'] = value;
}
}
addQuestion(true, "Question "+props.id);
addQuestion(false, "");
addOption("a", "");
addOption("b", "");
addOption("c", "");
addOption("d", "");
local_question_mcq['title'] = "Question " + props.id;
local_question_mcq['id'] = props.id;
setPackedQuestion(local_question_mcq);
}
useEffect(() =>fillUpQuestionWithDefault(), []);
function optionOnInputFunc(character, value) {
local_question_mcq = {...packedQuestion};
local_answerChoices_mcq = {...local_question_mcq["answer_choices"]};
local_answerChoices_mcq[character] = value;
local_question_mcq["answer_choices"] = local_answerChoices_mcq;
setPackedQuestion(local_question_mcq);
}
function questionOnInputFunc(title, value) {
if(title){
local_question_mcq = {...packedQuestion};
local_question_mcq['title'] = value;
setPackedQuestion(local_question_mcq);
}
else {
local_question_mcq = {...packedQuestion};
local_question_mcq['question_text'] = value;
setPackedQuestion(local_question_mcq);
}
}
function mcqChoiceGeneratingFunc() {
return (
<div class = "Opt">
<TextField onChange = {e => optionOnInputFunc('a', e.target.value)} label="Option A" variant="filled" multiline rowsMax={4}/>
<TextField onChange = {e => optionOnInputFunc('b', e.target.value)} label="Option B" variant="filled" multiline rowsMax={4}/>
<TextField onChange = {e => optionOnInputFunc('c', e.target.value)} label="Option C" variant="filled" multiline rowsMax={4}/>
<TextField onChange = {e => optionOnInputFunc('d', e.target.value)} label="Option D" variant="filled" multiline rowsMax={4}/>
</div>
);
}
return (
<Paper class="Question">
<form class="Question-form">
<a class = "editpencil">✎</a>
<InputBase class = "questionedit"
onChange = {e => questionOnInputFunc(true, e.target.value)}
defaultValue={"Question "+props.id}
inputProps = {{"maxlength": 40}}/>
<div class="question-text">
<TextField onChange = {e => questionOnInputFunc(false, e.target.value)} variant="outlined" fullWidth="true" label="Type your question"></TextField>
</div>
{mcqChoiceGeneratingFunc()}
</form>
</Paper>
);
}
export default McqQuestion;
The behavior I am describing can be seen in these screenshots.
The first two screenshots are expected. Two new questions were added and their respective objects were in the console log.
Expected Behavior at the start of the state
Expected behavior when two new questions were added
When question 1 was edited while questions 2 and 3 were there, the objects for question 3 disappeared.
Why is this happening and how do I fix this?
Issues
ExamArea
Storing react components in state is a React anti-pattern and sure-fire way to get yourself some stale state enclosures.
Store just the data in state and render the UI from it.
Any time you are updating react state that depends on the previous state (i.e. appending an element to an array, incrementing a count/id, etc...) you don't use a functional state update.
Use a functional state update to correctly update from any previous state versus state from the previous render cycle.
McqQuestion
Once I resolved your issues in ExamArea I was a bit thrown off by the usage of local_question_mcq and local_answerChoices_mcq. At first glance they appeared to be "state" that wasn't part of component state.
Limit the scope of utility variables such as local_question_mcq and local_answerChoices_mcq
Similar issues with the functional updates, but coupled to the overscoped local_question_mcq and local_answerChoices_mcq.
Use a functional state update to directly update packedQuestion in the onChange handlers.
Solution
ExamArea
Store only data in component state.
Map state to UI in render function.
Use functional state update to map previous state to next state. Use the question ID to match the question that needs to be updated and also shallow copy it.
Pass returnFromMcqQuestion as prop directly (not stored in state either).
Code:
function ExamArea(props) {
const [currentQuesID, setCurrentQuesID] = useState(2);
const [mcqQuestionList, setmcqQuestionList] = useState([{ id: 1 }]); // <-- store data only
function returnFromMcqQuestion(quesID, thisQuestion) {
setmcqQuestionList((mcqQuestionList) => // <-- functional state update
mcqQuestionList.map((question) =>
question.id === quesID // <-- shallow copy matching question
? {
...question,
...thisQuestion
}
: question
)
);
}
function generateMCQ(questionid) {
return {
id: questionid
};
}
function addAnotherQuestion() {
setmcqQuestionList((mcqQuestionList) => // <-- functional state update
mcqQuestionList.concat(generateMCQ(currentQuesID))
);
setCurrentQuesID((c) => c + 1); // <-- functional state update
}
return (
<div className="ExamArea">
{mcqQuestionList.map(({ id }) => (
<McqQuestion
key={id}
returnfunc={returnFromMcqQuestion} // <-- pass callback directly
id={id}
/>
))}
<button
onClick={addAnotherQuestion}
className="add_another_question_button"
>
+ Add Another Question
</button>
</div>
);
}
McqQuestion
Use functional state update to map previous state to next state.
Limit the scope of local_question_mcq and local_answerChoices_mcq, move them into fillUpQuestionWithDefault and declare them const.
Make code more DRY where possible.
Fix class vs className and other various React warnings.
Code:
function McqQuestion(props) {
const [packedQuestion, setPackedQuestion] = useState({});
useEffect(() => {
props.returnfunc(props.id, packedQuestion); // <-- update state in parent
}, [packedQuestion]);
function fillUpQuestionWithDefault() {
/*These two variables store a local copy of packedQuestion. These variables are first updated with the information from
onChange (or a variation of it), and then packedQuestion is set to an instance of this. */
const local_question_mcq = { // <-- provide initial values, then override
id: props.id,
title: `Question ${props.id}`,
};
const local_answerChoices_mcq = {};
function addOption(character, value = '') {
local_answerChoices_mcq[character] = value;
local_question_mcq["answer_choices"] = local_answerChoices_mcq;
}
function addQuestion(title, value) {
local_question_mcq[title ? "title" : "question_text"] = value; // <-- DRY
}
addQuestion(true, "Question " + props.id);
addQuestion(false, "");
['a', 'b', 'c', 'd'].forEach(c => addOption(c, '')); // <-- DRY
setPackedQuestion(local_question_mcq);
}
useEffect(() => {
fillUpQuestionWithDefault();
}, []);
function optionOnInputFunc(character, value) {
setPackedQuestion((question) => ({ // <-- functional state update
...question,
answer_choices: {
...question.answer_choices,
[character]: value
}
}));
}
function questionOnInputFunc(title, value) {
setPackedQuestion((question) => ({ // <-- functional state update
...question,
[title ? 'title' : 'question_text']: value
}));
}
function mcqChoiceGeneratingFunc() {
return (
<div className="Opt">
...
</div>
);
}
return (
<Paper className="Question">
...
</Paper>
);
}
When you are calling this function from child component then ques take the value of initial state in hook that is {}. Now you are adding key quesID in temp and updating the state. So it will be an expected behavior.
function returnFromMcqQuestion(prevQues, quesID, thisQuestion) {
var temp = {...prevQues};
temp["ques"+quesID] = thisQuestion;
setQues(prevQues);
}
So you need something like this.
<McqQuestion ques={ques} returnfunc={returnFromMcqQuestion} id={questionid}/>)
useEffect(()=> props.returnfunc(props.ques, props.id, packedQuestion));

How to divide business logic in one react component?

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

Is it ok to define nested component with React Hooks?

I am using Hooks + Render Props in my Preact/React application. Instead of passing simple state to the render(), I am passing a component function that can be used by the user of this component.
Also, my component is Hybrid in a sense that that SortDropdown is not UI-Less like we typically have with Render Prop components. It provides some basic UI in the form of anchor element which triggers the actual drop down and shows the custom content via Render Props render function. This is how I have implemented it:
export function SortDropdown(props: SortDropdownProps) {
const { render, onSelect, value } = props;
const anchor = (ref: Ref<Element>, open: VoidFunction) => (
<SortButton buttonElm={ref} onClick={open} />
);
const surfaceContent = (ref: Ref<any>, select: SortSelectFn) => {
// NESTED INNER COMPONENT - IS IT RIGHT?
function SortMenuItem(props: SortMenuItemProps) {
const { children, field } = props;
// THE HOOK IN QUESTION
const [someState, setSomeState] = useState(false);
const isSelected = value.key === field.key;
const onClick = () => select(field);
return (
<SortMenuButton canTrigger={someState} selected={someState} onClick={onClick}>
{children}
</SortMenuButton>
);
}
return (
<div ref={ref}>{render(SortMenuItem, select)}</div>
);
};
return (
<BaseDropdown anchor={anchor} surface={surfaceContent} onSelect={onSelect as any} />
);
}
Here I have two questions. First, does defining an inner nested component, here - SortMenuItem which is then passed to the render function violates the rules of Hooks? The component may be called n-times or may not be used at all by the calling component.
Second, is it a right practice to define a nested Higher-Order Component, here - SortMenuItem being an abstraction over SortMenuButton?

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