Avoid rerendering when doing button.click() inside useeffect() - javascript

I'm trying to set the data from child component to parent component's state in turn that data will be assigned to input text field. And then I want to automatically click the button to submit the data from input text field to handleBtnSubmit() function.
But when I did this child component is rendering indefinitely. Can someone please help resolving this kind of situation?
Parent
const [responses, setResponses] = useState([]);
const [currentMessage, setCurrentMessage] = useState('');
const submitAction = () => {
setResponses((responses) => [...responses, message]);
handleMessageSubmit(message.text);
setCurrentMessage('');
};
const handleBtnSubmit = () => {
submitAction();
};
<Messages
messages={responses}
parentData={{ currentMessage, setCurrentMessage, btnRef, handleBtnSubmit }}
/>
<div className='typing-area'>
<div className='input-field'>
<input
type='text'
placeholder='Type something here'
required
value={currentMessage}
onChange={handleMessageChange}
onKeyDown={handleSubmit}
/>
</div>
<button onClick={handleBtnSubmit} ref={btnRef}>
<img src={sendButton} alt='Send Button' />
</button>
</div>
Child
const setMessage = (option) => {
parentData.setCurrentMessage(option);
// parentData.btnRef.current.click();
// console.log(parentData.currentMessage);
};
useEffect(() => {
console.log(setCurrentMessage.currentMessage);
// parentData.btnRef.current.click(); //tried this
// parentData.handleBtnSubmit(); //also also tried directly calling handleBtnSubmit();
//both are causing indefinite rerender
}, [parentData.currentMessage]); //tried this too
<li className='option' key={i} onClick={() => setMessage(option)}>
{option}
</li>

First, pass currentMessage as a separate property, instead of passing it within an object. Something like:
<Messages
messages={responses}
currentMessage={currentMessage}
parentData={{ setCurrentMessage, btnRef, handleBtnSubmit }}
/>;
Then try passing the currentMessage prop as a dependency into the useEffect as shown below:
useEffect(() => {
console.log(currentMessage);
setCurrentMessage.btnRef.current.click();
}, [currentMessage]); // child's message state
This way, the useEffect code is called only when the currentMessage changes, and not indefinitely.

Create another state that stores the full value onclick/submit. And use that value as a dependency to the useEffect()
const [messageText, setMessageText] = useState('');
useEffect(() => {
submitBtnRef.current.click();
}, [messageText]);

Related

map() shows new messages from state only if I type something into input

