I've implemented user list and can delete users dispatching action deleteUser().
Now I add user but once I click add button the data is not mapped in the list.
this is a reducer:
case ADD_USERS:
const newId = state.users[state.users.length-1] + 1
return {
...state,
users: [
...state.users,
{
id: newId,
name: action.payload
}
],
loading: false
}
initial state consists of 2 objects and loading key.
The action function is simple:
export function addUser (name) {
return {
type: ADD_USERS,
payload: name
}
and the component is there:
const mapStateToProps = (state) => ({ users: state.users });
const mapDispatchToProps = (dispatch) => {
return {
deleteUser: id => {
dispatch(deleteUser(id))
},
addUser: name => {
dispatch(addUsers(name))
}
}
};
const Users = (props) => {
const { users } = props.users;
useEffect(() => {
getUsers();
}, []);
return (
<>
<input type='text' placeholder='name..'/>
<button onClick={() => props.addUser(name)}>add</button>
<h2>Users</h2>
{users.map((user) => {
return (
<div className="d-flex justify-content-between align-items-center mb-1">
<li>{user.name}</li>
<button onClick={() => props.deleteUser(user.id)}>x</button>
</div>
);
})}
</>
);
};
}
I consider getUsers don't work or I can be wrong. cause I map state to props and display the data inside {user.name}
I think it should work same with getUsers()
Maybe this is not the only one issue, but at least this looks strange to me:
const { users } = props.users;
Because, with the line above you are creating a constant with value from props.users.users. You have not shown how you use the Users component and what it gets from outside, but this looks at least strange to me.
<button onClick={() => props.addUser(name)}>add</button>
Your button calls addUser with a variable name, but that variable doesn't exist!
You need to change your input into a controlled component so that you can call addUser with the name from the input field.
const [name, setName] = useState("");
return (
<>
<input
type="text"
placeholder="name.."
value={name}
onChange={(e) => setName(e.target.value)}
/>
<button onClick={() => props.addUser(name)}>add</button>
...
Related
Whenever I dispatch a search action using context and useReducer for an object in an array stored in local storage, it returns the object, but when I delete the search query from the input box, the list is not returned and the page is blank, can anyone help please?
This is my context:
const NotesContext = createContext(null);
const NotesDispatchContext = createContext(null);
const getStoredNotes = (initialNotes = InitialNotes) => {
return JSON.parse(localStorage.getItem("storedNotes")) || initialNotes;
};
export const NotesProvider = ({ children }) => {
const [NOTES, dispatch] = useReducer(NotesReducer, getStoredNotes());
useEffect(() => {
localStorage.setItem("storedNotes", JSON.stringify(NOTES));
}, [NOTES]);
return (
<NotesContext.Provider value={NOTES}>
<NotesDispatchContext.Provider value={dispatch}>
{children}
</NotesDispatchContext.Provider>
</NotesContext.Provider>
);
};
export const useNotesContext = () => {
return useContext(NotesContext);
};
export const useNotesDispatchContext = () => {
return useContext(NotesDispatchContext);
};
const App = () => {
const [query, setQuery] = useState("");
const dispatch = useNotesDispatchContext();
useEffect(() => {
if (query.length !== 0) {
dispatch({
type: "searchNotes",
query: query,
});
}
}, [query]);
return (
<div className="container">
<header>
<Title title={"Notes"} className={"app_title"} />
<form className="search_container">
<span class="material-symbols-outlined">search</span>
<input
type="search"
placeholder="search notes"
value={query}
onChange={(e) => setQuery(e.target.value)}
/>
</form>
</header>
This is my reducer function
case "searchNotes": {
[...NOTES].filter((note) =>
note.title.toLowerCase().includes(action.query)
);
}
The function seems to actually remove the all data from the local storage instead of filtering based on the query string.
Issue
When you dispatch searchNotes you are changing NOTES and the blow useEffect runs. So if the filter resulted to an empty array, there would be nothing in localStorage.
useEffect(() => {
localStorage.setItem("storedNotes", JSON.stringify(NOTES));
}, [NOTES]);
Solution
What you can do is to remove that useEffect in App that has query as dependency and dispatching searchNotes. And filter directly while rendering, something like this:
{
NOTES.filter((note) => note.title.toLowerCase().includes(query)).map((note, index) => (
<div key={index}>{note.title}</div>
))
}
And at this point you can remove searchNotes case from your reducer.
I have the parent Posts.js component which map every object in posts array. In this function I try to filter all notes have same post_id as id of the current mapped post object. All stored in filteredNotes variable. Then I pass it to each child. Now the issue. When I want to add new note in specific post, the view doesn't update (new note was not added to the list) although the database and redux store has been updated successfully.
But when I try to remove that filter function, everything works just fine so I guess the main problem is there. Any idea how to fix this? Thanks
Posts.js
const posts = useSelector((state) => state.post.posts);
const notes = useSelector((state) => state.notes.notes);
useEffect(() => {
dispatch(getPosts());
dispatch(getNotes());
}, []);
const addNoteHandle = (val) => {
dispatch(addNote({new_note: val}));
}
return (
<div className="post__page">
<div className="post__list">
{posts.map((data) => {
let filteredNotes = notes.filter((i) => i.post_id === data.id);
return <Post data={data} notes={filteredNotes} />;
})}
</div>
<PostForm addNewNote={addNoteHandle} />
</div>
);
Post.js
export const Post = ({ data, notes }) => {
return (
<div className="post__item">
<div className="post__title">{data.title}</div>
<div className="post__note">
{notes.map(note => <div>{note.text}</div>)}
</div>
</div>
);
};
NoteForm.js
const NoteForm = ({ addNewNote }) => {
const [text, setText] = useState("");
return (
<div>
<Input value={text} onChange={(e) => setText(e.target.value)} />
<Button type="primary" onClick={() => addNewNote(text)} >
<SendOutlined />
</Button>
</div>
);
};
Action
export const addNote = ({ new_note }) => async (dispatch) => {
try {
const res = await axios.post("http://localhost:9000/api/note", new_note);
dispatch({ type: ADD_NOTE, payload: res.data });
} catch (err) {
dispatch({ type: NOTE_FAIL });
}
};
Reducer
case ADD_NOTE:
return {
...state,
notes: [...state.notes, payload]
};
use useSelector to get the component value from redux store. for some reason hook setText will not work to update the page component. I had a similar problem and could not find any solution. This code may help:
let text ='';
text = useSelector((state) =>
state.yourReducer.text);
Now show your text wherever you want
this will fix the issue until you find real solution
For learning purpose,
I am trying prevent re-render on <InputWithLable /> component whenever i Dismiss a search result (see deploy in Full code)
I have use React.memo but it still re-render. So I think maybe its props is the culprit. I use React.useCallback to handleSearch prop, but it doesn't work.
Full code
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
import React from 'react';
const API_ENDPOINT = 'https://hn.algolia.com/api/v1/search?query=';
const useSemiPersistentState = (key, initialState) => {
const [value, setValue] = React.useState(
localStorage.getItem(key) || initialState
);
React.useEffect(() => {
localStorage.setItem(key, value);
}, [value, key]);
return [value, setValue];
};
function storiesReducer(prevState, action) {
switch (action.type) {
case "SET":
return { ...prevState, data: action.data, isLoading: false, isError: false };
case "REMOVE":
return {
...prevState,
data: prevState.data.filter(
story => action.data.objectID !== story.objectID
)
}
case "ERROR":
return { ...prevState, isLoading: false, isError: true };
default:
throw new Error();
}
}
const App = () => {
const [searchTerm, setSearchTerm] = useSemiPersistentState(
'search',
'Google'
);
const [stories, dispatchStories] = React.useReducer(storiesReducer, { data: [], isLoading: true, isError: false });
const [url, setUrl] = React.useState("");
const handleFetchStories = React.useCallback(() => {
fetch(url)
.then((response) => response.json())
.then((result) => {
console.log(result);
dispatchStories({ type: "SET", data: result.hits })
})
.catch(err => dispatchStories({ type: "ERROR", data: err }))
}, [url])
React.useEffect(() => {
handleFetchStories();
}, [handleFetchStories])
const handleRemoveStory = React.useCallback(
(item) => {
dispatchStories({ type: "REMOVE", data: item });
},
[], // chi render 1 lan vi props khong thay doi
)
const handleSearch = React.useCallback(
(e) => {
setSearchTerm(e.target.value);
},
[],
)
// Chuc nang filter la cua server (vd: database)
// const searchedStories = stories.data ? stories.data.filter(story =>
// story.title.toLowerCase().includes(searchTerm.toLowerCase())
// ) : null; // nghich cai nay!
console.log('App render');
return (
<div>
<h1>My Hacker Stories</h1>
<InputWithLabel
id="search"
value={searchTerm}
isFocused
onInputChange={handleSearch}
>
<strong>Search:</strong>
</InputWithLabel>
<button onClick={() => setUrl(API_ENDPOINT + searchTerm)}>Search!</button>
<hr />
{stories.isError && <h4>ERROR!</h4>}
{stories.isLoading ? <i>Loading...</i>
: <List list={stories.data} onRemoveItem={handleRemoveStory} />}
</div>
);
};
const InputWithLabel = React.memo(
({
id,
value,
type = 'text',
onInputChange,
isFocused,
children,
}) => {
const inputRef = React.useRef();
React.useEffect(() => {
if (isFocused) {
inputRef.current.focus();
}
}, [isFocused]);
console.log('Search render')
return (
<>
<label htmlFor={id}>{children}</label>
<input
ref={inputRef}
id={id}
type={type}
value={value}
onChange={onInputChange}
/>
</>
);
}
);
// Prevent default React render mechanism: Parent rerender -> Child rerender
const List = React.memo(
({ list, onRemoveItem }) =>
console.log('List render') || list.map(item => (
<Item
key={item.objectID}
item={item}
onRemoveItem={onRemoveItem}
/>
))
);
const Item = ({ item, onRemoveItem }) => (
<div>
<span>
<a href={item.url}>{item.title}</a>
</span>
<span>{item.author}</span>
<span>{item.num_comments}</span>
<span>{item.points}</span>
<span>
<button type="button" onClick={() => onRemoveItem(item)}>
Dismiss
</button>
</span>
</div>
);
export default App;
You should not be looking at how many times a component's render function gets called; React is free to call it as many times as it likes (and indeed, in strict mode, it calls them twice to help you not make mistakes).
But to answer your question (with the actual code that uses children):
<InputWithLabel>
<strong>Search:</strong>
</InputWithLabel>
compiles down to
React.createElement(InputWithLabel, null,
React.createElement("strong", null, "Search:"))
the identity of the children prop (the <strong /> element) changes for each render of the parent component since React.createElement() returns new objects for each invocation. Since that identity changes, React.memo does nothing.
If you wanted to (but please don't), you could do
const child = React.useMemo(() => <strong>Search:</strong>);
// ...
<InputWithLabel>{child}</InputWithLabel>
but doing that for all of your markup leads to nigh-unreadable code.
I started learning React not so long ago. Decided to make some kind of "life checklist" as one of my beginner projects. I have been using Functional Components in the core.
FYI:
I have data.js as an array of objects where "action", "emoji" and unique ID are stored.
I import it into my App.js.
const App = () => {
//Looping over data
const items = data.map((item) => {
return (
<ChecklistItem action={item.action} emoji={item.emoji} key={item.id} />
);
});
return (
<>
<GlobalStyle />
<StyledHeading>Life Checklist</StyledHeading>
<StyledApp>{items}</StyledApp>
<h2>Overall number: {data.length}</h2>
</>
);
};
export default App;
Here is my <ChecklistItem/> component:
const ChecklistItem = ({ action, emoji }) => {
//State
const [isActive, setIsActive] = useState(false);
//Event Handlers
const changeHandler = () => {
setIsActive(!isActive);
};
return (
<StyledChecklistItem isActive={isActive}>
<input type="checkbox" checked={isActive} onChange={changeHandler} />
<StyledEmoji role="img">{emoji}</StyledEmoji>
<StyledCaption>{action}</StyledCaption>
</StyledChecklistItem>
);
};
export default ChecklistItem;
I would be satisfied with the functionality so far, but I need to show how many "active" checklist items were chosen in the parent <App/> component like "You have chosen X items out of {data.length}. How can I achieve this?
I assume that I need to lift the state up, but cannot understand how to implement this properly yet.
You can do that by simply creating a state for storing this particular count of active items.
To do that, you would need to update your <App/> component to something like this
const App = () => {
const [activeItemsCount, setActiveItemsCount] = useState(0);
//Looping over data
const items = data.map((item, index) => {
return (
<ChecklistItem
key={index}
action={item.action}
emoji={item.emoji}
setActiveItemsCount={setActiveItemsCount}
/>
);
});
return (
<>
<h1>Life Checklist</h1>
<div>{items}</div>
<div>Active {activeItemsCount} </div>
<h2>Overall number: {data.length}</h2>
</>
);
};
export default App;
And then in your <ChecklistItem /> component, you would need to accept that setActiveItemsCount function so that you can change the state of the activeItemsCount.
import React, { useState, useEffect } from "react";
const ChecklistItem = ({ action, emoji, setActiveItemsCount }) => {
const [isActive, setIsActive] = useState(false);
const changeHandler = () => {
setIsActive(!isActive);
};
useEffect(() => {
if (!isActive) {
setActiveItemsCount((prevCount) => {
if (prevCount !== 0) {
return prevCount - 1;
}
return prevCount;
});
}
if (isActive) {
setActiveItemsCount((prevCount) => prevCount + 1);
}
}, [isActive, setActiveItemsCount]);
return <input type="checkbox" checked={isActive} onChange={changeHandler} />;
};
export default ChecklistItem;
By using the useEffect and the checks for isActive and 0 value, you can nicely increment or decrement the active count number by pressing the checkboxes.
How about this?
const data = [
{ action: '1', emoji: '1', id: 1 },
{ action: '2', emoji: '2', id: 2 },
{ action: '3', emoji: '3', id: 3 },
];
const ChecklistItem = ({ action, emoji, isActive, changeHandler }) => {
return (
<div isActive={isActive}>
<input type="checkbox" checked={isActive} onChange={changeHandler} />
<div>{emoji}</div>
<div>{action}</div>
</div>
);
};
const PageContainer = () => {
const [checkedItemIds, setCheckedItemIds] = useState([]);
function changeHandler(itemId) {
if (checkedItemIds.indexOf(itemId) > -1) {
setCheckedItemIds((prev) => prev.filter((i) => i !== itemId));
} else {
setCheckedItemIds((prev) => [...prev, itemId]);
}
}
const items = data.map((item) => {
const isActive = checkedItemIds.indexOf(item.id) > -1;
return (
<ChecklistItem
isActive={isActive}
changeHandler={() => changeHandler(item.id)}
action={item.action}
emoji={item.emoji}
key={item.id}
/>
);
});
return (
<div className="bg-gray-100">
<div>{items}</div>
<h2>
You have chosen {checkedItemIds.length} items out of {data.length}
</h2>
</div>
);
};
When data is used by a child component, but the parent needs to be aware of it for various reasons, that should be state in the parent component. That state is then handed to the child as props.
One way to do this would be to initialize your parent component with a piece of state that was an array of boolean values all initialized to false. Map that state into the checkbox components themselves and hand isActive as a prop based on that boolean value. You should then also hand the children a function of the parent that will change the state of the boolean value at a certain index of that array.
Here's a bit of a contrived example:
// Parent.tsx
const [checkBoxes, setCheckboxes] = useState(data.map(data => ({
id: data.id,
action: data.action,
emoji: data.emoji
isActive: false,
})));
const handleCheckedChange = (i) => {
setCheckboxes(checkBoxes => {
checkBoxes[i].isActive = !checkBoxes[i].isActive;
return checkBoxes;
})
}
return(
checkBoxes.map((item, i) =>
<ChecklistItem
action={item.action}
emoji={item.emoji}
key={item.id}
index={i}
isActive={item.isActive}
handleChange={handleCheckedChange}
/>
)
);
// CheckListItem.tsx
const CheckListItem = ({ action, emoji, index, isActive, handleChange }) => (
<StyledChecklistItem isActive={isActive}>
<input type="checkbox" checked={isActive} onChange={() => handleChange(index)} />
<StyledEmoji role="img">{emoji}</StyledEmoji>
<StyledCaption>{action}</StyledCaption>
</StyledChecklistItem>
)
action.ts:
export const fetchField = (dispatch) => {
console.log("!!!")
const Form = new Service();
Form
.getProduct()
.then((spec: Spec) => {
dispatch({
type: ACTIONS.SPEC.SHOW,
spec : specification,
});
})
.catch((err) => {});
};
appReducer:
export interface FormsState {
products: Array<Specification>
}
let initialState: FormsState = {
products: []
};
export let appReducer = (
state: FormsState = initialState,
action
) => {
switch (action.type) {
case ACTIONS.SPEC.SHOW:
return Object.assign({}, state, {
products: [...action.products],
});
default:
return state;
}
};
App.tsx:
const mapStateToProps = (state: FormsState) => {
return state;
};
const mapDispatchToProps = dispatch => {
return {
fetchField: () => fetchField(dispatch),
};
}
interface Props{
fetchField: Function;
details: Array<Specification>
}
componentDidMount() {
this.props.fetchSwaggerField();
}
render(){
<TextInput
invalidText="A valid value is required"
labelText="API title"
type = "text"
value={this.props.fetchField.length}
name="title"
/>
}
I am trying to get the value in text input from the redux api call, and expecting the value of api call in text field. Once I will be getting its value. I want to edit it value, and save its new value so that whenever i come back to the form, the new value should be retained
Here is the answer for your new issue to add/delete fields
You can use FieldArray. The FieldArray component is how you render an array of fields (ref)
A sample code with add/delete:
// renderSubFields.js
const renderSubFields = (member, index, fields) => (
<li key={index}>
<button
type="button"
title="Remove Member"
onClick={() => fields.remove(index)}
/>
<h4>Member #{index + 1}</h4>
<Field
name={`${member}.firstName`}
type="text"
component={renderField}
label="First Name"
/>
<Field
name={`${member}.lastName`}
type="text"
component={renderField}
label="Last Name"
/>
</li>
);
const renderMembers = ({ fields }) => (
<ul>
<button type="button" onClick={() => fields.push({})}>
Add Member
</button>
{fields.map(renderSubFields)}
</ul>
);
Then in your wizard page
<FieldArray name="members" component={renderMembers} />
Here is a demo: https://codesandbox.io/s/redux-form-wizard-example-m26bk?file=/WizardFormFirstPage.js:586-641