Why isn't this React component updated when dependency changes? - javascript

I have this simple React component with a search bar, it gets the input value and navigates to an URL setting the input value as a query param. Once in the component, it gets the 'keyword' from the URL query params and calls an API to get results.
It only works the first time. But if I change the input value it doesn't update the 'keyword' variable value, even though the URL is properly updated.
Why isn't my variable updated?
export default function Resultados () {
let keyword = new URLSearchParams(window.location.search).get('keyword')
const apiKey = '123' ;
useEffect(()=>{
axios
.get(`https://api.themoviedb.org/3/search/movie?api_key=${apiKey}&language=es-ES&query=${keyword}&page=1&include_adult=false
`)
.then(res=>{
console.log(res.data)
})
},[keyword])
return (
<>
<h2>Resultados de {keyword}</h2>
</>
)
}
I've included the keyword variable on the useEffect dependency array, but it seems the variable is not changing.

The dependency array to a useEffect will never cause your component to render. In fact, it's only useful if the component is already rendering, at which point it can skip the effect if nothing has changed. Instead, if you want the component to render, you must set state (either here or in some parent component).
So you either need to write custom code which detects when the url changes, and at that point set state; or you can use an existing routing library such as react-router or react-location, which have already written that code for you. For example, here's how you would do it in react-router:
import { useSearchParams } from 'react-router';
export default function Resultados () {
const [searchParams] = useSearchParams();
const keyword = searchParams.get('keyword');
// rest of your code is the same
}

Related

useContext giving me undefined value on different components after setting that value

I am creating a context for a log in, so after creating the context I wrap all my routes with that context.provider and send the initial value to each child, which in my case is a useState:
<userContext.Provider value={{loggedUser, setLoggedUser}}>
I have a component LogInMenu.jsx that basically sets the loggedUser (loggedUser has a username/password structure) to a valid user. I consume the context in LogInMenu.jsx like this:
const { loggedUser, setLoggedUser } = useContext(userContext)
After succesfully console logging the loggedUser value in LogInMenu.jsx (just to make sure it was correct) I go to my other component called Dashboard.jsx and consume the context there the same way as I consumed it in LogInMenu.jsx, but this time, If I console log the loggedUser it gives me undefined value.
Note: I have imported useContext hook in each of the components that use it like this:
import React, { useState, useContext } from 'react';
and the context I created like this:
import { userContext } from './App';
If you need more code to understand the issue I can provide it.
I was going thru the code sandbox example, few points to consider
try moving the code inside the submit event handler
const submitData = async (e) => {
e.preventDefault();
// Ideally use post instead of get
// pass user id and password to Api
// password should never be stored in plain text in backend
// depending on the backend store it as hash and authenticate
// user Id/name and password in the backend
const loginResponse = await axios.get(`http://localhost:8002/users/${user}`)
if (loginResponse.isSuccess) {
setLoggedUser(loginResponse.userInfo)
}
};
You can get a good idea about React with their new documentation.
Cheers

Context value not accessible inside inner function of functional component