Every time I send a message, map() only shows my new message when I type something in the input (re-render).
The listener works fine, it displays new messages in the console immediately after sending a new message, curiously, the messages state also updates when I look in React Developers Tools,
But useEffect[messages] does not trigger on a new message, and it only displays new messages after I type something in the input.
Here is my component with comments:
import { useState, useEffect, useRef } from "react";
const ChatWindowChannel = ({ window, chat }) => {
const [messages, setMessages] = useState([]);
const [message, setMessage] = useState("");
const loaded = useRef(null);
const messagesEndRef = useRef(null);
const channelMessagesHandler = async () => {
const channelMessagesListener = await chat.loadMessagesOfChannel(window);
channelMessagesListener.on((msgs) => {
console.log(msgs); // Here shows new messages after I click `send`
// Works correct.
setMessages(msgs); // In React Developers Tools the state `messages` are update if I click `send`
// Works correct.
console.log(messages); // Shows always empty array[]
// Dont works correct
});
};
async function send() {
await chat.sendMessageToChannel(window, message, {
action: "join",
alias: chat.gun.user().alias,
pubKey: chat.gun.user().is.pub,
name: "grizzly.crypto",
});
setMessage("");
}
useEffect(() => {
// Shows only once. It shold every time on new message
console.log("Messages changed");
}, [messages]);
useEffect(() => {
loaded.current !== window.key && channelMessagesHandler();
loaded.current = window.key;
}, [window]);
return (
<div>
<div className="ChatWindow">
<h2>
Channel {window.name} - {window.isPrivate ? "Private" : "Public"}
</h2>
<div>
<details style={{ float: "right" }}>
<summary>Actions</summary>
<button
onClick={async () => {
await chat.leaveChannel(window);
}}
>
Leave channel
</button>
</details>
</div>
<div className="msgs">
{messages.map((message, key) => (
<div key={`${key}-messages`}>
<small>{message.time}</small>{" "}
<small>{message.peerInfo.alias}</small> <p>{message.msg}</p>
<div ref={messagesEndRef} />
</div>
))}
</div>
<div>
<div>
<input
type="text"
value={message}
onChange={(e) => setMessage(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter") send();
}}
/>
</div>
<div>
<button onClick={send}>Send</button>
</div>
</div>
</div>
</div>
);
};
export default ChatWindowChannel;
Note 1: setMessages(msgs); do not change the value of messages immediately, it will be set only on next render so console.log(messages) just after setting will give you incorrect results.
Note 2: In case the reference of the array msgs that you are trying to set to a state variable is not changed even if array is modified - re-render will not be executed and useEffects will not be triggered, reference to an array needs to be new.
setMessages([...msgs]);
Same happens also with {objects}.
Can you try something like this:
setMessages([...msgs]);
instead of setMessages(msgs);
State updates in React are asynchronous; when an update is requested, there is no guarantee that the updates will be made immediately. The updater functions enqueue changes to the component state, but React may delay the changes, updating several components in a single pass. In that case you can't view (console.log) immediately after using state setter function.

How do I pass a value from parent to child, then edit it and pass it back in React?

Using hooks.
The parent component passes a value to the child component, which the child component displays. But I'd like that component editable and once the user clicks save, its new value should be passed back to parent component and is then used to update.
parent component:
const [value, setValue] = useState("");
// value is set by some function
<Child
value={value}
/>
child component:
<h2 contentEditable=true >props.value</h2>
// somehow save the value, even to a different variable, and pass it back to parent component to setValue
I have tried setting in my child component file something like
const childValue=props.value
or
const [childValue, setChildValue] = useState(props.value)
and I tried console.log on those values but they're all empty, and if I passed them to h2 in the child component, nothing displays.
--- EDIT
I have tried passing the function to set value to parent but I'm struggling getting the changed value.
For child component I'd like to save either onChange or on clicking a button, but I'm missing a way to capture the new value.
parent component has a saveValue(newValue) function which I pass to child
in child component
const {value, saveValue} = props;
return
<h2
onChange={() => saveValue(e.target.value)}
contentEditable=true> {value} </h2>
I have saveValue in parent component and tried changing the function to print out the argument in the console but nothing gets logged.
I also tried saving the changes with a button, I have no method of capturing the actual changes made to h2 though, as my code looks something like this:
const {value, setValue} = props;
<h2 contentEditable=true>{value}</h2>
<button onClick={()=>setValue(value)}>Save</button>
This just sets the value to the old value and not the edited one
Pass setValue to the Child component as a prop, and call it inside. For example:
const ChildComponent = ({value, setValue}) => {
return (
<>
<button onClick={() => setValue('my new value')}>
change value
</button>
<span>{value}</span>
</>
)
}
const ParentComponent = () => {
const [value, setValue] = useState("");
return <ChildComponent value={value} setValue={setValue}/>
}
You can approach this problem in 2 ways. Both the approach are basically the same thing, as you are ultimately passing a function to handle the state change.
Approach 1: Create a handler function (say handleValueChange) to manage the state change using setValue in the parent component. Then you can pass this handler function to the child component.
Parent component:
const Parent = () => {
const [value, setValue] = useState("");
function handleChange() {
// Some logic to change the "value" state
setValue("A new value");
}
return <Child value={value} handlerFunction={handleChange} />
}
Child component:
const Child = (props) => {
const { value, handlerFunction } = props;
// Utilize the props as per your needs
return (
<>
<h2 contentEditable>Value: {value}</h2>
<button onClick={handlerFunction}>Change state</button>
</>
);
};
Approach 2: Pass on the setValue function to the child component as props.
Parent component:
const Parent = () => {
const [value, setValue] = useState("");
return <Child value={value} setValue={setValue} />
}
Child component:
const Child = (props) => {
const { value, setValue } = props;
// Utilize the props to change the state, as per your needs
function handleChange() {
setValue("A new value");
}
return (
<>
<h2 contentEditable>Value: {value}</h2>
<button onClick={handleChange}>Change state</button>
</>
);
};

