I am using React-native typescript for my app . I have a TextInput, where user can pass some value(letter/letters) that value pass it to the Api's query string, and give search result. I have made one helper function which does not accept any space. It only accepts letter/letters.
I want to make helper function which take spaces but it will only trigger when there is letter/letter. It's really hard to explain. But I found Linkedin has this kind search filter, which logic I want implement in my app. But I don't know how to do that.
This is my code
import debounce from 'lodash/debounce';
import React, { useState, useRef } from 'react';
const SearchScreen = () => {
const [searchText, setSearchText] = useState('');
const [searchedText, setSearchedText] = useState('');
const delayedSearch = useRef(
debounce((text: string) => {
_search(text);
}, 400),
).current;
const _clearSearch = () => {
if (searchText.length === 0) {
Keyboard.dismiss();
}
setSearchText('');
setSearchedText('');
};
const _search = (text: string) => {
setSearchedText(text);
};
const removeExtraSpace = (s: string) => s.trim().split(/ +/).join(' '); // this is my helper function which does not accept white space
const _onSearchChange = (text: string) => {
setSearchText(removeExtraSpace(text)); // in here I am using the helper function
delayedSearch(removeExtraSpace(text)); // in here I am using the helper function
};
return (
<Container>
<SearchBar
text={searchText}
onClearText={_clearSearch}
onChangeText={_onSearchChange}
/>
<ProductSearchResult
queryString={searchedText} // in here I a passing Search
/>
</Container>
);
};
export default SearchScreen;
I made a mistake. My function works fine. But mistakenly I passed function to local state.
It should be like this:
const _onSearchChange = (text: string) => {
setSearchText(text);
delayedSearch(removeExtraSpace(text));
};
Related
I've got a component that has most of the logic for a particular task.
That component has to call the onSubmit function that it's getting through the props based on the component that it's been called form.
This is a sample code of the component having the logic:
type ComponentTwoProps = {
onSubmit: () => void
}
const ComponentTwo: React.FC<ComponentTwoProps> = ({onSubmit}) => {
const firstName = 'John';
const lastName = 'Doe';
const email = 'johndoe#gmail.com';
const country = 'Germany';
switch (type) {
case 'component-one':
onSubmit(firstName, lastName);
break;
case 'component-two':
onSubmit(firstName, lastName, email);
break;
case 'component-three':
onSubmit(firstName, lastName, email, country);
break;
default:
}
return (
//code
)
}
export default ComponentTwo
Now I have multiple components (three in this example) that will pass the onSubmit to this component as shown:
import React from 'react';
import ComponentTwo from './ComponentTwo';
const ComponentOne: React.FC = () => {
const onClickHandler = (...props) => {
//does stuff based on props FOR COMPONENT ONE
//NEEDS ACCESS TO THE TYPE OF PROPS
};
return <ComponentTwo onClick={onClickHandler} />;
};
export default ComponentOne;
In my actual case the amount of parameters and their types vary greatly. So i wanted to know if there's a way I could access those types inside onClickHandler function.
I was thinking of using generics in some way but cant come up with a solution that supports any number of arguments and those arguments collectively have a unique type.
You can use this if you know that the arguments are always going to be strings
import React from 'react';
type ComponentTwoProps = {
onSubmit: <T extends string>(...props: T[]) => void
}
const ComponentTwo: React.FC<ComponentTwoProps> = ({onSubmit}) => {
const firstName = 'John';
const lastName = 'Doe';
const email = 'johndoe#gmail.com';
const country = 'Germany';
return (
<></>
)
}
const ComponentOne: React.FC = () => {
const onClickHandler = (firstName: string, lastName: string) => {
//does stuff based on props FOR COMPONENT ONE
//NEEDS ACCESS TO THE TYPE OF PROPS
};
return <ComponentTwo onSubmit={onClickHandler} />;
};
playground
I really do not understand why this is not working, basically, I have a header component with its own context. On the other hand, I have a popOver component that goes inside the header, and this popOver also has its own context.
Now, there is a list of elements that are rendered inside the popOver, the user picks which elements to render, and such list needs to be rendered simultaneously in the header, for that reason I am trying to keep both contexts synchronized, the problem appears when I try to consume the header context inside the popOver context, the values consumed appear to be undefined.
const HeaderContext = createContext();
export const HeaderProvider = ({ children }) => {
const [headChipList, setHeadChipList] = useState([]);
const [isChipOpen, setIsChipOpen] = useState(false);
useEffect(() => {
if (headChipList.length) setIsChipOpen(true);
}, [headChipList]);
return (
<HeaderContext.Provider value={{ headChipList, setHeadChipList, isChipOpen, setIsChipOpen }}>
{children}
</HeaderContext.Provider>
);
};
export const useHeaderContext = () => {
const context = useContext(HeaderContext);
if (!context) throw new Error('useHeaderContext must be used within a HeaderProvider');
return context;
};
As you can see at the end there's a custom hook that allows an easier consumption of the context and also is a safeguard in case the custom hook is called outside context, the popOver context follows this same pattern:
import React, { useState, useContext, createContext, useEffect } from 'react';
import { useHeaderContext } from '(...)/HeaderProvider';
const PopoverContext = createContext();
export const PopoverProvider = ({ children }) => {
const { setHeadChipList, headChipList } = useHeaderContext; // this guys are undefined
const [menuValue, setMenuValue] = useState('Locations with Work Phases');
const [parentId, setParentId] = useState('');
const [chipList, setChipList] = useState([]);
const [locations, setLocations] = useState([]);
useEffect(() => setChipList([...headChipList]), [headChipList]);
useEffect(() => setHeadChipList([...chipList]), [chipList, setHeadChipList]);
return (
<PopoverContext.Provider
value={{
menuValue,
setMenuValue,
chipList,
setChipList,
parentId,
setParentId,
locations,
setLocations
}}
>
{children}
</PopoverContext.Provider>
);
};
export const usePopover = () => {
const context = useContext(PopoverContext);
if (!context) throw new Error('usePopover must be used within a PopoverProvider');
return context;
};
I would really appreciate any highlight about this error, hopefully, I will be able to learn how to avoid this type of error in the future
You're not calling the useHeaderContext function. In PopoverProvider, change the line to
const { setHeadChipList, headChipList } = useHeaderContext();
I'm having troubles passing data from one context to the other in React. I have some job data that is received from a SignalR connection and I need to pass it to a specific job context, but I'm not sure how to do this.
I have the following code:
SignalRContext
export interface SignalRContextProps {
connect: () => void;
}
export const SignalRContext = createContext<SignalRContextProps>(null!);
export const useSignalRContext = (): SignalRContextProps => {
const {onProgressReceived} = useContext(JobsContext);
const connect = () => {
//Removed a lot of connection setup code for readability
const connection = new HubConnectionBuilder().build();
connection.on('JobReportProgress', onProgressReceived);
connection.start();
};
return {
connect,
};
};
SignalRContextProvider
type Props = {
children: ReactElement | ReactElement[];
}
export const SignalRContextProvider = (props: Props) => {
const {children} = props;
const signalRContext = useSignalRContext();
return (
<SignalRContext.Provider value={signalRContext}>
{children}
</SignalRContext.Provider>
);
};
JobsContext
export const JobsContext = createContext<JobsContextProps>(null!);
export const useJobsContext = (): JobsContextProps => {
const [jobs, setJobs] = useState<Job[]>([]);
const load = async (): Promise<void> => {
const jobs = await getAllJobs();
setJobs(jobs);
};
const onProgressReceived = (progress: JobProgress) => {
console.log(jobs);
const currentJob = jobs.find((job) => job.id === progress.id);
console.log(currentJob); //currentJob will always be empty because jobs array is NULL on receiving progress.
}
};
return {
load, onProgressReceived, jobs
};
};
JobsContextProvider
interface Props {
children: ReactElement | ReactElement[];
}
export const JobsContextProvider = (props: Props): ReactElement => {
const {children} = props;
const jobsContext = useJobsContext();
return (
<JobsContext.Provider value={jobsContext}>
{children}
</JobsContext.Provider>
);
};
index
ReactDOM.render(
<JobsContextProvider>
<SignalRContextProvider>
<App />
</SignalRContextProvider>
</JobsContextProvider>,
document.getElementById('root'),
);
Flow
In my app.tsx I start the SignalR connection by calling signalRContext.connect() created by const signalRContext = useContext(SignalRContext);
I go to my job page where my 6 jobs are loaded from the backend via my context
const {load} = useContext(JobsContext);
await load();
I trigger a job and I see that the SignalR context is calling onProgressReceived on the jobContext. But for some reason the jobs array is empty so I can't update the correct job. Is seems that a new context is created instead of reusing the existing context.
Anyone has an idea how I can make my SignalRContext pass data to my JobContext? Or maybe there is a better system that using context for this?
UPDATE 1:
I have the feeling that there is something strange going on with the HubConnection instance. When I register my 'onProgressReceived' function as a callback function on the 'JobReportProgress' event then it doesn't work. But when I first save the progress with setState and trigger the 'onProgressReceived' function with useEffect it seems to be working. Small example:
const {jobs, onProgressReceived} = useContext(JobsContext);
const [progress, setProgress] = useState<JobProgress | null>(null);
//Change the connection.on line with the following
connection.on('JobReportProgress', onReceived);
//And then we trigger the function on change
const onReceived = (progress: JobProgress) => {
setProgress(progress);
};
useEffect(() => {
if (progress !== null) {
onProgressReceived(progress);
}
}, [progress]);
This seems to be working, but not sure why I first need to save my progress with useState.
I had very same issue a while ago. I even asked question about it Here
There are ways to connect nested context to each other, but then code gets messy.
At first I used event listeners and was dispatching events when some data received, but didn't worked well.
Then I tried passing callbacks in context value. Pass callback from JobsContextProvider ex: onSignalData and call this callback when data is updated in SignalRContextProvider and save in JobsContextProvider.
Combined these 2 contexts into one and it worked well, but didn't liked it as well. Code got messy.
Then I give up using contexts in this type of data and used redux with Redux Toolkit and RTK Query. It works very vell, code is well organized and this is the best solution I have found so far.
Let me know in comments which solution works best for you and I can write exact pseudocode for that solution.
I'm working on a new major release for react-xarrows, and I came up with some messy situation.
It's not going to be simple to explain, so let's start with visualization:
consider the next example - 2 draggable boxes with an arrow drawn between them, and a wrapping context around them.
focused code:
<Xwrapper>
<DraggableBox box={box} />
<DraggableBox box={box2} />
<Xarrow start={'box1'} end={'box2'} {...xarrowProps} />
</Xwrapper>
Xwrapper is the context, DraggableBox and Xarrow are, well, you can guess.
My goal
I want to trigger a render on the arrow, and solely on the arrow, whenever one of the connected boxes renders.
My approach
I want to be able to rerender the arrow from the boxes, so I have to consume 'rerender arrow'(let's call it updateXarrow) function on the boxes, we can use a context and a useContext hook on the boxes to get this function.
I will call XelemContext to the boxes context.
also, I need to consume useContext on Xarrow because I want to cause a render on the arrow whenever I decide.
this must be 2 different contexts(so I could render xarrow solely). one on the boxes to consume 'updateXarrow', and a different context consumed on Xarrow to trigger the reredner.
so how can I pass this function from one context to another? well, I can't without making an infinite loop(or maybe I can but could not figure it out), so I used a local top-level object called updateRef.
// define a global object
const updateRef = { func: null };
const XarrowProvider = ({ children }) => {
// define updateXarrow here
...
// assign to updateRef.func
updateRef.func = updateXarrow;
return <XarrowContext.Provider value={updateXarrow}>{children}</XarrowContext.Provider>;
};
//now updateRef.func is defined because XelemProvider defined later
const XelemProvider = ({ children }) => {
return <XelemContext.Provider value={updateRef.func}>{children}</XelemContext.Provider>;
};
the thing is, that this object is not managed by react, and also, i will need to handle cases where there is multiple instances of Xwrapper, and I'm leaving the realm of React, so i have 2 main questions:
there is a better approach? maybe I can someone achieve my goal without going crazy?
if there is no better option, is this dangerous? I don't want to release a code that will break on edge cases on my lib consumer's apps.
Code
DraggableBox
const DraggableBox = ({ box }) => {
console.log('DraggableBox render', box.id);
const handleDrag = () => {
console.log('onDrag');
updateXarrow();
};
const updateXarrow = useXarrow();
return (
<Draggable onDrag={handleDrag} onStop={handleDrag}>
<div id={box.id} style={{ ...boxStyle, position: 'absolute', left: box.x, top: box.y }}>
{box.id}
</div>
</Draggable>
);
};
useXarrow
import React, { useContext, useEffect, useLayoutEffect, useRef, useState } from 'react';
import { XelemContext } from './Xwrapper';
const useXarrow = () => {
const [, setRender] = useState({});
const reRender = () => setRender({});
const updateXarrow = useContext(XelemContext);
useLayoutEffect(() => {
updateXarrow();
});
return reRender;
};
export default useXarrow;
Xwrapper
import React, { useState } from 'react';
export const XelemContext = React.createContext(null as () => void);
export const XarrowContext = React.createContext(null as () => void);
const updateRef = { func: null };
const XarrowProvider = ({ children }) => {
console.log('XarrowProvider');
const [, setRender] = useState({});
const updateXarrow = () => setRender({});
updateRef.func = updateXarrow;
return <XarrowContext.Provider value={updateXarrow}>{children}</XarrowContext.Provider>;
};
const XelemProvider = ({ children }) => {
console.log('XelemProvider');
return <XelemContext.Provider value={updateRef.func}>{children}</XelemContext.Provider>;
};
const Xwrapper = ({ children }) => {
console.log('Xwrapper');
return (
<XarrowProvider>
<XelemProvider>{children}</XelemProvider>
</XarrowProvider>
);
};
export default Xwrapper;
const Xarrow: React.FC<xarrowPropsType> = (props: xarrowPropsType) => {
useContext(XarrowContext);
const svgRef = useRef(null);
....(more 1100 lines of code)
logs
I left some logs.
on drag event of a single box you will get:
onDrag
DraggableBox render box2
XarrowProvider
xarrow
Note
currently, this is working as expected.
Update
after many hours of testing, this seems to work perfectly fine. I manage my own object that remember the update function for each Xwrapper instance, and this breaks the dependency between the 2 contexts. I will leave this post in case someone else will also come across this issue.
Update (bad one)
this architecture breaks on react-trees with <React.StrictMode>...</React.StrictMode> :cry:
any idea why? any other ideas ?
just in case someone would need something similar: here's a version that will work even with react strictmode(basically being rellyed of effect which called once and not renders):
import React, { FC, useEffect, useRef, useState } from 'react';
export const XelemContext = React.createContext(null as () => void);
export const XarrowContext = React.createContext(null as () => void);
// will hold a object of ids:references to updateXarrow functions of different Xwrapper instances over time
const updateRef = {};
let updateRefCount = 0;
const XarrowProvider: FC<{ instanceCount: React.MutableRefObject<number> }> = ({ children, instanceCount }) => {
const [, setRender] = useState({});
const updateXarrow = () => setRender({});
useEffect(() => {
instanceCount.current = updateRefCount; // so this instance would know what is id
updateRef[instanceCount.current] = updateXarrow;
}, []);
// log('XarrowProvider', updateRefCount);
return <XarrowContext.Provider value={updateXarrow}>{children}</XarrowContext.Provider>;
};
// renders only once and should always provide the right update function
const XelemProvider = ({ children, instanceCount }) => {
return <XelemContext.Provider value={updateRef[instanceCount.current]}>{children}</XelemContext.Provider>;
};
const Xwrapper = ({ children }) => {
console.log('wrapper here!');
const instanceCount = useRef(updateRefCount);
const [, setRender] = useState({});
useEffect(() => {
updateRefCount++;
setRender({});
return () => {
delete updateRef[instanceCount.current];
};
}, []);
return (
<XelemProvider instanceCount={instanceCount}>
<XarrowProvider instanceCount={instanceCount}>{children}</XarrowProvider>
</XelemProvider>
);
};
export default Xwrapper;
I am wondering how I would refactor the following. Basically, after I get the user information from google, it is validated since a specific domain might be allowed. If it passes that one, we validate the token against a back end api.
Sonar is complaining on the use of return displayAlert('LoginFailed') three times. Given the methods have unique use, I am wondering what could be done to improve it?
import React, {useContext} from 'react';
import {View, Alert} from 'react-native';
import {LocalizationContext} from 'library/localization';
import ServiceAuth from 'services/ServiceAuth';
import {saveLoggedInDetails} from 'library/userInformation';
const MyComponent = (props) => {
const {translations} = useContext(LocalizationContext);
const displayAlert = (msg) =>{
//translations happen here
Alert.alert("Failed", msg)
return false;
}
const validateToken = async (userGoogleInfo) => {
const {idToken} = userGoogleInfo;
let serviceAuth = ServiceAuth.getInstance();
try {
return await serviceAuth.validateT(idToken);
} catch (error) {
//show alert here
return displayAlert('LoginFailed')
}
};
const authorize = async (userGoogleInfo) => {
const allowedDomains = [];
let reply = {ok: false};
//check if domain is allowed etc and set to true.
if (!reply['ok']) {
return displayAlert('InvalidDomain');
}
try {
const userInfo = await validateToken(userGoogleInfo);
if (userInfo) {
console.log("okay")
} else {
return displayAlert('LoginFailed')
}
} catch (error) {
return displayAlert('LoginFailed')
}
};
return (
<>
<View>
<Text>Hello</Text>
</View>
</>
);
};
export default MyComponent;
The message is a little bit confusing but sonar is complaining only about the LoginFailed string used three times as parameter.
Create a constant as, for example:
const LOGIN_FAILED = 'LoginFailed';
And then use it in all the three calls to displayAlert:
return displayAlert(LOGIN_FAILED);