Local storage in react todo list - javascript

I created to do list using react, but I want it to be local storage - so when the user refresh the page it still saved the items and will present them.
I read I need to use localStorage but I'm not sure where and how, attach the app.js and TodoItem component
class App extends Component {
state = {
items: [],
id: uuidv4(),
item: "",
editItem: false
};
handleChange = e => {
...
};
handleSubmit = e => {
e.preventDefault();
const newItem = {
id: this.state.id,
title: this.state.item
};
const updatedItems = [...this.state.items, newItem];
this.setState({
items: updatedItems,
item: "",
id: uuidv4(),
editItem: false
});
};
...
render() {
return (
<TodoInput
item={this.state.item}
handleChange={this.handleChange}
handleSubmit={this.handleSubmit}
editItem={this.state.editItem}
/>
<TodoList
items={this.state.items}
clearList={this.clearList}
handleDelete={this.handleDelete}
handleEdit={this.handleEdit}
/>
);
}
}
export default class TodoItem extends Component {
state = {
avatarURL: '',
}
componentDidMount() {
imgGen().then(avatarURL => this.setState({ avatarURL }));
}
render() {
const { title, handleDelete, handleEdit } = this.props;
const { avatarURL } = this.state;
return (
<h6>{title}</h6>
<span className="mx-2 text-success" onClick={handleEdit}>
</span>
<span className="mx-2 text-danger" onClick={handleDelete}>
</span>
);
}
}

You can do it like this, mind the comments
class App extends Component {
state = {
// load items while initializing
items: window.localStorage.getItem('items') ? JSON.parse(window.localStorage.getItem('items')) : [],
id: uuidv4(),
item: "",
editItem: false
};
handleChange = e => {
// ...
};
handleSubmit = e => {
e.preventDefault();
const newItem = {
id: this.state.id,
title: this.state.item
};
const updatedItems = [...this.state.items, newItem];
// Save items while changing
window.localStorage.setItem('items', JSON.stringify(updatedItems));
this.setState({
items: updatedItems,
item: "",
id: uuidv4(),
editItem: false
});
};
// ...
render() {
return (
<>
<TodoInput
item={this.state.item}
handleChange={this.handleChange}
handleSubmit={this.handleSubmit}
editItem={this.state.editItem}
/>
<TodoList
items={this.state.items}
clearList={this.clearList}
handleDelete={this.handleDelete}
handleEdit={this.handleEdit}
/>
</>
);
}
}

