i have a question, i'm not sure if possible or not.
Here is a sample code.
I have a component, i want everytime they use my component and a children
it should only accept the specific component.
for e.g:
<tableComponent>
<tableHeaderComponent/>
<tableHeaderComponent/>
<tableHeaderComponent/>
</tableComponent>
but for this type it should not be accepted
<tableComponent>
<th>blah</th>
<th>blah2</th>
<yourowncomponent/>
</tableComponent>
thanks,
ping pong
Assign a displayName to the component you want to allow and check inside the parent component if the children have the allowed displayName
const Good = () => <h1>Good</h1>;
Good.displayName = "Good";
const Bad = () => <h1>Bad</h1>;
const WithGoodOnly = ({ children }) => {
let allowed;
if (Array.isArray(children)) {
allowed = children.every(child => child.type.displayName === "Good");
} else {
allowed = children.type.displayName === "Good";
}
return allowed ? <div>{children}</div> : null;
};
rendered
const App = () => (
<WithGoodOnly>
<Good />
</WithGoodOnly>
);
rendered
const App = () => (
<WithGoodOnly>
<Good />
<Good />
</WithGoodOnly>
);
not rendered
const App = () => (
<WithGoodOnly>
<Good />
<Bad />
</WithGoodOnly>
);
Related
I'm using react-navigation v6 and context api, when I navigate from my component to a screen, the context state image which even though wraps my component, becomes undefined. Below is the code flow and relevant code from my components & screens.
EXPECTED OUTPUT -> image context state should persist (as I'm using context provider)
ACTUAL OUTPUT -> image context state becomes undefined when I navigate to different screen.
AppStack
const AppStack = () => {
return (
<Stack.Navigator>
<Stack.Screen name={'AppDrawer'} component={AppDrawer} />
<Stack.Screen name={'StoreStack'} component={StoreStack} />
</Stack.Navigator>
)
}
AppDrawer
const AppDrawer = () => {
return (
<Drawer.Navigator>
<Drawer.Screen name={'AppBotTab'} component={AppBotTab} />
</Drawer.Navigator>
)
}
AppBotTab
const BotTab = createBottomTabNavigator();
const AppBotTab = () => {
return (
<BotTab.Navigator>
<BotTab.Screen name='CameraPreview' component={CameraPreview} />
//... other screens
</BotTab.Navigator>
)
}
CameraPreview -> The Culprit (seems like)
const CameraPreview = ({ navigation }) => {
const isFocused = useIsFocused();
const cameraRef = React.useRef<ExpoCamera>(null);
const [isCameraReady, setIsCameraReady] = useState(false);
const [cameraType, setCameraType] = useState(CameraType.back);
return (
isFocused && (
<Camera
cameraRef={cameraRef}
cameraType={cameraType}
flashMode={flashMode}
onCameraReady={() => setIsCameraReady(true)}>
<MediaContextProvider>
<CameraControls
isCameraReady={isCameraReady}
onToggleCamera={toggleCamera}
setFlashMode={setFlashMode}
cameraRef={cameraRef}
onDismiss={() => navigation?.goBack()}
/>
</MediaContextProvider>
</Camera>
)
);
}
CameraControls
const CameraControls = () => {
const { image, setImageAndUpdateSize } = useMedia();
const [cameraState, setCameraState] = useState('PREVIEW');
useEffect(() => {
console.log('Mounting CAMERA CONTROLS');
return () => {
console.log('image: ', image); // undefined
console.log('Umnounting CAMERA CONTROLS'); // gets called when navigating to StoreStack screen
};
}, []);
const takePhoto = async () => {
if (cameraRef.current) {
const data = await cameraRef.current.takePictureAsync(CAMERA_OPTIONS);
const source = data.uri;
if (source) {
setImageAndUpdateSize({ path: source })
cameraRef.current.pausePreview();
setCameraState('IMAGE_PREVIEW');
}
}
};
switch (cameraState) {
case 'PREVIEW':
return <CameraPreviewControls onTakePhoto={takePhoto} />;
case 'IMAGE_PREVIEW':
if (!image?.path) return null;
return <PhotoPreview imageURI={image?.path} />;
default:
return null;
}
}
PhotoPreview
const PhotoPreview = ({ imageURI }) => {
useEffect(() => {
console.log('Mounting PHOTO REVIEW');
return () => {
console.log('Umnounting PHOTO PREVIEW'); // gets called when navigating to StoreStack screen
};
}, []);
return (
<ImageBackground source={{uri:imageURI}} />
//... other components
<FiltersPanel />
)
}
FiltersPanel
const FiltersPanel = () => {
...
return (
//...
<Gifts />
)
}
Gifts Component
const Gifts = () => {
const navigation = useNavigation();
const goToStore = () => {
navigation.navigate('StoreStack', {
screen: StoreStackPages.StoreScreen,
});
};
return (
<TouchableOpacity onPress={goToStore}>
<Image source={GIFT} resizeMode="contain" style={styles.image} />
</TouchableOpacity>
);
}
MediaContext
const MediaContext = createContext({
// other default values
setImage: () => {},
});
const MediaContextProvider = ({ children }) => {
const [image, setImage] = useState<Partial<PickerImage>>();
// other states
return (
<MediaContextProvider value={{image, setImage}}></MediaContextProvider>
)
}
export const useMedia = () => {
const context = useContext(MediaContext);
const setImageAndUpdateSize = (capturedImage) => {
const {setImage} = context;
return setImage(capturedImage);
}
return {
...context,
// other functions & utilities
setImageAndUpdateSize
}
}
This is the whole flow. So, again, the problem is, in simple words, when I capture a photo in CameraPreviewControls, the image is set successfully and as the cameraState now changes to 'IMAGE_PREVIEW', the PhotoPreview component gets mounted, and I can see the ImageBackground loaded with my context image, now when I tap the GIFT icon (being in the PhotoPreview component), I navigate to StoreStack, BUT NOW when I go back from StoreStack, the context image changes to undefined and the cameraState changes to PREVIEW which shouldn't be the case as I'm still wrapped under MediaContextProvider so the image context state shouldn't be undefined and also when I navigate to StoreStack and comes, the CameraControls and PhotoPreview component gets un-mounted which also shouldn't be the case in react navigation, as according to react-navigation docs, when we navigate to a screen, the previous screen does not get un-mounted.
Can someone shed some light on this, it would be really helpful!!
Thank you in advance!
useIsFocused is triggering a rerender when you navigate away. When you come back the context is rerendered. useIsFocused can be removed to stop the rerender when navigating away.
i want to pass the data of text-input from child to parent to submit the dynamic form. when i use useEffect the phone blocked but i don't know why.please can someone help me to solve this problem.thanks to tell me if there are another way to pass the data.
child component
const RenderComponents = ({ sendChildToParent) => {
const [inputsVal, setInputsVal] = useState({});
const handleChange = (name, value) => {
setInputsVal({ ...inputsVal, [name]: value });
};
const senddata = () => {
sendChildToParent(inputsVal);
};
useEffect(senddata);
return (
<>
{getData.length === 0 ? (
<Empty />
) : (
getData.map((item, index) => {
switch (item.type) {
case "TextInput":
return (
<>
<InputText
onChangeText={(text) => handleChange(item.nameC, text)}
ModuleName={item.nameC}
placeholder={item.options.placeholder}
required={item.options.required}
key={index}
/>
</>
);
case "Phone":...
Parent Component
export function TemplateScreen(props) {
const navigation = useNavigation();
const [getData, setData] = React.useState(Mydata);
const [childData, setChildData] = useState([]);
const sendChildToParent = (dataFromChild) => {
setChildData(dataFromChild);
};
//*************************************Child Componenet*************** */
const RenderComponents = () => {
const [userTeam, setUserTeam] = useState({});
[...other code here...];
**********Parent Component*******
return (
<ScrollView>
<RenderComponents />
<Button
title="Submit"
onPress={()=>null}
/>...
The structure of your parent component is fine. The issues are in your child component, in the following lines:
const RenderComponents = ({ sendChildToParent) => {
const [inputsVal, setInputsVal] = useState({});
const handleChange = (name, value) => {
setInputsVal({ ...inputsVal, [name]: value });
};
const senddata = () => {
sendChildToParent(inputsVal);
};
useEffect(senddata);
it's not good practice to duplicate the input value in local state. Pass the value down from the parent component as well as the setter function.
you're not passing a dependency array to your useEffect function, so it runs on every render of the component. This sets off the following chain of events:
the parent renders
the child renders
useEffect runs, setting the value of the state in the parent
the parent re-renders
This is an endless loop and what causes your app to lock.
there's no need to wrap the state setting functions in your own functions unless you are planning to do additional work there later. There's also no need to run those functions in your component lifecycle (useEffect), because they will run when the input changes.
missing bracket in the first line.
You could rewrite the components in the following way:
// parent component
export function TemplateScreen(props) {
const navigation = useNavigation();
const [getData, setData] = React.useState(Mydata);
const [childData, setChildData] = useState({});
return (
<ScrollView>
<RenderComponents childData={childData} setChildData={setChildData} />
...
// child component
const RenderComponents = ({ childData, setChildData }) => {
const handleChange = (name, value) => {
setChildData({ ...childData, [name]: value });
};
return (
...
I am trying to use the forwardRef in typescript React to access a child state. I've followed examples online but many are for javascript. I'm getting compiler errors with this.
This is the simplest version I can come up with and the compiler doesn't like the console log. It says ref.current may be undefined, so I've used some && logic but now it says getMyState() does not exist on type 'never'. I've also looked up that error as I've seen it before but don't understand it in this context.
const Parent = () => {
const ref = useRef();
console.log(ref.current && ref.current.getMyState());
return(
<>
<Child ref={ref}/>
</>
);
}
const Child = forwardRef((props, ref) => {
const [item, setItem] = useState('hello');
useImperativeHandle(ref, () => ({getMyState: () => {return item}}), [item]);
return (
<>
bob
</>
);
})
Note: I've read the React docs, my use case is a valid one for forwardRef.
Why am I getting this error and how do I extract the state from the child?
You need to define type for the ref with the form of functions you expose in the Child component.
Try like below.
type ChildHandle = {
getMyState: () => string | null;
};
const Child = forwardRef((props, ref) => {
const [item, setItem] = useState("hello");
useImperativeHandle(
ref,
() => ({
getMyState: () => {
return item;
}
}),
[item]
);
return <>bob</>;
});
const Parent = () => {
const ref = useRef<ChildHandle>();
console.log(ref.current && ref.current.getMyState());
return (
<>
<Child ref={ref} />
</>
);
};
Example of using the handle of Child within Parent
I have a logic in my code that i don't understand, i can update a state (useState) throught my child components but without the "set" function.
Here is my (simplified) code :
const Main = () =>{
const [mission, setMission] = useState({activity:"", env:""})
const see = () =>{
console.log(mission)
}
return (
<Child1 data={mission} />
<button onClick={see}>TEST</button>
)
}
const Child1 = (props) =>{
const {data} = props
const [mission, setMission] = useState(data)
const handleChange = (value) =>{
mission["activity"] = value
}
return (
<Child2 data={mission} onChange={handleChange} />
)
}
const Child2 = (props) =>{
const {data} = props
const [activity, setActivity] = useState(data.activity)
const handleChange = (e) =>{
setActivity(e.target.value)
props.onChange(e.target.value)
}
return (
<input value={data} onChange={handleChange} />
)
}
I tried in sandbox and it work too, "mission" did update it's value throught all childs without any "setMission".
I'm relativily new to react so i miss something but i don't know what, can someone explain ?
Thank you
You probably want to "lift the state up". Only the Main component should useState. Then pass both the state value and update function to the child component. The child component will call this update function when it updates. This will update the state on the parent properly and re-render.
Using your example:
const Main = () => {
// Only one state at the top level
const [mission, setMission] = useState({ activity: "", env: "" });
const see = () => {
console.log(mission);
};
return (
<>
{/* Pass both the value and state update function to the child */}
<Child1 data={mission} update={setMission} />
<button onClick={see}>TEST</button>
<div>{mission.activity}</div>
</>
);
};
const Child1 = (props) => {
const { data, update } = props;
const handleChange = (e) => {
// This will set parent's state
update({ ...data, activity: e.target.value });
};
// You can follow the similar pattern to the next child
return <Child2 data={data} onChange={handleChange} />;
};
You can see a complete working example in this sandbox.
I am using this react library https://github.com/gregberge/loadable-components to load a Component with Ref to access instance values using useImperativeHandle but ref is always null.
Here is my code
import loadable from '#loadable/component';
export default function ParentComponent(props){
const currentPageRef = useRef();
const[currentPage,setCurrentPage]=useState();
const loadPage= (page= 'home') => {
const CurrentPage = loadable(() => import(`./${page}`));
return (
<CurrentPage
ref={currentPageRef}
/>
);
}
useEffect(() => {
console.log(currentPageRef); //This is always logging current(null);
let pageTitle= currentPageRef.current?.getTitle();
let pageSubTitle= currentPageRef.current?.getSubTitle();
console.log(` Page Title=${pageTitle}`); //This is always coming back as null
console.log(`Page SubTitle=${pageSubTitle}`); //This is also always coming back as null
}, [currentPage]);
return (
<div>
<button onClick={() => {
setCurrentPage(loadPage('youtube));
}}>
LoadPage
</button>
</div>
);
}
Where each of the child components contains a useImperativeHandle to expose instance functions but I can't seem to access any of the functions because currentPageRef is always null
Here is an example of one of the child pages that contains the useImperativeHandle implementation
const YouTubePage= React.forwardRef((props,ref)=>{
const [connected, setConnected] = useState(false);
const getTitle = () => {
return connected ? "Your YouTube Channels" : "YouTube";
}
const getSubTitle = () => {
return connected ? "Publishable content is pushed to only connected channels. You may connect or disconnect channel(s) as appropriate" : "Connect a YouTube account to start posting";
}
useImperativeHandle(ref, () => ({ getTitle, getSubTitle }));
return (<div></div>);
});
Any ideas as to why that might be happening?
Thank you
From your code example your aren't actually rendering the component which you set by the state setter:
export default function ParentComponent(props) {
//...
// Render the page
return (
<>
{currentPage}
<div>...</div>
</>
);
}