I want to add items to an array with the useState hook instead of doing array.push. This is the original code:
let tags = []
data.blog.posts.map(post => {
post.frontmatter.tags.forEach(tag => {
if (!tags.includes(tag)){
tags.push(tag)
}
})
})
This is one of several things I've tried with React:
const [tags, setTags] = useState([])
data.blog.posts.map(post => {
post.frontmatter.tags.map(tag => {
if (!tags.includes(tag)){
setTags(tags => [...tags, tag])
}
})
})
The "tags" state variable does not receive anything in the above example.
I have looked at a variety of similar threads but the problems and solutions there are difficult to translate to this situation.
You can try setting the tags state in initial render or on any event as per your requirement .
const [tags, setTags] = useState([]);
useEffect(()=>{
const arr=[];
data.blog.posts.map(post => {
post.frontmatter.tags.map(tag => {
if (!arr.includes(tag)){
arr.push(tag)
}
})
});
setTags([...arr]);
},[]);
Ok, I did understand what you wanted to do.
Here is the code and I did add some commest and there is also a working code sandbox
so it will show the "tags" you have on your state and when you click on the button it will filter and add those tags that are missing
import React, { useState } from "react";
//mock data.blog.posts
const data = {
blog: {
posts: [
{
frontmatter: {
tags: ["tag1", "tag2", "tag3"]
}
}
]
}
};
const App = () => {
const [tags, setTags] = useState(["tag1"]);
const filterTags = () => {
const myTags = ["tag1"];
let result;
data.blog.posts.map((post) => {
// check what tags are not included in tag stateon line 18
result = post.frontmatter.tags.filter((item) => !tags.includes(item));
});
// here it will show that 'tag2' and 'tag3' does not exist
console.log("result", result);
// here we are setting the state
setTags((oldState) => [...oldState, ...result]);
};
return (
<div className="App">
<h1>My tags</h1>
{tags.map((tag) => (
<h4>{tag}</h4>
))}
<button onClick={() => filterTags()}>add tags</button>
<hr />
<h1>My tags from posts</h1>
{data.blog.posts.map((posts) => {
return posts.frontmatter.tags.map((tag) => <div>{tag}</div>);
})}
</div>
);
};
export default App;
and here is the codeSandBox
Related
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)
)
);
});
}, []);
I have an app like this
function App(){
const [appState, setAppState] = useState(
{
questions: []
}
)
const addToQuestion = (questionObject) =>{
setAppState((prevState) => {
return {...prevState, questions: [...prevState.questions, questionObject]}
})
}
let removeFromQuestionArray = () => {
setAppState((prevState) => {
let a = prevState.questions
a.pop()
return {...prevState, questions: a}
})
}
const onBackButtonClicked = () => {
removeFromQuestionArray()
}
}
But when I call removeFromQuestionsArray() the setAppState works unpredictably. It sometimes removes all the items in the array even though I'm only popping the last item in the array. What could be causing this. I just want to remove the last item from the array, how can I go about this without having issues?
Your code works fine.
Posting this as an answer to show the snippet:
Note: I edited the addToQuestions function to make it work for the example, but I didn't touch removeFromQuestionsArray
const { useState, useRef } = React
function App(){
const [appState, setAppState] = useState(
{
questions: [
'What is Love',
'Whats the meaning of life',
'Whats the answer to this question'
]
}
)
const input = useRef();
const addToQuestion = () =>{
let question = input.current.value;
setAppState((prevState) => {
return {...prevState, questions: [...prevState.questions, question]}
})
input.current.value = '';
input.current.focus();
}
let removeFromQuestionArray = () => {
setAppState((prevState) => {
let a = prevState.questions
a.pop()
return {...prevState, questions: a}
})
}
return (
<div>
{appState.questions.map(question => <li>{question}</li>)}
<input ref={input}></input>
<button onClick={addToQuestion}>Add Question</button>
<button onClick={removeFromQuestionArray}>Remove</button>
</div>
)
}
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>
,
root
)
<script crossorigin src="https://unpkg.com/react#18/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#18/umd/react-dom.production.min.js"></script>
<div id="root"></div>
I'm new to coding (it's been around three months) and I have a problem with React JS.
I took freecodecamp's eleven hour REact JS Course on YouTube and in the end of the video, there is a quiz application challenge called quizzy.
You can go to my github project file and check it out
I came to a point where I can't get the answer options selected.
I want to toggle between a different colored background whenever I click on an answer button, and I wanted it to stay as long as that button is clicked. As far as I checked, there seems to be a problem with the App.js file where I try to manipulate the data's isSelected key inside toggle function. I kindly ask anyone for help. I just don't know what I am doing wrong and it's driving me crazy.
My App.js file looks like this:
import { nanoid } from 'nanoid';
import React from 'react';
import data from '../data';
import QuestionsAndAnswers from './QuestionsAndAnswers';
function Quiz() {
const [quiz, setQuiz] = React.useState(data);
// const [isSelected, setIsSelected] = React.useState(false);
React.useEffect(() => {
const newData = data.map((data) => ({
...data,
answerOptions: data.answerOptions.map(answerOptions => ({
...answerOptions,
optionsID: nanoid()
}))
}))
setQuiz(newData);
}, [])
const handleSubmit = (event) => {
event.preventDefault();
console.log("completed")
}
function toggle(id, value) {
console.log(id, value)
setQuiz((oldState) => oldState.map((data) => {
return data.id === id
? {
...data,
answerOptions: data.answerOptions.map(answerOptions => {
return answerOptions.answerText === value
? {
...answerOptions,
isSelected: !answerOptions.isSelected
}
: {
...answerOptions,
isSelected: false
}
})
}
: data
}))
}
const selectedOptions = data.map(data => {
return (data.answerOptions.isSelected ? data : null)
})
console.log(selectedOptions)
const questions = quiz.map((quiz, index) => {
return (
<QuestionsAndAnswers
key={index}
quiz={quiz}
setQuiz={setQuiz}
toggle={toggle}
/>
)
})
// main function
return (
<main>
<form className="form-container" onSubmit={handleSubmit}>
<h2 className='header'>QuizCript</h2>
{questions}
<button className="complete-quiz-button" type='submit'>Complete the Quiz</button>
</form>
</main>
)
}
export default Quiz;
Been trying to print out a simple list from db for 2 days now, here's the code right now:
function CategoriesTable() {
const [isLoading, setLoading] = useState(true);
let item_list = [];
let print_list;
useEffect(() =>{
Axios.get('http://localhost:3000/categories').then((response) => {
const category_list = response.data.result;
if(category_list) {
for(let i = 0; i < category_list.length; i++){
item_list.push(category_list[i].category_name)
}
}
print_list = function() {
console.log(item_list.map((item) => <li>item</li>))
return item_list.map((item) => <li>item</li>)
}
setLoading(false);
})
}, [])
return (
<div>
{ !isLoading && print_list }
</div>
)
}
I think the function should be executed after the loading state gets changed to false, right? For some reason the function is not executing
By the way, I can print out the list in console without a problem, rendering the list is the problem.
I would suggest to do something like this:
function CategoriesTable() {
const [fetchedData, setFetchedData] = useState({
result: [],
isLoading: true,
});
useEffect(() =>{
Axios.get('http://localhost:3000/categories').then(response => {
const category_list = response.data.result;
setFetchedData({
isLoading: false,
result: category_list.map(category => <li>{ category.category_name }</li>),
})
})
}, [])
return (
<div>
{ !fetchedData.isLoading && fetchedData.result }
</div>
)
}
Basically rewrite from the ground up since the original code is quite messy I'm afraid.
Feel free to ask in the comments if you have any questions.
import { useState, useEffect } from "react";
import Axios from "axios";
/**
* Why mix cases here? Are you gonna use camel or snake case? Choose one and only one.
*/
function CategoriesTable() {
const [categoryNames, setCategoryNames] = useState([]);
useEffect(() => {
// not a good idea to use full URL on localhost
Axios.get('/categories').then((response) => {
const categoryList = response.data.result;
if (categoryList) {
const categoryNames = categoryList.map(({ category_name }) => category_name);
console.log(categoryNames); // in case you want ot keep the console output
setCategoryNames(categoryNames);
}
});
}, []);
return (
<div>
{/* You should either wrap <li> with either <ol> or <ul> */}
<ol>
{categoryNames.map(categoryName => (
// key is required and should be unique in map statement. In here I assume there are no duplicated categoryName
<li key={categoryName}>{categoryNames}</li>
))}
</ol>
</div>
);
}
I'm building a simple toast notification system using React Context. Here is a link to a simplified but fully working example which shows the problem https://codesandbox.io/s/currying-dust-kw00n.
My page component is wrapped in a HOC to give me the ability to add, remove and removeAll toasts programatically inside of this page. The demo has a button to add a toast notification and a button to change the activeStep (imagine this is a multi-step form). When the activeStep is changed I want all toasts to be removed.
Initially I did this using the following...
useEffect(() => {
toastManager.removeAll();
}, [activeStep]);
...this worked as I expected, but there is a react-hooks/exhaustive-deps ESLint warning because toastManager is not in the dependency array. Adding toastManager to the array resulted in the toasts being removed as soon as they were added.
I thought I could have fixed that using useCallback...
const stableToastManager = useCallback(toastManager, []);
useEffect(() => {
stableToastManager.removeAll();
}, [activeStep, stableToastManager]);
...however, not only does this not work but I would rather fix the issue at the source so I don't need to do this every time I want this kind of functionality, as it is likely to be used in many places.
This is where I am stuck. I'm unsure as to how to change my Context so that I don't need add additional logic in the components that are being wrapped by the HOC.
export const ToastProvider = ({ children }) => {
const [toasts, setToasts] = useState([]);
const add = (content, options) => {
// We use the content as the id as it prevents the same toast
// being added multiple times
const toast = { content, id: content, ...options };
setToasts([...toasts, toast]);
};
const remove = id => {
const newToasts = toasts.filter(t => t.id !== id);
setToasts(newToasts);
};
const removeAll = () => {
if (toasts.length > 0) {
setToasts([]);
}
};
return (
<ToastContext.Provider value={{ add, remove, removeAll }}>
{children}
<div
style={{
position: `fixed`,
top: `10px`,
right: `10px`,
display: `flex`,
flexDirection: `column`
}}
>
{toasts.map(({ content, id, ...rest }) => {
return (
<button onClick={() => remove(id)} {...rest}>
{content}
</button>
);
})}
</div>
</ToastContext.Provider>
);
};
export const withToastManager = Component => props => {
return (
<ToastContext.Consumer>
{context => {
return <Component toastManager={context} {...props} />;
}}
</ToastContext.Consumer>
);
};
If you want to "Fix it from the core", you need to fix ToastProvider:
const add = useCallback((content, options) => {
const toast = { content, id: content, ...options };
setToasts(pToasts => [...pToasts, toast]);
}, []);
const remove = useCallback(id => {
setToasts(p => p.filter(t => t.id !== id));
}, []);
const removeAll = useCallback(() => {
setToasts(p => (p.length > 0 ? [] : p));
}, []);
const store = useMemo(() => ({ add, remove, removeAll }), [
add,
remove,
removeAll
]);
Then, the useEffect will work as expected, as the problem was that you re-initialized the ToastProvider functionality on every render when it needs to be a singleton.
useEffect(() => {
toastManager.removeAll();
}, [activeStep, toastManager]);
Moreover, I would recommend to add a custom hook feature as the default use case, and providing wrapper only for class components.
In other words, do not use wrapper (withToastManager) on functional components, use it for classes, as it is considered an anti-pattern, you got useContext for it, so your library should expose it.
// # toastContext.js
export const useToast = () => {
const context = useContext(ToastContext);
return context;
};
// # page.js
import React, { useState, useEffect } from 'react';
import { useToast } from './toastContext';
const Page = () => {
const [activeStep, setActiveStep] = useState(1);
const { removeAll, add } = useToast();
useEffect(() => {
removeAll();
}, [activeStep, removeAll]);
return (
<div>
<h1>Page {activeStep}</h1>
<button
onClick={() => {
add(`Toast at ${Date.now()}!`);
}}
>
Add Toast
</button>
<button
onClick={() => {
setActiveStep(activeStep + 1);
}}
>
Change Step
</button>
</div>
);
};
export default Page;