If I had my component without HOC it did fire but now i wrapped my component inside withSpinner Hoc but it does not fire the fetching start.
const CollectionPage = (props) => {
const { isCollectionLoaded, isCollectionFetching } = props;
useEffect(() => {
props.fetchCollectionsStart();
}, []);
const { title, items } = props.collection;
return (
<div className="collection-page">
<SearchBar />
<h2 className="title">{title} </h2>
<div className="items">
{items.map((item) => (
<CollectionItem key={item.id} {...props} item={item} />
))}
</div>
</div>
);
};
const mapStateToProps = (state, ownProps) => ({
collection: selectCollection(ownProps.match.params.collectionId)(state),
isCollectionFetching: selectIsCollectionFetching(state),
isCollectionLoaded: selectIsCollectionsLoaded(state),
});
export default WithSpinner(
connect(mapStateToProps, { fetchCollectionsStart })(CollectionPage)
);
here is the console of the state.
and this is the withSpinner Hoc:
const WithSpinner = (WrappedComponent) => ({
isCollectionLoaded,
...otherProps
}) => {
return !isCollectionLoaded ? (
<SpinnerOverlay>
<SpinnerContainer />
</SpinnerOverlay>
) : (
<WrappedComponent {...otherProps} />
);
};
export default WithSpinner;
As you can see from the image, I just see the spinner is spinning becuase fetchCollectionStart is not firing so redux store is not updated.
isCollectionLoaded will be true (as I suspect) once dispatch fetchCollectionsStart finishes and redux state is updated.
But you have an issue, fetchCollectionsStart is only dispatched at CollectionPage mount phase which never occurs since isCollectionLoaded is false by default and WithSpinner blocks CollectionPage.
I suggest to move the dispatch useEffect to Spinner Hoc, which makes sense given your code structure. your hoc may look something like:
const WithSpinner = (WrappedComponent) => ({
isCollectionLoaded,
fetchCollectionsStart,
...otherProps
}) => {
useEffect(() => {
fetchCollectionsStart();
}, []);
return !isCollectionLoaded ? (
<SpinnerOverlay>
<SpinnerContainer />
</SpinnerOverlay>
) : (
<WrappedComponent {...otherProps} />
);
};
export default WithSpinner
It's because your property isCollectionLoaded isn't being updated, and your view to update the spinner to the WrappedComponent depends on the property isCollectionLoaded being changed.
You're already using a higher-order component with redux's connect, but what you're attempting to do is create a composite component, with the Spinner and collection searcher. Your instance of withSpinner in the second example will need to expose or call the connect function, so that redux can do its magic.
By exposing the named component in the first example, you're exposing a React component that has bound logic:
export default WithSpinner(
connect(mapStateToProps, { fetchCollectionsStart })(CollectionPage)
);
This can be used as:
<WithSpinner/>
The easier solution, rather than creating a composite component, is to add the spinner to the CollectionPage component:
if (!isContentLoaded) {
return (<Spinner/>);
}
return (
<div className="collection-page">
<SearchBar />
<h2 className="title">{title} </h2>
<div className="items">
{items.map((item) => (
<CollectionItem key={item.id} {...props} item={item} />
))}
</div>
</div>
);
Related
**I'm getting react Error while trying to change parent component styles onMouseEnter event. How could I change the styles via child component button?
The error is - Uncaught Error: Too many re-renders. React limits the number of renders to prevent an infinite loop.
That seems odd for onMouseLeave={() => setHovered(false)} is an arrow function.
https://codesandbox.io/s/trusting-moon-djocul?
**
// App js
import React, { useState, useEffect } from "react";
import Categories from "./Categories";
import ShopPage from "./components/Products";
export default function App() {
const [data, setData] = useState(Categories);
useEffect(() => {
setData(data);
}, []);
return (
<div className="wrapper">
<ShopPage products={data} filterResult={filterResult} />
</div>
);
}
// Shopping page
const ShopPage = ({ products }) => {
return (
<>
<div>
<Products products={products} />
</div>
</>
);
};
// Parent component, the main part goes here
const Products = ({products}) => {
const [hovered, setHovered] = useState(false);
const [style, setStyle] = useState(false);
if (hovered) {
setStyle({
// inline styles
});
} else {
setStyle({
// inline styles
});
}
return (
<>
<Product setHovered={setHovered} style={style} products={products}/>
</>
);
};
export default Products;
// Child component
const Product = ({ setHovered, style, products }) => {
return (
<div className={styles.items}>
{products.map((value) => {
return (
<>
<div style={style}>
<button
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => setHovered(false)}
>
Add to Cart
</button>
</div>
</>
);
})}
</div>
);
};
export default Product;
The issue is you are setting setHovered state in component, the simple solution could be to use it in useEffect and add required dependency.
If we talk about your code so you can easily do this by using the state in child component instead of passing through props.
I have updated your code below:
https://codesandbox.io/s/nameless-cookies-e6pwxn?file=/src/components/Product.js:141-151
I have the following code `
import { useState, useEffect } from 'react'
import LayoutContent from './layout_content';
type Props = {
children: JSX.Element | JSX.Element[]
}
const Layout = ({ children }: Props) => {
const [selected, setSelected] = useState(countries[0]);
const country= selected.id
return (
<>
<Sidebar onClick={toggle} sidebar={open} />
<LayoutContent sidebar={open} countriesWithsrc ={countriesWithsrc} selected={selected} lected={setSelected} >
{children}
</LayoutContent>
</>
)
}
export default Layout;
`
How do I pass the variable country from the Layout component to the children without state management ?.I.e I want to drill it.
If you don't want any state management you can use React.Children. It provides utilities to work with the children prop. React.Children.map will run a method for every immediate child of the component. You can use cloneElement along with that to create a clone of your element by passing in extra properties. Infact you can even modify the children of an element you are cloning, but that is not the ask here.
Do note that context would be the better way to do it.
const Layout = ({ children }: Props) => {
....
....
const modifiedChildren = React.Children.map(children, child => {
if (React.isValidElement(child)) {
return React.cloneElement(child, { testProp : 'test' });
}
return child;
});
....
....
return (
<>
<Sidebar onClick={toggle} sidebar={open} />
<LayoutContent sidebar={open} countriesWithsrc ={countriesWithsrc} selected={selected} lected={setSelected} >
{modifiedChildren}
</LayoutContent>
</>
)
}
I faced with some problem. I have heavy JSX Element with multipe states. In another part of app I need to pass this Element to Modal window with keeping all states. What is the best solution for solving this problem? Of course I can make Parent with all states and pass it to Child. But maybe it's possible to freeze all states and pass JSX Element as independent component?
Structure will look like:
ParentElement
|_
AnotherElement
|_
SomeHeavyElement
ParentElement:
const ParentElement= () => {
return (
<React.Fragment>
<AnotherElement />
<SomeHeavyElement />
</React.Fragment>
);
};
export default ParentElement;
AnotherElement:
const AnotherElement= () => {
return (
<React.Fragment>
<dialog>
<SomeHeavyElement/>
</dialog>
</React.Fragment>
);
};
export default AnotherElement;
SomeHeavyElement
const SomeHeavyElement= () => {
const [state1, setState1] = useState(true);
...
const [state99, setState99] = useState(false);
return (
<React.Fragment>
{/*some logic*/}
</React.Fragment>
);
};
export default SomeHeavyElement;
You have to lift state up, meaning you should define your state on top of both component (in <ParentElement>). You can't really freeze your component internal state.
Here is a minimal example:
const ParentElement= () => {
const [state1, setState1] = useState(true);
// ...
const [state99, setState99] = useState(false);
return (
<React.Fragment>
<AnotherElement state={{state1, state99}} />
<SomeHeavyElement state={{state1, state99}} />
</React.Fragment>
);
};
export default ParentElement;
const SomeHeavyElement= ({state}) => {
return (
<React.Fragment>
{/*some logic*/}
</React.Fragment>
);
};
export default SomeHeavyElement;
const AnotherElement= ({state}) => {
return (
<React.Fragment>
<dialog>
<SomeHeavyElement state={state} />
</dialog>
</React.Fragment>
);
};
export default AnotherElement;
Also, when you have a lot of useState defined, you could useReducer to centralize your component state. Also, if you want to avoid props drilling, you could define handle your state using React API context.
I am trying to use the state hook in my react app.
But setTodos below seems not updating the todos
link to my work: https://kutt.it/oE2jPJ
link to github: https://github.com/who-know-cg/Todo-react
import React, { useState } from "react";
import Main from "./component/Main";
const Application = () => {
const [todos, setTodos] = useState([]);
// add todo to state(todos)
const addTodos = message => {
const newTodos = todos.concat(message);
setTodos(newTodos);
};
return (
<>
<Main
addTodos={message => addTodos(message)}
/>
</>
);
};
export default Application;
And in my main.js
const Main = props => {
const input = createRef();
return (
<>
<input type="text" ref={input} />
<button
onClick={() => {
props.addTodo(input.current.value);
input.current.value = "";
}}
>
Add message to state
</button>
</>
);
};
I expect that every time I press the button, The setTodos() and getTodos() will be executed, and the message will be added to the todos array.
But it turns out the state is not changed. (still, stay in the default blank array)
If you want to update state of the parent component, you should pass down the function from the parent to child component.
Here is very simple example, how to update state with hook from child (Main) component.
With the help of a button from child component you update state of the parent (Application) component.
const Application = () => {
const [todos, setTodos] = useState([]);
const addTodo = message => {
let todosUpdated = [...todos, message];
setTodos(todosUpdated);
};
return (
<>
<Main addTodo={addTodo} />
<pre>{JSON.stringify(todos, null, 2)}</pre>
</>
);
};
const Main = props => {
const input = createRef();
return (
<>
<input type="text" ref={input} />
<button
onClick={() => {
props.addTodo(input.current.value);
input.current.value = "";
}}
>
Add message to state
</button>
</>
);
};
Demo is here: https://codesandbox.io/s/silent-cache-9y7dl
In Application.jsx :
You can pass just a reference to addTodos here. The name on the left can be whatever you want.
<Main addTodos={addTodos} />
In Main.jsx :
Since getTodo returns a Promise, whatever that promise resolves to will be your expected message.
You don't need to pass message as a parameter in Main, just the name of the function.
<Main addTodos={addTodos} />
You are passing addTodos as prop.
<Main
addTodos={message => addTodos(message)}
/>
However, in child component, you are accessing using
props.addTodo(input.current.value);
It should be addTodos.
props.addTodos(input.current.value);
I wanted to make a HOC that would make use of the underlying dom element. I was looking into the ref-forwarding API in order to do this.
Effectively, I am using a context component to locate the position of DOM nodes and register their positions. I was trying something like this:
export default function createIntersectable<TProps = {}>(
Component: React.ComponentType<TProps & { ref: React.RefObject<HTMLElement> }>,
) {
class Intersectable extends React.PureComponent<ComponentProps<TProps>> {
componentDidMount() {
this.props.register(this.props.forwardedRef.current);
}
componentWillUnmount() {
this.props.unregister(this.props.forwardedRef.current);
}
render() {
const { forwardedRef, ...rest } = this.props;
return <Component ref={forwardedRef} {...rest} />;
}
}
return React.forwardRef((props: TProps, ref: React.RefObject<any>) => {
return (
<IntersectionContext.Consumer>
{({ register, unregister }) => (
<Intersectable
{...props}
forwardedRef={ref}
register={register}
unregister={unregister}
/>
)}
</IntersectionContext.Consumer>
);
});
}
I am then consuming it like this:
renderNodes() {
return this.props.nodes.map((node) => {
const ref = React.createRef();
<Node {...node} ref={ref} key={node.id} />
});
}
Where Node is a component wrapped by createIntersectable.
However, I get null for my ref at all times. I'm not sure of the appropriate way to do this.
What's the correct way to forward a ref with react 16.3 in a HOC?