Get value from appended child component in React

I have a main react component which appends child components say <Child /> on button click
My Child component is of the format
<form>
<input .... />
<button type="submit">Submit</button>
<form>
Now I need to get the value of these input elements from every Child component which is appended but am not able to figure out a way to do so.
I won't know how many Child components would be added as the user can click the button any number of times to append yet another <Child /> so I can't have a fixed number of variables exported from the Child component to a variable in parent component.
Any suggestions would be highly appreciated.
Edit:
Code to append the child:
The submit function:
const [val, setVal] = useState([]);
const submit = () => {
setVal([...val, <Child />]);
}
Append button:
<Button onClick={submit}>Add Child</Button>
To render:
{val}
Since val is an array, it prints all the components inside it
I took some liberties because I don't know what would be the expected output of all the forms.
What I basically did is to create a map-like structure where I will map each child form with its value/s (depending on your needs could be modified) and I passed a submit function to the child components in order to store the values on the parent.
In that way on a child submit I will be able to get the values passed from the child as well as its index.
Parent component
const Parent = () => {
const [val, setVal] = useState([]);
// Map like structure to store the values (is just an example, you can use whatever you want)
const [mapOfValues, setMapOfValues] = useState({});
// Pass submit function as prop
const submit = () => {
setVal([...val, <Child submit={onChildSubmit} />]);
};
// Submit function that stores the value for the child
const onChildSubmit = (childIndex, value) => {
setMapOfValues({
...mapOfValues,
[childIndex]: value
});
};
return (
<div className="App">
{val.map((value, index) =>
// Key to prevent React warning and childIndex to map the value to the child form
React.cloneElement(value, { key: index, childIndex: index })
)}
<button onClick={submit}>Add</button>
</div>
);
}
Child component
const Child = (props) => {
const [value, setValue] = useState("");
const submitForm = (e) => {
e.preventDefault();
props.submit(props.childIndex, value);
};
return (
<form onSubmit={submitForm}>
<input onChange={(e) => setValue(e.target.value)} value={value} />
<button>Submit</button>
</form>
);
};
Once you are done, and you want to submit from the parent component, you could use a reduce, map or whatever you need to format the values as you want.
The link to the sandbox is this
If you have any other question let me know.

ReactJs - Input Component behaves strangely when its returned from a function

Scenario
I declared a react component that renders a simple html input tag.
const MyComponent = (props) => (
<input
defaultValue="test"
onChange={(e) => {
props.setTitle(e.target.value);
}}
/>
);
Then I declared a function that takes a setState as a parameter and returns that component with the setState inside the input's onChange.
const getComponent = (setTitle) => (props) => (
<input
defaultValue="test"
onChange={(e) => {
setTitle(e.target.value);
}}
/>
);
Then I called my function to get the component and to render it:
const Root = () => {
const [title, setTitle] = React.useState('');
const Component = getComponent(setTitle);
return (
<div>
<div>{title}</div>
<Component />{' '}
</div>
);
};
Expected:
The input element behaves normally, and changes its value
Reality:
The input loses focus after each character typed, and won't retain its value.
Here is a simple example of the error:
CodeSandbox
The reason this is happening is that when your code comes to this line:
const Component = getComponent(setTitle);
This generates new function (i.e. new instance) that is not rendered again, but mounted again. That is the reason you get unfocused from field.
There is no way you will make this work in this way, it's just not meant to be working like this. When you do it once when you are exporting it, than its ok, but every time === new instance.
If this is just an experiment that you are trying, than ok. But there is no reason not to pass setState as prop to that component.
I found a solution that let me use the function and keep the component from mounting each time.
If you put the function call inside the jsx, the component won't remount each render.
const Root = () => {
const [title, setTitle] = React.useState('');
const Component = ;
const someprops = {};
return (
<div>
<div>{title}</div>
{getComponent(setTitle)(someprops)}
</div>
);
};

