I have a problem making a HOC component with hooks inside.
I am trying to find a correct solution to not break rules-of-hooks.
If I change the name of this component to start with lowercase: withNetworkDetector
instead of uppercase, I will not get errors, but did I break the rule then?
Is this code will be secure then?
export const WithNetworkDetector = (Component: FC<{}>) => (props: any) => {
const [isDisconnected, setIsDisconnected] = useState(false);
const handleConnectionChange = useCallback(() => {
...
setIsDisconnected(true);
}, []);
return (
<div>
{isDisconnected && (
<Toast type="warning" text={TOAST_ERRORS.LOST_INTERNET_CONNECTION} />
)}
<Component />
</div>
);
};
Hooks can be used inside the React functional component. One of the criterias that classify a function as a react function is the name starting in uppercase, then only we can use that as a React component. So, it will break the rule.
Related
I want to do something like this:
const GreetingWithCounter = (props) => {
const { name, count } = props;
return (
<div>
<div>Hello {name}</div>
<button onClick={() => render({ ...props, count: count + 1 })}>
{count}
</button>
</div>
);
}
<GreetingWithCounter name="Alice" count={0} />
Ie. I want to re-render a component with new values for its props. Is there a way to do that? Looking through these three questions, I'm seeing ways to re-render a component but not with new values for props (1, 2, 3).
Context
I'm thinking about a way to simplify React. I really like the mental model of React being the view layer in MVC, where UI = F(state). But things can get confusing when "state" can come from so many different places: props, useState, useReducer, "raw" useContext, Redux (which uses useContext I think), whatever else.
What if everything was just based off of props?
For local state you'd do what I did in that example above. You'd initialize the local state of count when doing <GreetingWithCounter name="Alice" count={0} /> and then update it by re-rendering. This means less DRYness because you'd have to repeat the count={0} code instead of only having it once inside of GreetingWithCounter.
You'd have to do prop drilling instead of useContext stuff.
This approach would probably make React slower.
Still, I hypothesize 1) that the mental model of having everything coming from props is simpler and 2) that pro outweighs the cons in a non-trivial amount of apps.
Props are not supposed to be mutated in React. That is precisely the difference between props and state. The React way to do this is to use state for the count. You can pass the initial state of the count as a prop and do this: const [count, setCount] = useState(initialCount). Your onClick handler would then increment count, which again is state. I realize that this is not what you want but it's how React works.
In React Props values cannot be changed in child component but we can do it in parent component.
const GreetingWithCounter = (props) => {
const { name, count, updateCount } = props;
return (
<div>
<div>Hello {name}</div>
<button onClick={updateCount}>{count}</button>
</div>
);
};
function App() {
const [count, setCount] = useState(0);
const updateCount = () => {
setCount(count + 1);
};
return (
<div className='App'>
<h1>Greeting With Counter:</h1>
<GreetingWithCounter
name='Alice'
count={count}
updateCount={updateCount}
/>
</div>
);
}
Appreciate the change you want to point out and value you want to add but there might be some points that you're missing what React conceptually trying to provide with seperation between props and state.
The props within components coming with React are specifically conceptually designed to be immutable as per the documentation
here.
So what you're trying to do is conceptually not ok for that purpose and violating what React tries to accomplish.
Infact you may mention about creating another library/framework which successfully getting it done while introducing props are the new state concept but in this specific case, there's no possible way to succeed on it in a React way.
You cannot change value of props in child but you have 2 ways to handle it
first, I assume that you only want to use count in child component and you don't need count value in parent, in this case you can use props.count as initial state, sth like this :
const GreetingWithCounter = props => {
const [count, setCount] = useState(props.count);
const { name } = props;
return (
<div>
<div>Hello {name}</div>
<button onClick={() => setCount(prevState => prevState + 1)}>{count}</button>
</div>
);
};
<GreetingWithCounter name="Alice" count={0} />;
but if you wanna access it's value from parent, it's better to pass setter to child
sth like this :
const GreetingWithCounter = ({name,count,setCount}) => {
return (
<div>
<div>Hello {name}</div>
<button onClick={() => setCount(prevState => prevState + 1)}>{count}</button>
</div>
);
};
const App = ()=>{
const [count, setCount] = useState(0);
return (<GreetingWithCounter name="Alice" count={count} setCount={setCount} />)
}
or if it's child is so deep that you need to send props to all it's tree, its better to use state management like Redux,Context or ...
Is this the way you want to do ? :
import React from 'react'
import ReactDOM from 'react-dom'
export default function renderComponent(Component, props, container) {
ReactDOM.render(<Component {...props} />, container)
}
What you are trying to do goes against the philosophy of state management of react. For correct way to do it, you can check other answers, and even you yourself have posted it in the questions.
But if you really want to do it, behind its magic, React is also just JavaScript. Therefore, we just need to implement the render function outside of React way of thinking. We know that React re-renders on state change magic or on props change. We need to just somehow connect the render method you asked for with set state. Something like the below should work.
const ParentStuff = () => {
const [props, setProps] = useState({ name: "Alice", count: 0 });
render = setProps;
return (<GreetingWithCounter name={props.name} count={props.count} />);
}
let render;
const GreetingWithCounter = props => {
const { name, count } = props;
return (
<div>
<div>Hello {name}</div>
<button onClick={() => render({ ...props, count: count + 1 })}>{count}</button>
</div>
);
};
A lot of people will scream though at code above. It definitely strays away from the intended use.
If you want to go further, you can also just have one state for the entire app, and pass this state fo every component. And voila! You just created a singleton state and an uni directional data flow, which is a poor man version of the redux and this will probably kill performance of the webapp, as things like typing each letter in a textbox will re-render the entire page.
As others already mentioned, component is either controlled or uncontrolled (or mix of both) in react.
If you keep state in component itself - it's uncontrolled. You can reset its state to internal by changing key prop from parent though.
If you keep state in parent - it's controlled component and changes it's state through props/callbacks.
What you have shown in your example, you want to achieve uncontrolled component with some syntactic sugar on top.
Example implementation:
const usePropsWithRender = (props) => {
const [currentProps, setCurrentProps] = useState(props);
return {
...currentProps,
render: setCurrentProps,
};
};
const GreetingWithCounter = (props) => {
const { name, count, render } = usePropsWithRender(props);
return (
<div>
<div>Hello {name}</div>
<button onClick={() => render({ ...props, count: count + 1 })}>
{count}
</button>
</div>
);
};
You can reuse usePropsWithRender through all you project, but it's nothing more than a thin wrapper around useState. I don't see how it is better than using useState directly.
I'm beginner with React testing, learning by coding, here i have a component 'cam.tsx'
i want to test it, when i want to test Add function it goes straight like this, but when i want to test Update function it still shows Add function in my test, how to test both of them ?
Add and Update functions are forms where user can fill.
describe("Testing component ", () => {
const Camera = (): RenderResult =>
render(
<Provider store={store}>
<Cam
}}
/>{" "}
</Provider>
);
test("Cam", () => {
Camera();
const name = screen.queryByTestId(/^AddName/i);
});
});
cam.tsx:
const ADD = "ADD";
let [state, setState] = useState<State>({mode: ADD });
if (props.mode) {
state.mode = props.mode;
}
const option = state.mode;
return (
<React.Fragment>
<div data-testid="header">
{option == ADD ? Add() : <></>}
{option == UPDATE ? Update() : <></>}
</div>
</React.Fragment>
Basically cam.tsx is a component which has two forms one for updating camera and another for adding new camera.When user clicks add/update icon then cam component gets 'mode' via props ' state.mode = props.mode '
English is not my mother language, so could be mistakes
Here is how to test a component that conditionally renders components from state and can be updated via props.
import {render, screen} from '#testing-library/react';
import {Cam} from './Cam';
test('renders add by default', () => {
render(<Cam/>);
expect(screen.getByTestId('addForm'))
.toBeInTheDocument();
expect(screen.queryByTestId('updateForm'))
.not.toBeInTheDocument();
});
test('renders edit by passing props', () => {
const {rerender} = render(<Cam mode={undefined}/>);
rerender(<Cam mode={'UPDATE'} />)
expect(screen.getByTestId('updateForm'))
.toBeInTheDocument();
expect(screen.queryByTestId('addForm'))
.not.toBeInTheDocument();
});
However, it is known in the React community that updating state via props is usually an anti-pattern. This is because you now have two sources of truth for state and can be easy to have these two states conflicting. You should instead just use props to manage rendering.
If state comes from a parent component, use props.
export function Cam(props) {
const option = props.mode;
return (
<div data-testid="header">
{option === ADD ? Add() : <></>}
{option === UPDATE ? Update() : <></>}
</div>
);
}
If you really want to keep state in the child component even if props are passed in, you should update props in an useEffect hook. Additionally, you should use the setState function rather than setting state manually state.mode = props.mode
Use the useEffect hook to update state via props.
...
const [state, setState] = useState({mode: ADD});
useEffect(() => {
if (props.mode) {
setState({mode: props.mode});
}
}, [props.mode]) <-- checks this value to prevent infinite loop.
const option = state.mode;
return (
...
I've created 2 components C1 and C2 and one custom hook useCounter.
C1 displays "count" property of useCounter hook.
C2 displays "count" property and also increment or decrement it on button click.
Current behavior: When the "count" is changed, the updated value is displayed only in C2 and not in C1.
Expected behavior: Both components should re-render on the "count" update.
Please let me know if I'm missing something.
PS: I've already done this using Context API and redux. Just want to know if the same behavior could be achieved using custom hooks :)
Codesandbox link: Custom hooks demo
import { useState } from "react";
function useCounter() {
const [count, setCount] = useState(0);
const updateCounter = newVal => {
setCount(newVal);
};
//return [count, useCallback( (newVal) => setCount(newVal))];
return [count, updateCounter];
}
export default useCounter;
You might want to use Context API to share the same instance, as the custom hook useCounter will assign a new instance of count on mount:
export const CounterContext = React.createContext();
function App() {
const counterController = useCounter();
return (
<CounterContext.Provider value={counterController}>
<div className="App">
<h1>App Component</h1>
<hr />
<C1 />
<hr />
<C2 />
</div>
</CounterContext.Provider>
);
}
// Use context
function C1() {
const [count] = useContext(CounterContext);
return (
<div>
Component 1 <br />
Count: {count}
</div>
);
}
Additionally, you can use a library like reusable:
const useCounter = createStore(() => {
const [counter, setCounter] = useState(0);
return {
counter,
increment: () => setCounter(prev => prev + 1)
}
});
const Comp1 = () => {
const something = useCounter();
}
const Comp2 = () => {
const something = useCounter(); // same something
}
Hey I am totally agree with #RVRJ's explanations. So what happens when you import any hooks it will create new object of that hook. Let's suppose if you import same hook in two different files that means you are creating two difference object of that hook.
Here I have tried to solve your problem using hooks only, but I am importing hook only once and passed its object to child component <C1 /> and <C2 />.
Here is example how I created single object of useCounter hook
import React from "react";
import C1 from "./components/C1";
import C2 from "./components/C2";
import useCounter from "./hooks/useCounter";
function App() {
const [count, updateCount] = useCounter(); // <<-- created one object
return (
<div className="App">
<h1>App Component</h1>
<hr />
<C1 count={count} updateCount={updateCount} /> {/* passing values ad props */}
<hr />
<C2 count={count} updateCount={updateCount} /> {/* passing values ad props */}
</div>
);
}
export default App;
and now you can access count and updateCount as props in every child.
Here is C1 component after change
// C1 component
import React from "react";
function C1({ count }) { {/* <-- access count as props */}
return (
<div>
Component 1 <br />
Count: {count}
</div>
);
}
export default C1;
And here is your C2 component
// C2 component
import React from "react";
function C3({ count, updateCount }) { {/* <-- access count and as updateCount props */}
const handleIncr = () => {
updateCount(count + 1);
};
const handleDecr = () => {
updateCount(count - 1);
};
return (
<div>
Component 2 <br />
<button onClick={handleIncr}>Increment</button>
<button onClick={handleDecr}>Decrement</button>
<br />
<br />
<br />
Count: {count}
</div>
);
}
export default C3;
Here is updated and working solutions of your problem https://codesandbox.io/s/romantic-fire-b3exw
Note: I don't know what is use case of using hooks for same state values, so I still recommend you should use redux for sharing states between components.
Do two components using the same Hook share state? No. Custom Hooks are a mechanism to reuse stateful logic (such as setting up a subscription and remembering the current value), but every time you use a custom Hook, all state and effects inside of it are fully isolated.
Refer point #3 in Using a Custom Hook
They’re not a way to share state — but a way to share stateful logic. We don’t want to break the top-down data flow!
Reference: Making Sense of React Hooks
I'm using useDispatch hook (from Redux) in onSelect callback in the Tree component (from Ant library):
export const MyComponent = () => {
const dispatch = useDispatch();
const onSelect = (selectedNode) => {
const selectedItem = selectedNode[0];
dispatch(fetchSelectedItems(selectedItem));
};
return
<Tree
onSelect={onSelect}
>
<TreeNode .. />
<TreeNode .. />
<TreeNode .. />
</Tree
}
export const fetchSelectedItems = (selected: string) =>
(dispatch) =>
axios({
url: `/api/items?id=${selected}`,
method: 'GET',
}).then(response => {
dispatch(fetchSelectedItemsSuccess(response.data))
}).catch((error: any) => {throw(error)});
Why does useDispatch re-render parent components? Is there any way to prevent from this? I tried useCallback like it's in Redux documentation but this solution is to prevent child components from re-rendering, not parents.
It looks like my assumption in the comment was correct.
So I will show you the workaround.
You can extract the part that uses clickValue in the container to another component, say ClickValue.
Doing so will isolate the update to ClickValue component only.
My fork: https://codesandbox.io/s/soanswer60515755-9cc7u
function ClickValue() {
const clickValue = useSelector(state => state.value);
console.log(clickValue);
return clickValue;
}
export default function Container() {
return (
<div className="Container">
<h3>Container</h3>
<ParentComponent />
<ClickValue />
</div>
);
}
Check out the profile result below.
I would think that on every render you are redeclaring the onSelect function. Functions are reference types. Passing that redeclared function with its new reference on ever render will cause a rerender. Perhaps you should look into using context.
My problem with re-rendering components was caused by useSelector used in parent components where I directly refer to state. Most probably because of new result of this selector..
Solution:
I rewrote this selectors with reselect library to make them memoized (it was suggested in one of comments here but I don't know why its've been removed). I did exactly what is in redux documentation about memoized selectors.
I have a pair of functional components and want to pass one in as an argument to the other as shown below:
const compA = () => ( <div> Hello World! </div>);
const compB = (AnotherComp) => ( <AnotherComp />);
compB(compA);
The above snippet is a dumbed-down version of what I'm after, as each component has its own set of React hooks.
Google-fu turned up many guides on Higher-Order Components, but most address the difference between HOC and react hooks, or for getting props from a class component into a functional sub-component.
Is there a way to use Higher-order functional components, as shown above?
I hope I'm not misunderstanding your question, but it seems to me that for something to be considered higher order, you need a function to return a function, as in makeBlue, below.
const Button = ({ children, ...props }) => (
<button {...props}>{children}</button>
);
const makeBlue = (Component) => (props) =>
<Component style={{ background: "blue" }} {...props} />;
const BlueButton = makeBlue(Button);
Yes, they work with functional components.