I have following code inside my functional component.
const { id } = useParams();
const categories = useSelector((state) => {
const allCategories = state.allCategories;
return allCategories.filter(cat => cat.id === id);
});
const [code, setCode] = useState(categories.length === 1 ? categories[0].code : '');
However this code runs correctly only once. Then if I refresh the page, it doesn't work. Please help me to fix this issue. Also, if this is against best practices, please let me know the correct way to do this.
Actual use case is, there is a category table and user clicks on "edit category" and then I navigate to this component to display the current data (ie: category code). Since, the category table already has data, allCategories is filled
because the data is changed in some other place of the program its prolematic to use it as a default state value...
instead bind the value with useEffect()
const { id } = useParams();
const categories = useSelector((state) => {
const allCategories = state.allCategories;
return allCategories.filter(cat => cat.id === id);
});
const [code, setCode] = useState('');
useEffect(()=>{
if(categories.length === 1)
setCode(categories[0].code)
},[categories.length&&categories[0].?code])
NOTE: the code will be set like so whenever categories[0].code is changed...
You can use something like redux-persist to save the redux state to local storage or to another storage system
Related
This is a next/react project.
folder structure:
components > Navbar.js
pages > index.js (/ route)(includes Navbar)
> submitCollection.js (/submitCollection)(includes Navbar)
I am trying to have the user submit a specific string as an input and i store it inside the account variable.
Navbar.js
const Navbar = ({}) => {
const [account,setAccount] = useState()
const handleClick = () => {
setAccount(randomNumberThatIHaveGenerated)
}
...
return (
<Link href="/">home</Link>
<Link href="/submitCollection">submit collection</Link>
...
<button onClick={handleClick} >press to set account</button>
...
{account?(<p>{account}</p>):(<p>u need to set an accout</p>)}
)
}
when i visit home using the navbar link, the account is again set to undefineed and i need to press the button again in order to set it. How can i make the string remain set. like persist on the navbar
useState is not persistent, it is bound to its component, in order to make it persist, you have to use localStorage
const [account,_setAccount] = useState();
const setAccount = (val) => {
_setAccount(val);
localStorage.setItem('account', val);
}
useEffect(() => {
const storedAccount = localStorage.getItem('account');
if (storedAccount) _setAccount(storedAccount);
}, [])
const handleClick = () => {
setAccount(randomNumberThatIHaveGenerated)
}
useEffect is called when the component renders, check for stored account and displays it.
And notice how we reimplement setAccount, so that everytime it is called, we update the localStorage.
You can also create a custom hook with this logic, so the component would look cleaner. Or even better, use something like use-state-persist
You can solve this problem using localstorage and useEffect
Adding this piece of code to your work will do the trick
const [account,setAccount] = useState(localStorage.getItem('account') ?localStorage.getItem('account') : null)
useEffect(()=>{
localstorage.setItem(account)
},[account])
For example
const [account,setAccount] = useState(localStorage.getItem('account') ?localStorage.getItem('account') : null)
useEffect(()=>{
localStorage.setItem('account',account)
},[account])
const handleClick = () => {
setAccount(randomNumberThatIHaveGenerated)
}
Hope it helped
I am creating to-do app in react, and storing the data in localstorage,when user click on particular task it is mark completed, for that purpose i have "complete" boolean property for all task in localStorage.now i want to change that property onclick of that particular task,How to achieve this?.Here is the code link :
https://github.com/Khatri-Jinal/react-app/tree/practical4
get the value from local storage and update it and then set it again
for ex:
let data=localStorage.getItem('tasks')
//make changes in data
localStorage.setItem("tasks", JSON.stringify(tasks));
I suggest you make a custom hook for local storage (ie. useLocalStorage). This ensures that when you update a value in the local storage, the components that are using the updated value are automatically re-rendered.
You may look it up online or use this youtube viode for your reference. The first example there is useLocalStorage.
EDIT:
Your task variable should be an array of object. When rendering the task, pass an id or anything unique to the task on the onclick function (In this example, I'll just use task name but I advise to create your own).
// tasks hook
const [tasks, setTasks] = useState([
{desc: 'jog', isComplete: true},
{desc: 'walk', isComplete: false},
{desc: 'read', isComplete: true},
]);
// rendering tasks
tasks.map(task => {
<div key={task.desc} onClick={() => onClickTask(task.desc)}
task.desc
</div>
});
And this is your onClickTask function
const onClickTask = (identifier) => {
const index = tasks.findIndex(task => task.desc === identifier);
const newTasks = [...tasks];
newTasks[index].isComplete = true;
setTasks(newTasks);
};
I made this solution, I hope it helps.
const [todoList, setTodoList] = useState([]);
// Get todo list from localStorage when the component is mounted
useEffect(() => {
setTodoList(JSON.parse(localStorage.getItem('todoList')));
}, [])
function updateTodo(todoId, data) {
const todoIndex = todoList.findIndex(todo => todo.id === todoId);
// Get a copy of the todo
let updatableTodo = { ...todoList[todoIndex] };
// Update the todo here...
updatableTodo.title = data.title;
// Update the state and localStorage
setTodoList(todoList => {
const newTodoList = [...todoList];
newTodoList[todoIndex] = updatableTodo;
localStorage.setItem('todoList', JSON.stringify(newTodoList));
return newTodoList;
})
}
return (
<div>
{todoList.map((todo) =>
<button onClick={() => updateTodo(todo.id, data)} key={todo.id}>Update</button>
)}
</div>
);
I am using React and Material UI to create a table (XGrid) with some buttons. When you click the row, it should set the row id using useState. When you click the delete button, it should delete the row. It seems that the delete click handler is not using the value from use state. This is either some kind of closure thing or some kind of React thing.
const MyTableThing: React.FC = (props) =>
{
const { data } = props;
const [filename, setFilename] = React.useState<string>("")
const [columns, setColumns] = React.useState<GridColDef[]>([])
const handleDelete = () =>
{
someFunctionThatDeletes(filename); // filename is always ""
setFilename(""); // Does not do anything.. !
}
React.useEffect(() =>
{
if (data)
{
let columns: GridColumns = data.columns;
columns.forEach((column: GridColDef) =>
{
if (column.field === "delete")
{
column.renderCell = (cellParams: GridCellParams) =>
{
return <Button onClick={handleDelete}>Delete</Button>
}
}
})
setColumns(columns)
}
}, [data?.files])
// Called when a row is clicked
const handleRowSelected = (param: GridRowSelectedParams) =>
{
console.log(`set selected row to ${param.data.id}`) // This works every time
setFilename(param.data.id)
}
}
The reason for this behavior is that React does not process setState action synchronously. It is stacked up with other state changes and then executed. React does this to improve performance of the application. Read following link for more details on this.
https://linguinecode.com/post/why-react-setstate-usestate-does-not-update-immediately
you can disable your deleteRow button till the filename variable is updated. you can use useEffect or setState with callback function.
useEffect(() => {
//Enable your delete row button, fired when filename is updated
}, filename)
OR
this.setFilename(newFilename, () => {
// ... enable delete button
});
Let me know if this helps! Please mark it as answer if it helps.
The main problem I see here is that you are rendering JSX in a useEffect hook, and then saving the output JSX into columns state. I assume you are then returning that state JSX from this functional component. That is a very bizarre way of doing things, and I would not recommend that.
However, this explains the problem. The JSX being saved in state has a stale version of the handleDelete function, so that when handleDelete is called, it does not have the current value of filename.
Instead of using the useEffect hook and columns state, simply do that work in your return statement. Or assign the work to a variable and then render the variable. Or better yet, use a useMemo hook.
Notice that we add handleDelete to the useMemo dependencies. That way, it will re-render every time handleDelete changes. Which currently changes every render. So lets fix that by adding useCallback to handleDelete.
const MyTableThing: React.FC = (props) => {
const { data } = props;
const [filename, setFilename] = React.useState<string>('');
const handleDelete = React.useCallback(() => {
someFunctionThatDeletes(filename); // filename is always ""
setFilename(''); // Does not do anything.. !
}, [filename]);
const columns = React.useMemo(() => {
if (!data) {
return null;
}
let columns: GridColumns = data.columns;
columns.forEach((column: GridColDef) => {
if (column.field === 'delete') {
column.renderCell = (cellParams: GridCellParams) => {
return <Button onClick={handleDelete}>Delete</Button>;
};
}
});
return columns;
}, [data?.files, handleDelete]);
// Called when a row is clicked
const handleRowSelected = (param: GridRowSelectedParams) => {
console.log(`set selected row to ${param.data.id}`); // This works every time
setFilename(param.data.id);
};
return columns;
};
I am currently rendering user's search results in my component and those results are being stored in a state called
const [searchData, setSearchData] = useState<any>([]);
However I've added a left navbar that user can search by location, date posted, etc....
I've managed to get the list of cities(location) of the search results on the left navbar, and if user wanted to click each city I wanted to change the search results filtering only by that city.
I so far have been able to get the console.log(location) of each onClick so now I am stuck how I should re render the results. If I update setSearchData with this onClick handle. I get an undefined .map error of other functions in this component.
So.. How am I suppose to handle re-rendering search results based on clicking location filter?
Please try doing this:
const [searchData, setSearchData] = useState<any>({data: [], save: []});
const onChange = (value: string) => {
setSearchData({
...searchData,
save: searchData.data.filter((x) x.name.indexOf(value) > -1)
})
}
const state = ["Fish","Dog","Cat"]
const data = state.filter((x) => x.indexOf("Fish") > -1)
console.log(data)
Please find a rough code :
const [searchData, setSearchData] = useState<any>([]);
filteredData use this to display
const [filterOption , setFilterOption] =useState();
const [filteredData, setFilteredData] = useState<any>([]);
const onFilterChange = () =>{
setFilterOption()//set filter here
}
useEffect(()=>{
//... Apply Filter Logic
let filteredRows = []
setFilteredData(filteredRows)
}, [filterOption])
Hello I am building photo gallery where I would like to add feature that user will be able filter by Category. I tried some solutions but there are two bugs that I am not able to fix. First is that if I go to the GalleryPage (using Swtich) it does NOT render dynamically added buttons from FilterButton component. I have to click one more time on the link and then it DOES render the buttons. I dont know why it does not work on the first render.
Other issue is that I am able to filter by category but it causes the infinite loop in the useEffect and I dont know how to fix it.
I have got GalleryPage component where I am getting data from API and parsing the data for using later in other components. Here it seems that is all working fine.
const GalleryPage = () => {
const url = 'someurl';
const [data, setData] = useState([]);
const [categoryList, setCategoryList] = useState([]);
const [category, setCategory] = useState('All');
useEffect(() => {
const fetchData = async () => {
const result = await axios(url,);
setData(result.data)
result.data.forEach(item => {
imageUrl.push(item.image)
if (categoryList.indexOf(item.group) === -1) {
categoryList.push(item.group)
}
})
}
fetchData();
}, [])
return (
<FilterButton setCategory={setCategory} categoryList={categoryList}/>
<Gallery data={data} category={category}/>
)
}
If I go to the GalleryPage the h3 and 'All' button is rendered. But I have to click on the link one more time to render the buttons inside the map function:
const FilterButton = ({setCategory, categoryList}) => {
return(
<h3>Gallery</h3>
<button onClick={()=> setCategory('All')}>All</button>
{categoryList.map(item => (
<button key={item} onClick={()=> setCategory(item)}>{item}</button>
))}
)
};
export default FilterButton;
And here I am not able to fix the infinite loop:
const Gallery = ({data, category}) => {
const [photos, setPhotos] = useState([]);
useEffect(()=>{
let temp = []
if (category === 'All'){
setPhotos(data)
}else{
data.map(item => {
temp.push(item)
})
}
setPhotos(temp)
})
return(
photos.map((item =>
<img key={item.id} src={item.image}/>
))
)
};
export default Gallery;
If I add empty array to the useEffect it does not work at all. Also I am using styled components and framer motion but it should not have affect on this I hope.
First, I see that you're never setting your state for categoryList.
After modifying categoryList, you should call setCategoryList() with the new category list. This way, the state variable will be 'remembered' when the component is re-rendered.
You can read about the useState hook here.
Additionally, for the useEffect hook, the 'empty array' you pass in at the end is actually an array of variables to 'watch' for changes. If you pass an empty array, the useEffect will only run once, at the first page load. However, you can pass in something like [category] so that the useEffect is only called when the category variable is modified, which I persume is what you want to do.