I am attempting to build a component that takes in an arbitrarily large list of items and displays a chunk of them at a time. As the user scrolls the window down, I want to automatically load more items if any exist.
The problem I am running into is that the my appState variable is not acting consistently. When I log it at the top of the component, it always reads the correct value out of the loaded context. However, when I read the value inside the onScroll function, it always returns the default uninitialized state. Where did my context go on the inner function?
Here's a stripped down version that illustrates my problem:
Component
import { useContext } from 'react'
import { useLifecycles} from 'react-use'
import AppState from '../../models/AppState'
import { Context } from '../../store/create'
export default () => {
const appState:AppState = useContext(Context)
console.log('appState.items (root)=', appState.items.length) // Returns `100`, as it should
useLifecycles(
() => {
window.addEventListener('scroll', onScroll)
},
() => {
window.removeEventListener('scroll', onScroll)
}
)
const onScroll = (evt:any) => {
console.log('appState.items (onScroll)', appState.items.length) // Returns `0` (the default uninitialized state).
}
return (
<div className='ItemList'>
<h1>Hello world</h1>
{/* The list of items goes here */}
</div>
)
}
../../store/create
import React from 'react'
import AppState, { getDefaultState } from '../models/AppState'
let state:AppState = getDefaultState()
export const Context:React.Context<AppState> = React.createContext<AppState>(state)
export const setAppState = (newState:AppState):void => {
_state = newState
}
export const getAppState = ():AppState => {
return _state
}
I've read the rule of hooks, and to my understanding I am not breaking anything. My useContext and useLifecycle calls are in a fixed order at the top; no conditionals, no loops.
What am I missing?
I am not aware of how useLifecycles work. But the problem I can see is that you are binding the event a function. That function has the state in it's closure and so it captures that value of state. Whenever state changes, your handler isn't aware of the state change and so it just keeps using the data that was previously captured. Now to solve it, you need to listen for state change and remove the listener that was previously attached, add the new listener that has new values in its closure. I think the useLifecycles should have a dependency option to achieve that. If not the other way could be to use useEffect hook.
Edit:
I just checked the react-use docs and turns out what you really need is useEvent. Look at the example in docs. To make sure it works in your case, you should pass your dependency in useCallback.

React useState hook is not working when submitting form

