Search bar in react works, but experiencing very strange behaviour - javascript

In a component when an input is provided it dispatches the input to a reducer
const changeSearchTerm = (e) => {
setSearchTerm(e.target.value)
dispatch(userActions.searchByName(searchTerm));
}
Then the reducer takes that value and tries to filter the array based on that input
searchByName: (state, action) => {
state.search = action.payload;
let newArray = state.users.filter((user) =>
user.name.toLowerCase().includes(state.search.toLowerCase())
);
state.users = newArray;
},
Then this whole array is displayed in one of the other components. The search bar works to some extent:
When you start typing into the search bar it only starts to work after second input. In console I have noticed that if you type "Timmothy" it will only register "Timmoth" in the state.
Additionaly, if I delete text from the input, the state of the array doesn't update and stays the same even though the input value changes.
So what happens currently is if you start typing in the search bar, it updates the array, but always only registers second to last keystroke. Additionally, if you delete input from the search bar, it doesn't register. The value changes, but the array stays with only few items left.
I have tried to not use the additional variable to store the state and do it like this
state.users.filter((user) =>
user.name.toLowerCase().includes(state.search.toLowerCase())
But then absolutely nothing happens. Would appreciate if someone could tell me what I'm missing here
EDIT
This is the component that receives that input
<input onChange={changeSearchTerm} type="text" value={searchTerm}></input>
And the searchTerm value is saved in useState - const [searchTerm, setSearchTerm] = useState("");

From what I can tell, it only works after the second input because of this block:
const changeSearchTerm = (e) => {
setSearchTerm(e.target.value)
dispatch(userActions.searchByName(searchTerm));
}
State setters like setSearchTerm are actually asynchronous. This means that after you call setSearchTerm, on the next line the value of searchTerm is still the old value.
You can instead do one of the following:
const changeSearchTerm = (e) => {
setSearchTerm(e.target.value)
dispatch(userActions.searchByName(e.target.value));
}
const changeSearchTerm = (e) => {
setSearchTerm(e.target.value, () => {
// This callback runs only after state has changed
dispatch(userActions.searchByName(searchTerm));
});
}
Edit: Sorry I missed that you used useState/functional. You can use useEffect:
const changeSearchTerm = (e) => {
setSearchTerm(e.target.value);
}
useEffect(() => {
if (searchTerm.length > 0) {
dispatch(userActions.searchByName(searchTerm));
}
}, [searchTerm]);
This way your dispatch will correctly run whenever searchTerm's value changes for sure.
https://reactjs.org/docs/react-component.html#setstate

Related

React component doesn't re-render on first prop-change

I am new to React and I am trying to build a hangman game.
At the moment I am using a hardcoded list of words that the program can choose from. So far everything worked great, but now I am trying to reset the game and the react component that should rerender upon one click only re-renders after two clicks on the reset button and I don't know why
these are the states that I am using :
function App() {
const [numberInList, setNumberInList] = useState(0)
const randomWordsList = ["comfort", "calm", "relax", "coffee", "cozy"];
const [generatedWord, setGeneratedWord] = useState(
randomWordsList[numberInList]
);
const [generatedWordLetters, setGeneratedWordLetters] = useState(
randomWordsList[numberInList].split("").map((letter) => {
return { letter: letter.toUpperCase(), matched: false };
})
);
function resetGame(){
setNumberInList(prev => prev + 1)
setGeneratedWord(randomWordsList[numberInList])
setGeneratedWordLetters(
generatedWord.split("").map((letter) => {
return { letter: letter.toUpperCase(), matched: false };
})
);
setFalseTries(0)
}
this is the reset function I am using
within teh function every state gets updated correctly apart from the generatedWordLetters state, which only gets updated upon clicking the reset button two times.
I can't seem to solve this problem on my own, so any help is appreciated!
Please check useEffect on React. You can use boolean flag as state, put the useEffect parameters like below
React.useEffect(() => {
// here your code works
},[flag])
flag is your boolean state when it changes on reset function, your component re render

React state updating but rendering late

I've tried almost every solution similar to my problem, yet none is working. I have a simple state and changing the value of this state in a function as like below, handleOnClick is calling in a button's onClick event. I'm also using Router(if it's change something);
import { useState} from "react"
import { BrowserRouter as Router, Route, Link, useHistory} from "react-router-dom";
const Buton = () => {
let x = "";
const [lowerState, setLower] = useState("")
const history = useHistory();
const handleOnClick = () => {
x = document.getElementById("my_input").value.toLowerCase();
setLower(x)
console.log(x) //this prints the current value
console.log(lowerState) //this DOES NOT prints the current value, but
// when I put another text into the input and click
// to button, it prints the first value I put here
history.push('/test', {params : lowerState})
};
.
.
.
return (...)
}
export default Buton
Now x is a value that returns from an input HTML element. When I set this value as a state and console log, it doesn't print the value first, when I put something in input again, then it prints the first value. So it's like it's coming 1 step behind.
I've used useEffect() , I did put a second parameter to setLower as console.log(lowerState) and other things on the internet that people suggested, but none is working. Every time, the state is coming 1 step behind. How can I make this state changes immediately?
If you want to use the value of an input in a user event function, the best way (and least buggy) is to bind your input value to local state and then just reference that state in your callback function.
Please try to avoid imperatively pulling values from the DOM using getElementById etc. Here's what I mean:
const [value, setValue] = useState('');
// This will keep everything updated until you need to use it
handleChange(event) {
setValue(event.target.value);
}
// Then just grab whatever is in local state
handleClick() {
history.push('/test', {params : value});
}
return (
<input value={value} onChange={handleChange} />
// Your button is here too
)
This is because when you call setLower(x) it is not an async call. So it doesn't wait. That's why you get the 1 step before value in your state right after setting the value.
Official doc - https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous
When you call setLower(x), it doesn't immediately update the lowerState. The new value will be available the next time it renders. Because of that the console.log(x) "works" (since it uses the new value that you gain as a parameter) but console.log(lowerState) uses the old value that hasn't updated to the state yet at that point.
If you want history.push('/test', {params : lowerState}) to work, then you need to use the x there instead of lowerState. Or call it within a useEffect with the lowerState and having lowerState as a dependency parameter to the hook.
This is expected behaviour since React is updating state in a batch
Which mean that the state only gets an update after an eventHandler/function is finished
If you want to do some condition, wrap your logic inside a useEffect
useEffect(() => {
if (lowerState === "your-condition-value") {
history.push("/test", { params: lowerState });
}
}, [lowerState]);
Or in your case, just use the variable directly:
const handleOnClick = () => {
x = document.getElementById("my_input").value.toLowerCase();
history.push("/test", { params: x });
};
You should not worry about that since your app still working as expected
So i would like to suggest that use useRef if need for reference only object which may not causing rerendering. also using let x= "" is not correct, you should write code immutable way
const Buton = () => {
const lowerCaseRef = useRef("")
const history = useHistory();
const handleOnClick = () => {
lowerCaseRef.current =
document.querySelector("#my_input").value.toLowerCase();
console.log(lowerCaseRef.current) //this DOES NOT prints the current value, but
// when I put another text into the input and click
// to button, it prints the first value I put here
history.push('/test', {params : lowerCaseRef.current})
};
return (...)
}

Can't use updated state in function in React

I am currently using Agents.tsx as a parent function and StickyFilter.tsx as the child. I am setting a state with an initial value of an empty string in Agents.tsx like so:
const Agents = (props: AgentsProps): ReactElement => {
const { agentsMode, recruitClient, recruitGlobals, setRecruitClient, setShowLoading } = props;
const [savedSearchName, setSavedSearchName] = useState(''); // set state here
I then am passing that setSavedSearchName as a prop to StickyFilter.tsx:
const StickyFilter = (props: StickyFilterProps): ReactElement => {
const { otherProps,
setSavedSearchName
} = props;
later down in StickFilter.tsx I am calling on that state function like so:
const setSavedSearch = (savedSearch: RecruitAgentSearch): void => {
let revisedSearches: AgentSearch[] = [];
// Redacted code -- bunch of logic
// Value gets set here
setSavedSearchName(revisedSearchDetails.criteria.searchName || '');
};
Once that value gets set in StickyFilter.tsx I am able to have it console log in the parent component just fine. I have a table that renders and once I click on a certain item it fires off the saveNewNote function, but for some reason the function is still pulling in the empty string but whenever I click on the item a SECOND time, it'll pull in the right value. What am I missing here? I tried inputting a useEffect hook like so:
useEffect(() => {
console.log(savedSearchName)
}, [savedSearchName]);
but it will not console log the correct value until I do the action on the table a second time.
Any advice is appreciated.
Edit: Here is the saveNewNote function in Agents.tsx
const saveNewNote = async (agent: Agent, agentsMode: AgentsMode): Promise<void> => {
// just trying to console log here after the state is set
console.log(savedSearchName); // returning empty string
console.log(recruitStoreApi.savedSearchName);
console.log(stateApi);
// Simple logic redacted
}
I ended up changing the data structure that injects into the table. I could not find a way to get the latest state.

React listen to 1st state change only

I am using useEffect in react to listen to redux(easy-peasy) state change, but I want to listen to 1st value change only.
Because when my page loads the state has a default value and then API call is made and hence data changes but the API is a polling API, hence it keeps getting the data again and again in a short interval of time. But one of my requirement is to listen only to the 1st API data.
This is what I tried:
1st Approach with empty dependency
useEffect(() => {
// my code
},[])
In this case, I get the default value of my state and not the 1st API response.
2nd Approach with state property in the dependency
useEffect(() => {
// my code
},[myState])
In this case, I keep getting the updated response from the API
both of these approaches didn't work for me. So please suggest a better solution.
You can do so using a ref variable and comparing the state with initial state (which could be null, undefined, empty object depending on your implementation):
const hasRun = useRef(false)
useEffect(() => {
if (!hasRun.current && myState !== initialState) {
hasRun.current = true
// run my code
}
},[myState])
A ref variable won't participate in re-rendering.
What I usually do with this is to have a "previous" state.
You can use this hook for to do that one:
const usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
}, [value]); // only re-run if value changes
// return previous value (happens before update in useEffect)
return ref.current;
}
You can then do:
// I usually create a distinct initial state
const [var, setVar] = useState(null);
const prevVar = usePrevious(var);
useEffect(() => {
if (var !== prevVar && var !== null) {
// logic here
}
}, [prevVar, var]);
Yers you can simplify this by not using the usePrevious hook, but the hook is quite handy and can be used to check the previous value and the current one.

