Redux store is updated but view is not - javascript

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

Related

why isn't my array being rendered on my page?

I am trying to render listed property information from an array of objects. I used this method in another part of my project with success, but in this instance, I am not getting anything at all.
here is the code I have
import { database } from "../../components/firebase";
import { ref, child, get } from "firebase/database";
import { useState, useEffect } from "react";
export default function Dashboard() {
const dbRef = ref(database);
const [users, setUsers] = useState([]);
const array = [];
const getData = () => {
get(child(dbRef, "users/"))
.then((snapshot) => {
const data = snapshot.val();
setUsers(data);
})
.catch((err) => {
console.log(err);
});
};
const getProperties = () => {
Object.values(users).forEach((user) => {
Object.values(user?.properties).forEach((property) => {
array.push(property);
console.log(property);
});
});
console.log(array);
};
useEffect(() => {
getData();
getProperties();
}, [dbRef]);
return (
<>
<div>Properties </div>
<div>
{array.map((property) => (
<div key={property.property_id}>
<h1>{property?.property_name}</h1>
<p>{property?.description}</p>
<p>{property?.rooms}</p>
<p>{property?.phone}</p>
</div>
))}
</div>
<p>oi</p>
</>
);
}
Nothing happens, it only prints "properties" and "oi"
getData is asynchronous. When you execute getProperties, your users state will still be its initial, empty array value.
You don't appear to be using users for anything else but assuming you want to keep it, the easiest way to drive some piece of state (array) from another (users) is to use a memo hook.
// this is all better defined outside your component
const usersRef = ref(database, "users");
const getUsers = async () => (await get(usersRef)).val();
export default function Dashboard() {
const [users, setUsers] = useState({}); // initialise with the correct type
// Compute all `properties` based on `users`
const allProperties = useMemo(
() =>
Object.values(users).flatMap(({ properties }) =>
Object.values(properties)
),
[users]
);
// Load user data on component mount
useEffect(() => {
getUsers().then(setUsers);
}, []);
return (
<>
<div>Properties </div>
<div>
{allProperties.map((property) => (
<div key={property.property_id}>
<h1>{property.property_name}</h1>
<p>{property.description}</p>
<p>{property.rooms}</p>
<p>{property.phone}</p>
</div>
))}
</div>
<p>oi</p>
</>
);
}
The memo hook will recompute allProperties any time users is changed.
If you don't need the users state, then there's not much need for the memo hook. Instead, just maintain the state you do need
const [allProperties, setAllProperties] = useState([]); // init with empty array
useEffect(() => {
getUsers().then((users) => {
setAllProperties(
Object.values(users).flatMap(({ properties }) =>
Object.values(properties)
)
);
});
}, []);

Prevent local storage from being changed when filtering in React

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.

React.JS: Form problem to make post caused from useEffect

