Faced a problem with the navigate() in Gatsy function, using the navigate function, you can pass an object as a second parameter to another page. I did as in the documentation, but when getting data I get undefined.
const Panel = () => {
const [name, setName] = useState('')
useEffect (() => {
if (name !== undefined) {
setName('Hello World')
}
}, [name])
const handleRedirect = () => {
navigate('/cabinet/', { state: { name }})
}
return (
<div>
<button onClick={handleRedirect}>Redirect</button>
<div>
)
}
const Cabinet = ({location}) => {
console.log(location.state.name) // undefined
return (
<div>
<h1>{location.state.name}</h1>
</div>
)
}
Your problem is a matter of timings/asynchronously. Your navigate built-in function looks good, however, you are setting the name (with your setName setter) after the navigation occurs. In addition, you are missing the key for the state. Try something like:
const handleRedirect = () => {
if(name) navigate('/cabinet', {state: {'name': name}}); // Try hardcoding Hello World for debugging purposes {'name'; 'Hello World'}
}
Then in your Cabinet component:
const Cabinet = ({location}) => {
console.log(location.state.name) // will be your Hello World
return (
<div>
<h1>{location.state.name}</h1>
</div>
)
}
Alternatively, you can do an async/await approach, something like:
const setNameFunction = ()=>{
setName('Hello World')
return name
}
const handleRedirect = async () => {
let nameFromFunction= await setNameFunction;
navigate('/cabinet/', { state: { 'name': nameFromFunction }}) // use if(nameFromFunction) navigate('/cabinet/', { state: { name: nameFromFunction }}) alternatively
}
This second approach will make the ecosystem dynamic, allowing you to pass a custom name based on some logic that you will need to add (passing the name as a parameter through those functions).
Alternatively, you can look for your state in your Cabinet component using window object in window.history.state.
Related
const BankSearch = ({ banks, searchCategory, setFilteredBanks }) => {
const [searchString, setSearchString] = useState();
const searchBanks = (search) => {
const filteredBanks = [];
banks.forEach((bank) => {
if (bank[searchCategory].toLowerCase().includes(search.toLowerCase())) {
console.log(bank[searchCategory].toLowerCase());
filteredBanks.push(bank);
}
});
setFilteredBanks(filteredBanks);
};
const debounceSearch = useCallback(_debounce(searchBanks, 500), []);
useEffect(() => {
if (searchString?.length) {
debounceSearch(searchString);
} else setFilteredBanks([]);
}, [searchString, searchCategory]);
const handleSearch = (e) => {
setSearchString(e.target.value);
};
return (
<div className='flex'>
<Input placeholder='Bank Search' onChange={handleSearch} />
</div>
);
};
export default BankSearch;
filteredBanks state is not updating
banks is a grandparent state which has a lot of objects, similar to that is filteredBanks whose set method is being called here which is setFilteredBanks
if I add a console log and save or remove it the state updates
Adding or removing the console statement and saving the file, renders the function again, the internal function's state is updated returned with the (setState) callback.
(#vnm)
Adding filteredBanks to your dependency array won't do much because it is part of the lexical scope of the function searchBanks
I'm not entirely sure of the total context of this BankSearch or what it should be. What I do see is that there are some antipatterns and missing dependencies.
Try this:
export default function BankSearch({ banks, searchCategory, setFilteredBanks }) {
const [searchString, setSearchString] = useState();
const searchBanks = useCallback(
search => {
const filteredBanks = [];
banks.forEach(bank => {
if (bank[searchCategory].toLowerCase().includes(search.toLowerCase())) {
filteredBanks.push(bank);
}
});
setFilteredBanks(filteredBanks);
},
[banks, searchCategory, setFilteredBanks]
);
const debounceSearch = useCallback(() => _debounce(searchBanks, 500), [searchBanks]);
useEffect(() => {
if (searchString?.length) {
debounceSearch(searchString);
} else setFilteredBanks([]);
}, [searchString, searchCategory, setFilteredBanks, debounceSearch]);
const handleSearch = e => {
setSearchString(e.target.value);
};
return (
<div className="flex">
<Input placeholder="Bank Search" onChange={handleSearch} />
</div>
)}
It feels like the component should be a faily simple search and filter and it seems overly complicated for what it needs to do.
Again, I don't know the full context, however, I'd look into the compont architecture/structuring of the app and state.
I am using react usestate() and I want to update device state(It is an object)
my problem is when ShowRelays component renders for the first time device is an empty object and It does not get updated during first rendering, but for the next renders everything is fine
How can I update device state for the first time rendering?
(sorry for my bad english)
.
function ShowRelays(props) {
const [device, setDevice] = useState({})
let reduxDevices = useSelector(state => state.devicesReducer.devices)
let findDevice = () => {
let myDevice = reduxDevices.find(x => x._id === props.id)
setDevice(prevState => {
return {
...prevState,
...myDevice
}
})
}
useEffect(() => {
if (props.show) {
findDevice()
}
}, [props.show])
return
(
<div>
test
</div>
)
}
myDevice object is like:
{active: true, name: "device1", id: "deviceId"}
You can pass a function to your useState-hook which will calculate your initial value for device.
see lazy init state
function ShowRelays(props) {
let reduxDevices = useSelector(state => state.devicesReducer.devices);
const [device, setDevice] = useState(() => {
return reduxDevices.find(x => x._id === props.id)
});
return <div>test</div>;
}
An other possible solution for your problem without using a separate state could be the following (directly select the right device from your selector function):
function ShowRelays(props) {
const device = useSelector(state => {
return state.devicesReducer.devices.find(x => x._id === props.id);
});
return <div>test</div>;
}
This is how my Categories react functional component looks like. For easier testing, I split up the handleClick and the react component itself - but that shouldn't be an issue for this question.
How do I pass the component string value from the map() to the handleClick()? handleClick already passes some operator parameter, which gets me struggling with this simple issue...
export const useCategories = () => {
const handleClick = (operator) => {
updateCategory({
variables: {
id: '123',
operator,
category // <-- this value is missing
}
})
}
return {
icon: {
onClick: handleClick('$pull') // <-- Here I add some operator value
}
}
}
export const Categories = () => {
const { icon } = useCategories()
return (
<div>
{categories.map((category) => <Icon onClick={icon.onClick} />)} {/* <-- how to pass category value to handleClick...? */}
</div>
)
}
In order to "add" a variable, you can use currying.
Hence, return a function that receives the new argument and use it internally:
export const useCategories = () => {
return (category) => {
const handleClick = (operator) => {
updateCategory({
variables: {
id: '123',
operator,
category // now this is the argument of the wrapper function
}
})
};
return () => handleClick('$pull');
};
}
And you can use it like this:
export const Categories = () => {
const getOnClick = useCategories();
return (
<div>
{categories.map((category) => {
const onClick = getOnClick(category);
return <Icon onClick={onClick} />;
})}
</div>
)
}
I have to get a users from backend and push the result array into a property of third-party component. I can't push the result into the component's state, like setUsersList, because it leads to infinity render-loop. So I decided to use a function which will put the data from backend into a variable and return variable's value.
<InputAutocomplete
...
value={selectedUserName}
onChange={(value: string) => setSelectedUserName(value)}
options={getFilteredUsers(selectedUserName)}
/>
My function is:
const getFilteredUsers = (typedValue: any): any[] => {
console.log('***getting into getFilteredUsers method')
if (!typedValue) {
return []
}
let usersList: any[]
repository.GetUsers(typedValue).then((response) => {
if (response.StatusCode !== null) {
response.Message.then((errorText) => showNotificationPopup(errorText, consts.ERROR_OCCURED, 'error'))
return
}
response.Data.then((users) => {
console.log('filling usersList')
usersList = (users.map((u) => {
return {key: u.Id, value: `${u.Name} - ${u.Position} - ${u.DopOfficeName}` }
})
)
})
})
console.log('leaving function')
return usersList
}
It works unacceptable for me (although it works as expected):
***getting into getFilteredUsers method
leaving function (with empty array)
filling usersList (too late)
The property "options" of third's party component doesn't accept Promise<any[]>, it can only accept any[], thus I can't mark my function as async and await the result from repository before to leave the function.
How to prevent leaving function before I'll get the result?
Maybe I've made a wrong decision at all. If I'm wrong I need a help, how to implement server-side refilling of autocomplete input component.
I can't push the result into the component's state, like setUsersList, because it leads to infinity render-loop
This should not be the case.
const SomeComponent = () => {
const [options, setOptions] = React.useState([]);
const [username, setUsername] = React.useState([]);
React.useEffect(() => {
if (!typedValue) return [];
repository.GetUsers(typedValue).then((response) => {
if (response.StatusCode !== null) {
response.Message.then((errorText) => showNotificationPopup(errorText, consts.ERROR_OCCURED, 'error'));
return;
}
response.Data.then((users) => {
console.log('filling usersList');
const usersList = users.map(u => {
return { key: u.Id, value: `${u.Name} - ${u.Position} - ${u.DopOfficeName}`};
});
setOptions(usersList);
})
})
console.log('leaving function')
}, [username]);
return (
<InputAutocomplete
value={username}
onChange={(value) => setUsername(value)}
options={options}
/>
);
};
Above code should not go in infinite loop.
The best way to describe question is my code:
function EstateParamsList({ estateType, category }) {
const [isLoading, setIsLoading] = useState(true)
const [params, setParams] = useState({})
const [showPopUp, setShowPopUp] = useState(false)
useEffect(() => {
if (category && typeof category.id !== undefined) {
return db.collection(`dictionaries/ESTATE_PARAMS/${estateType}/${category.id}/params`).onSnapshot(response => {
const paramsObject = {}
response.forEach(param => {
paramsObject[param.id] = {
...convertParamObjetcToFieldsConfig(param.data()),
change: fieldChangedHandler
}
})
setParams(paramsObject)
setIsLoading(false)
})
} else {
setIsLoading(false)
}
}, [category])
console.log(params)
const fieldChangedHandler = (event, fieldIdentifier) => {
if(params)
console.log(params)
}
So i have params variable, that im init with object, that i'm getting async from firebase. Implementation of initializing you can see in useEffect method. For every object i want to pass ref for the function "fieldChangedHandler", for managing value of inputs.
fieldChangedHandler is a method of my EstateParamsList. But there i cant get value of params!
Question is WHY? I'm calling fieldChangedHandler only after everything was rendered, and async request was done.
Below is console log of params. Why in func is empty params?
Calling:
const renderParamsAsFields = params => {
const fields = []
for (const key in params) {
fields.push(<Field {...params[key]} changed={event => params[key].change(event, key)} />)
}
return fields.length ? fields : <div className='EstateParamsManager-EmptyValues'>Нет параметров</div>
}
Why not use curried function?
const createFieldChangedHandler = (
params,
fieldIdentifier
) => event => {
if (params) console.log(params);
};
function EstateParamsList({ estateType, category }) {
//... other code
useEffect(() => {
//...other code
change: createFieldChangedHandler(
paramsObject,
param.id
),
//...other code
}, [category, estateType]);//fixed missing dependency
When you call the change function you should already have the right values in scope:
<Field {...params[key]} changed={params[key].change} />