Looking for assistance in getting proper results from .filter()

I am trying to filter an array with a string that is input by user. The results are not updating properly with the first key input, then if the box is cleared or characters removed/changed, results that may now pass the filter are not being displayed.
The goal is to have all results displayed on initial page render, then properly updated with each keystroke.
Apologies; I'm just learning to code. Thanks for all assistance.
searchCompUsers = () => {
const newState = {}
const filteredEmps = this.props.employees.filter(
user => user.name.includes(this.state.searchName)
)
console.log(filteredEmps)
`` newState.filterEmps = filteredEmps
this.setState(newState)
}
empSearch = evt => {
const stateToChange = {};
stateToChange[evt.target.id] = evt.target.value;
this.setState(stateToChange);
this.searchCompUsers()
};
These lines are being run in sequence:
this.setState(stateToChange);
this.searchCompUsers();
...
const filteredEmps = this.props.employees.filter(
user => user.name.includes(this.state.searchName)
)
...
this.setState(newState);
I am assuming in your example, evt.target.id is searchName.
Two things you're doing here which you shouldn't do:
Running two setStates in sequence. This isn't necessarily a problem, but there's generally no reason for it and it could mean your code is structured poorly.
Referencing the state immediately after setState. setState is run asynchronously, so you can't guarantee the state will be updated by the time you reach your filter.
The weird results you're getting are probably stemming from (2).
Something like this would work better, assuming the rest of your code is fine:
empSearch = evt => {
const key = evt.target.id;
const value = evt.target.value;
if (key === "searchName") {
const filteredEmps = this.props.employees.filter(
user => user.name.includes(value);
);
this.setState({
filterEmps: filteredEmps
});
}
};
This way, you're only calling setState once per event, and you're not relying on the results of an earlier setState.
If you need to keep searchName in the state for some reason (such as using a controlled component), then you can simply add it to the same setState.
this.setState({
filterEmps: filteredEmps,
searchName: value
});
The only places you can assume the state is up-to-date is in the render() function, and in certain React lifecycle functions. You can also provide a callback to setState if necessary, though this should be relatively rare: this.setState({ ...someState }, () => { ...someCodeToRun() });

Categories