I have created a form and I have noticed that when I submit data, they are not writing in the db (with error 400). So I have investigated and I have noticed that one api call that I make in useEffect is done about 5 time during the submit. (I have tried to comment this part and It works!)
I have a first part of form, in which with a select I make a choose, this value is used to make an api call (and there is the problem) to give back some data to use in the form.
return (
<AvForm model={isNew ? {} : userClientAuthorityEntity} onSubmit={saveEntity}>
<AvInput
id="client-application"
data-cy="application"
type="select"
className="form-control"
name="application"
onChange={handleChangeApp} // there i save the value applicationApp
required
value={applicationApp}
>
<option value="" key="0">
Select
</option>
{applicationListAPP ?
applicationListAPP.map(value => {
return (
<option value={value.appCod} key={value.appCod}>
{value.appDescription}
</option>
);
})
: null}
</AvInput>
</AvGroup>
<ShowRoleApp applicationRole={applicationApp} /> // so there I pass the value to make the api call
)
const ShowRoleApp = ({ applicationRole }) => {
const [profili, setProfili] = useState([]);
const [isLoading, setIsLoading] = useState(false);
if (!applicationRole) {
return <div />;
}
// I think that it the problem, because it recall GetProfili
useEffect(() => {
async function init() {
await GetProfili(applicationRole)
.then((res) => {
console.log('res ', res);
setProfili(res);
setIsLoading(true);
})
.catch((err) => console.log('err ', err));
}
init();
}, []);
return isLoading ? (
RenderProfili(profili, applicationRole)
) : (
<div className='d-flex justify-content-center'>
<div className='spinner-border text-primary' role='status'>
<span className='visually-hidden'></span>
</div>
</div>
);
};
const GetProfili = async (appCod) => {
const chiamata = 'myApi' + appCod.toString();
const res = await fetch(chiamata);
const result = res.clone().json();
return result;
};
const RenderProfili = (profili, applicationRole) => {
const ruoliOperatore = profili ? profili.filter(it => it.appCod.toString() === applicationRole.toString()) : null;
return (
<AvGroup>
<Label for="sce-profiloutentepa-pucCod">Profile (*)</Label>
// other code for the form...
So in your opinion how can i do to call the GetProfili without recall every time when I submit the form?
Thank you
You could define GetProfili as a custom hook an manage the useEffect call in it.
It will return the isLoading and profili instances.
Try to change your code like this.
GetProfili:
const GetProfili = (appCod) => {
const [isLoading, setIsLoading] = useState(true)
const [profili, setProfili] = useState([])
const loadProfili = async () => {
const chiamata = 'myApi' + appCod.toString();
const res = await fetch(chiamata);
setProfili(res.json())
setIsLoading(false)
}
useEffect(() => {
loadProfili()
}, [])
return { isLoading, profili };
};
ShowRoleApp:
const ShowRoleApp = ({ applicationRole }) => {
if (!applicationRole) {
return <div />;
}
const { isLoading, profili } = GetProfili(applicationRole)
return isLoading ? (
RenderProfili(profili, applicationRole)
) : (
<div className='d-flex justify-content-center'>
<div className='spinner-border text-primary' role='status'>
<span className='visually-hidden'></span>
</div>
</div>
);
};
I didn't really understand the question but I can say something that might help. The useEffect() hook gets called on every rerender of the component so if it updates 5 times its because some states inside the component get updated 5 times. Also states are updated in child components update the parent.

how to implement addItem using react-redux

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>
...

Why am I getting first response as an empty array in react?

Below I am trying to fetch data and use the onInputValue function in my other component called Search. It's working fine after first attempt, but I am getting an empty array in my initial button click
const App = () => {
const [results, setResults] = useState([]);
const onInputValue = async (input) => {
const { data } = await nasa.get('/search', {
params: {
q: input,
},
});
if(!results) {
return;
}
setResults(data.collection.items);
console.log(results);
};
return (
<div>
<Search onInputValue={onInputValue} />
</div>
);
};
import React, { useState} from 'react';
const Search = ({ onInputValue }) => {
const [input, setInput] = useState('');
return (
<div className='input-group mb-3'>
<input
type='text'
className='form-control'
placeholder='To infinity and beyond!'
onChange={(e) => setInput(e.target.value)}
/>
<div className='input-group-append'>
<button
onClick={() => {onInputValue(input)}}
className='btn btn-outline-secondary'
type='button'
>
<i className='fas fa-rocket'></i>
</button>
</div>
</div>
);
};
export default Search;
Below is the result I get.
Please advise
setResults is asynchronous if you want to check results you can use useEffect
const App = () => {
const [results, setResults] = useState([]);
useEffect(() => {
console.log(results);
}, [results])
const onInputValue = async (input) => {
const {data} = await nasa.get('/search', {
params: {
q: input,
},
});
if (!results) {
return;
}
setResults(data.collection.items);
};
return (
<div>
<Search onInputValue={onInputValue} />
</div>
);
};
This is due to asynchronous nature of setResults function - after calling it, the updated value of results will be available on the next component render - in your example you're logging in to console immediately after invoking setResults.
It could be because the setResults is batched and therefore move to the next line.
See useState batch updates.
Also
if(!results) { return; }
Is a bit suspect. ![] === false and !['someValue'] === false

Categories