Enzyme wrapper.update() causes ref input to no longer have value prop

Here is a Code Sandbox that contains a test simulating this issue. The test in this Code Sandbox fails as described in this question: https://codesandbox.io/s/react-jest-and-enzyme-testing-c7vng
I'm trying to test the value of an <input /> that gets updated inside a useEffect. This is the code from the Code Sandbox, which is a simplified version of something I'm trying to do in a project.
import React, { useEffect, useRef, useState } from "react";
const App = () => {
const [count, setCount] = useState(0);
useEffect(() => {
ref.current.value = "";
console.log(typeof ref.current.value);
}, [count]);
const ref = useRef(null);
const handleClick = () => {
setCount(count + 1);
console.log(count);
};
return (
<div>
<input ref={ref} type="text" />
<button onClick={handleClick}>click me</button>
</div>
);
};
export default App;
I use useRef to set the value of the <input />.
The useEffect gets called when the <button /> is clicked. The <button /> updates the useState count. useEffect is watching updates to count, and it gets called as a side-effect.
In the useEffect, I set ref.current.value to an empty string, and then I log the typeof this value to verify that it's a string.
In the test, I try to simulate this behavior:
describe("App", () => {
const wrapper = mount(<App />);
wrapper.find("input").props().value = "hello";
act(() =>
wrapper
.find("button")
.props()
.onClick()
);
console.log(wrapper.find("input").debug());
wrapper.update();
console.log(wrapper.find("input").debug());
expect(wrapper.find("input").length).toBe(1);
expect(wrapper.find("input").props().value).toBe("hello");
});
I set the value prop to 'hello'. I then invoke the <button /> onClick prop, effectively clicking it. I then call wrapper.update(), and I also debug() log the <input /> before and after update().
Before, update(), the <input /> has a value prop containing 'hello'. After the update(), the <input /> does not have a value prop. This causes the test to fail, saying that the <input /> value is undefined after the update.
Shouldn't the input's value be '' after the update?
Here is a list of issues with the current way:
<input ref={ref} type="text" /> is describes a React Element and has no value prop
Prop value should be controlled via state and not mutated directly
wrapper.find("input").props().value = "hello";
Setting value on a DOM Node isn't the same as setting a prop value. Using React means that you ceed DOM manipulation to it.
useRef allows for access to the underlying DOM Node when passed an initial value of null and this following line mutates DOM in spite of App state.
ref.current.value = "";
In certain scenarios, it's expedient to manipulate DOM in spite of the App state. The tests should then deal with the DOM Node and check changes to it.
describe("App", () => {
const wrapper = mount(<App />);
wrapper.find("input").getDOMNode().value = "hello";
act(() =>
wrapper
.find("button")
.props()
.onClick()
);
wrapper.update();
expect(wrapper.find("input").length).toBe(1);
expect(wrapper.find("input").getDOMNode().value).toBe("");
});
If you consider that your use case doesn't require this much of a direct control of the DOMNode.
The input element value prop can be controlled with state. For example,
const App = () => {
const [count, setCount] = useState(0);
const [value, setValue] = useState("hello");
useEffect(() => {
setValue("");
}, [count]);
const handleClick = () => {
setCount(count + 1);
};
return (
<div>
<input type="text" value={value} />
<button onClick={handleClick}>click me</button>
</div>
);
};
export default App;

Categories

Resources