I am having a problem where i'm trying to render pass an array's data to a card component but it doesn't appear on the page, the card component renders normally on its own:
import React, { Component } from 'react'
import { Container, Grid, Card, Segment, Header } from 'semantic-ui-react';
import ArticleCard from './ArticleCard';
export default class NewsGrid extends Component {
render() {
const {News} = this.props
console.log(News)
return (
<div>
<Container style={{marginTop: '7%'}}>
<Grid stackable divided >
<Grid.Column style={{width: '66.66%'}}>
<Card.Group>
{
News.map(({id, ...otherArticleProps}) => (
<ArticleCard key={id} {...otherArticleProps} />
))
}
</Card.Group>
</Grid.Column>
</Grid>
</Container>
</div>
)
}
}
console.log() shows that the data is actually there
and i'm passing the array as props from the parent page component, the data is delivered through the flamelink API in useEffect as shown bellow:
import React, { useEffect, useState } from 'react';
import NewsGrid from '../components/NewsGrid';
import BusinessContainer from '../components/BusinessContainer';
import PolitiqueContainer from './../components/PolitiqueContainer';
import app from '../Firebase';
import { withRouter } from 'react-router-dom';
const Homepage = () => {
const [News] = useState([])
const [Business] = useState([])
const [Politique] = useState([])
const [Opinion] = useState([])
const [Blog] = useState([])
useEffect(() => {
app.content.get({schemaKey: 'articles',
fields: ['title', 'author', 'date', 'thumbnail', 'slug', 'summary', 'category', 'id'],
orderBy:{field: 'date',
order: 'desc'},
})
.then(articles => {
for(var propName in articles) {
const propValue = articles[propName]
switch(propValue.category) {
default :
break;
case 'News':
News.push(propValue)
break;
case 'Business':
Business.push(propValue)
break;
case 'Politique':
Politique.push(propValue)
break;
case 'Opinion':
Opinion.push(propValue)
break;
case 'Blog':
Blog.push(propValue)
break;
}
}
})
})
return (
<div >
<NewsGrid News={News} Opinion={Opinion} Blog={Blog} />
<BusinessContainer content={Business} />
<PolitiqueContainer content={Politique} />
</div>
);
};
export default withRouter(Homepage);
I am using firebase as a backend combined with Flamelink for the CMS.
Thanks in advance.
When you use const [News] = React.useState([]), any changes you make to News will not be reflected in your React application, since mutating News will not update the state of your component. If you want to update the state of News, you need to use the state dispatch function provided by React.useState. Try this instead:
const [News, updateNews] = React.useState([])
// We can't use News.push('Some news'), but we can update News this way:
updateNews([...News, 'Some news'])
import React, { useEffect, useState } from 'react';
import NewsGrid from '../components/NewsGrid';
import BusinessContainer from '../components/BusinessContainer';
import PolitiqueContainer from './../components/PolitiqueContainer';
import app from '../Firebase';
import { withRouter } from 'react-router-dom';
const Homepage = () => {
const [news, setNews] = useState([]);
const [business, setBusiness] = useState([]);
const [politique, setPolitique] = useState([]);
const [opinion, setOpinion] = useState([]);
const [blog, setBlog] = useState([]);
useEffect(() => {
app.content
.get({
schemaKey: 'articles',
fields: ['title', 'author', 'date', 'thumbnail', 'slug', 'summary', 'category', 'id'],
orderBy: { field: 'date', order: 'desc' },
})
.then(articles => {
for (let propName in articles) {
const propValue = articles[propName];
switch (propValue.category) {
case 'News':
setNews(propValue);
break;
case 'Business':
setBusiness(propValue);
break;
case 'Politique':
setPolitique(propValue);
break;
case 'Opinion':
setOpinion(propValue);
break;
case 'Blog':
setBlog(propValue);
break;
default:
break;
}
}
});
}, []);
return (
<div>
<NewsGrid News={news} Opinion={opinion} Blog={blog} />
<BusinessContainer content={business} />
<PolitiqueContainer content={politique} />
</div>
);
};
export default withRouter(Homepage);
I would suggest using camel case variables:
const [stateVariable, setStateVariable] = useState();
(https://reactjs.org/docs/hooks-state.html) and to add the [] to the useEffect array of dependencies. This will ensure that the useEffect will trigger only a single time. Hope that this helps.
Related
So I am trying to store a global state using context to allow me to use the same state across different components.
The issue I am having is that when I set the global state in 1 component and try to access it in the other component to use the state. It appears to be null and I cannot figure out why?
The first component where I set the global state in will always be rendered before the component shown that seems to have an empty value for the global state.
GlobalStateProvider component:
import React from "react";
import { useState, useEffect } from "react";
import axios from "axios";
const defaultActivitiesState = [];
const globalStateContext = React.createContext(defaultActivitiesState);
const dispatchStateContext = React.createContext([]);
export const useGlobalState = () =>
[
React.useContext(globalStateContext),
React.useContext(dispatchStateContext)
];
const GlobalStateProvider = ({ children }) => {
const [state, dispatch] = React.useReducer((state, newValue) => (state, newValue),
defaultActivitiesState
);
return (
<globalStateContext.Provider value={state}>
<dispatchStateContext.Provider value={dispatch}>
{children}
</dispatchStateContext.Provider>
</globalStateContext.Provider>
);
}
export default GlobalStateProvider;
Component I set the global state in:
import react from "react";
import { useState, useEffect, useMemo } from "react";
import { MapContainer, TileLayer, Popup, Polyline } from "react-leaflet";
import axios from "axios";
import polyline from "#mapbox/polyline";
import MapComp from "./MapComp";
import { useGlobalState } from "./GlobalStateProvider";
function Map() {
// ------- global state
const [activities, setActivities] = useGlobalState(); // global state
//const [activities, setActivities] = useState([]);
//const [polylines, setPolylines] = useState(null); // as empty array value is still truthy
const [isLoading, setIsLoading] = useState(true);
const [mapMode, setMapMode] = useState("light");
const [mapStyle, setMapStyle] = useState(
"https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
);
const [showMarkers, setShowMarkers] = useState(true);
useEffect(() => {
setActivitieData();
console.log("activities after useEffect", activities)
}, []);
const polylines = useMemo(() => {
console.log("activities inside memo", activities)
console.log("activities.len =", activities.length);
if (activities.length) {
console.log("past len");
const polylineArray = [];
for (const item of activities) {
const polylineData = item.map.summary_polyline;
const activityName = item.name;
const activityType = item.type;
polylineArray.push({
positions: polyline.decode(polylineData),
name: activityName,
activityType: activityType,
});
}
setIsLoading(false);
return polylineArray;
}
return null;
}, [activities]);
const toggleMarkers = () => {
setShowMarkers((show) => !show);
};
const getActivityData = async () => {
console.log("calling")
const response = await axios.get(
"http://localhost:8800/api/"
);
return response.data;
};
const setActivitieData = async () => {
const activityData = await getActivityData();
setActivities(activityData);
console.log("Global activities state = ", activities);
};
return !isLoading && polylines ? (
<>
<div className="select-container">
<button className="toggle-markers" onClick={() => toggleMarkers()}>
Toggle Markers
</button>
</div>
<MapComp
className={`${mapMode}`}
activityData={{ polylines }}
showMarkers={showMarkers}
/>
</>
) : (
<div>
<p>Loading...</p>
</div>
);
}
export default Map;
component that has an empty value for global state:
import React from 'react';
import { useGlobalState } from './GlobalStateProvider';
function ActivityList() {
const [activities, setActivities] = useGlobalState();
let displayValues;
displayValues =
activities.map((activity) => {
return (
<div>
<p>{activity.name}</p>
<p>{activity.distance}m</p>
</div>
);
})
return (
<>
<p>Values</p>
{displayValues}
</>
);
}
export default ActivityList;
App.js:
function App() {
return (
<GlobalStateProvider>
<div className="App">
<NavBar />
<AllRoutes />
</div>
</GlobalStateProvider>
);
}
export default App;
I am making a small blog application using React JS. I am using the context api to store the user's responses globally (in InputContext.js), so that it can be used across different components.
What I want to achieve is, when the user inputs a new blog entry on a separate input page (WriteBlogPost.js) display all the blog entries on a separate page (AllBlogs.js). The page changes are being handled with react router. I have a problem where I am unable to add the new blog objects into the array defined in the context api component (allBlogPosts). I am unsure what is causing this, any explanations and guidance towards the right direction would greatly be appreciated.
InputContext.js
import React, { useState, createContext, useMemo } from 'react'
//create context
export const InputContext = createContext();
const InputContextProvider = (props) => {
const [blogPost, setBlogPost] = useState({
id: '',
title: '',
author: '',
text: ''
});
//create an array to push all the blogPosts
const [allBlogPosts, setAllBlogPosts] = useState([]);
console.log(allBlogPosts)
//put value inside useMemo so that the component only rerenders when there is change in the value
const value = useMemo(() => ({ blogPost, setBlogPost, allBlogPosts, setAllBlogPosts }), [blogPost, allBlogPosts])
return (
<InputContext.Provider value={value}>
{props.children}
</InputContext.Provider>
)
}
export default InputContextProvider;
WriteBlogPost.js
import React, { useState, useContext } from 'react'
import { useHistory } from 'react-router-dom'
import { InputContext } from '../Contexts/InputContext'
import { TextareaAutosize } from '#material-ui/core'
import { v4 as uuidv4 } from 'uuid';
export const WriteBlogPost = () => {
const [blog, setBlog] = useState({
id: '',
title: '',
author: '',
text: ''
});
const history = useHistory();
const { setBlogPost } = useContext(InputContext);
const { allBlogPosts, setAllBlogPosts } = useContext(InputContext)
const handleBlogPost = () => {
setAllBlogPosts(setBlogPost(blog))
history.push("/blogs")
console.log({ blog })
console.log({ allBlogPosts })
}
const handleChange = (e) => {
const value = e.target.value
setBlog({
...blog,
id: uuidv4(),
[e.target.name]: value
})
}
return (
<div>
<label>
Title:
<input type="text" onChange={handleChange} value={blog.title} name="title" />
</label>
<label>
Author:
<input type="text" onChange={handleChange} value={blog.author} name="author" />
</label>
<TextareaAutosize aria-label="minimum height" minRows={20} style={{ width: '70%' }} placeholder="Your blog post"
onChange={handleChange}
value={blog.text}
name="text" />
<div>
<button onClick={handleBlogPost}>Submit</button>
</div>
</div>
)
}
AllBlogs.js(currently unable to map through the array as the array is empty)
import React, { useContext } from 'react'
import { InputContext } from '../Contexts/InputContext'
export const AllBlogs = () => {
const { allBlogPosts } = useContext(InputContext)
console.log(allBlogPosts)
return (
<div>
<h1>All blogs</h1>
{allBlogPosts.map((post) =>
<div>
<p>{post.title}</p>
<p>{post.author}</p>
<p>{post.text}</p>
</div>
)}
</div>
)
}
Just update handleBlogPost
const handleBlogPost = () => {
setBlogPost(blog);
setAllBlogPosts([...allBlogPosts, blog]);
history.push("/blogs");
};
I am creating a basic shopping cart app with Reactjs. I created a useContext file to make the states globally available.
Unfortunately, the objects in the useReducer state are not responding to action, except the array of products called 'cart'. The 'amount' and 'total' are not rendering.
Though, the actions can be seen to be updated when I checked the console log. That means I am not returning the right variables.
The action I want to achieve is that when <MdKeyboardArrowUp> is clicked, the 'amount' variable should increase by 1. It increases on console log but not rendered on the page.
The product list:
export default [
{
id: 1,
title: 'Samsung Galaxy S7',
price: 599.99,
img:
'https://res.cloudinary.com/diqqf3eq2/image/upload/v1583368215/phone-2_ohtt5s.png',
amount: 1,
},
useContext file
import React, {useState, useContext, useReducer, useEffect} from 'react';
import cartData from '../component/data'; //this is the source file for the product list//
import customReducer from './reducer'; //the file that handles the useReducer
const Appcontext = React.createContext();
const initialState = {
loading: false,
cart: cartData,
total: 0,
amount: 0,
}
const AppProvider = ({children}) =>{
const [state, dispatch] = useReducer(customReducer, initialState);
const increaseProduct = (id) =>{
dispatch({type: "INCREASE_PRODUCT", payload: id})
}
const decreaseProduct = (id) =>{
dispatch({type: "DECREASE_PRODUCT", payload: id})
}
return(
<Appcontext.Provider value={{...
state,
clearShopCart,
clearShopCart,
removeProduct,
decreaseProduct,
increaseProduct,
}}>
{children}
</Appcontext.Provider>
)
}
export const useGlobalContext = () =>{
return useContext(Appcontext)
}
export {Appcontext, AppProvider}
useReducer file
const customReducer = (state, action) => {
if(action.type === 'CLEAR_SHOPP_CART'){
return{...state, cart: []}
}
if(action.type === 'REMOVE_ITEM'){
const newProducts = state.cart.filter((singleProduct) => singleProduct.id !==
action.payload)
return{...state, cart: newProducts}
}
**if(action.type === "INCREASE_PRODUCT"){
let newValue = state.cart.map((singleProduct) => {
if(singleProduct.id === action.payload){
return {... singleProduct, amount: singleProduct.amount + 1}
}
return singleProduct
});
console.log(newValue)
return {...state, cart: newValue}** //these codes on bold format are the codes that
increases by 1 each time the button is clicked//
}
return state
}
export default customReducer;
The home file where the codes are rendered
import React, { useState, useEffect } from 'react';
import {HiShoppingCart} from 'react-icons/hi';
import {MdKeyboardArrowUp} from 'react-icons/md';
import {RiArrowDownSLine} from 'react-icons/ri';
import { useGlobalContext } from '../component/context';
export default function Home() {
const {cart, amount, total, clearShopCart, removeProduct, decreaseProduct, increaseProduct} =
useGlobalContext();
{cart.map((singleData) => {
const {id, title, price, img} = singleData;
return(
<>
<div key={id} className='product-container'>
<div className='img-container'>
<img src={img} alt={title} />
<div className='product-text-container'>
<h4>{title}</h4>
< h4>${price}</h4>
<h5 className='btn1' onClick={() => removeProduct(id)}>Remove</h5>
</div>
</div>
<div className='item-control'>
<MdKeyboardArrowUp className='iconUp' onClick={() =>
increaseProduct(id)}/> **//when clicked, should inrease 'amount' by
1**//
<p>{amount}</p>
{console.log(amount)}
<RiArrowDownSLine className='iconDown' onClick={() =>
decreaseProduct(id)}/>
</div>
</div>
</>
)
})}
I eventually found the solution. I needed to destructure the 'amount' object coming from the product array. That way, I was able to increase the individual products' amount. It should be like this while destructuring the array:
{cart.map((singleData) => {
const {id, title, price, img, amount} = singleData;
So I have a Context created with reducer. In reducer I have some logic, that in theory should work. I have Show Component that is iterating the data from data.js and has a button.I also have a windows Component that is iterating the data. Anyway the problem is that when I click on button in Show Component it should remove the item/id of data.js in Windows Component and in Show Component, but when I click on it nothing happens. I would be very grateful if someone could help me. Kind regards
App.js
const App =()=>{
const[isShowlOpen, setIsShowOpen]=React.useState(false)
const Show = useRef(null)
function openShow(){
setIsShowOpen(true)
}
function closeShowl(){
setIsShowOpen(false)
}
const handleShow =(e)=>{
if(show.current&& !showl.current.contains(e.target)){
closeShow()
}
}
useEffect(()=>{
document.addEventListener('click',handleShow)
return () =>{
document.removeEventListener('click', handleShow)
}
},[])
return (
<div>
<div ref={show}>
<img className='taskbar__iconsRight' onClick={() =>
setIsShowOpen(!isShowOpen)}
src="https://winaero.com/blog/wp-content/uploads/2017/07/Control-
-icon.png"/>
{isShowOpen ? <Show closeShow={closeShow} />: null}
</div>
)
}
```Context```
import React, { useState, useContext, useReducer, useEffect } from 'react'
import {windowsIcons} from './data'
import reducer from './reducer'
const AppContext = React.createContext()
const initialState = {
icons: windowsIcons
}
const AppProvider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initialState)
const remove = (id) => {
dispatch({ type: 'REMOVE', payload: id })
}
return (
<AppContext.Provider
value={{
...state,
remove,
}}
>
{children}
</AppContext.Provider>
)
}
export const useGlobalContext = () => {
return useContext(AppContext)
}
export { AppContext, AppProvider }
reducer.js
const reducer = (state, action) => {
if (action.type === 'REMOVE') {
return {
...state,
icons: state.icons.filter((windowsIcons) => windowsIcons.id !== action.payload),
}
}
}
export default reducer
``data.js```
export const windowsIcons =[
{
id:15,
url:"something/",
name:"yes",
img:"/images/icons/crud.png",
},
{
id:16,
url:"something/",
name:"nine",
img:"/images/icons/stermm.png",
},
{
id:17,
url:"domething/",
name:"ten",
img:"/images/icons/ll.png",
},
{
id:18,
url:"whatever",
name:"twenty",
img:"/images/icons/icons848.png",
},
{
id:19,
url:"hello",
name:"yeaa",
img:"/images/icons/icons8-96.png",
},
]
``` Show Component```
import React from 'react'
import { useGlobalContext } from '../../context'
import WindowsIcons from '../../WindowsIcons/WindowsIcons'
const Show = () => {
const { remove, } = useGlobalContext()
return (
<div className='control'>
{windowsIcons.map((unin)=>{
const { name, img, id} = unin
return (
<li className='control' key ={id}>
<div className='img__text'>
<img className='control__Img' src={img} />
<h4 className='control__name'>{name}</h4>
</div>
<button className='unin__button' onClick={() => remove(id)} >remove</button>
</li> )
</div>
)
}
export default Show
import React from 'react'
import {windowsIcons} from "../data"
import './WindowsIcons.css'
const WindowsIcons = ({id, url, img, name}) => {
return (
<>
{windowsIcons.map((icons)=>{
const {id, name , img ,url} =icons
return(
<div className='windows__icon' >
<li className='windows__list' key={id}>
<a href={url}>
<img className='windows__image' src={img}/>
<h4 className='windows__text'>{name}</h4>
</a>
</li>
</div>
)
})}
</>
)
}
Issue
In the reducer you are setting the initial state to your data list.
This is all correct.
However, then in your Show component you are directly importing windowsIcons and looping over it to render. So you are no longer looping over the state the reducer is handling. If the state changes, you won't see it.
Solution
In your Show component instead loop over the state that you have in the reducer:
const { remove, icons } = useGlobalContext()
{icons.map((unin) => {
// Render stuff
}
Now if you click remove it will modify the internal state and the icons variable will get updated.
Codesandbox working example
I have to use useDispatch() for my toggle buttons so I have to refractor them from react to redux state. I was following the tutorial of basics of Redux and I think I have done that properly but when I try to at least useSelector to display the redux'state of button it doesnt show anything.
So here is my code:
// types.js in actions folder
export const TOGGLE = "TOGGLE";
// buttonActions in actions folder
export const toggle = () => {
return {
type: 'TOGGLE'
};
};
// buttonReducer in reducers folder
const buttonReducer = (state = true, action) => {
switch(action.type) {
case 'TOGGLE':
return !state;
default:
return state;
};
};
export default buttonReducer;
And the buttonReducer is imported into combineReducers which go to store.
The component code:
import React, { useState, useEffect } from 'react'
import isloff from './mainpage_imgs/isloff.png'
import islon from './mainpage_imgs/islon.png'
import PropTypes from "prop-types";
import { connect, useDispatch, useSelector } from "react-redux";
import { toggle } from '../../actions/buttonActions'
const Islbutton = props => {
const [open, setOpen] = useState(true);
const [role, setRole] = useState('');
useEffect(() => {
if (props.auth.user)
{
setRole(props.auth.user.role);
}
}, []);
const test = useSelector(state => state.button);
const checkRole = (role) => {
if (role === 'Menager' || role === 'Technolog')
{
return true }
else
{
return false
};
}
const toggleImage = () => {
if(checkRole(role)) {
setOpen(!open)
};
}
const getImageName = () => open ? 'islOnn' : 'islOfff'
const dispatch = useDispatch();
return(
<div>
<img style={islplace} src={open ? islon : isloff }
onClick={()=> dispatch(toggle())} />
</div>
);
}
Islbutton.propTypes = {
button: PropTypes.func.isRequired,
auth: PropTypes.obj.isRequired
};
const mapStateToProps = state => ({
button: state.button,
auth: state.auth
});
export default connect(mapStateToProps, {}), (Islbutton);
Based on your latest comments and my understanding of your use case I may suggest following distilled approach:
//dependencies
const { render } = ReactDOM,
{ createStore } = Redux,
{ connect, Provider } = ReactRedux
//action creators
const SET_ROLE = 'SET_ROLE',
MANAGER_APPROVED = 'MANAGER_APPROVED',
setRole = role => ({type:SET_ROLE, role}),
mngAppr = () => ({type:MANAGER_APPROVED})
//initial state, reducer, store
const initialState = {role:'Technolog', approved:false},
appReducer = (state=initialState, action) => {
switch(action.type){
case SET_ROLE : {
const {role} = state,
{role: newRole} = action
return {...state, role: newRole}
}
case MANAGER_APPROVED : {
const {approved} = state
return {...state, approved: !approved}
}
default: return state
}
},
store = createStore(appReducer)
//ui component to emulate toggling roles
const SwitchRoles = ({currentRole, switchRole}) => (
<div>
<label><input type="radio" name="role" value="Manager" onChange={e => switchRole(e.target.value)} />Manager</label>
<label><input type="radio" name="role" value="Technolog" onChange={e => switchRole(e.target.value)} />Technolog</label>
</div>
)
//connect radio buttons click to togling roles action
const mapDispatch = dispatch => ({switchRole: role => dispatch(setRole(role))}),
SwitchRolesContainer = connect(null,mapDispatch)(SwitchRoles)
//ui component to toggle 'approved' within global state
const ToggleApprove = ({onApprove,isManager}) => (
<button onClick={onApprove} disabled={!isManager}>Toggle</button>
)
//connect onToggle handler to dispatching 'toggle' action
const mapStateToProps = ({role}) => ({isManager: role == 'Manager'}),
mapDispatchToProps = dispatch => ({onApprove: () => dispatch(mngAppr())}),
ToggleApproveContainer = connect(mapStateToProps, mapDispatchToProps)(ToggleApprove)
//ui component to display current state of 'open'
const IsApproved = ({isApproved}) => <div>{isApproved ? 'Approved by manager' : 'Not approved by manager'}</div>
//attach isOpen prop to global 'open' variable
const mapState = ({approved}) => ({isApproved: approved}),
IsApprovedContainer = connect(mapState)(IsApproved)
//render the app
render (
<Provider store={store}>
<SwitchRolesContainer />
<IsApprovedContainer />
<ToggleApproveContainer />
</Provider>,
document.getElementById('root')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.5/redux.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.1.3/react-redux.min.js"></script><div id="root"></div>
Hopefully, it gives a piece of mind about toggling global variables and mapping their values onto local components state.