I am having a React(version used 16.8) component, I have a const updateDiffText = useCallback(() callback on click of anchor GENERATE DIFF onclick onClick={updateDiffText} I call call back this updateDiffText
My requirement is I don't want one anchor code in my UI, I want whenever I have oldText and newText It should trigger the method updateDiffText and show the result. User should not click on anchor link to perform this.
My Code sand box here - https://codesandbox.io/s/react-diff-view-demo-htp06
if I have values in oldtext and newText it should call updateDiffText this method
My Code -
const DiffViewer = props => {
const oldText = useInput(props.startupConfigData);
const newText = useInput(props.runningConfigData);
const [{ type, hunks }, setDiff] = useState("");
const updateDiffText = useCallback(() => {
const diffText = formatLines(diffLines(oldText.value, newText.value), {
context: 3
});
const [diff] = parseDiff(diffText, { nearbySequences: "zip" });
setDiff(diff);
}, [oldText.value, newText.value, setDiff]);
const tokens = useMemo(() => tokenize(hunks), [hunks]);
return (
<div style={{ height: "450px", overflow: "auto" }}>
<a href="#" onClick={updateDiffText}>
GENERATE DIFF
</a>
{setDiff ? (
<Diff
viewType="split"
diffType={type}
hunks={hunks || EMPTY_HUNKS}
tokens={tokens}
>
{hunks => hunks.map(hunk => <Hunk key={hunk.content} hunk={hunk} />)}
</Diff>
) : (
""
)}
</div>
);
};
Let me know if query is not clear. Thanks.
Try to use the useEffect instead of useCallback. In your case you are are not calling the memoized function in the render stage. useCallback will return a memoized function. Check the modified version.
https://codesandbox.io/s/react-diff-view-demo-izdyi
const updateDiffText = useCallback(() => {
const diffText = formatLines(diffLines(oldText.value, newText.value), {
context: 3
});
const [diff] = parseDiff(diffText, { nearbySequences: "zip" });
setDiff(diff);
}, [props.startupConfigData, props.runningConfigData]);
to
const updateDiffText = useCallback(() => {
const diffText = formatLines(diffLines(oldText.value, newText.value), {
context: 3
});
const [diff] = parseDiff(diffText, { nearbySequences: "zip" });
setDiff(diff);
}, [oldText.value, newText.value, setDiff]);
////////////// Older solution before i understood ///////////////////////
////////////// New solution i suggest ///////////////////////////////////
const updateDiffText = () => {
// do what you wanna do
}
and use useEffect instead of useCallback like this
useEffect(() => {
updateDiffText();
},[props.startupConfigData, props.runningConfigData])
Related
i've been solving this problem without any progress for the pas 2 hours or so, here is code:
export const useFetchAll = () => {
const [searchResult, setSearchResult] = useState([]);
const [loading, setLoading] = useState(false);
const [searchItem, setSearchItem] = useState("");
const [listToDisplay, setListToDisplay] = useState([]);
// const debouncedSearch = useDebounce(searchItem, 300);
const handleChange = (e) => {
setSearchItem(e.target.value);
if (searchItem === "") {
setListToDisplay([]);
} else {
setListToDisplay(
searchResult.filter((item) => {
return item.name.toLowerCase().includes(searchItem.toLowerCase());
})
);
}
console.log(searchItem);
};
useEffect(() => {
const searchRepo = async () => {
setLoading(true);
const { data } = await axios.get("https://api.github.com/repositories");
setSearchResult(data);
setLoading(false);
};
if (searchItem) searchRepo();
}, [searchItem]);
the problem is that when i enter characters in input and set state to event.target.value it doesn't pick up last character. here is an image:
enter image description here
BTW this is a custom hook, i return the onchange function here:
const HomePage = () => {
const { searchResult, loading, searchItem, handleChange, listToDisplay } =
useFetchAll();
and then pass it as a prop to a component like so:
<Stack spacing={2}>
<Search searchItem={searchItem} handleChange={handleChange} />
</Stack>
</Container>
any help? thanks in advance.
You are handling the searchItem and searchResult state variables as if their state change was synchronous (via setSearchItem and setSearchResult) but it isn't! React state setters are asynchronous.
The useEffect callback has a dependency on the searchItem state variable. Now every time the user types something, the state will change, that change will trigger a re-rendering of the Component and after that render finishes, the side-effect (the useEffect callback) will be executed due to the Components' lifecycle.
In our case, we don't want to initiate the fetch request on the next render, but right at the moment that the user enters something on the search input field, that is when the handleChange gets triggered.
In order to make the code work as expected, we need some a more structural refactoring.
You can get rid of the useEffect and handle the flow through the handleChange method:
export const useFetchAll = () => {
const [ loading, setLoading ] = useState( false );
const [ searchItem, setSearchItem ] = useState( "" );
const [ listToDisplay, setListToDisplay ] = useState( [] );
const handleChange = async ( e ) => {
const { value } = e.target;
// Return early if the input is an empty string:
setSearchItem( value );
if ( value === "" ) {
return setListToDisplay( [] );
}
setLoading( true );
const { data } = await axios.get( "https://api.github.com/repositories" );
setLoading( false );
const valueLowercase = value.toLowerCase(); // Tiny optimization so that we don't run the toLowerCase operation on each iteration of the filter process below
setListToDisplay(
data.filter(({ name }) => name.toLowerCase().includes(valueLowercase))
);
};
return {
searchItem,
handleChange,
loading,
listToDisplay,
};
};
function used for updating state value is asynchronous that why your state variable is showing previous value and not the updated value.
I have made some change you can try running the below code .
const [searchResult, setSearchResult] = useState([]);
const [loading, setLoading] = useState(false);
const [searchItem, setSearchItem] = useState("");
const [listToDisplay, setListToDisplay] = useState([]);
// const debouncedSearch = useDebounce(searchItem, 300);
const handleChange = (e) => {
setSearchItem(e.target.value); // this sets value asyncronously
console.log("e.target.value :" + e.target.value); // event.target.value does not omitting last character
console.log("searchItem :" + searchItem); // if we check the value then it is not set. it will update asyncronously
};
const setList = async () => {
if (searchItem === "") {
setListToDisplay([]);
} else {
setListToDisplay(
searchResult.filter((item) => {
return item.name.toLowerCase().includes(searchItem.toLowerCase());
})
);
}
};
const searchRepo = async () => {
const { data } = await axios.get("https://api.github.com/repositories");
setSearchResult(data);
setLoading(false);
};
// this useeffect execute its call back when searchItem changes a
useEffect(() => {
setList(); // called here to use previous value stored in 'searchResult' and display something ( uncomment it if you want to display only updated value )
if (searchItem) searchRepo();
}, [searchItem]);
// this useeffect execute when axios set fetched data in 'searchResult'
useEffect(() => {
setList();
}, [searchResult]);
// this useeffect execute when data is updated in 'listToDisplay'
useEffect(() => {
console.log("filtered Data") // final 'listToDisplay' will be availble here
console.log(listToDisplay)
}, [listToDisplay]);
I have a function that filters through some state and renders out the result for a search request.
const handleSearch = (value: string) => {
const searchResultData = users.filter((userId) => user.id.startsWith(value));
setSearchResult(searchResultData);
};
I am trying to work with lodash.throttle library to cause a delay before the request is sent. So we don't have a request go out every time a user types.
const handleSearch = useCallback(throttle((value: string) => {
const searchResultData = users.filter((userId) => user.id.startsWith(value));
setSearchResult(searchResultData);
}, 2500), []);
This works in delaying input as expected but for some reason, the user.filter method doesn't run, and so the state isn't updated with the search result. I believe the problem might be from the useCallback hook, but the throttle function is dependent on it to run. Any ideas on how I can work around this problem?
If your throttled/debounced handler uses props or state, like this:
const { fetcherFunctionFromProps } = props;
const eventHandler = async () => {
const resp = await fetcherFunctionFromProps();
};
const debouncedEventHandler = useMemo(
() => throttle(eventHandler, 300)
), [fetcherFunctionFromProps]);
And it doesn't work,
you can refactor it to the following:
const { fetcherFunctionFromProps } = props;
const eventHandler = async (fetcher) => {
const resp = await fetcher();
};
const debouncedEventHandler = useMemo(() => throttle(eventHandler, 300), []);
...
<Component onClick={() => debouncedEventHandler(fetcherFunctionFromProps)}>
I'm working on a project that has to do with playlist, so what I want to execute is whenever one of the songs on the playlist is clicked the image that is attached to the song should be viewed.
So, I have my code this way...
const Component = () => {
const value = useContext(DataContext);
const [data, setData] = useState(null);
const [currentData, setCurrentData] = useState(null);
useEffect(() => {
const url =
"https://52-90-82-235.maverickmaven.com/geotourdata/json.cfm?h=-107,37,s,en,3A771765";
const currentValue = value;
axios({
method: "get",
url,
responseType: "stream",
}).then((response) => {
let features = response.data.features.filter((elem) => {
return elem.type === "Feature";
});
setData(features);
const currentDatafile = data?.filter((data) => {
return data?.assets[0].audio === value;
});
setCurrentData(currentDatafile);
});
}, [setCurrentData]);
};
So, what this code does is that it returns the array that has the picture, but the problem is that it only filters once and repeatedly returns the same value even if I click on another song, and I need it to filter every time I clicked on the songs(i.e the function is executed).
I tried filtering and mapping at the same time, but it didn't work. or maybe I didn't write the syntax well enough.
Please I need help.
Move these lines to new useEffect hook. Will trigger after you set data
useEffect(() => {
const currentDatafile = data?.filter((item) => {
return item.assets[0].audio === value;
});
setCurrentData(currentDatafile)},[data])
You shouldn't re-fetch the data from the remote source every time. I've wrapped that in a custom hook instead, here (and a custom fetcher function to make testing/mocking easier).
Then, you shouldn't hold the selected object in the state unless you need to modify it internally (in which case you should copy it into a state atom anyway); instead, just hold the ID.
function fetchTourData() {
return fetch('https://52-90-82-235.maverickmaven.com/geotourdata/json.cfm?h=-107,37,s,en,3A771765')
.then(response => response.json())
.then(data => data.features.filter((elem) => elem.type === 'Feature'));
}
function useTourData() {
const [data, setData] = React.useState(null);
React.useEffect(() => {
fetchTourData().then(setData);
}, [setData]);
return data;
}
const Component = () => {
const tourData = useTourData();
const [selectedId, setSelectedId] = React.useState(null);
const selectedTour = (tourData || []).find(t => t.id === selectedId);
if (tourData === null) {
return <div>Loading</div>
}
return (
<div>
<div>
Selected: {JSON.stringify(selectedTour || "nothing")}
</div>
<ul>
{tourData.map(t => <li key={t.id}><a href="#" onClick={() => setSelectedId(t.id)}>{t.name}</a></li>)}
</ul>
</div>
);
};
ReactDOM.render(<Component />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>
I have this custom hook:
const useSomething = () => {
const displayAlert = (text) => {
alert(text);
};
return {displayAlert};
};
Now I want to use it somewhere in my code like following:
const SampleComponent = () => {
const {displayAlert} = useSomething();
const navigateHandler = (event, page) => {
// some api
// ...
displayAlert('Some alert');
};
const navigateHandlerCallback = useCallback(() => {
navigateHandler(null, 1);
}, []);
useEffect(navigateHandlerCallback, []);
return (
<button onClick={e => { navigateHandler(e, 5); }}>
Navigate to 5th page
</button>
)
};
Now the problem is eslint warning that says:
React Hook useCallback has a missing dependency: 'navigateHandler'. Either include it or remove the dependency array
And when I include navigateHandler as a dependency into useCallback dependency array, eslint says:
e 'navigateHandler' function makes the dependencies of useCallback Hook (at line XXX) change on every render. To fix this, wrap the 'navigateHandler' definition into its own useCallback() Hook
I cant change navigateHandler function.
I'm not sure if another callback can solve my problem with best performance or not.
👉 What should I do about this?
Update your custom hooks with useCallback:
const useSomething = () => {
const displayAlert = useCallback((text) => {
alert(text);
};, [])
return {displayAlert};
};
Then within your component:
const SampleComponent = () => {
const {displayAlert} = useSomething();
const navigateHandler = useCallback((event, page) => {
// some api
// ...
displayAlert('Some alert');
}, [displayAlert]);
const navigateHandlerCallback = useCallback(() => {
navigateHandler(null, 1);
}, [navigateHandler]);
useEffect(navigateHandlerCallback, []);
return (
<button onClick={e => { navigateHandler(e, 5); }}>
Navigate to 5th page
</button>
)
};
By using useCallback this will surely improve performance during renders.
I have a <Day/> component which renders a table full of times through the day.
Right now I have a handleChange function in the <TableLayout/> which is listening for changes in the text-area.
function TableLayout({ sendEventTargetToParent }) {
var [amountOfRows, setAmountOfRows] = useState(24);
var [textValue, setTextValue] = useState('');
var [eventName, setEventName] = useState('');
useEffect(() => {
sendDataToParent();
}, [eventName, textValue]);
function sendDataToParent() {
sendEventTargetToParent(eventName, textValue);
}
function handleChange(event) {
var { name, value } = event.target;
setEventName(name);
setTextValue(value);
}
The markup:
<TextArea
rows={2}
name="textarea"
value={textValue}
onChange={(e) => handleChange(e)}
placeholder="Tell us more"
/>
I'd like to send that info from the text-area to the component, you probably noticed already noticed the prop TableLayout is consuming:
function TableLayout({ sendEventTargetToParent }) {
Which I thought belongs in useEffect because it's creating a side-effect:
useEffect(() => {
sendDataToParent();
}, [eventName, textValue]);
function sendDataToParent() {
sendEventTargetToParent(eventName, textValue);
}
Anyway the idea is when those local variables/state change in TableLayout it gets kicked up to Day...
export default function Day({ dayInfo }) {
var [dayInfoInChild, setDayInfoInChild] = useState({});
var [currentDate, setCurrentDate] = useState('');
var [timeOfDay, setTimeOfDay] = useState('');
var [eventNameFromChild, setEventNameFromChild] = useState('');
var [textValueFromChild, setTextValueFromChild] = useState('');
function getEventTargetFromChild(eventName, textValue) {
setEventNameFromChild(eventName);
setTextValueFromChild(textValue);
}
useEffect(() => {
if (dayInfo !== null) {
var modifiedDayInfo = dayInfo
.split(' ')
.map((item) => {
if (item.indexOf(',')) return item.replace(/,/g, '');
})
.join('-');
setCurrentDate(modifiedDayInfo);
if (localStorage.getItem(modifiedDayInfo)) {
var stringVersionOfModifiedDayInfo = modifiedDayInfo;
modifiedDayInfo = JSON.parse(localStorage.getItem(modifiedDayInfo));
if (!dayInfoInChild.hasOwnProperty(stringVersionOfModifiedDayInfo)) {
setDayInfoInChild({
...dayInfoInChild,
[stringVersionOfModifiedDayInfo]: modifiedDayInfo,
});
}
} else {
localStorage.setItem(modifiedDayInfo, JSON.stringify({}));
}
if (dayInfoInChild.hasOwnProperty(currentDate)) {
setDayInfoInChild({
...dayInfoInChild,
[currentDate]: {
[eventNameFromChild]: textValueFromChild,
},
});
}
}
}, []);
And the code in the Day'suseEffectessentially creates an JSON object in localstorage with its key based on the date if needed if not it pulls that key and object converts it to a JS object and puts it into the state usinguseState`.
var [eventNameFromChild, setEventNameFromChild] = useState('');
var [textValueFromChild, setTextValueFromChild] = useState('');
function getEventTargetFromChild(eventName, textValue) {
setEventNameFromChild(eventName);
setTextValueFromChild(textValue);
}
this is the part where the state from the Child gets set in Day.
if (dayInfoInChild.hasOwnProperty(currentDate)) {
setDayInfoInChild({
...dayInfoInChild,
[currentDate]: {
[eventNameFromChild]: textValueFromChild,
},
});
}
But right now I able to add one key stroke into the object and then the UI locks:
So how can I create a cohesive flow from my Child to the Parent using hooks?
By understanding your problem i suggest you to use useContext
Here is a link which can help you to solve this problem.
https://vimalselvam.com/post/react-hooks-lift-up-pass-down-state-using-usecontext-and-usereducer/