In my React + Redux + Redux-Form app, I need to enable/disable a "Next" button depending on the validity state of the form. This button is controlled by the parent component, so I can't just have <Button disabled={!props.valid}/>. Ideally redux-form would give me an event like onValidityChanged that I could listen for, and call the setCompleted() function passed down by the parent component whenever this happens. However such an event doesn't seem to exist. I also can't use the onChange hook, because at this point it won't have access to the props passed by the parent.
e.g.
function Parent(props){
const [completed, setCompleted] = React.useState(false);
return (
<div>
<Child setCompleted=setCompleted/>
<button disabled={!completed}>Next</button>
</div>
);
}
const Child = reduxForm({form: 'form'})(props => {
const {setCompleted} = props;
// Call setCompleted once this form becomes valid
});
How can I run an event whenever the validity of the form changes, but still have access to props?
One simple way of doing so would be to read the current validity status as Redux-Form updates it in your redux state, using a selector. You can even use one provided by the redux-form package: isValid. Since you're using hooks, you can also use react-redux's useSelector hook (if you're using react-redux v7.1.0 or higher).
The result would be:
import { useSelector } from 'react-redux`;
import { reduxForm, isValid } from 'redux-form`;
const Child = reduxForm({form: 'form'})(props => {
const { setCompleted } = props;
const isFormValid = useSelector(state => isValid('form')(state));
// Call setCompleted once this form becomes valid
if (isFormValid) {
setCompleted(true);
}
});
Of course you may want to extract this selector in another file and memoize it.
You may also want to use this hook (or its mapStateToProps equivalent) directly inside the parent component to avoid using the useState hook in the first place.
Related
In the beginning, I have a component called Search which has two child components SearchBar, and FilterBar. I created a function called onSearchSubmit, and onFilterSubmit which I pass them as callback prop functions to SearchBar and FilterBar respectively.
so it looks like that in the Search.js
const onFilterSubmit = (e) => {
e.preventDefault()
const filteredStacks = filterInput.map((stack) => { return stack.value }) //Extract the values field only
setCompanies([])
fetchData(fetchFilterData, filteredStacks) //API Call
setFilterInput([])
}
//use the API providing it the search input, and
//setCompanies hook to update list of companies
//I did this approach to avoid re-rendering
//whenever the user types in the input field
const onSearchSubmit = (e) => {
e.preventDefault()
setCompanies([])
fetchData(fetchSearchData, inputRef.current.value) //API Call
inputRef.current.value = ""; //useRef hook also passed as prop to the searchBar to access the input value in the input field.
}
and also the component is returned as follows
...
<SearchBar inputRef={inputRef} onSubmit={onSearchSubmit} />
<FilterBar filterInput={filterInput} onChange={setFilterInput} onSubmit={onFilterSubmit}/>
...
Now, inside the SearchBar I wrote a useEffect hook to re-render the component when the onSearchSubmit function changes or gets called (I guess?)
const SearchBar = ({inputRef,onSubmit}) => {
useEffect(()=>{
console.log('search now')
},[onSubmit])
return (
<Form
className="search-form"
onSubmit={onSubmit}
>
....
Then I tried to run it now, and found that search now is logged three times, which is weird actually I don't understand how.
In addition it will also log for 3 times when I search for a keyword. And logs one time if I applied a filter
I understand that a child component which is the SearchBar here will re-render because the component changed when onSubmit was called, but I don't understand why the parent component re-rendered since it I believe created the function once again three times causing the change in the SearchBar component.
Yet when I wrapped the function in useCallback hook, it only logged search now just once, and didn't log when I submitted a search nor when I applied a filter. Which is understandable since the callback prop function onSearchSubmit is now memoed.
So my question is why is the parent component is affected and re-creates the onSearchSubmit three times although the child component is one who is changed? Is there anything I am missing?
I have a button component rendered like below. I can't modify the button component itself but I can modify the parent component that renders it. I want to make it so that when I modify the button element's disabled attribute elsewhere in the code (that is modify the DOM like button.disabled = true) Given that I can't pass it from parent props, the button component gets re-rendered. I tried to use useRef and useEffect hook but it didn't work. I think I used them wrongly. Is there anyway I can achieve what I want?
const elementRef = useRef()
const [disabled, setDisabled] = useState(elementRef.current?.disabled)
useEffect(() => {
const buttonElement = elementRef.current
buttonElement.addEventListener('disabled', handleDisabled)
}, [elementRef.current])
const handleDisabled = (e: any) => {
setDisabled(e.target?.disabled)
}
return( <Button ref={elementRef} disabled={props.isDisabled || disabled}></Button> )
You seemingly are working in two different "worlds": ReactJs and the DOM.
I suggest to use only React, and never modify any DOM properties directly.
You don't show how you want to change the disabled attribute/property "elsewhere in the code", but I assume you want to do something like
const setMyButtonDisabled = function(){
document.querySelector('#myButton').disabled = true;
}
React is simply not able to know about this change. You have to tell React explicitly, like:
// not recommended:
const setMyButtonDisabled = function( setButtonDisabledCallback ){
document.querySelector('#myButton').disabled = true;
setButtonDisabledCallback( true );
}
And then find a way to pass the props around, so that the desired components have it
(I can't know the relation between your code example and the "elsewhere").
But ideally you would never set the DOM buttonElements .disabled property, but set a React state buttonDisabled instead,
and then just pass that around to where ever you need it:
// recommended:
const setMyButtonDisabled = function( setButtonDisabledCallback ){
setButtonDisabledCallback( true );
}
It's a known React behavior that code runs twice.
However, I'm creating a form builder in which I need to be able to give each form input a dynamic Id and use that Id for a lot of other purposes later. Here's a simple code of an input:
const Text = ({placeholder}) => {
const [id, setId] = useState(Math.random());
eventEmitter.on('global-event', () => {
var field = document.querySelector(`#${id}`); // here, id is changed
});
}
But since Math.random() is a side-effect, it's called twice and I can't create dynamic ids for my form fields.
The reason I'm using document.querySelector can be read here.
My question is, how can I create consistent dynamic ids for my inputs?
It seems you think that useState(Math.random()); is the side-effect causing you issue, but only functions passed to useState are double-invoked.
I think the issue you have is that the eventEmitter.on call is the unintentional side-effect since the function component body is also double invoked.
Strict mode can’t automatically detect side effects for you, but it
can help you spot them by making them a little more deterministic.
This is done by intentionally double-invoking the following functions:
Class component constructor, render, and shouldComponentUpdate methods
Class component static getDerivedStateFromProps method
Function component bodies <-- this
State updater functions (the first argument to setState)
Functions passed to useState, useMemo, or useReducer <-- not this
To remedy this I believe you should place the eventEmitter.on logic into an useEffect hook with a dependency on the id state. You should also probably use id values that are guaranteed a lot more uniqueness. Don't forget to return a cleanup function from the effect to remove any active event "listeners", either when id updates, or when the component unmounts. This is to help clear out any resource leaks (memory, sockets, etc...).
Example:
import { v4 as uuidV4 } from 'uuid';
const Text = ({placeholder}) => {
const [id, setId] = useState(uuidV4());
useEffect(() => {
const handler = () => {
let field = document.querySelector(`#${id}`);
};
eventEmitter.on('global-event', handler);
return () => {
eventEmitter.removeListener('global-event', handler);
};
}, [id]);
...
}
How do I cause re-rendering to the component
function Bookmarks({ data }: any) {
const bookmarkedBlogs = useBookmarks().getBookmarkedBlogs(data.allMdx.nodes);
.....
}
when bookmarks change in the hook
function useBookmarks() {
const [bookmarks, setBookmarks, accessDenied] = useLocalStorage<BlogType['id'][]>('bookmarks', []);
const getBookmarkedBlogs = (blogs: BlogType[]) => {
return blogs.filter(checkIsBookmarked)
};
because as of now, even if I toggle bookmarks, the getBookmarkedBlogs function doesn't execute except in the initial render of the component.
How the implementation of useLocalStorage, and how you toggle bookmarks?
localStorage changes don't notify your every hooks except your make a observer model
if you don't make observer model, toggle bookmark in other hooks or other ways wouldn't notify your this hook, so it don't rerun
Your hook doesn't actually work.
It seems like you want a hook that subscribes to updates from some source outside the React render tree.
Here's a simple example of something that works like that:
function MyComp() {
let eventValue = useEventListener()
return (
<div>
{eventValue}
</div>
)
}
function useEventListener() {
let [ value, setValue ] = React.useState(1)
React.useEffect(() => {
setTimeout(() => setValue(5), 1000)
}, [])
return value
}
What makes it work is that the custom hook invokes a built-in hook when data should change. The React Hooks framework handles the rest.
Instead of a setTimeout, you could subscribe to an event stream, or listen for IPC messages, or something else. But:
The custom hook has to actually return something
The custom hook can only trigger a re-render by invoking a builtin hook
The custom hook must set up its subscribe-to-changes logic on its first invocation, which is often best handled by useEffect configured to run once
I have a component that is connected to a store using react-redux (I have shortened my code for simplicity sake):
const Address = (props) => {
const { userAddresses, removeAddress } = props
let { showEdit } = props
return (
<div>
{userAddresses.map(address => (
<div key={address.id}>
<p>{address.name}</p>
<Button onClick={removeAddress.bind(this, address)}>Delete</Button>
</div>
))}
<Button
onClick={() => { showEdit = true }}>
Add new
</Button>
{showEdit ? (
// show some edit stuff here!!!
): null}
</div>
)
}
const mapState = state => {
return {
userAddresses: state.account.userAddresses,
showEdit: false
}
}
const mapDispatch = (dispatch) => {
return {
addAddress: address => dispatch(addUserAddress(address)),
removeAddress: address => dispatch(removeUserAddress(address)),
}
}
export default connect(mapState, mapDispatch)(Address)
When you click the button (Add new), a form is supposed to popup (marked as show some edit stuff here!!!). I know this can be easily done if Address was a state component. However, I need to use react-redux, and as far as I know, you have to use a stateless component to use react-redux. Can anyone point me in the right direction?
No, you do not "have to use a stateless/function component" to use React-Redux!
connect accepts both class components and function components (and even "special" React components like React.memo()), and it doesn't matter whether they use component state (or hooks) inside or not.
Also, as a side note, you can simplify your code using the "object shorthand" form of mapDispatch:
const mapDispatch = {
addAddress : addUserAddress,
removeAddress : removeUserAddress
}
(Note that that could be even shorter if your prop names were named the same as the functions.)
Keeping to strictly use redux, you should have another action to dispatch when the user clicks the button. Then, a reducer will update the value of the showEdit property, which will cause a re-render of your stateless component allowing you to conditionally render the editing form.
But, this is an information (the visibility or not of the editing form) not useful to the rest of your application, so it could be the case to transform your component into a stateful one and track the showEdit property in the local state.
A third option could be the use of useState hook, but it depends on the version of React you have in your project, because they are currently in alpha...