Wy state is not updating in another state, I don't know how I can fix this problem.
I wanna do a multiplayer card game with Socket.io, but I run into this problem. Whenever the selectColor state is changing, it's not going to update in the other state. I tried to print the state whenever I click on the component, but the state is just equal as the initial state. Does anybody know how to solve this?
Thanks
const [cards, setCards] = useState([]);
const [id, setId] = useState();
const [color, setColor] = useState("?");
const [selectColor, setSelectColor] = useState(false);
useEffect(() => {
socket.on("id", (id) => setId(id));
socket.on("forceDisconnect", () => setId("Full"));
socket.on("cards", (data) => {
setKarten([
data.map((element) => {
return (
<div
onClick={() =>
console.log(selectColor)
}
key={element}
>
<Card card={element} />
</div>
);
}),
]);
});
socket.on("selectColor", () => {
setSelectColor(true);
console.log("selectColor");
});
}, []);
You have created a closure and placed the value of selectColor in it when your socket.on("cards", etc. ) callback was executed (which is therefore 'frozen in time').
It is no good to create your react elements when your data arrives and store them away in your state. You are supposed to create them when your render function is called. Something like so:
socket.on("cards", (data) => setCards(data));
return (
<>
{ cards.map(
card => (
<div onClick={() => console.log(selectColor)} key={card}>
<Card card={card} />
</div>
)
)}
</>
);
Related
I have the following component which shows a question, there's a button within it that allows you to reveal the answer, this is handled through the revealedResults property/state.
const Question = ({
item
}: {
item: QuestionType;
}) => {
const [revealedResults, setRevealedResults] = useState(false);
const { question, answers } = item;
useEffect(() => {
setRevealedResults(false);
}, [item]);
const handleResultReveal = () => {
setRevealedResults(true);
};
return (
<section>
<h1>Question: {question}</h1>
<button onClick={() => handleResultReveal()}>Reveal Answer</button>
<div>
{revealedResults && answers.map((answer) => <p>{answer}</p>)}
</div>
</section>
);
};
export default Question;
const Questionaire = () => {
const [question, setQuestion] = useState(questions[0]);
const [correctAnswers, setCorrectAnswers] = useState(0);
const [incorrectAnswers, setIncorrectAnswers] = useState(0);
const handleQuestionAnswer = (isCorrect: boolean): void => {
if (isCorrect) {
setCorrectAnswers(correctAnswers + 1);
} else {
setIncorrectAnswers(incorrectAnswers + 1);
}
setQuestion(questions[1]);
};
return (
<>
<Question item={question} />
<section>
<div>
<p> Did you get the answer correct?</p>
<button onClick={() => handleQuestionAnswer(true)}>Yes</button>
<button onClick={() => handleQuestionAnswer(false)}>No</button>
</div>
</section>
</>
);
};
export default Questionaire;
The question updates through the item prop. The idea is that when the item prop updates setRevealedResults is ran again to hide the revealed result of the next question.
The problem I'm having is that the prop of the new question is being flashed right before the useEffect side effect is being ran. You can see this here:
What is the correct way to deal with this?
useEffect runs after the render is done. That's why you see the page change for a moment there.
Try to use useMemo instead. It should update during the render.
I wanna implement a live Search function on my Redux State which I use in my home page via useSelector. and when user delete the search content original data show up as well. I use filter but the data doesn't affect. how can I achieve that? any help would be appreciated:
const Home = (props) => {
const companies = useSelector(state => state.companies.availableCompanies); //this is my data
const handleSearch = (e) => {
companies.filter(el => el.name.includes(e));
console.log(companies) // here I see my data changes but doesn't affect on UI
}
return (
<SearchBar onChangeText={handleSearch} />
<View style={styles.cardContainer}> // here I show data.
{companies.map((el, index) => {
return (
<Card
key={el.id}
companyId={el.id}
companyName={el.name}
companyImage={el.image}
companyMainAddress={el.mainAddress}
companyPhoneNumber={el.telephoneNumber}
companyDetails={el.details}
onPress={() => {
navigation.navigate('CardDetails', {
id: el.id,
companyName: el.name,
});
}}
/>
)
})}
</View>
Have a try with the below changes
Hope it will work for you.
const Home = (props) => {
const [searchQuery, setSearchQuery] = useState();
const [filteredData, setFilteredData] = useState();
const companies = useSelector(state => state.companies.availableCompanies); //this is my data
const handleSearch = (e) => {
setSearchQuery(e);
}
useEffect(() => {
if (searchQuery && typeof searchQuery === "string" && searchQuery.length > 0) {
const searchData = companies.filter(el => el.name.includes(searchQuery));
setFilteredData([...searchData]);
} else {
setFilteredData();
}
}, [searchQuery, companies])
return (
<SearchBar onChangeText={handleSearch} />
<View style={styles.cardContainer}>
{(filteredData && Array.isArray(filteredData) ? filteredData : companies).map((el, index) => {
return (
<Card
key={el.id}
companyId={el.id}
companyName={el.name}
companyImage={el.image}
companyMainAddress={el.mainAddress}
companyPhoneNumber={el.telephoneNumber}
companyDetails={el.details}
onPress={() => {
navigation.navigate('CardDetails', {
id: el.id,
companyName: el.name,
});
}}
/>
)
})}
</View>
This variable below is a copy of state.companies.availableCompanies that is replaced on every render with the original value from state.companies.availableCompanies.
const companies = useSelector(state => state.companies.availableCompanies); //this is my data
Since you're assigning the result of filter to the copy, and not to the original variable inside the redux store. The results are not reflected there, and every time rerender happens, the Functional Component is called again, making all the code inside this function execute again. So, there is a new variable companies that is not related to the old one.
To actually update the original variable inside redux. You need to create a redux action, and dispatch it.
You need to go back and learn the fundamental concepts of redux before proceeding with this.
Here is the link to the documentation explaining how the data flow works in redux.
https://redux.js.org/tutorials/fundamentals/part-2-concepts-data-flow
You need to use useDispatch() to get dispatcher and dispatch an action to your reducer with state to update in your handleSearch ()
Something like:
const dispatch = useDispatch();
const handleSearch = (e) => {
dispatch({type:"YOUR_ACTION",payload:companies.filter(el => el.name.includes(e))})
console.log(companies) ;
}
Refer: https://medium.com/#mendes.develop/introduction-on-react-redux-using-hooks-useselector-usedispatch-ef843f1c2561
I'm working on random user api, the fetching of user name and pagination is working fine but not the search event. Please help.
I pushed my code on stackblitz, to help you guys to debug it easily.
here's the link: https://stackblitz.com/edit/search-and-pagination-in-react-by-react-hooks?file=src/App.js
below in image you can see that the name i mentioned in search box is present in api but its not comming on first place.
Working example in here.
const App = () => {
const [myApi, setMyApi] = useState([]);
const [data, setData] = useState([]); // add your data to here
const [currentPage, setCurrentPage] = useState(1);
const [postsPerPage] = useState(10);
const [searchUser, setSearchUser] = useState("");
useEffect(() => {
fetch("https://randomuser.me/api/?results=50")
.then(data => data.json())
.then(json_result => {
setData(json_result.results); // set your data to state
let myApi = renderData(json_result.results); // render your component
setMyApi(myApi); // set it to state
});
}, []);
const renderData = (data) => {
return data.map((item, idx) => {
return (
<div key={idx}>
<img src={item.picture.thumbnail} alt="" /> {item.name.first}
<hr />
</div>
);
});
}
// get current post
const indexOfLastPost = currentPage * postsPerPage; // 1 * 10 = 10
const indexOfFirstPost = indexOfLastPost - postsPerPage; // 10 - 10 = 0
const currentPosts = myApi?.slice(indexOfFirstPost, indexOfLastPost); // 0 to 10
// search users by user input
const handleSearchInput = event => {
setSearchUser(event.target.value);
const newData = renderData(data.filter(item => item.name.first.toLowerCase().includes(event.target.value))); // render filtered data
setMyApi(newData); // and set it to state
};
const paginate = pageNumber => setCurrentPage(pageNumber);
return (
<div>
<Search onChange={handleSearchInput} />
<Pagination
postsPerPage={postsPerPage}
totalPosts={myApi?.length}
paginate={paginate}
/>
{currentPosts}
</div>
);
};
const Search = ({ onChange }) => {
return (
<div>
<input
type="text"
autoFocus={true}
placeholder="search users"
onChange={onChange}
/>
</div>
);
};
Since you're useEffect has [] (empty array) as the dependency, you're user fetching logic will only be called once i.e. on the initial rendering. You can add searchUser as useEffect's dependency so you can fetch users whenever the searchUser text changes.
My component looks like this:
constMyComponent = props => {
const [events, setEvents] = useState();
useEffect(() => {
getData(id).then(function(myEvents){
setEvents(myEvents);
});
}, [id]);
When I render the variable "events" it looks good thanks to the question mark operator:
<ul>
{ events && Object.keys(events.events).map( (data, i) => (
<li key = { i }>
<span>
{ events.events[data].mykey}
</span>
</li>
))}
</ul>
However, in "useEffect" I need to apply some logic to the retrieved data. After the row with
setEvents(myEvents);
I cannot access the variable "events". I guess it is not ready yet. I read a little bit about callback. Is that the way to go?
Just adding a ".then" won't work. How do you usually access data when it is accessible in this case?
If i understand ur question correctly u wanna make the new events made logically depending on old events And wana access it,
U can access it like this,
setEvents(prevEvents => {
// some logical computations
return theNewEvents
});
As of this example,
function Counter({initialCount}) {
const [count, setCount] = useState(initialCount);
return (
<>
Count: {count}
<button onClick={() => setCount(initialCount)}>Reset</button>
<button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
<button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
</>
);
}
From React Docs
But, if u wanna make the logical changes to the new events,
U can easily do it in the function before setting the state (as #jonrsharpe pointed out)
constMyComponent = props => {
const [events, setEvents] = useState();
useEffect(() => {
getData(id).then(function(myEvents){
// make ur changes to `myEvents`
...
// then, set it to state
setEvents(myEvents);
});
}, [id]);
Can We use context values to initiate a state variable inside a function component?
Here I am trying to initiate a component state with values from context. But the state doesnot update when context value changes.
function Parent() {
return (
<ContextProvider>
<Child />
</ContextProvider>
);
}
function Child() {
const mycontext = useContext(Context);
const [items, setItems] = useState(mycontext.users);
console.log(mycontext.users, items); //after clicking fetch, => [Object, Object,...], [] both are not equal. why??????
return (
<>
<button onClick={() => mycontext.fetch()}>fetch</button>
{/* <button onClick={()=>mycontext.clear()} >Clear</button> */}
{items.map(i => (
<p key={i.id}>{i.name}</p>
))}
</>
);
}
/* context.js */
const Context = React.createContext();
function ContextProvider({ children }) {
const [users, setUsers] = useState([]);
function fetchUsers() {
fetch("https://jsonplaceholder.typicode.com/users")
.then(response => response.json())
.then(json => setUsers(json));
}
return (
<Context.Provider
value={{ users, fetch: fetchUsers, clear: () => setUsers([]) }}
>
{children}
</Context.Provider>
);
}
The above code can be tested in codesandbox.
I can use context values directly, but I want to maintain state inside the component.
If we cannot initiate state value with a context value, what is the best approach If I want to get data from context and also want to maintain state internally?
The argument to useState is only used once.
You do not need to copy context value in state and can directly use it from context.
If however you would like to do it you need to make use of useEffect
const [items, setItems] = useState(mycontext.users);
useEffect(() => {
setItems(mycontext.users);
}, [mycontext.users]);
updated demo