I am new to React and I have some doubt regarding useState hook.I was recently working on an API based recipe react app .The problem I am facing is when I submit something in search form a state change should happen but the state is not changing but if I resubmit the form the state changes.
import React,{useState,useEffect} from "react";
import Form from "./componnents/form";
import RecipeBlock from "./componnents/recipeblock"
import './App.css';
function App() {
const API_id=process.env.REACT_APP_MY_API_ID;
const API_key=process.env.REACT_APP_MY_API_KEY;
const [query,setQuery]=useState("chicken");
const path=`https://api.edamam.com/search?q=${query}&app_id=${API_id}&app_key=${API_key}`
const [recipe,setRecipe]=useState([]);
useEffect(() => {
console.log("use effect is running")
getRecipe(query);
}, []);
function search(queryString){
setQuery(queryString);
getRecipe();
}
async function getRecipe(){
const response=await fetch(path);
const data=await response.json();
setRecipe(data.hits);
console.log(data.hits);
}
queryString in search() function holds the value of form input,Every time I submit the form this value is coming correctly but setQuery(queryString) is not changing the query value or state and if I resubmit the form then it change the state.
The code you provided something doesn't make sense.
useEffect(() => {
console.log("use effect is running")
getRecipe(query);
}, []);
Your getRecipe doesn't take a variable. But from what I am understanding whenever you search you want to set the Query then get the recipe from that Query.
With the useEffect you can pass in a parameters to check if they changed before running a function. So update the setQuery then when the component reloads it will fire the useEffect if query has changed. Here is the code to explain:
useEffect(() => {
console.log("use effect is running")
getRecipe(query); <-- this doesn't make sense on your code
}, [query]);
function search(queryString){
setQuery(queryString);
}
By doing this when the state updates it causes the component to re-render and therefore if query has changed it will call your getRecipe function.
The main issue in your code is that you are running getRecipe() directly after setQuery(queryString). setQuery(queryString) is asynchronous and will queue a state change. When you then run getRecipe() directly after, the state will still hold the old value of query (and path) and therefore does not fetch the new data correctly.
One solution would be to call getRecipe() within a useEffect() dependent on path.
useEffect(() => {
getRecipe();
}, [path]);
function search(queryString){
setQuery(queryString);
// getRecipe() <- removed
}
With [path] given as dependencies for useEffect(), getRecipe() will be called automatically whenever path changes. So we don't have to call it manually from search() and therefore can remove getRecipe() from the function body. This also makes the current useEffect() (without [path] dependency) redundant, so it can be removed.
Another solution would be to provide the new query value through the getRecipe() parameters, removing the dependency upon the state.
function search(queryString){
setQuery(queryString);
getRecipe(queryString);
}
async function getRecipe(query) {
const path = `https://api.edamam.com/search?q=${query}&app_id=${API_id}&app_key=${API_key}`;
const response = await fetch(path); // <- is no longer dependent upon the state
const data = await response.json();
setRecipe(data.hits);
}
This does require moving the path definition inside getRecipe().

Do functions get the latest state value in React?

I have a function inside of my functional component that uses a value saved in state. However, when it is called, it has the original value in state, not the updated value. When I look at my component in Chrome React Dev Tools, I see that the updated value is stored in state. Aren't functions supposed to get the latest state value in React? I didn't think I'd have to wrap my functions in a useEffect every time some value in state they depend on changes. Why is this happening?
const Editor = (props) => {
const [template, setTemplate] = useState(null);
const [openDialog, setOpenDialog] = useState(false);
useEffect(() => {
if (props.templateId) {
getTemplate(props.templateId));
}
},[]);
const getTemplate = (templateId) => {
{...make API to get template...}
.then((response) => {
if (response.template) setTemplate(response.template);
});
}
/* THIS FUNCTION SAYS TEMPLATE IS ALWAYS NULL */
const sendClick = async () => {
if (template) {
await updateTemplate();
} else {
await initializeTemplate();
}
setOpenDialog(true);
};
}
UPDATE: I figured out the issue. The sendClick function is being used inside an object that I have in state. When that object is created, it creates a version of the sendClick function based on the state at that time. I realized I needed to refactor my code so that the function is not stored within my object in state so that the function will always have the latest state values.
Please correct the code there its setTemplate(template)); not getTemplate(template));
I'm guessing that you have that right in the source code... if Yes then,
You have got into a trap that all developers new to React fall into.
This code is betraying you ...
useEffect(() => {
if (props.template) {
setTemplate(template)); // Mentioned as getTemplate(template));
}
},[]); // Here is where you make the mistake
The second argument you pass to the useEffect is called as Dependencies. Meaning if your useEffect is dependent on any state or any variable or function, Ii should be pass as the second argument inside the []. By now you should have got the answer.
Clearly, your useEffect is dependent on template. You should pass that inside the [].
So the code will be : -
useEffect(() => {
if (props.template) {
setTemplate(template)); // Mentioned as getTemplate(template));
}
},[template]);
Now React will automatically run the function every time the value of template changes therefore, updates template.
For more information about useEffect ...
Refer React Documentation
Refer the useEffect API

Alternative to getState for recurring variables in Redux

I use getState to get a clientId that I need to include in every api call right now. Problem is that this interrupts data flow as the app doesn't rerender when clientId changes. Do I have to manually get the clientId in every component that I need to include it in or is there a better alternative? (clientId is also in store and is fetched first when the user logs in)
Sounds like a good candidate for the use of Context.
Here's a fictitious example of how you can set the client ID at a high level but reference it in nested components without having to query the Redux store each time using Hooks:
App
const ClientContext = React.createContext(null);
function App(props) {
return (
<ClientContext.Provider value={props.clientId}>
<MyApiComponent />
</ClientContext>
)
}
const mapStateToProps = getState => ({
clientId: getState().clientId
})
export default connect(mapStateToProps, {})(App);
So we only need to connect the App to the store to retrieve the client ID, then using Context we can make this value accessible to nested components. We can make use of useContext to pull that value in to each component
function MyApiComponent() {
const clientId = useContext(ClientContext);
...
return <MyNestedApiComponent />;
}
function MyNestedApiComponent() {
const clientId = useContext(ClientContext);
...
}
Whether it's function or class components you are using, the principle is the same - Context is used to share global state down to nested components.

Categories

Resources