Here's some simple logic you can use in your componentDidMount() method of your App.
const localStorageList = localStorage.getItem('todo-list')
if (!localStorageList) {return null} else {this.setState({items: localStorageList})
To add to the localStorage please look at this question
and this resource

Let me help you with this, using the least no. of codes. I have written a clear explanation of the steps, for you all to better understand, please bear with me , it is definitely with the time to read.
Also, note this solution is perfectly crafted for functional components. However I have mentioned how to do it in class components, you have to tweak some things if you are using class components. Like you can not use hooks in class-based components, but access this instance, so it will be fine, either ways
Please give it a full read, if you are having a tough time understanding the functionality, I have tried to break down the process in layman. The explanation is long, the lines of code is just under 10. happy to help
Persisting states of the todo app, upon page refresh, is pretty simple.
We can use State management libraries for it, or local storage as well.
Here, we will just go with the most simple one - using local storage.
Before we jump to the code, let us build the functionality visually.
So, after the user enters things in the todo space, we want few things to happen:
We want to store the list of items (which will essentially be an array) in the local storage. (We can skip the JSON.parse, here, since the array that will be saved, will be string, bcz user enters string in the todo-app, generally, however, it's not a bad idea to parse the userinputs).
useEffect(()=>{
window.localStorage.setItems("key" , value)
}, [dependency])
After you do this, make sure you check the dev-tools => application => localStorage => to see if the key and values are being stored. You shall be able to see them.
However, you will notice, that upon refresh, the localStorage values stay, but the data in the UI are lost. Not surprising.
This is the last and important step.
What we want upon page reload? Let us break it down :
We want to check if there is any data that is there in the localStorage. If there is: we will change the state of the app, based on the previous user inputs.
If there is no data in the LocalStorage, we will just pass an empty array.
Using hooks, in the functional component is actually What I prefer, class components require many boiler plates, so, the code...
import {useState} from 'react';/for functional components
//for class components you can just init the state , in the constructor(props) and
change it using the this.setState method
//to getItems from localStorage to render in the UI
useEffect(()=>{
const storedData = localStorage,getItems("keys" , value)
storedData ? setValue(value) : [];
},[])
[] : because we want it to render on every reload, once.
smake sure to initiliaze the state using useState;
const [value , setValue] = useState("")
//to setItems in localStorage
useEffect(()=>{
window.localStorage.setItems("key" , value)
}, [dependency])
useEffect is essentially a hook for functional components which is similar to componentDidMount in-class components.
If using class components, instead of using the useState, hook, use this.setState.

You could format your todolist into a JSON string and store it using :
localStorage.setItem("todolist", "your_JSON_string_here");
However, web Local Storage have storage limitations which will cause issues if the data stored are getting larger in time.
More info at here
Perhaps you could consider IndexedDB (if you are storing huge data) INFO

Related

Filter in react query not working properly on first attempt

I am trying to get only females from an array using a filter, but on the first attempt react query returns the whole array, after that it is working fine. Any idea what property I have to add or remove, so this side effect disappears.
Here is my code:
import React, { useState } from "react";
import { useQuery } from "react-query";
import getPersonsInfo from "../api/personCalls";
export default function Persons() {
const [persons, setPersons] = useState([]);
const { data: personData, status } = useQuery("personsData", getPersonsInfo, {
onSuccess: (data) => {
setPersons(data.data);
},
onError: (error) => {
console.log(error);
}
});
const getFemaleOnlyHandler = () => {
const result = personData.data.filter(
(person) => person.gender === "female"
);
setPersons(result);
};
return (
<>
<button onClick={getFemaleOnlyHandler}>Female only</button>
{status === "loading" ? (
<div>Loading ... </div>
) : (
<div>
{persons.map((person) => (
<div>
<p>{person.name}</p>
<p>{person.lastName}</p>
<p>{person.address}</p>
<p>{person.gender}</p>
<p>{person.country}</p>
<p>{person.city}</p>
</div>
))}
</div>
)}
</>
);
}
I added the full code in code sandbox: https://codesandbox.io/s/relaxed-drake-4juxg
I think you are making the mistake of copying data from react-query into local state. The idea is that react-query is the state manager, so the data returned by react-query is really all you need.
What you are experiencing in the codesandbox is probably just refetchOnWindowFocus. So you focus the window and click the button, react-query will do a background update and overwrite your local state. This is a direct result of the "copy" I just mentioned.
What you want to do is really just store the user selection, and calculate everything else on the fly, something like this:
const [femalesOnly, setFemalesOnly] = React.useState(false)
const { data: personData, status } = useQuery("personsData", getPersonsInfo, {
onError: (error) => {
console.log(error);
}
});
const getFemaleOnlyHandler = () => {
setFemalesOnly(true)
};
const persons = femalesOnly ? personData.data.filter(person => person.gender === "female") : personData.data
you can then display whatever you have in persons, which will always be up-to-date, even if a background update yields more persons. If the computation (the filtering) is expensive, you can also use useMemo to memoize it (compute it only when personData or femalesOnly changes - but this is likely a premature optimization.
I'm not totally familiar with react-query however the problem is likely that it is re-fetching (async!) everytime the component updates. Since setPersons() triggers an update (ie. sets state) it'll update the new persons state to be the filtered female list and then trigger a fetch of all persons again which comes back and sets the persons state back to the full list (ie. see what happens when you click the female filter button and then just leave it).
There is a more idiomatic way to achieve this in React which is to keep a "single source of truth" (ie. all the persons) and dynamically filter that based on some local ui state.
For example see below where data becomes the source of truth, and persons is a computed value out of that source of truth. This has the benefit that if your original data changes you don't have to manually (read: imperatively) update it to also be females only. This is the "unidirectional data flow" and "reactivity" people always talk about and, honestly, it's what makes React, React.
const { data = { data: [] }, status } = useQuery(
"personsData",
getPersonsInfo,
{
onSuccess: (data) => {},
onError: (error) => {
console.log(error);
}
}
);
const [doFilterFemale, setFilterFemale] = useState(false);
const persons = doFilterFemale
? data.data.filter((person) => person.gender === "female")
: data.data;
https://codesandbox.io/s/vigorous-nobel-9n117?file=/src/Persons/persons.jsx
This is ofc assuming you are always just loading from a json file. In a real application setting, given a backend you control, I would always recommend implementing filtering, sorting and pagination on the server side otherwise you are forced to over-fetch on the client.

React - How to re-render a component using another component?

I have a NavBar component that has a list of dynamically generated links (these links are generated after querying my backend for some categories). These links are stored inside a child component of the NavBar, called DrawerMenu.
The NavBar is a child of the main App.js component.
In my Category component, I have a "delete" function that deletes a category. Once I delete a category I want to remove the link to it in the NavBar. How would I go about doing this?
For further context, my components are given below:
DrawerMenu component
class DrawerMenu extends Component {
state = {
menuItems: [] // Takes a series of objects of the shape { name: "", link: "" }
}
getData = (query) => {
// Query backend for category data and set it to this.state.menuItems
}
componentDidMount() {
this.getData(menuItemsQuery)
}
render() {
const { classes, handleDrawerClose, open } = this.props
const { menuItems } = this.state
const drawer = (classes, handleDrawerClose) => (
<div>
...
{
menuItems.map((menuItem, index) => (
<Link color="inherit" key={index} to={menuItem.link} className={classes.drawerLink} component={RouterLink}>
<ListItem button className={classes.drawerListItem} onClick={handleDrawerClose}>
<ListItemText primary={menuItem.name} />
</ListItem>
</Link>
))
}
...
</div>
)
...
return (
<div>
<Drawer
variant="temporary"
anchor='left'
open={open}
onClose={handleDrawerClose}
classes={{
paper: `${open ? classes.drawerOpen : null} ${!open ? classes.drawerClose : null}`,
}}
ModalProps={{
keepMounted: true, // Better open performance on mobile.
}}
>
{drawer(classes, handleDrawerClose)}
</Drawer>
</div>
)
}
}
NavBar component
function PrimarySearchAppBar(props) {
return (
<div className={classes.grow}>
...
<DrawerMenu
classes={classes}
handleDrawerClose={handleDrawerClose}
open={open}
/>
...
</div>
)
}
Category component
class Category extends Component {
...
deleteCategory = async () => {
// Code to request backend to delete category
this.props.history.push(`/`)
}
...
}
There are two common ways of doing this: You can either use a state management tool, like Redux or pass your state down the component tree as props.
Redux is often used when several components depend on the same state or when the component that depends on a state is several layers deep, so it would get cumbersome to pass it down as props.
I'll assume your component tree is not very large, so I will create a simple example passing props down the tree.
class DrawerMenu extends Component {
// We're gonna manage the state here, so the deletion
// will actually be handled by this component
state = {
menuItems: [] // Takes a series of objects of the shape { name: "", link: "" }
}
handleDelete = (id) => {
let updatedMenuItem = [...this.state.menuItems]; //Create a copy
updatedMenuItem = updatedMenuItem(item => item.id !== id) // Remove the
deleted item
this.setState({
menuItems: updatedMenuItem
})
}
...
// Then wherever you render the category component
<Category handleDelete = {handleDelete}/> //Pass a reference to the delete method
}
Category Component
class Category extends Component {
...
deleteCategory = async () => {
// Code to request backend to delete category
this.props.handleDelete(categoryId) //Pass the id of the category
this.props.history.push(`/`)
}
...
}
I would suggest reading about state management, it is a core concept in React and you will use it everywhere. Redux and Context API for example.
Not sure why Dennis Vash deleted their answer, they are correct, but perhaps not descriptive enough in the solution.
The way you delete the category is not to call the backend itself from inside the category component, because then the navbar doesn't know that you made a call, but to call a callback that is in an ancestor shared by both the category component and the navbar to delete a category, and then rerequest the categories list from the server. In the example below, this ancestor that is shared is MyCategoriesProvider
Because the category component is likely to be in a much different place (or multiple places) in the tree than the NavBar, it's best to use context.
Honestly, this is a great place for redux, but I'm not going to push redux on you and instead will just demo a Context solution.
// We're going to create a context that will manage your categories
// The only job of this context is to hold the current categories,
// and supply the updating functions. For brevity, I'll just give
// it a handleDelete function.
// Ideally, you'd also store the status of the request in this context
// as well so you could show loaders in the app, etc
import { createContext } from 'react';
// export this, we'll be using it later
export const CategoriesContext = createContext();
// export this, we'll render it high up in the app
// it will only accept children
export const MyCategoriesProvider = ({children}) => {
// here we can add a status flag in case we wanted to show a spinner
// somewhere down in your app
const [isRequestingCategories,setIsRequestingCategories] = useState(false);
// this is your list of categories that you got from the server
// we'll start with an empty array
const [categories,setCategories] = useState([]);
const fetch = async () => {
setIsRequestingCategories(true);
setCategories(await apiCallToFetchCategories());
setIsRequestingCategories(false);
}
const handleDelete = async category => {
await apiCallToDeleteCategory(category);
// we deleted a category, so we should re-request the list from the server
fetch();
}
useEffect(() => {
// when this component mounts, fetch the categories immediately
fetch();
// feel free to ignore any warnings if you're using a linter about rules of hooks here - this is 100% a "componentDidMount" hook and doesn't have any dependencies
},[]);
return <CategoriesContext.Provider value={{categories,isRequestingCategories,handleDelete}}>{children}</CategoriesContext.Provider>
}
// And you use it like this:
const App = () => {
return (
<MyCategoriesProvider>
<SomeOtherComponent>
<SomeOtherComponent> <- let's say your PrimarySearchBar is in here somewhere
<SomeOtherComponent>
</MyCategoriesProvider>
)
}
// in PrimarySearchBar you'd do this:
function PrimarySearchBar(props) => {
const {categories} = useContext(CategoriesContext); // you exported this above, remember?
// pass it as a prop to navbar, you could easily put the useContext hook inside of any component
return <NavBar categories={categories}/>
}
// in your category component you could do this:
class Category extends Component {
render() {
// Don't forget, categoriesContext is the thing you exported way up at the top
<CategoriesContext.Consumer>
{({handleDelete}) => {
return <button onClick={() => handleDelete(this.props.category)}>
}}
</CategoriesContext.Consumer>
}
}
EDIT:
I see you're mixing class and functional components, which is fine. You should check out this article on how to use the context api in either of them - in functional components you typically use a useContext hook, while in class components you'll use a consumer.
I would just refresh the list of categories that come from the server, after the delete request is done.
I'd do it as follows:
I would make the drawer component not so smart, making it receive the list of menuItems.
<DrawerMenu
classes={classes}
handleDrawerClose={handleDrawerClose}
open={open}
items={/* ... */}
/>
This is an important step, because now, to refresh the list of items rendered, you just pass another list. The server-side logic remains disconnected from this component in this way.
I'm not sure where you render the Category components, but supposing it is rendered outside the PrimarySearchAppBar it seems that this menuItems might need to be passed to the components from an upper level. I see 2 solutions:
I'd do the request for the menuItems from the same place where I do the request for the categories:
const App = props => {
const [categories, setCategories] = React.useState([])
const [menuItems, setMenuItems] = React.useState([])
const fetchCategories = useCallback(()=> {
yourApi.getCategories().then(categories => setCategories(categories))
})
const fetchMenuItems = useCallback(() => {
yourApi.getMenuItems().then(menuItems => setMenuItems(menuItems))
})
useEffect(() => {
fetchCategories()
}, [])
useEffect(() => {
fetchMenuItems()
}, [categories])
const handleDeleteCategory = useCallback(idToDelete => {
yourApi.deleteCategory(idToDelete).then(fetchCategories)
})
return (
<div>
<PrimarySearchAppBar menuItems={menuItems}/>
<Categories categories={categories} onDeleteClick={handleDeleteCategory} />
</div>
)
}
you can do the same thing but do it with a provider and using the content API if you do not want to have all the logic here. It is good to have smart/fetches/server-side logic in a top level component and then pass down props to dumb components.
PS.
There is also a nice hook to make fetches easier:
https://github.com/doasync/use-promise
I currently use a custom version of a usePromise hook I found because I added some interesting features. I can share it if you want but I don't want to add noise to the answer.

Passing data up through nested Components in React

Prefacing this with a thought; I think I might require a recursive component but that's beyond my current ability with native js and React so I feel like I have Swiss cheese understanding of React at this point.
The problem:
I have an array of metafields containing metafield objects with the following structure:
{
metafields: [
{ 0:
{ namespace: "namespaceVal",
key: "keyVal",
val: [
0: "val1",
1: "val2",
2: "val3"
]
}
},
...
]
}
My code maps metafields into Cards and within each card lives a component <MetafieldInput metafields={metafields['value']} /> and within that component the value array gets mapped to input fields. Overall it looks like:
// App
render() {
const metafields = this.state.metafields;
return (
{metafields.map(metafield) => (
<MetafieldInputs metafields={metafield['value']} />
)}
)
}
//MetafieldInputs
this.state = { metafields: this.props.metafields}
render() {
const metafields = this.state;
return (
{metafields.map((meta, i) => (
<TextField
value={meta}
changeKey={meta}
onChange={(val) => {
this.setState(prevState => {
return { metafields: prevState.metafields.map((field, j) => {
if(j === i) { field = val; }
return field;
})};
});
}}
/>
))}
)
}
Up to this point everything displays correctly and I can change the inputs! However the change happens one at a time, as in I hit a key then I have to click back into the input to add another character. It seems like everything gets re-rendered which is why I have to click back into the input to make another change.
Am I able to use components in this way? It feels like I'm working my way into nesting components but everything I've read says not to nest components. Am I overcomplicating this issue? The only solution I have is to rip out the React portion and take it to pure javascript.
guidance would be much appreciated!
My suggestion is that to out source the onChange handler, and the code can be understood a little bit more easier.
Mainly React does not update state right after setState() is called, it does a batch job. Therefore it can happen that several setState calls are accessing one reference point. If you directly mutate the state, it can cause chaos as other state can use the updated state while doing the batch job.
Also, if you out source onChange handler in the App level, you can change MetafieldInputs into a functional component rather than a class-bases component. Functional based component costs less than class based component and can boost the performance.
Below are updated code, tested. I assume you use Material UI's TextField, but onChangeHandler should also work in your own component.
// Full App.js
import React, { Component } from 'react';
import MetafieldInputs from './MetafieldInputs';
class App extends Component {
state = {
metafields: [
{
metafield:
{
namespace: "namespaceVal",
key: "keyVal",
val: [
{ '0': "val1" },
{ '1': "val2" },
{ '2': "val3" }
]
}
},
]
}
// will never be triggered as from React point of view, the state never changes
componentDidUpdate() {
console.log('componentDidUpdate')
}
render() {
const metafields = this.state.metafields;
const metafieldsKeys = Object.keys(metafields);
const renderInputs = metafieldsKeys.map(key => {
const metafield = metafields[key];
return <MetafieldInputs metafields={metafield.metafield.val} key={metafield.metafield.key} />;
})
return (
<div>
{renderInputs}
</div>
)
}
}
export default App;
// full MetafieldInputs
import React, { Component } from 'react'
import TextField from '#material-ui/core/TextField';
class MetafieldInputs extends Component {
state = {
metafields: this.props.metafields
}
onChangeHandler = (e, index) => {
const value = e.target.value;
this.setState(prevState => {
const updateMetafields = [...prevState.metafields];
const updatedFields = { ...updateMetafields[index] }
updatedFields[index] = value
updateMetafields[index] = updatedFields;
return { metafields: updateMetafields }
})
}
render() {
const { metafields } = this.state;
// will always remain the same
console.log('this.props', this.props)
return (
<div>
{metafields.map((meta, i) => {
return (
<TextField
value={meta[i]}
changekey={meta}
onChange={(e) => this.onChangeHandler(e, i)}
// generally it is not a good idea to use index as a key.
key={i}
/>
)
}
)}
</div>
)
}
}
export default MetafieldInputs
Again, IF you out source the onChangeHandler to App class, MetafieldInputs can be a pure functional component, and all the state management can be done in the App class.
On the other hand, if you want to keep a pure and clean App class, you can also store metafields into MetafieldInputs class in case you might need some other logic in your application.
For instance, your application renders more components than the example does, and MetafieldInputs should not be rendered until something happened. If you fetch data from server end, it is better to fetch the data when it is needed rather than fetching all the data in the App component.
You need to do the onChange at the app level. You should just pass the onChange function into MetaFieldsInput and always use this.props.metafields when rendering

Managing multiple calls to the same Apollo mutation

So taking a look at the Apollo useMutation example in the docs https://www.apollographql.com/docs/react/data/mutations/#tracking-loading-and-error-states
function Todos() {
...
const [
updateTodo,
{ loading: mutationLoading, error: mutationError },
] = useMutation(UPDATE_TODO);
...
return data.todos.map(({ id, type }) => {
let input;
return (
<div key={id}>
<p>{type}</p>
<form
onSubmit={e => {
e.preventDefault();
updateTodo({ variables: { id, type: input.value } });
input.value = '';
}}
>
<input
ref={node => {
input = node;
}}
/>
<button type="submit">Update Todo</button>
</form>
{mutationLoading && <p>Loading...</p>}
{mutationError && <p>Error :( Please try again</p>}
</div>
);
});
}
This seems to have a major flaw (imo), updating any of the todos will show the loading state for every single todo, not just the one that has the pending mutation.
And this seems to stem from a larger problem: there's no way to track the state of multiple calls to the same mutation. So even if I did want to only show the loading state for the todos that were actually loading, there's no way to do that since we only have the concept of "is loading" not "is loading for todo X".
Besides manually tracking loading state outside of Apollo, the only decent solution I can see is splitting out a separate component, use that to render each Todo instead of having that code directly in the Todos component, and having those components each initialize their own mutation. I'm not sure if I think that's a good or bad design, but in either case it doesn't feel like I should have to change the structure of my components to accomplish this.
And this also extends to error handling. What if I update one todo, and then update another while the first update is in progress. If the first call errors, will that be visible at all in the data returned from useMutation? What about the second call?
Is there a native Apollo way to fix this? And if not, are there options for handling this that may be better than the ones I've mentioned?
Code Sandbox: https://codesandbox.io/s/v3mn68xxvy
Admittedly, the example in the docs should be rewritten to be much clearer. There's a number of other issues with it too.
The useQuery and useMutation hooks are only designed for tracking the loading, error and result state of a single operation at a time. The operation's variables might change, it might be refetched or appended onto using fetchMore, but ultimately, you're still just dealing with that one operation. You can't use a single hook to keep track of separate states of multiple operations. To do that, you need multiple hooks.
In the case of a form like this, if the input fields are known ahead of time, then you can just split the hook out into multiple ones within the same component:
const [updateA, { loading: loadingA, error: errorA }] = useMutation(YOUR_MUTATION)
const [updateB, { loading: loadingB, error: errorB }] = useMutation(YOUR_MUTATION)
const [updateC, { loading: loadingC, error: errorC }] = useMutation(YOUR_MUTATION)
If you're dealing with a variable number of fields, then we have to break out this logic into a separate because we can't declare hooks inside a loop. This is less of a limitation of the Apollo API and simply a side-effect of the magic behind hooks themselves.
const ToDo = ({ id, type }) => {
const [value, setValue] = useState('')
const options = { variables = { id, type: value } }
const const [updateTodo, { loading, error }] = useMutation(UPDATE_TODO, options)
const handleChange = event => setValue(event.target.value)
return (
<div>
<p>{type}</p>
<form onSubmit={updateTodo}>
<input
value={value}
onChange={handleChange}
/>
<button type="submit">Update Todo</button>
</form>
</div>
)
}
// back in our original component...
return data.todos.map(({ id, type }) => (
<Todo key={id} id={id} type={type] />
))

Updating local state through React Bootstrap DropdownButton and dropdown items rendered from an array

I am building an application that gets a list of job descriptions from backend and takes user input to select one of them along with several parameters in order to make a second backend request. Since there will be a few more components involved, I have decided to use Redux for managing global state, and that appears to cause complications in my code.
My DropdownButton component uses handleJobSelect to modify local state and thereby show current selectedJobValue as the value of a FormControl component, and all is apparently well and good if my items are rendered directly (e.g. "Good item" in the code below). If I use a fetched list to map my items, however, selecting one of them will somehow cause the entire website to reload, losing all state changes and resetting both the selectedJobValue and a FormControl value property.
class Settings extends Component {
constructor() {
super();
this.state = {
descriptions: [],
selectedJobValue: 'None'
};
this.handleJobSelect = this.handleJobSelect.bind(this);
}
componentDidMount() {
this.props.fetchData('http://127.0.0.1:5000/api/1.0/descriptions');
}
handleJobSelect = evt => {
const someVal = evt;
console.log(someVal);
console.log(this.state);
this.setState({ selectedJobValue: someVal });
console.log(this.state);
};
render() {
const { selectedJobValue } = this.state;
return (
<InputGroup classname="mb-3">
<DropdownButton
as={InputGroup.Prepend}
variant="outline-primary"
title="Description"
id="input-group-dropdown-1"
onSelect={this.handleJobSelect}
>
{this.props.items.map(item => (
<Dropdown.Item href={item} key={item} eventkey={item}>
{item.toString()}
</Dropdown.Item>
))}
<Dropdown.Item href="#">Good item</Dropdown.Item>
</DropdownButton>
<FormControl
placeholder="Placeholder job description"
aria-label="Job description"
aria-describedby="basic-addon1"
value={selectedJobValue}
readOnly="True"
/>
<InputGroup.Append>
<Button variant="outline-primary">Submit</Button>
</InputGroup.Append>
</InputGroup>
const mapStateToProps = state => {
return {
items: state.items,
};
};
const mapDispatchToProps = dispatch => {
return {
fetchData: url => dispatch(itemsFetchData(url))
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(Settings);
I suspect that my attempt to use both local and global state is complicit in this issue, but this seems impossible to avoid given my plans for the application. Is there a way to resolve the problem without abandoning Redux?
After going through my code a few more times, I think that the problem is in my use of href and handleJobSelect() function. Since my description items represent relative file paths with slashes, they are read as valid hrefs that can call the page by themselves, thereby ruining everything. Is it possible to extract through my handleJobSelect() function different properties of the target dropdown item, for example, its text (that is, {item})?

Categories

Resources