Can we pass hook as a function to a component and use it in that component?
Like in the example below I am passing useProps to withPropConnector which returns a Connect component. This useProps is being used in the Connect component. Am I violating any hook rules?
// Landing.jsx
export const Landing = (props) => {
const { isTypeOne, hasFetchedData } = props;
if (!hasFetchedData) {
return <Loader />;
}
const renderView = () => {
if (isSomeTypeOne) {
return <TypeOneView />;
}
return <TypeTwoView />;
};
return (
<>
<Wrapper>
{renderView()}
<SomeNavigation />
</Wrapper>
<SomeModals />
</>
);
};
const useProps = () => {
const { query } = useRouter();
const { UID } = query;
const { isTypeOne, isTypeTwo } = useSelectorWithShallowEqual(getType);
const hasFetchedData = useSelectorWithShallowEqual(
getHasFetchedData(UID)
);
const props = {
isTypeOne,
isTypeTwo,
hasFetchedData
};
return props;
};
export default withPropConnector(useProps, Landing);
// withPropConnector.js
const withPropConnector = (useProps, Component) => {
const Connect = (propsFromParent = emptyObject) => {
const props = useProps(propsFromParent);
return <Component {...propsFromParent} {...props} />;
};
return Connect;
};
export default withPropConnector;
Related
PropTable should render only if receives props from other component
const PropTable = (props) => {
const { availableProp } = props;
...
return(...)
}
This'd get your job done
const PropTable = (props) => {
const { availableProp } = props;
...
return availableProp ? (...) : null
}
I'm using useReducer with useContext to manage state, but I can't return initial value of state in a child component.
In my reducer file I have:
import context from './context';
const initialValue = {
case1token: null,
case2token: false,
};
const reducer = (state, action) => {
switch (action.type) {
case 'case1':
return {
case1token: action.payload,
};
case 'case2':
return {
case2token: action.payload,
};
default:
return initialValue;
}
};
export const {Provider, Context} = context(
reducer,
{
someFunction,
},
{initialValue},
);
In my context file I have :
import React, {useReducer} from 'react';
export default (reducer, actions, defaultValue) => {
const Context = React.createContext();
const Provider = ({children}) => {
const [state, dispatch] = useReducer(reducer, defaultValue);
const boundActions = {};
for (let key in actions) {
boundActions[key] = actions[key](dispatch);
}
return (
<Context.Provider value={{state, ...boundActions}}>
{children}
</Context.Provider>
);
};
return {Context, Provider};
};
In my App.js file I can successfully return the case1token:
import {
Provider as AuthProvider,
Context as AuthContext,
} from './src/Context/auth/authContext';
function App() {
const {state} = useContext(AuthContext);
return (
<NavigationContainer ref={navigationRef}>
<View style={styles.root}>
<StatusBar barStyle="light-content" />
{state.case1token ? <MainFlow /> : <AuthFlow />}
</View>
</NavigationContainer>
);
}
export default () => {
return (
<AuthProvider>
<App />
</AuthProvider>
);
};
In this file I'm trying to return the case2token but it's only returning case1token:
import {Context as AuthContext} from '../../Context/auth/authContext/';
const Home = () => {
const {state} = useContext(AuthContext);
console.log(state);
return (
<SafeAreaView style={styles.screen}>
<Header/>
<Text>{state.case2token}</Text>
</SafeAreaView>
);
};
When I log in App.js both tokens get returned.
Why isn't it returning in the child component?
Appreciate any help!
If value of state.case2token == false Text will not render false as text so u need to do like:
<Text>{state.case2token ? 'true' : 'false'}</Text>
I'm trying to access the MetaDetails component in the test case but unable to do so since its wrapped with the Provider. The goal is to access the PrintLabelButton component on click of which I need to mock 'handlePrintLabelButtonClick' function.
Utils.js
export const handlePrintLabelButtonClick = (
e,
rmaNumber,
labelUrl,
getReturnLabel
) => {
const rmaList = [];
e.preventDefault();
if (!labelUrl) {
const container = { rmaNo: "" };
container.rmaNo = rmaNumber;
rmaList.push(container);
getReturnLabel(rmaList);
} else {
window.open(labelUrl, "_blank");
}
};
Page.js
import {
filterReturnLabelResponse,
handlePrintLabelButtonClick
} from "../../utils/common-util";
import { PrintLabelButton } from "./printLabel";
Const MetaDetails = () => {
//a lot of code
{showPrintLabelButton && (
<PrintLabelButton
onClickHandle={e =>
handlePrintLabelButtonClick(
e,
rmaNumber,
labelUrl,
getReturnLabel
)
}
url={labelUrl}
target="_blank"
type="printLabel"
text={intl.formatMessage(messages.printLabelText)}
/>
)}
// again a lot of code
}
const mapStateToProps = state => ({
// some code
});
const mapDispatchToProps = dispatch => ({
// some code
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(injectIntl(MetaDetails));
PrintLabelButton.js
export const PrintLabelButton = ({
target,
url,
type,
text,
onClickHandle
}: PrintLabelButtonProps) => {
return (
<ButtonWrapper>
<AnchorWrapper
href={url}
target={target}
type={type}
>
<DefaultButton
tabIndex="0"
onClick={onClickHandle}
data-test="print-label-button"
>
{text}
</DefaultButton>
</AnchorWrapper>
</ButtonWrapper>
);
};
What I've tried so far
MetaDetails.test.js
test("Returns Component Whats next button click", () => {
const wrapper = mount(
<Provider store={store}>
<MetaDetails/>
</Provider>
);
const middle = wrapper.find(MetaDetails);
const component = wrapper.find(`[data-test="print-label-button"]`).hostNodes();
component.simulate("click");
expect(mockSetModal).toHaveBeenCalled();
});
Can someone please tell me how can I access nodes when your component is surrounded by the Provider.
Note: I am using react/#emotion
I need to make a click on the button in one component and on this click call a function in the adjacent one. What's the easiest way?
I implemented like this. https://stackblitz.com/edit/react-l5beyi But I think you can do it much easier. React is new to me, and this construction looks strange ...
const App = () => {
const [isAdded, setIsAdded] = useState(false);
function handleClick(status) {
setIsAdded(status)
}
return (
<div>
<ComponentFirst
HandleClick={handleClick}
/>
<ComponentSecond
isAdded={isAdded}
handleCreate={handleClick}
/>
</div>
);
}
const ComponentFirst = ({ HandleClick }) => {
return (
<button
onClick={HandleClick}
>button</button>
)
}
const ComponentSecond = (props) => {
let { isAdded, handleCreate } = props;
const result = () => {
alert('work')
console.log('work')
}
React.useEffect(() => {
if (isAdded) {
result()
handleCreate(false);
}
}, [isAdded, handleCreate]);
return (
<></>
)
}
In your (contrived, I suppose) example the second component doesn't render anything, so it doesn't exist. The work should be done by the parent component:
const App = () => {
const handleClick = React.useCallback((status) => {
alert(`work ${status}`);
// or maybe trigger some asynchronous work?
}, []);
return (
<div>
<ComponentFirst handleClick={handleClick} />
</div>
);
};
const ComponentFirst = ({ handleClick }) => {
return <button onClick={() => handleClick("First!")}>button</button>;
};
You can also use a CustomEvent to which any component can listen to.
import React, { useState, useEffect } from "react";
import "./styles.css";
export default function App() {
return (
<div>
<ComponentFirst />
<ComponentSecond />
</div>
);
}
const ComponentFirst = () => {
const handleClick = (e) => {
// Create the event.
const event = new CustomEvent("myCustomEventName", {
detail: "Some information"
});
// target can be any Element or other EventTarget.
window.dispatchEvent(event);
};
return <button onClick={handleClick}>button</button>;
};
const ComponentSecond = (props) => {
function eventHandler(e) {
console.log("Dispatched Detail: " + e.detail);
}
//Listen for this event on the window object
useEffect(() => {
window.addEventListener("myCustomEventName", eventHandler);
return () => {
window.removeEventListener("myCustomEventName", eventHandler);
};
});
return <></>;
};
I have a Context and 2 Components: one is displaying what is in the context, the other updating it.
By having the following code in the updater Component, it will re-render upon changing the context.
const [, setArray] = React.useContext(context);
setArray(prevArray => { return [...prevArray, []] }
This means infitie re-renders. I need to avoid this. As the updater doesn't use the data in the context it should not update.
Complete example: I'm storing and displaying Profiler data about a Component.
https://codesandbox.io/s/update-react-context-without-re-rendering-the-component-making-the-update-k8ogr?file=/src/App.js
const context = React.createContext();
const Provider = props => {
const [array, setArray] = React.useState([]);
const value = React.useMemo(() => [array, setArray], [array]);
return <context.Provider value={value} {...props} />;
};
const Metrics = () => {
const [array] = React.useContext(context);
return <TextareaAutosize value={JSON.stringify(array, null, 2)} />;
};
const Component = () => {
const [, setArray] = React.useContext(context);
const onRenderCallback = (id, _phase, actualDuration) => {
setArray(prevArray => {
return [...prevArray, [id, actualDuration]];
});
};
return (
<React.Profiler id="1" onRender={onRenderCallback}>
<div />
</React.Profiler>
);
};
export default function App() {
return (
<div className="App">
<Provider>
<Metrics />
<Component />
</Provider>
</div>
);
}
This is what I came up with using the following article: https://kentcdodds.com/blog/how-to-optimize-your-context-value
Use 2 contexts, one for storing the state, one for updating it:
const stateContext = React.createContext();
const updaterContext = React.createContext();
const array = React.useContext(stateContext);
const setArray = React.useContext(updaterContext);
Complete example:
https://codesandbox.io/s/solution-update-react-context-without-re-rendering-the-component-making-the-update-yv0gf?file=/src/App.js
import React from "react";
import "./styles.css";
import TextareaAutosize from "react-textarea-autosize";
// https://kentcdodds.com/blog/how-to-optimize-your-context-value
const stateContext = React.createContext();
const updaterContext = React.createContext();
const Provider = props => {
const [array, setArray] = React.useState([]);
return (
<stateContext.Provider value={array}>
<updaterContext.Provider value={setArray}>
{props.children}
</updaterContext.Provider>
</stateContext.Provider>
);
};
const useUpdaterContext = () => {
return React.useContext(updaterContext);
};
const Metrics = () => {
const array = React.useContext(stateContext);
return <TextareaAutosize value={JSON.stringify(array, null, 2)} />;
};
const Component = () => {
const setArray = useUpdaterContext();
const onRenderCallback = (id, _phase, actualDuration) => {
setArray(prevArray => [...prevArray, [id, actualDuration]]);
};
return (
<React.Profiler id="1" onRender={onRenderCallback}>
<div />
</React.Profiler>
);
};
export default function App() {
return (
<div className="App">
<Provider>
<Metrics />
<Component />
</Provider>
</div>
);
}